summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md68
-rw-r--r--plugins/regex.c4
-rw-r--r--src/ast/ast.h2
-rw-r--r--src/codegen/codegen.h2
-rw-r--r--src/codegen/codegen_decl.c9
-rw-r--r--src/codegen/codegen_main.c2
-rw-r--r--src/main.c4
-rw-r--r--src/parser/parser.h7
-rw-r--r--src/parser/parser_expr.c4
-rw-r--r--src/parser/parser_stmt.c54
-rw-r--r--src/parser/parser_utils.c32
-rw-r--r--src/utils/utils.c25
12 files changed, 176 insertions, 37 deletions
diff --git a/README.md b/README.md
index 2827662..256c022 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,14 @@ zc build hello.zc -o hello
zc repl
```
+### Environment Variables
+
+You can set `ZC_ROOT` to specify the location of the Standard Library (standard imports like `import "std/vector.zc"`). This allows you to run `zc` from any directory.
+
+```bash
+export ZC_ROOT=/path/to/Zen-C
+```
+
---
## Language Reference
@@ -238,7 +246,51 @@ unless is_valid { return; }
| `?.` | Safe Navigation (`ptr?.field`) | - |
| `?` | Try Operator (`res?` returns error if present) | - |
-### 7. Memory Management
+### 7. Printing and String Interpolation
+
+Zen C provides versatile options for printing to the console, including keywords and concise shorthands.
+
+#### Keywords
+
+- `print "text"`: Prints to `stdout` without a trailing newline.
+- `println "text"`: Prints to `stdout` with a trailing newline.
+- `eprint "text"`: Prints to `stderr` without a trailing newline.
+- `eprintln "text"`: Prints to `stderr` with a trailing newline.
+
+#### Shorthands
+
+Zen C allows you to use string literals directly as statements for quick printing:
+
+- `"Hello World"`: Equivalent to `println "Hello World"`. (Implicitly adds newline)
+- `"Hello World"..`: Equivalent to `print "Hello World"`. (No trailing newline)
+- `!"Error"`: Equivalent to `eprintln "Error"`. (Output to stderr)
+- `!"Error"..`: Equivalent to `eprint "Error"`. (Output to stderr, no newline)
+
+#### String Interpolation (F-strings)
+
+You can embed expressions directly into string literals using `{}` syntax. This works with all printing methods and string shorthands.
+
+```zc
+var x = 42;
+var name = "Zen";
+println "Value: {x}, Name: {name}";
+"Value: {x}, Name: {name}"; // shorthand println
+```
+
+#### Input Prompts (`?`)
+
+Zen C supports a shorthand for prompting user input using the `?` prefix.
+
+- `? "Prompt text"`: Prints the prompt (without newline) and waits for input (reads a line).
+- `? "Enter age: " (age)`: Prints prompt and scans input into the variable `age`.
+ - Format specifiers are automatically inferred based on variable type.
+
+```c
+var age = "How old are you? ";
+println "You are {age} years old.";
+```
+
+### 8. Memory Management
Zen C allows manual memory management with ergonomic aids.
@@ -265,7 +317,7 @@ impl Drop for MyStruct {
}
```
-### 8. Object Oriented Programming
+### 9. Object Oriented Programming
#### Methods
Define methods on types using `impl`.
@@ -305,7 +357,7 @@ struct Player {
}
```
-### 9. Generics
+### 10. Generics
Type-safe templates for Structs and Functions.
@@ -321,7 +373,7 @@ fn identity<T>(val: T) -> T {
}
```
-### 10. Concurrency (Async/Await)
+### 11. Concurrency (Async/Await)
Built on pthreads.
@@ -337,7 +389,7 @@ fn main() {
}
```
-### 11. Metaprogramming
+### 12. Metaprogramming
#### Comptime
Run code at compile-time to generate source or print messages.
@@ -366,7 +418,7 @@ Pass preprocessor macros through to C.
#define MAX_BUFFER 1024
```
-### 12. Attributes
+### 13. Attributes
Decorate functions and structs to modify compiler behavior.
@@ -386,7 +438,7 @@ Decorate functions and structs to modify compiler behavior.
| `@noreturn` | Fn | Function does not return (e.g. exit). |
| `@derived(...)` | Struct | Auto-implement traits (e.g. `Debug`). |
-### 13. Inline Assembly
+### 14. Inline Assembly
Zen C provides first-class support for inline assembly, transpiling directly to GCC-style extended `asm`.
@@ -435,7 +487,7 @@ fn add(a: int, b: int) -> int {
> **Note:** When using Intel syntax (via `-masm=intel`), you must ensure your build is configured correctly (for example, `//> cflags: -masm=intel`). TCC does not support Intel syntax assembly.
-### 14. Build Directives
+### 15. Build Directives
Zen C supports special comments at the top of your source file to configure the build process without needing a complex build system or Makefile.
diff --git a/plugins/regex.c b/plugins/regex.c
index b56515c..8fe7c67 100644
--- a/plugins/regex.c
+++ b/plugins/regex.c
@@ -5,7 +5,7 @@
#include <stdlib.h>
#include <string.h>
-static void emit_match_logic(const char *pattern, FILE *out, int *label_counter);
+static void emit_match_logic(const char *pattern, FILE *out);
void regex_transpile(const char *input_body, const ZApi *api)
{
@@ -47,7 +47,7 @@ void regex_transpile(const char *input_body, const ZApi *api)
free(pattern);
}
-static void emit_match_logic(const char *pattern, FILE *out, int * /*label_counter*/)
+static void emit_match_logic(const char *pattern, FILE *out)
{
const char *c = pattern;
diff --git a/src/ast/ast.h b/src/ast/ast.h
index 8c5bff4..1e14369 100644
--- a/src/ast/ast.h
+++ b/src/ast/ast.h
@@ -416,6 +416,8 @@ struct ASTNode
struct
{
char *content;
+ char **used_symbols;
+ int used_symbol_count;
} raw_stmt;
struct
diff --git a/src/codegen/codegen.h b/src/codegen/codegen.h
index d489fb3..4b66f99 100644
--- a/src/codegen/codegen.h
+++ b/src/codegen/codegen.h
@@ -24,7 +24,7 @@ const char *parse_original_method_name(const char *mangled);
void emit_auto_type(ParserContext *ctx, ASTNode *init_expr, Token t, FILE *out);
// Declaration emission (codegen_decl.c).
-void emit_preamble(FILE *out);
+void emit_preamble(ParserContext *ctx, FILE *out);
void emit_includes_and_aliases(ASTNode *node, FILE *out);
void emit_struct_defs(ParserContext *ctx, ASTNode *node, FILE *out);
void emit_trait_defs(ASTNode *node, FILE *out);
diff --git a/src/codegen/codegen_decl.c b/src/codegen/codegen_decl.c
index ac4ae14..4979236 100644
--- a/src/codegen/codegen_decl.c
+++ b/src/codegen/codegen_decl.c
@@ -8,7 +8,7 @@
#include <string.h>
// Emit C preamble with standard includes and type definitions.
-void emit_preamble(FILE *out)
+void emit_preamble(ParserContext *ctx, FILE *out)
{
if (g_config.is_freestanding)
{
@@ -54,8 +54,11 @@ void emit_preamble(FILE *out)
fputs("#include <unistd.h>\n#include <fcntl.h>\n", out); // POSIX functions
fputs("#ifdef __TINYC__\n#define __auto_type __typeof__\n#endif\n", out);
fputs("typedef size_t usize;\ntypedef char* string;\n", out);
- fputs("#include <pthread.h>\n", out);
- fputs("typedef struct { pthread_t thread; void *result; } Async;\n", out);
+ if (ctx->has_async)
+ {
+ fputs("#include <pthread.h>\n", out);
+ fputs("typedef struct { pthread_t thread; void *result; } Async;\n", out);
+ }
fputs("typedef struct { void *func; void *ctx; } z_closure_T;\n", out);
fputs("#define U0 void\n#define I8 int8_t\n#define U8 uint8_t\n#define I16 "
"int16_t\n#define U16 uint16_t\n",
diff --git a/src/codegen/codegen_main.c b/src/codegen/codegen_main.c
index c9c69f6..3634af3 100644
--- a/src/codegen/codegen_main.c
+++ b/src/codegen/codegen_main.c
@@ -202,7 +202,7 @@ void codegen_node(ParserContext *ctx, ASTNode *node, FILE *out)
if (!ctx->skip_preamble)
{
- emit_preamble(out);
+ emit_preamble(ctx, out);
}
emit_includes_and_aliases(kids, out);
diff --git a/src/main.c b/src/main.c
index d0e1530..580b555 100644
--- a/src/main.c
+++ b/src/main.c
@@ -306,9 +306,9 @@ int main(int argc, char **argv)
// TCC-specific adjustments?
// Already handled by user passing --cc tcc
- snprintf(cmd, sizeof(cmd), "%s %s %s %s %s -o %s out.c -lm -lpthread -I./src %s", g_config.cc,
+ snprintf(cmd, sizeof(cmd), "%s %s %s %s %s -o %s out.c -lm %s -I./src %s", g_config.cc,
g_config.gcc_flags, g_cflags, g_config.is_freestanding ? "-ffreestanding" : "", "",
- outfile, g_link_flags);
+ outfile, g_parser_ctx->has_async ? "-lpthread" : "", g_link_flags);
if (g_config.verbose)
{
diff --git a/src/parser/parser.h b/src/parser/parser.h
index 4aeb2c8..9cdd0d2 100644
--- a/src/parser/parser.h
+++ b/src/parser/parser.h
@@ -265,6 +265,7 @@ struct ParserContext
FILE *hoist_out; // For plugins to hoist code to file scope
int skip_preamble; // If 1, codegen_node(NODE_ROOT) won't emit preamble
int is_repl; // REPL mode flag
+ int has_async; // Track if async features are used
};
// Token helpers
@@ -376,7 +377,7 @@ void init_builtins();
// Expression rewriting
char *rewrite_expr_methods(ParserContext *ctx, char *raw);
-char *process_fstring(ParserContext *ctx, const char *content);
+char *process_fstring(ParserContext *ctx, const char *content, char ***used_syms, int *count);
char *instantiate_function_template(ParserContext *ctx, const char *name,
const char *concrete_type);
FuncSig *find_func(ParserContext *ctx, const char *name);
@@ -408,8 +409,8 @@ ASTNode *parse_guard(ParserContext *ctx, Lexer *l);
ASTNode *parse_match(ParserContext *ctx, Lexer *l);
ASTNode *parse_return(ParserContext *ctx, Lexer *l);
-char *process_printf_sugar(ParserContext *ctx, const char *content, int newline,
- const char *target);
+char *process_printf_sugar(ParserContext *ctx, const char *content, int newline, const char *target,
+ char ***used_syms, int *count);
ASTNode *parse_assert(ParserContext *ctx, Lexer *l);
ASTNode *parse_defer(ParserContext *ctx, Lexer *l);
ASTNode *parse_asm(ParserContext *ctx, Lexer *l);
diff --git a/src/parser/parser_expr.c b/src/parser/parser_expr.c
index f7af80f..9861de6 100644
--- a/src/parser/parser_expr.c
+++ b/src/parser/parser_expr.c
@@ -2240,7 +2240,7 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec)
}
// Reuse printf sugar to generate the prompt print
- char *print_code = process_printf_sugar(ctx, inner, 0, "stdout");
+ char *print_code = process_printf_sugar(ctx, inner, 0, "stdout", NULL, NULL);
free(inner);
// Checks for (args...) suffix for SCAN mode
@@ -2405,7 +2405,7 @@ ASTNode *parse_expr_prec(ParserContext *ctx, Lexer *l, Precedence min_prec)
newline = 0;
}
- char *code = process_printf_sugar(ctx, inner, newline, "stderr");
+ char *code = process_printf_sugar(ctx, inner, newline, "stderr", NULL, NULL);
free(inner);
ASTNode *n = ast_create(NODE_RAW_STMT);
diff --git a/src/parser/parser_stmt.c b/src/parser/parser_stmt.c
index 8a89cc7..484d742 100644
--- a/src/parser/parser_stmt.c
+++ b/src/parser/parser_stmt.c
@@ -37,6 +37,11 @@ ASTNode *parse_function(ParserContext *ctx, Lexer *l, int is_async)
Token name_tok = lexer_next(l);
char *name = token_strdup(name_tok);
+ if (is_async)
+ {
+ ctx->has_async = 1;
+ }
+
// Check for C reserved word conflict
if (is_c_reserved_word(name))
{
@@ -1668,7 +1673,8 @@ ASTNode *parse_for(ParserContext *ctx, Lexer *l)
return n;
}
-char *process_printf_sugar(ParserContext *ctx, const char *content, int newline, const char *target)
+char *process_printf_sugar(ParserContext *ctx, const char *content, int newline, const char *target,
+ char ***used_syms, int *count)
{
char *gen = xmalloc(8192);
strcpy(gen, "({ ");
@@ -1760,12 +1766,34 @@ char *process_printf_sugar(ParserContext *ctx, const char *content, int newline,
clean_expr++; // Skip leading spaces
}
- // Mark the variable as used (fixes "Unused variable" warnings for f-string
- // interpolation)
- Symbol *sym = find_symbol_entry(ctx, clean_expr);
- if (sym)
+ // Analyze usage
{
- sym->is_used = 1;
+ Lexer lex;
+ lexer_init(&lex, clean_expr);
+ Token t;
+ while ((t = lexer_next(&lex)).type != TOK_EOF)
+ {
+ if (t.type == TOK_IDENT)
+ {
+ char *name = token_strdup(t);
+ Symbol *sym = find_symbol_entry(ctx, name);
+ if (sym)
+ {
+ sym->is_used = 1;
+ }
+
+ if (used_syms && count)
+ {
+ *used_syms = xrealloc(*used_syms, sizeof(char *) * (*count + 1));
+ (*used_syms)[*count] = name;
+ (*count)++;
+ }
+ else
+ {
+ free(name);
+ }
+ }
+ }
}
char *type = find_symbol_type(ctx, clean_expr);
@@ -2092,7 +2120,9 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l)
// ; means println (end of line), .. means print (continuation)
int is_ln = (next_type == TOK_SEMICOLON);
- char *code = process_printf_sugar(ctx, inner, is_ln, "stdout");
+ char **used_syms = NULL;
+ int used_count = 0;
+ char *code = process_printf_sugar(ctx, inner, is_ln, "stdout", &used_syms, &used_count);
if (next_type == TOK_SEMICOLON)
{
@@ -2109,6 +2139,8 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l)
ASTNode *n = ast_create(NODE_RAW_STMT);
n->raw_stmt.content = code;
+ n->raw_stmt.used_symbols = used_syms;
+ n->raw_stmt.used_symbol_count = used_count;
free(inner);
return n;
}
@@ -2501,7 +2533,9 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l)
inner[t.len - 2] = 0;
}
- char *code = process_printf_sugar(ctx, inner, is_ln, target);
+ char **used_syms = NULL;
+ int used_count = 0;
+ char *code = process_printf_sugar(ctx, inner, is_ln, target, &used_syms, &used_count);
free(inner);
if (lexer_peek(l).type == TOK_SEMICOLON)
@@ -2511,6 +2545,8 @@ ASTNode *parse_statement(ParserContext *ctx, Lexer *l)
ASTNode *n = ast_create(NODE_RAW_STMT);
n->raw_stmt.content = code;
+ n->raw_stmt.used_symbols = used_syms;
+ n->raw_stmt.used_symbol_count = used_count;
return n;
}
}
@@ -3876,7 +3912,7 @@ char *run_comptime_block(ParserContext *ctx, Lexer *l)
zpanic("Could not create temp file %s", filename);
}
- emit_preamble(f);
+ emit_preamble(ctx, f);
fprintf(
f,
"size_t _z_check_bounds(size_t index, size_t size) { if (index >= size) { fprintf(stderr, "
diff --git a/src/parser/parser_utils.c b/src/parser/parser_utils.c
index 7733ef7..ba506a0 100644
--- a/src/parser/parser_utils.c
+++ b/src/parser/parser_utils.c
@@ -1554,7 +1554,7 @@ char *instantiate_function_template(ParserContext *ctx, const char *name, const
return mangled;
}
-char *process_fstring(ParserContext *ctx, const char *content)
+char *process_fstring(ParserContext *ctx, const char *content, char ***used_syms, int *count)
{
(void)ctx; // suppress unused parameter warning
char *gen = xmalloc(4096);
@@ -1621,6 +1621,36 @@ char *process_fstring(ParserContext *ctx, const char *content)
fmt = colon + 1;
}
+ // Analyze usage in expression
+ {
+ Lexer lex;
+ lexer_init(&lex, expr);
+ Token t;
+ while ((t = lexer_next(&lex)).type != TOK_EOF)
+ {
+ if (t.type == TOK_IDENT)
+ {
+ char *name = token_strdup(t);
+ Symbol *sym = find_symbol_entry(ctx, name);
+ if (sym)
+ {
+ sym->is_used = 1;
+ }
+
+ if (used_syms && count)
+ {
+ *used_syms = xrealloc(*used_syms, sizeof(char *) * (*count + 1));
+ (*used_syms)[*count] = name;
+ (*count)++;
+ }
+ else
+ {
+ free(name);
+ }
+ }
+ }
+ }
+
if (fmt)
{
strcat(gen, "sprintf(_t, \"%");
diff --git a/src/utils/utils.c b/src/utils/utils.c
index 2645b49..19a722e 100644
--- a/src/utils/utils.c
+++ b/src/utils/utils.c
@@ -561,7 +561,10 @@ void scan_build_directives(ParserContext *ctx, const char *src)
if (0 == strncmp(line, "link:", 5))
{
char *val = line + 5;
- while (*val == ' ') val++;
+ while (*val == ' ')
+ {
+ val++;
+ }
if (strlen(g_link_flags) > 0)
{
strcat(g_link_flags, " ");
@@ -571,7 +574,10 @@ void scan_build_directives(ParserContext *ctx, const char *src)
else if (0 == strncmp(line, "cflags:", 7))
{
char *val = line + 7;
- while (*val == ' ') val++;
+ while (*val == ' ')
+ {
+ val++;
+ }
if (strlen(g_cflags) > 0)
{
strcat(g_cflags, " ");
@@ -581,7 +587,10 @@ void scan_build_directives(ParserContext *ctx, const char *src)
else if (0 == strncmp(line, "include:", 8))
{
char *val = line + 8;
- while (*val == ' ') val++;
+ while (*val == ' ')
+ {
+ val++;
+ }
char flags[2048];
sprintf(flags, "-I%s", val);
if (strlen(g_cflags) > 0)
@@ -593,7 +602,10 @@ void scan_build_directives(ParserContext *ctx, const char *src)
else if (strncmp(line, "lib:", 4) == 0)
{
char *val = line + 4;
- while (*val == ' ') val++;
+ while (*val == ' ')
+ {
+ val++;
+ }
char flags[2048];
sprintf(flags, "-L%s", val);
if (strlen(g_link_flags) > 0)
@@ -605,7 +617,10 @@ void scan_build_directives(ParserContext *ctx, const char *src)
else if (strncmp(line, "define:", 7) == 0)
{
char *val = line + 7;
- while (*val == ' ') val++;
+ while (*val == ' ')
+ {
+ val++;
+ }
char flags[2048];
sprintf(flags, "-D%s", val);
if (strlen(g_cflags) > 0)