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)
|
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 11)
|
||||||
set(CMAKE_C_STANDARD_REQUIRED ON)
|
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).
|
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
|
## Idea
|
||||||
|
|
||||||
* Simplicity
|
- Simplicity
|
||||||
* Consistency
|
- Consistency
|
||||||
* Joy in coding
|
- Joy in coding
|
||||||
* Fun!
|
- Fun!
|
||||||
|
|
||||||
## Characteristics
|
## Characteristics
|
||||||
|
|
||||||
* Dynamic and optionally statically typed
|
- Dynamic and optionally statically typed
|
||||||
* Type safety
|
- Type safety
|
||||||
* Written in C
|
- Written in C and Fun
|
||||||
* Internal libs are written with no_camel_case even when written in 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 libraries are implemented in Fun
|
- Only a minimal function set is written in C, and most other core functions and libraries are implemented in Fun
|
||||||
|
|
||||||
## The Fun Manifesto
|
## The Fun Manifesto
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ Fun may not change the world — but it will make programming a little more fun.
|
||||||
|
|
||||||
## Documentation
|
## 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
|
## 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)
|
- 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)"
|
- 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](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/)
|
- GNU/Linux ([Alpine](https://alpinelinux.org/)) using GCC and the [musl libc](https://musl.libc.org/)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
// Demonstrates local include:
|
// Demonstrates local include:
|
||||||
// This resolves relative to the current working directory where 'fun' is executed.
|
// 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 ==")
|
print("== include local demo ==")
|
||||||
greet("Fun")
|
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)
|
b64 = b64_encode_bytes(bytes)
|
||||||
print(b64) // "SGVsbG8="
|
print(b64) // "SGVsbG8="
|
||||||
print(join(b64_decode_to_bytes(b64), ",")) // "72,101,108,108,111"
|
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
|
// OS
|
||||||
OP_ENV, // pops name string; pushes value string (or "")
|
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
|
// Threads
|
||||||
OP_THREAD_SPAWN, // operand: 0=no args, 1=has args; pops [args?], fn; pushes thread id (int>0)
|
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);
|
free(name);
|
||||||
return 1;
|
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) {
|
if (strcmp(name, "env") == 0) {
|
||||||
(*pos)++; /* '(' */
|
(*pos)++; /* '(' */
|
||||||
if (!emit_expression(bc, src, len, pos)) { parser_fail(*pos, "env expects 1 argument"); free(name); return 0; }
|
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/read_file.c"
|
||||||
#include "vm/io/write_file.c"
|
#include "vm/io/write_file.c"
|
||||||
|
#include "vm/io/input_line.c"
|
||||||
|
|
||||||
#include "vm/logic/and.c"
|
#include "vm/logic/and.c"
|
||||||
#include "vm/logic/eq.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",
|
"ENUMERATE","ZIP",
|
||||||
"MIN","MAX","CLAMP","ABS","POW","RANDOM_SEED","RANDOM_INT",
|
"MIN","MAX","CLAMP","ABS","POW","RANDOM_SEED","RANDOM_INT",
|
||||||
"MAKE_MAP","KEYS","VALUES","HAS_KEY",
|
"MAKE_MAP","KEYS","VALUES","HAS_KEY",
|
||||||
"READ_FILE","WRITE_FILE","ENV",
|
"READ_FILE","WRITE_FILE","ENV","INPUT_LINE",
|
||||||
"THREAD_SPAWN","THREAD_JOIN","SLEEP_MS",
|
"THREAD_SPAWN","THREAD_JOIN","SLEEP_MS",
|
||||||
"BAND","BOR","BXOR","BNOT","SHL","SHR","ROTL","ROTR"
|
"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