diff --git a/CMakeLists.txt b/CMakeLists.txt index 9b2b554..adcf949 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/README.md b/README.md index b6a4ca7..3febb58 100644 --- a/README.md +++ b/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/) diff --git a/examples/include_local.fun b/examples/include_local.fun index bb5cf8a..c819a44 100755 --- a/examples/include_local.fun +++ b/examples/include_local.fun @@ -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") diff --git a/examples/input_example.fun b/examples/input_example.fun new file mode 100755 index 0000000..fe09df1 --- /dev/null +++ b/examples/input_example.fun @@ -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 + * 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 + +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! 🎉 +*/ diff --git a/examples/stdlib_showcase.fun b/examples/stdlib_showcase.fun index 61b368e..eed9349 100644 --- a/examples/stdlib_showcase.fun +++ b/examples/stdlib_showcase.fun @@ -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 +*/ diff --git a/lib/io/console.fun b/lib/io/console.fun new file mode 100644 index 0000000..52c89ad --- /dev/null +++ b/lib/io/console.fun @@ -0,0 +1,31 @@ +/* + * Console utilities: prompt, ask, and yes/no helpers built on input(). + */ + +#include + +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 diff --git a/src/bytecode.h b/src/bytecode.h index 96b1c9f..9481a3c 100644 --- a/src/bytecode.h +++ b/src/bytecode.h @@ -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) diff --git a/src/parser.c b/src/parser.c index a7dfd5e..32f31bf 100644 --- a/src/parser.c +++ b/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; } diff --git a/src/vm.c b/src/vm.c index 4c53302..46f359b 100644 --- a/src/vm.c +++ b/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" diff --git a/src/vm.h b/src/vm.h index a053146..18fae9e 100644 --- a/src/vm.h +++ b/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" }; diff --git a/src/vm/io/input_line.c b/src/vm/io/input_line.c new file mode 100644 index 0000000..8c624d2 --- /dev/null +++ b/src/vm/io/input_line.c @@ -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; +}