Added user input support and many fixes. (0.16.5)
This commit is contained in:
parent
2b82a01fb5
commit
37abd5d6d3
11 changed files with 216 additions and 15 deletions
|
|
@ -1,5 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.16)
|
||||
project(fun VERSION 0.16.4 LANGUAGES C)
|
||||
project(fun VERSION 0.16.5 LANGUAGES C)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||
|
|
|
|||
24
README.md
24
README.md
|
|
@ -6,22 +6,22 @@ Fun is a highly strict programming language, but also highly simple. It looks li
|
|||
|
||||
Influenced by Bash, **[C](https://en.wikipedia.org/wiki/The_C_Programming_Language)**, Go, **[Lua](https://www.lua.org/)**, **[Python](https://www.python.org/)**, and Rust (Most influences came from linked languages).
|
||||
|
||||
Fun is and will ever be 100% free under the terms of the [ISC-License](https://opensource.org/license/isc-license-txt).
|
||||
Fun is and will ever be 100% free under the terms of the [Apache-2.0-License](https://opensource.org/license/apache-2-0).
|
||||
|
||||
## Idea
|
||||
|
||||
* Simplicity
|
||||
* Consistency
|
||||
* Joy in coding
|
||||
* Fun!
|
||||
- Simplicity
|
||||
- Consistency
|
||||
- Joy in coding
|
||||
- Fun!
|
||||
|
||||
## Characteristics
|
||||
|
||||
* Dynamic and optionally statically typed
|
||||
* Type safety
|
||||
* Written in C
|
||||
* Internal libs are written with no_camel_case even when written in Fun
|
||||
* Only a minimal function set is written in C and most other core libraries are implemented in Fun
|
||||
- Dynamic and optionally statically typed
|
||||
- Type safety
|
||||
- Written in C and Fun
|
||||
- Internal libs are written with no_camel_case even when written in Fun, except class Names
|
||||
- Only a minimal function set is written in C, and most other core functions and libraries are implemented in Fun
|
||||
|
||||
## The Fun Manifesto
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ Fun may not change the world — but it will make programming a little more fun.
|
|||
|
||||
## Documentation
|
||||
|
||||
Actually there does not exist any documenation. In the [examples/](https://git.xw3.org/fun/fun/src/branch/main/examples){:class="git"} directory should be an example of most Fun features.
|
||||
Actually, there does not exist any documentation. In the [examples/](https://git.xw3.org/fun/fun/src/branch/main/examples) directory should be an example of most Fun features.
|
||||
|
||||
## Development
|
||||
|
||||
|
|
@ -103,7 +103,7 @@ This requires Cygwin to be installed and configured. I will not cover this here.
|
|||
- Every commit message must contain the version at the end in the following format (1.2.3)
|
||||
- Version numbering follows "[Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html)"
|
||||
|
||||
### Developement systems
|
||||
### Development systems
|
||||
|
||||
- [GNU](https://gnu.org/)/[Linux](https://kernel.org/) ([Arch](https://archlinux.org/)/[Artix](https://artixlinux.org/), [Debian](https://www.debian.org/)) using [GCC](https://gcc.gnu.org/) and the [GNU C library](https://www.gnu.org/software/libc/) ([glibc](https://en.wikipedia.org/wiki/Glibc))
|
||||
- GNU/Linux ([Alpine](https://alpinelinux.org/)) using GCC and the [musl libc](https://musl.libc.org/)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
// Demonstrates local include:
|
||||
// This resolves relative to the current working directory where 'fun' is executed.
|
||||
#include "include_local_util.fun"
|
||||
#include "examples/include_local_util.fun"
|
||||
|
||||
print("== include local demo ==")
|
||||
greet("Fun")
|
||||
|
|
|
|||
47
examples/input_example.fun
Executable file
47
examples/input_example.fun
Executable file
|
|
@ -0,0 +1,47 @@
|
|||
#!/usr/bin/env fun
|
||||
|
||||
/*
|
||||
* This file is part of the Fun programming language.
|
||||
* https://hanez.org/project/fun/
|
||||
*
|
||||
* Copyright 2025 Johannes Findeisen <you@hanez.org>
|
||||
* Licensed under the terms of the Apache-2.0 license.
|
||||
* https://opensource.org/license/apache-2-0
|
||||
*
|
||||
* Added: 2025-10-02
|
||||
*/
|
||||
|
||||
/*
|
||||
* Interactive input example using input() and Console wrapper.
|
||||
*/
|
||||
|
||||
#include <io/console.fun>
|
||||
|
||||
print("=== input() builtin ===")
|
||||
name = input("Enter your name: ")
|
||||
print(join(["Hello, ", name, "!"], ""))
|
||||
|
||||
print("=== Console class ===")
|
||||
c = Console()
|
||||
city = c.ask("Your city")
|
||||
print(join(["Nice to meet you from ", city], ""))
|
||||
|
||||
yn = c.ask_yes_no("Do you like Fun?")
|
||||
if yn
|
||||
print("Great! 🎉")
|
||||
else
|
||||
print("Give it a try, it grows on you.")
|
||||
|
||||
/* Input given:
|
||||
Enter your name: Hanez
|
||||
Your city: Universe
|
||||
Do you like Fun? [y/n]: y
|
||||
*/
|
||||
|
||||
/* Expected output:
|
||||
=== input() builtin ===
|
||||
Hello, Hanez!
|
||||
=== Console class ===
|
||||
Nice to meet you from Universe
|
||||
Great! 🎉
|
||||
*/
|
||||
|
|
@ -52,3 +52,39 @@ bytes = [0x48,0x65,0x6c,0x6c,0x6f] // "Hello"
|
|||
b64 = b64_encode_bytes(bytes)
|
||||
print(b64) // "SGVsbG8="
|
||||
print(join(b64_decode_to_bytes(b64), ",")) // "72,101,108,108,111"
|
||||
|
||||
/* Expected output:
|
||||
=== Arrays ===
|
||||
2,2,3
|
||||
4,3,2,2,1
|
||||
3
|
||||
0
|
||||
1,2,3,4
|
||||
1,2,3,4,5
|
||||
=== Strings ===
|
||||
[Hello World]
|
||||
1
|
||||
1
|
||||
a|b|c
|
||||
baNANA
|
||||
fun
|
||||
FUN
|
||||
hahaha
|
||||
=== Math ===
|
||||
5
|
||||
10
|
||||
6
|
||||
42
|
||||
243
|
||||
2
|
||||
9
|
||||
1
|
||||
3
|
||||
=== Range ===
|
||||
0,1,2,3,4
|
||||
3,4,5,6,7
|
||||
10,7,4,1
|
||||
=== Base64 ===
|
||||
SGVsbG8=
|
||||
72,101,108,108,111
|
||||
*/
|
||||
|
|
|
|||
31
lib/io/console.fun
Normal file
31
lib/io/console.fun
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Console utilities: prompt, ask, and yes/no helpers built on input().
|
||||
*/
|
||||
|
||||
#include <strings.fun>
|
||||
|
||||
class Console()
|
||||
// Print a prompt and read a line (no trailing newline)
|
||||
fun prompt(this, text)
|
||||
return input(to_string(text))
|
||||
|
||||
// Ask a question with ": " suffix; returns the user's response string
|
||||
fun ask(this, question)
|
||||
q = to_string(question)
|
||||
if (len(q) == 0)
|
||||
return input("")
|
||||
else
|
||||
return input(join([q, ": "], ""))
|
||||
|
||||
// Ask a yes/no question; returns 1 for yes, 0 for no
|
||||
// Accepts: y, yes, n, no (case-insensitive). Keeps asking until valid.
|
||||
fun ask_yes_no(this, question)
|
||||
q = to_string(question)
|
||||
while true
|
||||
ans = input(join([q, " [y/n]: "], ""))
|
||||
a = str_to_lower(ans)
|
||||
if (a == "y" || a == "yes")
|
||||
return 1
|
||||
else if (a == "n" || a == "no")
|
||||
return 0
|
||||
// otherwise loop again
|
||||
|
|
@ -114,6 +114,7 @@ typedef enum {
|
|||
|
||||
// OS
|
||||
OP_ENV, // pops name string; pushes value string (or "")
|
||||
OP_INPUT_LINE, // operand: 0=no prompt; 1=has prompt. Pops [prompt?]; pushes input string (no trailing newline)
|
||||
|
||||
// Threads
|
||||
OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0)
|
||||
|
|
|
|||
13
src/parser.c
13
src/parser.c
|
|
@ -641,6 +641,19 @@ static int emit_primary(Bytecode *bc, const char *src, size_t len, size_t *pos)
|
|||
free(name);
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(name, "input") == 0) {
|
||||
(*pos)++; /* '(' */
|
||||
int hasPrompt = 0;
|
||||
skip_spaces(src, len, pos);
|
||||
if (*pos < len && src[*pos] != ')') {
|
||||
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "input expects 0 or 1 argument"); free(name); return 0; }
|
||||
hasPrompt = 1;
|
||||
}
|
||||
if (!consume_char(src, len, pos, ')')) { parser_fail(*pos, "Expected ')' after input arg(s)"); free(name); return 0; }
|
||||
bytecode_add_instruction(bc, OP_INPUT_LINE, hasPrompt ? 1 : 0);
|
||||
free(name);
|
||||
return 1;
|
||||
}
|
||||
if (strcmp(name, "env") == 0) {
|
||||
(*pos)++; /* '(' */
|
||||
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "env expects 1 argument"); free(name); return 0; }
|
||||
|
|
|
|||
1
src/vm.c
1
src/vm.c
|
|
@ -289,6 +289,7 @@ void vm_run(VM *vm, Bytecode *entry) {
|
|||
|
||||
#include "vm/io/read_file.c"
|
||||
#include "vm/io/write_file.c"
|
||||
#include "vm/io/input_line.c"
|
||||
|
||||
#include "vm/logic/and.c"
|
||||
#include "vm/logic/eq.c"
|
||||
|
|
|
|||
2
src/vm.h
2
src/vm.h
|
|
@ -33,7 +33,7 @@ static const char *opcode_names[] = {
|
|||
"ENUMERATE","ZIP",
|
||||
"MIN","MAX","CLAMP","ABS","POW","RANDOM_SEED","RANDOM_INT",
|
||||
"MAKE_MAP","KEYS","VALUES","HAS_KEY",
|
||||
"READ_FILE","WRITE_FILE","ENV",
|
||||
"READ_FILE","WRITE_FILE","ENV","INPUT_LINE",
|
||||
"THREAD_SPAWN","THREAD_JOIN","SLEEP_MS",
|
||||
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR"
|
||||
};
|
||||
|
|
|
|||
72
src/vm/io/input_line.c
Normal file
72
src/vm/io/input_line.c
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
case OP_INPUT_LINE: {
|
||||
/* operand: 0 = no prompt; 1 = has prompt (string or any value convertible to string) */
|
||||
int has_prompt = inst.operand ? 1 : 0;
|
||||
if (has_prompt) {
|
||||
/* pop prompt value and print without newline */
|
||||
Value pv = pop_value(vm);
|
||||
char *pstr = value_to_string_alloc(&pv);
|
||||
if (pstr) {
|
||||
fputs(pstr, stdout);
|
||||
fflush(stdout);
|
||||
free(pstr);
|
||||
}
|
||||
free_value(pv);
|
||||
}
|
||||
|
||||
/* read a line from stdin, dynamically grow buffer */
|
||||
size_t cap = 128;
|
||||
size_t len = 0;
|
||||
char *buf = (char*)malloc(cap);
|
||||
if (!buf) {
|
||||
fprintf(stderr, "Runtime error: out of memory reading input");
|
||||
push_value(vm, make_string(""));
|
||||
break;
|
||||
}
|
||||
|
||||
int ch;
|
||||
while ((ch = fgetc(stdin)) != EOF) {
|
||||
if (ch == '\r') {
|
||||
/* Handle CRLF by consuming optional following '\n' */
|
||||
int next = fgetc(stdin);
|
||||
if (next != EOF && next != '\n') {
|
||||
ungetc(next, stdin);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (ch == '\n') {
|
||||
break;
|
||||
}
|
||||
if (len + 1 >= cap) {
|
||||
cap *= 2;
|
||||
char *nb = (char*)realloc(buf, cap);
|
||||
if (!nb) {
|
||||
free(buf);
|
||||
fprintf(stderr, "Runtime error: out of memory reading input");
|
||||
push_value(vm, make_string(""));
|
||||
goto push_done;
|
||||
}
|
||||
buf = nb;
|
||||
}
|
||||
buf[len++] = (char)ch;
|
||||
}
|
||||
|
||||
/* null-terminate */
|
||||
if (len + 1 >= cap) {
|
||||
char *nb = (char*)realloc(buf, len + 1);
|
||||
if (!nb) {
|
||||
free(buf);
|
||||
fprintf(stderr, "Runtime error: out of memory finalizing input");
|
||||
push_value(vm, make_string(""));
|
||||
goto push_done;
|
||||
}
|
||||
buf = nb;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
|
||||
/* push as Fun string */
|
||||
push_value(vm, make_string(buf));
|
||||
free(buf);
|
||||
|
||||
push_done:
|
||||
break;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue