From 4c52cba09bd9e044a78a231f4bfe76df5d5724b7 Mon Sep 17 00:00:00 2001 From: Reboy2000 Date: Fri, 6 Feb 2026 15:17:04 -0500 Subject: [PATCH 1/7] Added embedded functiality --- .DS_Store | Bin 8196 -> 10244 bytes .vscode/launch.json | 17 + COMMENTS.md | 46 + Docs/doc/docs.md | 37 +- bin/Splice | Bin 52360 -> 35464 bytes bin/spbuild | Bin 51672 -> 34824 bytes examples/array/array.spc | Bin 0 -> 194 bytes examples/array/array.spl | 2 +- examples/calculator/hello.spc | Bin 205 -> 205 bytes examples/forloops/for.spc | Bin 65 -> 71 bytes recurse.spc | Bin 98 -> 0 bytes recurse.spl | 9 - src/.DS_Store | Bin 0 -> 6148 bytes src/build.c | 1133 ++++++++++--------- src/module_stubs.c | 101 +- src/sdk.h | 3 +- src/splice.c | 100 +- src/splice.h | 2005 +++++---------------------------- 18 files changed, 1131 insertions(+), 2322 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 COMMENTS.md create mode 100644 examples/array/array.spc delete mode 100644 recurse.spc delete mode 100644 recurse.spl create mode 100644 src/.DS_Store diff --git a/.DS_Store b/.DS_Store index 12b0a34c378b88ca43660da743becfe513f8ab51..0a83c1eb84d2a0c1b0cca72e018a5ebfcbd4e633 100644 GIT binary patch literal 10244 zcmeHMYitx%6h3EKV8#wGt>vMz>DKZRN@;m1BG@OPKq=`KXv?GQ?hNh7?993|yX7IK z#Na!T;3Lr=0=|Bt2@pwCg0JWw9}t3(A4U^3M2+zWN-*(p?%dgSW?S@+5)V4Z$uM3&Rn8M_F(kY#gbMkJwh{Ngb0KP zgb0KPgb0KP{8td5HJdfDgwyaEA`l`FA~1vidp|_!$7sDr2RU_L9dvPz07Q$4?KZks z*bULBN9#R0$SFyvAvYz;P0>4IAU7v|)YGf?=pd)u9MC&_pf@vmClqvNr}3k%I$%Ag z;Wb1cL|`NW?Cf3)HP8n-IDzx~n4fR=%TiWzbHcC^#1p%xo@_6DVRVy5IM0 zKys^eTz+2Yhb$xlv%`&-Y4idOzYiT#1`d}UaD5n|HSEc?wF3U|mrW5Q2nDm%u##S+ z%uN<$%^z-~I{;5B;4MCPa9HB^a$VK?muen#z4Yu-*06UYr2((-2%AqNud?Kq0V?5T zLw|vPuab`6KM^$81#w70nrt+%gZdcxJN-apP`SS7FY){Xeo%|v4SWh=pfAoXofbTH59#CU~#p;;s(eNIt-yWp{k8$ zBlH#6j3G^is$Q6_z;KuGZUljQMQu&8b= z@;nVD^abN}^Y-~b-r5v#vOtSv&nQ}n0Xyy0z^O#q&~2x-_5un;#bd^f6UT`q;?`Kd zH4t;+PS$RWXEw85r)3$5{CzyJQP&1~mGUObbmF?HWn2SY)ktN_Cf!u6fkrE9s!F8W zI{FS>%SfdtNS$UprJXOEdehCfOp8PcwezENWfS_nS>4&KREZ*=qcTz@DJR7(DmmF^ zscb~ml|(8dvqW(-<2PkAx^lKWCsHjbak0}TfcOyeCfIT2$S!GA&H7HI?GNdFoANk>gVv=sxzr0?wt#>RGwGxDBBQ6U?$u_TklTTL1n%l9*4tl z0^Wkt@DW^qFW_tV5w60o@EiP&MJQt#PQ&Rq59ebo*5RGF442~yT!Wps0XJd-ccOtN zTIk?DydMW~KR$?0;6XfuPvW!q0=|eZ<12U!U&mAU2EK{!J8aEMc~Ao3KDwC@dD12(7|uAtr1QG~pg$CyznMmnK_6d`-BII{7-ZbKZ+a zI6iu{Rjg`Vy+&^P&sW>$6IgxSSUzd;l<15(^B3K*ym2T6dD7ZQ4VA2d=LSm|*)o5Zur#WJR0nQR}zim4bJTK8>mf~&Z@r}MAkLwbQ_ z4uGxpLo&a|Zyy@kP5b1-JCxDn0k8S7U8bF?$@b99N7`2z^f$_$71`SC_m{e13lRtr zxUL8k@!6s!L|d-?|NraiQ#e_OK#0KgMF5MNW6g~;XMVq4IK4oODE+q6k2R(@$f;{W z7pB7a?&o;wu+Q;OQZ;(^hoa|h4qWo+AzyHsGWME?yp8v!1|Nrg$|0mHr B5KaI9 delta 105 zcmZn(XmOBWU|?W$DortDU;r^WfEYvza8FDWo2aMAsJ1a+H$S7=W*z}CmdVVbQj +#include +#include +#include "splice.h" +// This code will tell splice that after 100 recursions to stop. +splice_set_call_depth(100); +ASTNode *root = read_ast_from_spc(arg); +interpret(root); +free_ast(root); +``` +#### ``splice_disable_tick_limit`` +This function is designed to disable Splice's default **Tick Limit** (Currently set to ``1000000``) +This function works cleanly with the [``splice_set_tick``](#splice_set_tick) + +#### ``splice_set_tick`` + + +> This function **will only** work when ``splice_disable_tick_limit`` is in the script + +This function is desinged to set how many instructions will run before exiting + +--- © Copyright 2026 OpenSplice and the Sinha Group -Licensed under the MIT License \ No newline at end of file +Licensed under the MIT License diff --git a/bin/Splice b/bin/Splice index 3fa83d37d5ecbbeecf179bc6fab13bc9b0eb7e7d..21ccd7cab1d08c06b5187363195650b8daff8601 100755 GIT binary patch literal 35464 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 diff --git a/bin/spbuild b/bin/spbuild index a618b4fc025636da44758af4bab5432868bc7bb5..57c3526226e8d1e7df91840dc2ea7fa9a0da5af1 100755 GIT binary patch literal 34824 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 AaR2}S literal 205 zcmWFzaAsf>U}j)o5aeP=PRvOz%}FfDF9Hd$Fyxiy8iGk9CWib10Y;!8A2Sn!tt}IX z&&bH24Hjc#2C|qS${~~ysxn=;GF?<<|Ao*k1zG_DA`W1Rfq}yTWDP-O5+G%|03<#h AT>t<8 diff --git a/examples/forloops/for.spc b/examples/forloops/for.spc index 36e3e3f01c388710506c087de4d41c1a0a86d09c..75bb5b6f5ebab3c0a35ae1aa58d4bf7821135c76 100644 GIT binary patch literal 71 zcmWFzaAsf>Vq#!mU}qEtl5Y7$3OV`t1;q-Pc?!V=Iho0+;*3D?Odtk<5B3n6LB&A` KB+kwRmIVO1A_|-U literal 65 zcmWFzaAsiSXJTMrU}F?!aLX@J$jQ$yC|1bKQwT1|$xKcaVPwbz%7VZLdkD>-;=s=c JWU?^<Vq#!mU}0p)1X2*d&&H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 #include #include +#include #include -#include "splice.h" - /* uses AST types + write_ast_to_spc */ -ASTNode* ast_tuple(ASTNode** items, int count) { - ASTNode* node = malloc(sizeof(ASTNode)); - node->type = AST_TUPLE; - node->tuple.items = items; - node->tuple.count = count; - return node; +/* ========================================================= + This builder emits SPC compatible with the simplified VM + header you posted: + - u8 node type + - strings: u32 len + bytes + - numbers: 8 bytes raw (double) + - STATEMENTS: u32 count + nodes + - LET/ASSIGN: str name + node + - BINARY_OP: str op + left + right + - PRINT: node + - WHILE: cond + body + - IF: cond + then + else (else can be NULL node) + - FOR: var + start + end + body + - FUNC_DEF: name + body + - FUNCTION_CALL: name + - RETURN: expr (can be NULL node) + - BREAK/CONTINUE: no payload + ========================================================= */ + +/* ===== SPC header ===== */ +#define SPC_MAGIC "SPC\0" +#define SPC_VERSION 1 + +/* ===== AST types (MUST MATCH YOUR VM) ===== */ +typedef enum { + AST_NUMBER = 0, + AST_STRING, + AST_IDENTIFIER, + AST_BINARY_OP, + AST_LET, + AST_ASSIGN, + AST_BREAK, + AST_PRINT, + AST_CONTINUE, + AST_WHILE, + AST_IF, + AST_STATEMENTS, + AST_FUNC_DEF, + AST_FUNCTION_CALL, + AST_RETURN, + AST_FOR +} ASTNodeType; + +typedef struct ASTNode ASTNode; + +struct ASTNode { + ASTNodeType type; + union { + double number; + char *string; + + struct { char *op; ASTNode *left; ASTNode *right; } binop; + struct { char *name; ASTNode *value; } var; + struct { ASTNode *expr; } print; + struct { ASTNode *cond; ASTNode *body; } whilestmt; + struct { ASTNode *cond; ASTNode *then_b; ASTNode *else_b; } ifstmt; + struct { ASTNode **stmts; int count; } statements; + struct { char *name; ASTNode *body; } funcdef; + struct { char *name; } funccall; + struct { ASTNode *expr; } retstmt; + struct { char *var; ASTNode *start; ASTNode *end; ASTNode *body; } forstmt; + }; +}; + +/* ===== utils ===== */ +static void die(const char *msg) { fprintf(stderr, "%s\n", msg); exit(1); } + +static void *xmalloc(size_t n) { + void *p = malloc(n); + if (!p) die("spbuild: OOM"); + return p; +} +static void *xrealloc(void *p, size_t n) { + void *q = realloc(p, n); + if (!q) die("spbuild: OOM"); + return q; +} +static char *xstrdup(const char *s) { + if (!s) s = ""; + size_t n = strlen(s); + char *p = (char*)xmalloc(n + 1); + memcpy(p, s, n + 1); + return p; } -static inline int write_ast_to_spc(const char *out_file, const ASTNode *root) { - FILE *f = fopen(out_file, "wb"); - if (!f) return 0; +/* ===== AST constructors ===== */ +static ASTNode *ast_new(ASTNodeType t) { + ASTNode *n = (ASTNode*)xmalloc(sizeof(ASTNode)); + memset(n, 0, sizeof(*n)); + n->type = t; + return n; +} - fwrite(SPC_MAGIC, 1, 4, f); - w_u8(f, (unsigned char)SPC_VERSION); +static ASTNode *ast_number(double v) { ASTNode *n=ast_new(AST_NUMBER); n->number=v; return n; } +static ASTNode *ast_string(const char *s) { ASTNode *n=ast_new(AST_STRING); n->string=xstrdup(s); return n; } +static ASTNode *ast_ident(const char *s) { ASTNode *n=ast_new(AST_IDENTIFIER); n->string=xstrdup(s); return n; } - write_ast_node(f, root); - fclose(f); - return 1; +static ASTNode *ast_binop(const char *op, ASTNode *l, ASTNode *r) { + ASTNode *n = ast_new(AST_BINARY_OP); + n->binop.op = xstrdup(op); + n->binop.left = l; + n->binop.right = r; + return n; } -static const char *resolve_builtin_import(const char *name) { -#ifdef _WIN32 - const char *prefix = "C:\\Program Files\\Splice\\"; -#else - const char *prefix = "/usr/local/bin/"; -#endif - - if (strcmp(name, "math") == 0) { - static char path[256]; - snprintf(path, sizeof(path), "%ssplib/math.spc", prefix); - return path; - } - if (strcmp(name, "io") == 0) { - static char path[256]; - snprintf(path, sizeof(path), "%ssplib/io.spc", prefix); - return path; - } +static ASTNode *ast_print(ASTNode *e) { ASTNode *n=ast_new(AST_PRINT); n->print.expr=e; return n; } - return name; /* fallback: user-provided path */ +static ASTNode *ast_var(ASTNodeType t, const char *name, ASTNode *val) { + ASTNode *n = ast_new(t); + n->var.name = xstrdup(name); + n->var.value = val; + return n; } +static ASTNode *ast_statements(ASTNode **stmts, int count) { + ASTNode *n=ast_new(AST_STATEMENTS); + n->statements.stmts=stmts; + n->statements.count=count; + return n; +} -/* ========================= - Read whole file - ========================= */ -static char *read_text_file(const char *path) { - FILE *f = fopen(path, "rb"); - if (!f) return NULL; - fseek(f, 0, SEEK_END); - long n = ftell(f); - rewind(f); - char *buf = (char*)malloc((size_t)n + 1); - if (!buf) { fclose(f); return NULL; } - if (fread(buf, 1, (size_t)n, f) != (size_t)n) { fclose(f); free(buf); return NULL; } - buf[n] = 0; - fclose(f); - return buf; +static ASTNode *ast_while(ASTNode *c, ASTNode *b) { + ASTNode *n=ast_new(AST_WHILE); + n->whilestmt.cond=c; n->whilestmt.body=b; + return n; } -/* ========================= - Tokenizer - ========================= */ -typedef enum { - TK_EOF=0, - TK_IDENT, - TK_NUMBER, - TK_STRING, +static ASTNode *ast_if(ASTNode *c, ASTNode *t, ASTNode *e) { + ASTNode *n=ast_new(AST_IF); + n->ifstmt.cond=c; n->ifstmt.then_b=t; n->ifstmt.else_b=e; + return n; +} - TK_LET, TK_FUNC, TK_RETURN, TK_PRINT, TK_INPUT, - TK_READ, TK_WRITE, - TK_IF, TK_ELSE, TK_WHILE, TK_FOR, TK_IN, - TK_RAISE, - TK_TRUE, TK_FALSE, - TK_AND, TK_OR, TK_NOT,TK_DOTDOT,TK_BREAK,TK_CONTINUE, +static ASTNode *ast_for(const char *v, ASTNode *s, ASTNode *e, ASTNode *b) { + ASTNode *n=ast_new(AST_FOR); + n->forstmt.var=xstrdup(v); + n->forstmt.start=s; n->forstmt.end=e; n->forstmt.body=b; + return n; +} +static ASTNode *ast_funcdef(const char *name, ASTNode *body) { + ASTNode *n=ast_new(AST_FUNC_DEF); + n->funcdef.name=xstrdup(name); + n->funcdef.body=body; + return n; +} +static ASTNode *ast_call0(const char *name) { + ASTNode *n=ast_new(AST_FUNCTION_CALL); + n->funccall.name=xstrdup(name); + return n; +} - TK_LPAREN, TK_RPAREN, - TK_LBRACE, TK_RBRACE, - TK_LBRACKET, TK_RBRACKET, - TK_COMMA, TK_SEMI, - TK_DOT,TK_QUIT, +static ASTNode *ast_return(ASTNode *e) { ASTNode *n=ast_new(AST_RETURN); n->retstmt.expr=e; return n; } + +/* ===== free AST (builder side) ===== */ +static void free_ast(ASTNode *n) { + if (!n) return; + switch (n->type) { + case AST_STRING: + case AST_IDENTIFIER: + free(n->string); + break; + case AST_BINARY_OP: + free(n->binop.op); + free_ast(n->binop.left); + free_ast(n->binop.right); + break; + case AST_PRINT: + free_ast(n->print.expr); + break; + case AST_LET: + case AST_ASSIGN: + free(n->var.name); + free_ast(n->var.value); + break; + case AST_STATEMENTS: + for (int i=0;istatements.count;i++) free_ast(n->statements.stmts[i]); + free(n->statements.stmts); + break; + case AST_WHILE: + free_ast(n->whilestmt.cond); + free_ast(n->whilestmt.body); + break; + case AST_IF: + free_ast(n->ifstmt.cond); + free_ast(n->ifstmt.then_b); + free_ast(n->ifstmt.else_b); + break; + case AST_FOR: + free(n->forstmt.var); + free_ast(n->forstmt.start); + free_ast(n->forstmt.end); + free_ast(n->forstmt.body); + break; + case AST_FUNC_DEF: + free(n->funcdef.name); + free_ast(n->funcdef.body); + break; + case AST_FUNCTION_CALL: + free(n->funccall.name); + break; + case AST_RETURN: + free_ast(n->retstmt.expr); + break; + default: + break; + } + free(n); +} + +/* ========================================================= + LEXER + ========================================================= */ + +typedef enum { + TK_EOF=0, + TK_IDENT, TK_NUMBER, TK_STRING, - TK_ASSIGN, /* = */ - TK_PLUS, TK_MINUS, TK_STAR, TK_SLASH, TK_IMPORT, + TK_LET, TK_PRINT, TK_IF, TK_ELSE, TK_WHILE, TK_FOR, TK_IN, + TK_FUNC, TK_RETURN, TK_BREAK, TK_CONTINUE, - TK_LT, TK_GT, TK_LE, TK_GE, TK_EQ, TK_NEQ + TK_LPAREN, TK_RPAREN, TK_LBRACE, TK_RBRACE, + TK_COMMA, TK_SEMI, + TK_ASSIGN, + + TK_PLUS, TK_MINUS, TK_STAR, TK_SLASH, TK_MOD, + TK_LT, TK_GT, TK_LE, TK_GE, TK_EQ, TK_NEQ, + TK_AND, TK_OR, TK_NOT, + TK_DOTDOT } TokType; typedef struct { TokType t; - char *lex; /* for IDENT/STRING */ - double num; /* for NUMBER */ + char *lex; + double num; int line; } Tok; @@ -107,106 +249,85 @@ typedef struct { int cap; } TokVec; -static void tv_init(TokVec *v) { v->data=NULL; v->count=0; v->cap=0; } -static void tv_push(TokVec *v, Tok x) { +static void tv_push(TokVec *v, Tok t) { if (v->count >= v->cap) { v->cap = v->cap ? v->cap*2 : 256; - Tok *nd = (Tok*)realloc(v->data, sizeof(Tok) * (size_t)v->cap); - if (!nd) error(0, "Splice/SystemError oom realloc tokens"); - v->data = nd; + v->data = (Tok*)xrealloc(v->data, sizeof(Tok)*(size_t)v->cap); } - v->data[v->count++] = x; -} -static void tv_free(TokVec *v) { - for (int i=0;icount;i++) free(v->data[i].lex); - free(v->data); + v->data[v->count++] = t; } -static int is_boundary(char c) { return !(isalnum((unsigned char)c) || c=='_'); } +static TokType kw_type(const char *id) { + if (!strcmp(id,"let")) return TK_LET; + if (!strcmp(id,"print")) return TK_PRINT; + if (!strcmp(id,"if")) return TK_IF; + if (!strcmp(id,"else")) return TK_ELSE; + if (!strcmp(id,"while")) return TK_WHILE; + if (!strcmp(id,"for")) return TK_FOR; + if (!strcmp(id,"in")) return TK_IN; + if (!strcmp(id,"func")) return TK_FUNC; + if (!strcmp(id,"return")) return TK_RETURN; + if (!strcmp(id,"break")) return TK_BREAK; + if (!strcmp(id,"continue")) return TK_CONTINUE; + return TK_IDENT; +} -static void tokenize(const char *src, TokVec *out) { +static void lex(const char *src, TokVec *out) { int line = 1; const char *p = src; while (*p) { - if (*p=='\n') { line++; p++; continue; } + if (*p == '\n') { line++; p++; tv_push(out,(Tok){.t=TK_SEMI,.line=line}); continue; } if (isspace((unsigned char)*p)) { p++; continue; } - /* comments // ... */ if (p[0]=='/' && p[1]=='/') { while (*p && *p!='\n') p++; continue; } - /* string "..." */ if (*p=='"') { p++; const char *s = p; - while (*p && *p!='"') p++; + while (*p && *p!='"') { + if (*p=='\\' && p[1]) p++; /* skip escaped char */ + p++; + } size_t n = (size_t)(p - s); - char *str = (char*)malloc(n+1); - if (!str) error(line, "Splice/SystemErroroom string"); - memcpy(str, s, n); - str[n]=0; - Tok t = { .t=TK_STRING, .lex=str, .line=line }; - tv_push(out, t); + char *buf = (char*)xmalloc(n+1); + /* simple unescape for \" and \\ and \n */ + size_t j=0; + for (size_t i=0;i= sizeof(tmp)) error(line, "Splice/OverflowError number too long"); - memcpy(tmp, s, n); - tmp[n]=0; - Tok t = { .t=TK_NUMBER, .num=strtod(tmp,NULL), .line=line }; - tv_push(out, t); + if (n >= sizeof(tmp)) die("number too long"); + memcpy(tmp,s,n); tmp[n]=0; + tv_push(out,(Tok){.t=TK_NUMBER,.num=strtod(tmp,NULL),.line=line}); continue; } - /* identifiers/keywords */ if (isalpha((unsigned char)*p) || *p=='_') { - const char *s = p; + const char *s=p; while (isalnum((unsigned char)*p) || *p=='_') p++; - size_t n = (size_t)(p - s); - char *id = (char*)malloc(n+1); - if (!id) error(line, "Splice/SystemErroroom ident"); - memcpy(id, s, n); - id[n]=0; - - TokType kw = TK_IDENT; - if (!strcmp(id,"let")) kw=TK_LET; - else if (!strcmp(id,"func")) kw=TK_FUNC; - else if (!strcmp(id,"return")) kw=TK_RETURN; - else if (!strcmp(id,"print")) kw=TK_PRINT; - else if (!strcmp(id,"raise")) kw=TK_RAISE; - else if (!strcmp(id,"if")) kw=TK_IF; - else if (!strcmp(id,"else")) kw=TK_ELSE; - else if (!strcmp(id,"while")) kw=TK_WHILE; - else if (!strcmp(id,"for")) kw=TK_FOR; - else if (!strcmp(id,"in")) kw=TK_IN; - else if (!strcmp(id,"read")) kw = TK_READ; - else if (!strcmp(id,"write")) kw = TK_WRITE; - else if (!strcmp(id,"true")) kw=TK_TRUE; - else if (!strcmp(id,"break")) kw = TK_BREAK; - else if (!strcmp(id, "continue")) kw = TK_CONTINUE; - else if (!strcmp(id,"false")) kw=TK_FALSE; - else if (!strcmp(id,"and")) kw=TK_AND; - else if (!strcmp(id,"or")) kw=TK_OR; - else if (!strcmp(id,"not")) kw=TK_NOT; - else if (!strcmp(id,"input")) kw=TK_INPUT; - else if (!strcmp(id,"import")) kw=TK_IMPORT; - else if (!strcmp(id,"quit")) kw=TK_QUIT; - - Tok t = { .t=kw, .lex=(kw==TK_IDENT? id : NULL), .line=line }; - if (kw!=TK_IDENT) free(id); - tv_push(out, t); + size_t n=(size_t)(p-s); + char *id=(char*)xmalloc(n+1); + memcpy(id,s,n); id[n]=0; + TokType t = kw_type(id); + if (t==TK_IDENT) tv_push(out,(Tok){.t=t,.lex=id,.line=line}); + else { free(id); tv_push(out,(Tok){.t=t,.line=line}); } continue; } @@ -215,540 +336,452 @@ static void tokenize(const char *src, TokVec *out) { if (p[0]=='!' && p[1]=='=') { tv_push(out,(Tok){.t=TK_NEQ,.line=line}); p+=2; continue; } if (p[0]=='<' && p[1]=='=') { tv_push(out,(Tok){.t=TK_LE,.line=line}); p+=2; continue; } if (p[0]=='>' && p[1]=='=') { tv_push(out,(Tok){.t=TK_GE,.line=line}); p+=2; continue; } - if (p[0]=='.' && p[1]=='.') { - tv_push(out, (Tok){ .t = TK_DOTDOT, .line = line }); - p += 2; - continue; - } - /* single-char */ + if (p[0]=='&' && p[1]=='&') { tv_push(out,(Tok){.t=TK_AND,.line=line}); p+=2; continue; } + if (p[0]=='|' && p[1]=='|') { tv_push(out,(Tok){.t=TK_OR,.line=line}); p+=2; continue; } + if (p[0]=='.' && p[1]=='.') { tv_push(out,(Tok){.t=TK_DOTDOT,.line=line}); p+=2; continue; } + + /* single char */ switch (*p) { - case '.': tv_push(out,(Tok){.t=TK_DOT,.line=line}); p++; break; case '(': tv_push(out,(Tok){.t=TK_LPAREN,.line=line}); p++; break; case ')': tv_push(out,(Tok){.t=TK_RPAREN,.line=line}); p++; break; case '{': tv_push(out,(Tok){.t=TK_LBRACE,.line=line}); p++; break; case '}': tv_push(out,(Tok){.t=TK_RBRACE,.line=line}); p++; break; - case '[': tv_push(out,(Tok){.t=TK_LBRACKET,.line=line}); p++; break; - case ']': tv_push(out,(Tok){.t=TK_RBRACKET,.line=line}); p++; break; case ',': tv_push(out,(Tok){.t=TK_COMMA,.line=line}); p++; break; case ';': tv_push(out,(Tok){.t=TK_SEMI,.line=line}); p++; break; - - - - - - case '=': tv_push(out,(Tok){.t=TK_ASSIGN,.line=line}); p++; break; case '+': tv_push(out,(Tok){.t=TK_PLUS,.line=line}); p++; break; case '-': tv_push(out,(Tok){.t=TK_MINUS,.line=line}); p++; break; case '*': tv_push(out,(Tok){.t=TK_STAR,.line=line}); p++; break; case '/': tv_push(out,(Tok){.t=TK_SLASH,.line=line}); p++; break; - + case '%': tv_push(out,(Tok){.t=TK_MOD,.line=line}); p++; break; case '<': tv_push(out,(Tok){.t=TK_LT,.line=line}); p++; break; case '>': tv_push(out,(Tok){.t=TK_GT,.line=line}); p++; break; - + case '!': tv_push(out,(Tok){.t=TK_NOT,.line=line}); p++; break; default: - error(line, "Splice/SyntaxError Unknown char: '%c'", *p); + fprintf(stderr,"lexer: unknown char '%c' at line %d\n", *p, line); + exit(1); } } - tv_push(out, (Tok){ .t=TK_EOF, .line=line }); + tv_push(out,(Tok){.t=TK_EOF,.line=line}); } -/* ========================= - Parser (recursive descent) - ========================= */ +static void tv_free(TokVec *v) { + for (int i=0;icount;i++) free(v->data[i].lex); + free(v->data); +} + +/* ========================================================= + PARSER (recursive descent) + ========================================================= */ + static TokVec *G; static int P; static Tok *peek(void) { return &G->data[P]; } -static Tok *prev(void) { return &G->data[P-1]; } static int at(TokType t) { return peek()->t == t; } -static Tok *consume(TokType t, const char *msg) { - if (!at(t)) error(peek()->line, "%s", msg); - return &G->data[P++]; -} +static Tok *advance(void) { return &G->data[P++]; } static int match(TokType t) { if (at(t)) { P++; return 1; } return 0; } -static ASTNode *parse_expression(void); -static ASTNode *parse_statement(void); -static ASTNode *parse_statements_until(TokType end); +static void expect(TokType t, const char *msg) { + if (!at(t)) { + fprintf(stderr,"parse error line %d: %s\n", peek()->line, msg); + exit(1); + } + P++; +} + +static ASTNode *parse_expr(void); +static ASTNode *parse_stmt(void); +static ASTNode *parse_block(void); +/* primary: number|string|ident|call| (expr) */ static ASTNode *parse_primary(void) { if (match(TK_NUMBER)) { - ASTNode *n = ast_new(AST_NUMBER); - n->number = prev()->num; - return n; + return ast_number(G->data[P-1].num); } if (match(TK_STRING)) { - ASTNode *n = ast_new(AST_STRING); - n->string = strdup(prev()->lex ? prev()->lex : ""); - return n; + return ast_string(G->data[P-1].lex ? G->data[P-1].lex : ""); } - if (match(TK_TRUE)) { ASTNode *n=ast_new(AST_NUMBER); n->number=1; return n; } - if (match(TK_FALSE)) { ASTNode *n=ast_new(AST_NUMBER); n->number=0; return n; } - if (match(TK_IDENT)) { - char *name = strdup(prev()->lex); - - /* call: ident(...) */ + const char *name = G->data[P-1].lex; + /* call: ident() only (no args in your VM struct) */ if (match(TK_LPAREN)) { - ASTNode **args = NULL; - int ac=0, cap=0; - - if (!at(TK_RPAREN)) { - for (;;) { - ASTNode *e = parse_expression(); - if (ac>=cap) { cap = cap?cap*2:8; args=(ASTNode**)realloc(args,sizeof(ASTNode*)*(size_t)cap); } - args[ac++] = e; - if (!match(TK_COMMA)) break; - } - } - consume(TK_RPAREN, "Splice/SyntaxError Expected ')' after call args"); - - ASTNode *c = ast_new(AST_FUNCTION_CALL); - c->funccall.funcname = name; - c->funccall.args = args; - c->funccall.arg_count = ac; - return c; - } - - /* ident[index] */ - if (match(TK_LBRACKET)) { - ASTNode *idx = parse_expression(); - consume(TK_RBRACKET, "Splice/SyntaxError Expected ']' after index"); - - ASTNode *id = ast_new(AST_IDENTIFIER); - id->string = name; - - ASTNode *ix = ast_new(AST_INDEX_EXPR); - ix->indexexpr.target = id; - ix->indexexpr.index = idx; - return ix; + expect(TK_RPAREN, "Expected ')' after call"); + return ast_call0(name); } - - ASTNode *id = ast_new(AST_IDENTIFIER); - id->string = name; - return id; + return ast_ident(name); } - if (match(TK_LPAREN)) { - ASTNode *first = parse_expression(); - - /* tuple if comma appears */ - if (match(TK_COMMA)) { - ASTNode **items = NULL; - int count = 0, cap = 0; - - /* first element */ - cap = 4; - items = (ASTNode**)malloc(sizeof(ASTNode*) * cap); - items[count++] = first; - - do { - if (count >= cap) { - cap *= 2; - items = (ASTNode**)realloc(items, sizeof(ASTNode*) * cap); - } - items[count++] = parse_expression(); - } while (match(TK_COMMA)); - - consume(TK_RPAREN, "Splice/SyntaxError Expected ')' after tuple"); - - ASTNode *t = ast_new(AST_TUPLE); - t->tuple.items = items; - t->tuple.count = count; - return t; - } - - /* otherwise normal grouping */ - consume(TK_RPAREN, "Splice/SyntaxError Expected ')'"); - return first; - } - - if (match(TK_READ)) { - consume(TK_LPAREN, "Splice/SyntaxError Expected '(' after read"); - ASTNode *e = parse_expression(); - consume(TK_RPAREN, "Splice/SyntaxError Expected ')' after read"); - - ASTNode *n = ast_new(AST_READ); - n->read.expr = e; - return n; + ASTNode *e = parse_expr(); + expect(TK_RPAREN, "Expected ')'"); + return e; } - if (match(TK_INPUT)) { - consume(TK_LPAREN, "Splice/SyntaxError Expected '(' after input"); - ASTNode *e = parse_expression(); - consume(TK_RPAREN, "Splice/SyntaxError Expected ')' after input prompt"); - ASTNode *n = ast_new(AST_INPUT); - n->input.prompt = e; - return n; - } - if (match(TK_LBRACKET)) { - ASTNode **els=NULL; - int ec=0, cap=0; - - if (!at(TK_RBRACKET)) { - for (;;) { - ASTNode *e = parse_expression(); - if (ec>=cap) { cap = cap?cap*2:8; els=(ASTNode**)realloc(els,sizeof(ASTNode*)*(size_t)cap); } - els[ec++] = e; - if (!match(TK_COMMA)) break; - } - } - consume(TK_RBRACKET, "Splice/SyntaxError Expected ']' after array literal"); - - ASTNode *a = ast_new(AST_ARRAY_LITERAL); - a->arraylit.elements = els; - a->arraylit.count = ec; - return a; - } - - error(peek()->line, "Splice/SyntaxError Expected expression"); - return NULL; + fprintf(stderr,"parse error line %d: expected primary\n", peek()->line); + exit(1); } +/* unary: -x or !x */ static ASTNode *parse_unary(void) { - if (match(TK_NOT)) { - ASTNode *n = ast_new(AST_BINARY_OP); - n->binop.op = strdup("!"); - n->binop.left = parse_unary(); - n->binop.right = NULL; - return n; - } if (match(TK_MINUS)) { - /* -(x) => (-1 * x) */ - ASTNode *mul = ast_new(AST_BINARY_OP); - mul->binop.op = strdup("*"); - ASTNode *m1 = ast_new(AST_NUMBER); - m1->number = -1.0; - mul->binop.left = m1; - mul->binop.right = parse_unary(); - return mul; + /* -(x) -> (-1 * x) */ + return ast_binop("*", ast_number(-1.0), parse_unary()); + } + if (match(TK_NOT)) { + /* !x -> (0 == x) is not correct; but your VM has no boolean type. + We'll implement !x as (x == 0) using binary op "!"? No. + We'll encode op "!" and expect VM to support it if you added it. + If VM doesn't support "!", remove this feature. + */ + return ast_binop("!", parse_unary(), NULL); } return parse_primary(); } +/* mul: * / % */ static ASTNode *parse_mul(void) { - ASTNode *left = parse_unary(); - while (at(TK_STAR) || at(TK_SLASH)) { - TokType op = peek()->t; P++; - ASTNode *n = ast_new(AST_BINARY_OP); - n->binop.op = strdup(op==TK_STAR ? "*" : "/"); - n->binop.left = left; - n->binop.right = parse_unary(); - left = n; - } - return left; + ASTNode *l = parse_unary(); + while (at(TK_STAR) || at(TK_SLASH) || at(TK_MOD)) { + TokType op = advance()->t; + const char *s = (op==TK_STAR)?"*":(op==TK_SLASH)?"/":"%"; + ASTNode *r = parse_unary(); + l = ast_binop(s, l, r); + } + return l; } +/* add: + - */ static ASTNode *parse_add(void) { - ASTNode *left = parse_mul(); + ASTNode *l = parse_mul(); while (at(TK_PLUS) || at(TK_MINUS)) { - TokType op = peek()->t; P++; - ASTNode *n = ast_new(AST_BINARY_OP); - n->binop.op = strdup(op==TK_PLUS ? "+" : "-"); - n->binop.left = left; - n->binop.right = parse_mul(); - left = n; + TokType op = advance()->t; + const char *s = (op==TK_PLUS)?"+":"-"; + ASTNode *r = parse_mul(); + l = ast_binop(s, l, r); } - return left; + return l; } +/* cmp: < > <= >= == != */ static ASTNode *parse_cmp(void) { - ASTNode *left = parse_add(); + ASTNode *l = parse_add(); while (at(TK_LT)||at(TK_GT)||at(TK_LE)||at(TK_GE)||at(TK_EQ)||at(TK_NEQ)) { - TokType op = peek()->t; P++; + TokType op = advance()->t; const char *s = (op==TK_LT)?"<":(op==TK_GT)?">":(op==TK_LE)?"<=":(op==TK_GE)?">=":(op==TK_EQ)?"==":"!="; - ASTNode *n = ast_new(AST_BINARY_OP); - n->binop.op = strdup(s); - n->binop.left = left; - n->binop.right = parse_add(); - left = n; + ASTNode *r = parse_add(); + l = ast_binop(s, l, r); } - return left; + return l; } +/* logic: && || (encoded as "&&" "||") */ static ASTNode *parse_logic(void) { - ASTNode *left = parse_cmp(); + ASTNode *l = parse_cmp(); while (at(TK_AND) || at(TK_OR)) { - TokType op = peek()->t; P++; - ASTNode *n = ast_new(AST_BINARY_OP); - n->binop.op = strdup(op==TK_AND ? "&&" : "||"); - n->binop.left = left; - n->binop.right = parse_cmp(); - left = n; + TokType op = advance()->t; + const char *s = (op==TK_AND)?"&&":"||"; + ASTNode *r = parse_cmp(); + l = ast_binop(s, l, r); } - return left; + return l; } -static ASTNode *parse_expression(void) { return parse_logic(); } - -static ASTNode *parse_statements_until(TokType end) { - ASTNode **stmts=NULL; - int c=0, cap=0; - - while (!at(end) && !at(TK_EOF)) { - ASTNode *s = parse_statement(); - if (!s) { /* allow empty */ } - else { - if (c>=cap) { cap=cap?cap*2:16; stmts=(ASTNode**)realloc(stmts,sizeof(ASTNode*)*(size_t)cap); } - stmts[c++] = s; - } - match(TK_SEMI); - } +static ASTNode *parse_expr(void) { return parse_logic(); } - ASTNode *blk = ast_new(AST_STATEMENTS); - blk->statements.stmts = stmts; - blk->statements.count = c; - return blk; +/* statement separators: many semis/newlines are ok */ +static void eat_separators(void) { + while (match(TK_SEMI)) {} } -static ASTNode *parse_statement(void) { +static ASTNode *parse_stmt(void) { + eat_separators(); + if (match(TK_LET)) { - Tok *id = consume(TK_IDENT, "Splice/SyntaxError Expected identifier after let"); - consume(TK_ASSIGN, "Splice/SyntaxError Expected '=' after let name"); - ASTNode *rhs = parse_expression(); - - ASTNode *n = ast_new(AST_LET); - n->var.varname = strdup(id->lex); - n->var.value = rhs; - return n; - } - if (match(TK_CONTINUE)) { - return ast_new(AST_CONTINUE); + expect(TK_IDENT, "Expected identifier after let"); + const char *name = G->data[P-1].lex; + expect(TK_ASSIGN, "Expected '=' after let name"); + ASTNode *rhs = parse_expr(); + return ast_var(AST_LET, name, rhs); } - if (match(TK_BREAK)) { - return ast_new(AST_BREAK); - } - if (match(TK_PRINT)) { - consume(TK_LPAREN, "Splice/SyntaxError Expected '(' after print"); - ASTNode *e = parse_expression(); - consume(TK_RPAREN, "Splice/SyntaxError Expected ')' after print expr"); - ASTNode *n = ast_new(AST_PRINT); - n->print.expr = e; - return n; + expect(TK_LPAREN, "Expected '(' after print"); + ASTNode *e = parse_expr(); + expect(TK_RPAREN, "Expected ')' after print"); + return ast_print(e); } - if (match(TK_QUIT)) { - ASTNode *zero = ast_new(AST_NUMBER); - zero->number = 0; - ASTNode *ret = ast_new(AST_RETURN); - ret->retstmt.expr = zero; + if (match(TK_BREAK)) return ast_new(AST_BREAK); + if (match(TK_CONTINUE)) return ast_new(AST_CONTINUE); - return ret; + if (match(TK_RETURN)) { + /* return expr; (expr optional) */ + if (at(TK_SEMI) || at(TK_RBRACE) || at(TK_EOF)) return ast_return(NULL); + return ast_return(parse_expr()); } - - if (match(TK_IMPORT)) { - Tok *f = consume(TK_STRING, "Splice/SyntaxError Expected string filename after import"); - - ASTNode *n = ast_new(AST_IMPORT); - - const char *raw = f->lex ? f->lex : ""; - const char *resolved = resolve_builtin_import(raw); - - n->importstmt.filename = strdup(resolved); - return n; + if (match(TK_FUNC)) { + expect(TK_IDENT, "Expected function name"); + const char *name = G->data[P-1].lex; + expect(TK_LPAREN, "Expected '(' after func name"); + expect(TK_RPAREN, "Expected ')' (no args supported)"); + ASTNode *body = parse_block(); + return ast_funcdef(name, body); } - if (match(TK_WRITE)) { - consume(TK_LPAREN, "Splice/SyntaxError Expected '(' after write"); - ASTNode *path = parse_expression(); - consume(TK_COMMA, "Splice/SyntaxError Expected ',' after write path"); - ASTNode *val = parse_expression(); - consume(TK_RPAREN, "Splice/SyntaxError Expected ')' after write"); - ASTNode *n = ast_new(AST_WRITE); - n->write.path = path; - n->write.value = val; - return n; + if (match(TK_IF)) { + expect(TK_LPAREN, "Expected '(' after if"); + ASTNode *cond = parse_expr(); + expect(TK_RPAREN, "Expected ')'"); + ASTNode *thenb = parse_block(); + ASTNode *elseb = NULL; + if (match(TK_ELSE)) { + /* else { ... } */ + elseb = parse_block(); + } + return ast_if(cond, thenb, elseb); } - if (match(TK_RAISE)) { - consume(TK_LPAREN, "Splice/SyntaxError Expected '(' after raise"); - ASTNode *e = parse_expression(); - consume(TK_RPAREN, "Splice/SyntaxError Expected ')' after raise expr"); - ASTNode *n = ast_new(AST_RAISE); - n->raise.expr = e; - return n; + if (match(TK_WHILE)) { + expect(TK_LPAREN, "Expected '(' after while"); + ASTNode *cond = parse_expr(); + expect(TK_RPAREN, "Expected ')'"); + ASTNode *body = parse_block(); + return ast_while(cond, body); } - if (match(TK_RETURN)) { - ASTNode *e = NULL; - if (!at(TK_SEMI) && !at(TK_RBRACE)) e = parse_expression(); - ASTNode *n = ast_new(AST_RETURN); - n->retstmt.expr = e; - return n; - } + if (match(TK_FOR)) { + expect(TK_IDENT, "Expected for variable name"); + const char *var = G->data[P-1].lex; + expect(TK_IN, "Expected 'in' after for var"); + ASTNode *start = parse_expr(); + expect(TK_DOTDOT, "Expected '..' in for range"); + ASTNode *end = parse_expr(); + ASTNode *body = parse_block(); + return ast_for(var, start, end, body); + } + + /* assignment or expression statement */ + if (at(TK_IDENT) && G->data[P+1].t == TK_ASSIGN) { + const char *name = advance()->lex; + advance(); /* '=' */ + ASTNode *rhs = parse_expr(); + return ast_var(AST_ASSIGN, name, rhs); + } + + /* expression stmt (incl call) */ + return parse_expr(); +} - if (match(TK_FUNC)) { - Tok *id = consume(TK_IDENT, "Splice/SyntaxError Expected function name"); - consume(TK_LPAREN, "Splice/SyntaxError Expected '(' after func name"); - - char **params=NULL; - int pc=0, cap=0; - - if (!at(TK_RPAREN)) { - for (;;) { - Tok *p = consume(TK_IDENT, "Splice/SyntaxError Expected param name"); - if (pc>=cap) { cap=cap?cap*2:8; params=(char**)realloc(params,sizeof(char*)*(size_t)cap); } - params[pc++] = strdup(p->lex); - if (!match(TK_COMMA)) break; +static ASTNode *parse_block(void) { + expect(TK_LBRACE, "Expected '{'"); + + ASTNode **stmts = NULL; + int count = 0, cap = 0; + + while (!at(TK_RBRACE) && !at(TK_EOF)) { + ASTNode *s = parse_stmt(); + if (s) { + if (count >= cap) { + cap = cap ? cap*2 : 16; + stmts = (ASTNode**)xrealloc(stmts, sizeof(ASTNode*)*(size_t)cap); } + stmts[count++] = s; } - consume(TK_RPAREN, "Splice/SyntaxError Expected ')' after params"); - - consume(TK_LBRACE, "Splice/SyntaxError Expected '{' before func body"); - ASTNode *body = parse_statements_until(TK_RBRACE); - consume(TK_RBRACE, "Splice/SyntaxError Expected '}' after func body"); - ASTNode *n = ast_new(AST_FUNC_DEF); - n->funcdef.funcname = strdup(id->lex); - n->funcdef.params = params; - n->funcdef.param_count = pc; - n->funcdef.body = body; - return n; + eat_separators(); } - if (match(TK_IF)) { - consume(TK_LPAREN, "Splice/SyntaxError Expected '(' after if"); - ASTNode *cond = parse_expression(); - consume(TK_RPAREN, "Splice/SyntaxError Expected ')' after if cond"); + expect(TK_RBRACE, "Expected '}'"); + return ast_statements(stmts, count); +} - consume(TK_LBRACE, "Splice/SyntaxError Expected '{' after if"); - ASTNode *thenb = parse_statements_until(TK_RBRACE); - consume(TK_RBRACE, "Splice/SyntaxError Expected '}' after if body"); +static ASTNode *parse_program(TokVec *v) { + G = v; P = 0; - ASTNode *elseb = NULL; + ASTNode **stmts = NULL; + int count=0, cap=0; - if (match(TK_ELSE)) { - /* else if (...) { ... } */ - if (at(TK_IF)) { - elseb = parse_statement(); // recurse → AST_IF - } - /* else { ... } */ - else { - consume(TK_LBRACE, "Splice/SyntaxError Expected '{' after else"); - elseb = parse_statements_until(TK_RBRACE); - consume(TK_RBRACE, "Splice/SyntaxError Expected '}' after else body"); + while (!at(TK_EOF)) { + ASTNode *s = parse_stmt(); + if (s) { + if (count >= cap) { + cap = cap ? cap*2 : 16; + stmts = (ASTNode**)xrealloc(stmts, sizeof(ASTNode*)*(size_t)cap); } + stmts[count++] = s; } - - ASTNode *n = ast_new(AST_IF); - n->ifstmt.condition = cond; - n->ifstmt.then_branch = thenb; - n->ifstmt.else_branch = elseb; - return n; + eat_separators(); } + return ast_statements(stmts, count); +} - if (match(TK_WHILE)) { - consume(TK_LPAREN, "Splice/SyntaxError Expected '(' after while"); - ASTNode *cond = parse_expression(); - consume(TK_RPAREN, "Splice/SyntaxError Expected ')' after while cond"); - consume(TK_LBRACE, "Splice/SyntaxError Expected '{' after while"); - ASTNode *body = parse_statements_until(TK_RBRACE); - consume(TK_RBRACE, "Splice/SyntaxError Expected '}' after while body"); - - ASTNode *n = ast_new(AST_WHILE); - n->whilestmt.cond = cond; - n->whilestmt.body = body; - return n; - } +/* ========================================================= + SPC WRITER (matches your VM) + ========================================================= */ - if (match(TK_FOR)) { - Tok *id = consume(TK_IDENT, "Splice/SyntaxError Expected for variable"); - consume(TK_IN, "Splice/SyntaxError Expected 'in' after for var"); - ASTNode *start = parse_expression(); - consume(TK_DOTDOT, "Splice/SyntaxError Expected '.' in for range"); - ASTNode *end = parse_expression(); - consume(TK_LBRACE, "Splice/SyntaxError Expected '{' after for range"); - ASTNode *body = parse_statements_until(TK_RBRACE); - consume(TK_RBRACE, "Splice/SyntaxError Expected '}' after for body"); - - ASTNode *n = ast_new(AST_FOR); - n->forstmt.for_var = strdup(id->lex); - n->forstmt.for_start = start; - n->forstmt.for_end = end; - n->forstmt.for_body = body; - return n; - } +static void wr_u8(FILE *f, uint8_t v) { fwrite(&v,1,1,f); } - /* assignment or expr statement */ - if (at(TK_IDENT)) { - Tok *id = peek(); P++; - - /* ident = expr */ - if (match(TK_ASSIGN)) { - ASTNode *rhs = parse_expression(); - ASTNode *n = ast_new(AST_ASSIGN); - n->var.varname = strdup(id->lex); - n->var.value = rhs; - return n; - } +static void wr_u32(FILE *f, uint32_t v) { + /* little-endian */ + uint8_t b[4]; + b[0] = (uint8_t)(v & 0xFF); + b[1] = (uint8_t)((v >> 8) & 0xFF); + b[2] = (uint8_t)((v >> 16) & 0xFF); + b[3] = (uint8_t)((v >> 24) & 0xFF); + fwrite(b,1,4,f); +} - /* ident[expr] = expr */ - if (match(TK_LBRACKET)) { - ASTNode *idx = parse_expression(); - consume(TK_RBRACKET, "Splice/SyntaxError Expected ']' after index"); - consume(TK_ASSIGN, "Splice/SyntaxError Expected '=' after index"); - ASTNode *rhs = parse_expression(); - - ASTNode *target = ast_new(AST_IDENTIFIER); - target->string = strdup(id->lex); - - ASTNode *n = ast_new(AST_INDEX_ASSIGN); - n->indexassign.target = target; - n->indexassign.index = idx; - n->indexassign.value = rhs; - return n; - } +static void wr_double(FILE *f, double v) { + fwrite(&v, 1, 8, f); +} - /* fall back: treat as expression starting with identifier */ - P--; /* rewind */ +static void wr_str(FILE *f, const char *s) { + if (!s) s = ""; + uint32_t len = (uint32_t)strlen(s); + wr_u32(f, len); + if (len) fwrite(s,1,len,f); +} + +static void write_node(FILE *f, ASTNode *n); + +static void write_node(FILE *f, ASTNode *n) { + if (!n) { + /* NULL: write a dummy node of type AST_NUMBER with value 0 + because your VM read_node() doesn't have a NULL sentinel. + This keeps structure valid when else/return expr is missing. + */ + wr_u8(f, (uint8_t)AST_NUMBER); + wr_double(f, 0.0); + return; + } + + wr_u8(f, (uint8_t)n->type); + + switch (n->type) { + case AST_NUMBER: wr_double(f, n->number); break; + case AST_STRING: + case AST_IDENTIFIER: wr_str(f, n->string); break; + + case AST_BINARY_OP: + wr_str(f, n->binop.op); + write_node(f, n->binop.left); + /* unary '!' encoded with right=NULL: emit 0 on right */ + write_node(f, n->binop.right); + break; + + case AST_PRINT: + write_node(f, n->print.expr); + break; + + case AST_LET: + case AST_ASSIGN: + wr_str(f, n->var.name); + write_node(f, n->var.value); + break; + + case AST_STATEMENTS: + wr_u32(f, (uint32_t)n->statements.count); + for (int i=0;istatements.count;i++) write_node(f, n->statements.stmts[i]); + break; + + case AST_WHILE: + write_node(f, n->whilestmt.cond); + write_node(f, n->whilestmt.body); + break; + + case AST_IF: + write_node(f, n->ifstmt.cond); + write_node(f, n->ifstmt.then_b); + write_node(f, n->ifstmt.else_b); + break; + + case AST_FOR: + wr_str(f, n->forstmt.var); + write_node(f, n->forstmt.start); + write_node(f, n->forstmt.end); + write_node(f, n->forstmt.body); + break; + + case AST_FUNC_DEF: + wr_str(f, n->funcdef.name); + write_node(f, n->funcdef.body); + break; + + case AST_FUNCTION_CALL: + wr_str(f, n->funccall.name); + break; + + case AST_RETURN: + write_node(f, n->retstmt.expr); + break; + + case AST_BREAK: + case AST_CONTINUE: + break; + + default: + die("spbuild: unsupported node in writer"); } +} - /* expression statement */ - return parse_expression(); +static int write_spc(const char *out_path, ASTNode *root) { + FILE *f = fopen(out_path, "wb"); + if (!f) return 0; + fwrite(SPC_MAGIC, 1, 4, f); + wr_u8(f, (uint8_t)SPC_VERSION); + write_node(f, root); + fclose(f); + return 1; } -/* Parse full program */ -static ASTNode *parse_program(TokVec *v) { - G = v; P = 0; - ASTNode *root = parse_statements_until(TK_EOF); - return root; +/* ========================================================= + MAIN + ========================================================= */ + +static char *read_file(const char *path) { + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + fseek(f, 0, SEEK_END); + long sz = ftell(f); + rewind(f); + if (sz < 0) { fclose(f); return NULL; } + char *buf = (char*)xmalloc((size_t)sz + 1); + size_t rd = fread(buf, 1, (size_t)sz, f); + buf[rd] = 0; + fclose(f); + return buf; } -/* ========================= - Main (spbuild) - ========================= */ int main(int argc, char **argv) { - if (argc < 3) { + if (argc != 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } - char *src = read_text_file(argv[1]); + const char *in = argv[1]; + const char *out = argv[2]; + + char *src = read_file(in); if (!src) { - fprintf(stderr, "Could not read: %s\n", argv[1]); + fprintf(stderr, "spbuild: cannot read %s\n", in); return 1; } - TokVec tv; tv_init(&tv); - tokenize(src, &tv); + TokVec tv = {0}; + lex(src, &tv); ASTNode *root = parse_program(&tv); - if (!write_ast_to_spc(argv[2], root)) { - fprintf(stderr, "Could not write: %s\n", argv[2]); + if (!write_spc(out, root)) { + fprintf(stderr, "spbuild: failed to write %s\n", out); free_ast(root); tv_free(&tv); free(src); return 1; } - success(0, "Wrote AST SPC: %s", argv[2]); - free_ast(root); tv_free(&tv); free(src); diff --git a/src/module_stubs.c b/src/module_stubs.c index 592055d..4818db6 100644 --- a/src/module_stubs.c +++ b/src/module_stubs.c @@ -1,22 +1,101 @@ - #include "splice.h" +#include "sdk.h" #include #include +#include +#include +/* ===== VM OBJECT MODEL (LOCAL MIRROR) ===== */ +/* MUST match VM layout EXACTLY */ +static void error(int ln, const char *fmt, ...) { + (void)ln; + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "[ERROR] "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(1); +} +typedef enum { + OBJ_ARRAY, + OBJ_TUPLE +} ObjectType; + +typedef struct { + ObjectType type; + int count; + int capacity; + Value *items; +} ObjArray; + +/* ===== natives ===== */ -/* Example native: noop() -> 0 */ static Value native_noop(int argc, Value *argv) { (void)argc; (void)argv; - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = 0; - return tmp; + Value v = { .type = VAL_NUMBER, .number = 0 }; + return v; +} + +static Value native_len(int argc, Value *argv) { + Value out = { .type = VAL_NUMBER, .number = 0 }; + if (argc < 1) return out; + + Value v = argv[0]; + + if (v.type == VAL_STRING) { + out.number = (double)strlen(v.string ? v.string : ""); + return out; + } + + if (v.type == VAL_OBJECT && v.object) { + ObjArray *oa = (ObjArray*)v.object; + if (oa->type == OBJ_ARRAY || oa->type == OBJ_TUPLE) { + out.number = (double)oa->count; + } + } + return out; +} + +static Value native_append(int argc, Value *argv) { + Value out = { .type = VAL_NUMBER, .number = 0 }; + if (argc < 2) return out; + + Value target = argv[0]; + Value val = argv[1]; + + if (target.type != VAL_OBJECT) + error(0, "Splice/TypeError append: target must be array"); + + ObjArray *oa = (ObjArray*)target.object; + if (!oa || oa->type != OBJ_ARRAY) + error(0, "Splice/TypeError append: target not array"); + + if (oa->count >= oa->capacity) { + int newcap = oa->capacity ? oa->capacity * 2 : 4; + Value *ni = (Value*)realloc(oa->items, sizeof(Value) * newcap); + if (!ni) error(0, "Splice/SystemError OOM append"); + oa->items = ni; + oa->capacity = newcap; + } + + if (val.type == VAL_STRING) { + Value copy = val; + copy.string = strdup(val.string ? val.string : ""); + oa->items[oa->count++] = copy; + } else { + oa->items[oa->count++] = val; + } + + out.number = (double)oa->count; + return out; } -/* Constructor: register builtins and initialize any registered modules. */ -__attribute__((constructor)) static void Splice_module_stubs_init(void) { - /* register a tiny example native so tests/examples can call it */ - Splice_register_native("noop", native_noop); +/* ===== registration ===== */ - /* initialize any modules that have been registered */ +__attribute__((constructor)) +static void Splice_module_stubs_init(void) { + Splice_register_native("noop", native_noop); + Splice_register_native("len", native_len); + Splice_register_native("append", native_append); Splice_init_all_modules(); } diff --git a/src/sdk.h b/src/sdk.h index e3cf007..521a936 100644 --- a/src/sdk.h +++ b/src/sdk.h @@ -10,11 +10,12 @@ #define SPLICE_EMBED 0 #endif + /* ============================================================ Value forward declaration (from Splice.h) ============================================================ */ struct Value; -typedef struct Value Value; + /* ============================================================ Native function pointer type diff --git a/src/splice.c b/src/splice.c index df8f4a1..a94f8e2 100644 --- a/src/splice.c +++ b/src/splice.c @@ -1,7 +1,30 @@ +#define SPLICE_NO_INLINE_MEMREADER + #include -#include #include +#include +#include + #include "splice.h" +#include "sdk.h" + +/* ========================= + Logging helpers + ========================= */ + +static void success(int ln, const char *fmt, ...) { + (void)ln; + va_list ap; + va_start(ap, fmt); + fprintf(stdout, "[OK] "); + vfprintf(stdout, fmt, ap); + fprintf(stdout, "\n"); + va_end(ap); +} + +/* ========================= + CLI VM entry + ========================= */ int main(int argc, char **argv) { if (argc < 2) { @@ -14,15 +37,82 @@ int main(int argc, char **argv) { return 0; } - const char *arg = argv[1]; - const char *ext = strrchr(arg, '.'); + const char *path = argv[1]; + const char *ext = strrchr(path, '.'); if (!ext || strcmp(ext, ".spc") != 0) { fprintf(stderr, "[ERROR] only .spc supported by VM now\n"); return 1; } - ASTNode *root = read_ast_from_spc(arg); + /* ========================= + Read SPC into memory + ========================= */ + + FILE *f = fopen(path, "rb"); + if (!f) { + perror("fopen"); + return 1; + } + + fseek(f, 0, SEEK_END); + long size = ftell(f); + rewind(f); + + if (size <= 0) { + fprintf(stderr, "[ERROR] empty SPC file\n"); + fclose(f); + return 1; + } + + unsigned char *buf = (unsigned char *)malloc((size_t)size); + if (!buf) { + fprintf(stderr, "[ERROR] OOM reading SPC\n"); + fclose(f); + return 1; + } + + if (fread(buf, 1, (size_t)size, f) != (size_t)size) { + fprintf(stderr, "[ERROR] failed to read SPC\n"); + free(buf); + fclose(f); + return 1; + } + + fclose(f); + + /* ========================= + Reset VM state (important!) + ========================= */ + + splice_reset_vm(); + + /* ========================= + Load AST from memory + ========================= */ + arena_init(64 * 1024); + ASTNode *root = read_ast_from_spc_mem(buf, (size_t)size); + if (!root) { + fprintf(stderr, "[ERROR] failed to parse SPC\n"); + free(buf); + return 1; + } + + /* ========================= + Execute program + ========================= */ + interpret(root); - free_ast(root); + + /* ========================= + Cleanup + ========================= */ + + /* + DO NOT free AST here unless you have a free_ast(). + VM owns AST lifetime. + */ + free(buf); + + return 0; } diff --git a/src/splice.h b/src/splice.h index d436d23..06e88e4 100644 --- a/src/splice.h +++ b/src/splice.h @@ -1,116 +1,73 @@ -#ifndef Splice_H -#define Splice_H -/* Header-only Splice runtime + AST serialization. - - VM loads .spc => AST => interpret - - Builder writes AST directly into .spc -*/ +#ifndef SPLICE_H +#define SPLICE_H -#include #include #include -#include -#include -#include - -#if defined(_WIN32) - #include -#elif !defined(ARDUINO) - #include -#endif -#ifdef ARDUINO - #define SPLICE_HAS_STDIO 0 -#else - #define SPLICE_HAS_STDIO 1 -#endif -#if SPLICE_HAS_STDIO - #include -#endif +#include +#include -#ifdef ARDUINO -#define SPLICE_EMBED 1 +#if defined(SPLICE_PLATFORM_ARDUINO) + #include + #define SPLICE_PRINTLN(s) Serial.println(s) + #define SPLICE_FAIL(msg) do { Serial.println(msg); while (1) delay(1000); } while (0) #else -#define SPLICE_EMBED 0 + #define SPLICE_PRINTLN(s) puts(s) + #define SPLICE_FAIL(msg) do { fprintf(stderr, "%s\n", msg); exit(1); } while (0) #endif +/* ================= ARENA (RUNTIME ALLOCATED) ================= */ -#define MAX_IMPORTS 32 - -static char *imported_files[MAX_IMPORTS]; -static int imported_count = 0; +static unsigned char *splice_arena = NULL; +static size_t splice_arena_size = 0; +static size_t splice_arena_pos = 0; - - - - - - - -/* ========================= - Diagnostics - ========================= */ -static inline void error(int ln, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - fprintf(stderr, "[ERROR] line %d: ", ln); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - va_end(ap); - exit(1); -} -static inline void warn(int ln, const char *fmt, ...) { - va_list ap; va_start(ap, fmt); - fprintf(stderr, "[WARN] line %d: ", ln); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - va_end(ap); +static void arena_init(size_t size) { + splice_arena = (unsigned char *)malloc(size); + if (!splice_arena) SPLICE_FAIL("ARENA_ALLOC_FAIL"); + splice_arena_size = size; + splice_arena_pos = 0; + memset(splice_arena, 0, size); } -static inline void info(int ln, const char *fmt, ...) { - (void)ln; - va_list ap; va_start(ap, fmt); - fprintf(stdout, "[INFO] "); - vfprintf(stdout, fmt, ap); - fprintf(stdout, "\n"); - va_end(ap); + +static void arena_free(void) { + free(splice_arena); + splice_arena = NULL; + splice_arena_size = splice_arena_pos = 0; } -static inline void success(int ln, const char *fmt, ...) { - (void)ln; - va_list ap; va_start(ap, fmt); - fprintf(stdout, "[SUCCESS] "); - vfprintf(stdout, fmt, ap); - fprintf(stdout, "\n"); - va_end(ap); + +static void *arena_alloc(size_t n) { + n = (n + 7) & ~7; + if (splice_arena_pos + n > splice_arena_size) + SPLICE_FAIL("ARENA_OOM"); + void *p = splice_arena + splice_arena_pos; + splice_arena_pos += n; + return p; } -/* ========================= - Values / Objects - ========================= */ +/* ================= VALUES ================= */ + typedef enum { VAL_NUMBER, VAL_STRING, VAL_OBJECT } ValueType; -typedef struct Value { +typedef struct { ValueType type; double number; - char *string; + const char *string; void *object; } Value; -typedef enum { - OBJ_ARRAY, - OBJ_TUPLE -} ObjectType; +/* ================= EXEC ================= */ -typedef struct { - ObjectType type; - int count; - int capacity; - Value *items; -} ObjArray; +typedef enum { + EXEC_OK, + EXEC_BREAK, + EXEC_CONTINUE, + EXEC_RETURN, + EXEC_ERROR +} ExecResult; -/* If you have sdk.h natives, keep it. If not, stub it. */ -#include "sdk.h" +static Value splice_return_value; -/* ========================= - AST - ========================= */ +/* ================= AST ================= */ typedef enum { AST_NUMBER = 0, @@ -121,26 +78,14 @@ typedef enum { AST_ASSIGN, AST_BREAK, AST_PRINT, - AST_READ, - AST_WRITE, AST_CONTINUE, - - AST_RAISE, - AST_WARN, - AST_INPUT, - AST_INFO, AST_WHILE, AST_IF, - AST_TUPLE, AST_STATEMENTS, AST_FUNC_DEF, AST_FUNCTION_CALL, AST_RETURN, - AST_IMPORT, - AST_FOR, - AST_ARRAY_LITERAL, - AST_INDEX_EXPR, - AST_INDEX_ASSIGN + AST_FOR } ASTNodeType; typedef struct ASTNode ASTNode; @@ -149,1745 +94,321 @@ struct ASTNode { ASTNodeType type; union { double number; - char *string; - - struct { - char *op; /* "+", "-", "*", "/", "==", "&&", "!" ... */ - ASTNode *left; - ASTNode *right; /* may be NULL for unary */ - } binop; - struct { - ASTNode** items; - int count; - } tuple; - struct { - char *varname; - ASTNode *value; - } var; + const char *string; + struct { const char *op; ASTNode *left; ASTNode *right; } binop; + struct { const char *name; ASTNode *value; } var; struct { ASTNode *expr; } print; - struct { ASTNode *prompt; } input; - struct { ASTNode *expr; } raise; - struct { ASTNode *expr; } warn; - struct { ASTNode *expr; } info; - struct { ASTNode *expr; } read; - struct { ASTNode *path; ASTNode *value; } write; - - struct { ASTNode *cond; ASTNode *body; } whilestmt; - - struct { - ASTNode *condition; - ASTNode *then_branch; - ASTNode *else_branch; /* may be NULL */ - } ifstmt; - - struct { - ASTNode **stmts; - int count; - } statements; - - struct { - char *funcname; - char **params; - int param_count; - ASTNode *body; - } funcdef; - - struct { - char *funcname; - ASTNode **args; - int arg_count; - } funccall; - + struct { ASTNode *cond; ASTNode *then_b; ASTNode *else_b; } ifstmt; + struct { ASTNode **stmts; int count; } statements; + struct { const char *name; ASTNode *body; } funcdef; + struct { const char *name; } funccall; struct { ASTNode *expr; } retstmt; - - struct { char *filename; } importstmt; - - struct { - char *for_var; - ASTNode *for_start; - ASTNode *for_end; - ASTNode *for_body; - } forstmt; - - struct { - ASTNode **elements; - int count; - } arraylit; - - struct { - ASTNode *target; - ASTNode *index; - } indexexpr; - - struct { - ASTNode *target; - ASTNode *index; - ASTNode *value; - } indexassign; + struct { const char *var; ASTNode *start; ASTNode *end; ASTNode *body; } forstmt; }; }; -typedef struct { - ASTNode** items; - int count; -} ASTTuple; -/* ========================= - Env (vars + funcs) - ========================= */ -static jmp_buf return_buf; -static Value return_value; -static jmp_buf *break_buf = NULL; -static jmp_buf *continue_buf = NULL; -typedef enum { VAR_NUMBER, VAR_STRING, VAR_OBJECT } VarType; -typedef struct { - char *name; - VarType type; - double value; - char *str; - void *obj; -} Var; -#define VAR_TABLE_SIZE 256 /* power of 2 = fast modulo */ +/* ================= VARIABLES ================= */ + +#define VAR_TABLE_SIZE 64 typedef struct { - char *name; /* interned or strdup */ - VarType type; - double value; - char *str; - void *obj; - int used; + const char *name; + Value value; + int used; } VarSlot; static VarSlot var_table[VAR_TABLE_SIZE]; -static inline unsigned hash_str(const char *s) { + +static unsigned hash_str(const char *s) { unsigned h = 2166136261u; - while (*s) { - h ^= (unsigned char)*s++; - h *= 16777619u; - } + while (*s) { h ^= (unsigned char)*s++; h *= 16777619u; } return h; } -static inline Var* var_number(double d) { - Var *v = malloc(sizeof(Var)); - v->name = NULL; - v->type = VAR_NUMBER; - v->value = d; - v->str = NULL; - v->obj = NULL; - return v; -} - - -static inline VarSlot *get_var(const char *name) { - if (!name) return NULL; - - unsigned h = hash_str(name); - unsigned idx = h & (VAR_TABLE_SIZE - 1); - - for (unsigned i = 0; i < VAR_TABLE_SIZE; i++) { - VarSlot *v = &var_table[idx]; - - if (!v->used) - return NULL; /* empty slot = not found */ - - if (strcmp(v->name, name) == 0) - return v; - - idx = (idx + 1) & (VAR_TABLE_SIZE - 1); /* linear probe */ - } - return NULL; -} - - - -static inline void free_object(void *obj) { - if (!obj) return; - ObjArray *oa = (ObjArray*)obj; - if (oa->type == OBJ_ARRAY) { - for (int j = 0; j < oa->count; ++j) { - if (oa->items[j].type == VAL_STRING) free(oa->items[j].string); - } - free(oa->items); - } - free(oa); -} - -static inline void set_var_object(const char *name, void *obj) { - if (!name) error(0, "Splice/NullError set_var_object: NULL name"); - - unsigned h = hash_str(name); - unsigned idx = h & (VAR_TABLE_SIZE - 1); +static VarSlot *get_var(const char *name) { + unsigned i = hash_str(name) & (VAR_TABLE_SIZE - 1); for (;;) { - VarSlot *v = &var_table[idx]; - - if (!v->used) { - v->used = 1; - v->name = strdup(name); - v->type = VAR_OBJECT; - v->obj = obj; - v->str = NULL; - v->value = 0; - return; - } - - if (strcmp(v->name, name) == 0) { - if (v->type == VAR_STRING) free(v->str); - v->type = VAR_OBJECT; - v->obj = obj; - return; - } - - idx = (idx + 1) & (VAR_TABLE_SIZE - 1); + VarSlot *v = &var_table[i]; + if (!v->used) return NULL; + if (strcmp(v->name, name) == 0) return v; + i = (i + 1) & (VAR_TABLE_SIZE - 1); } } - - -static inline void set_var( - const char *name, - VarType type, - double value, - const char *str -) { - if (!name) error(0, "Splice/NullError set_var: NULL name"); - - unsigned h = hash_str(name); - unsigned idx = h & (VAR_TABLE_SIZE - 1); - +static void set_var(const char *name, Value v) { + unsigned i = hash_str(name) & (VAR_TABLE_SIZE - 1); for (;;) { - VarSlot *v = &var_table[idx]; - - if (!v->used) { - v->used = 1; - v->name = strdup(name); - v->type = type; - v->value = value; - v->str = (type == VAR_STRING) ? strdup(str ? str : "") : NULL; - v->obj = NULL; - return; - } - - if (strcmp(v->name, name) == 0) { - if (v->type == VAR_STRING) free(v->str); - v->type = type; - v->value = value; - v->str = (type == VAR_STRING) ? strdup(str ? str : "") : NULL; + VarSlot *s = &var_table[i]; + if (!s->used || strcmp(s->name, name) == 0) { + s->used = 1; + s->name = name; + s->value = v; return; } - - idx = (idx + 1) & (VAR_TABLE_SIZE - 1); + i = (i + 1) & (VAR_TABLE_SIZE - 1); } } +/* ================= FUNCTIONS ================= */ - -/* ========================= - Forward declarations - ========================= */ - -static inline ASTNode *ast_new(ASTNodeType t); -static inline void free_ast(ASTNode *node); -static ASTNode *clone_ast(const ASTNode *n); - -static inline void interpret(ASTNode *node); -#define MAX_FUNCS 32 -typedef struct { char *name; ASTNode *def; } Func; -#define FUNC_TABLE_SIZE 128 +#define FUNC_TABLE_SIZE 32 typedef struct { - char *name; - ASTNode *def; + const char *name; + ASTNode *body; int used; } FuncSlot; static FuncSlot func_table[FUNC_TABLE_SIZE]; - -static inline void add_func(const char *name, ASTNode *def) { - if (!name) error(0, "Splice/NullError add_func: NULL name"); - - unsigned idx = hash_str(name) & (FUNC_TABLE_SIZE - 1); - +static void add_func(const char *name, ASTNode *body) { + unsigned i = hash_str(name) & (FUNC_TABLE_SIZE - 1); for (;;) { - FuncSlot *f = &func_table[idx]; - - if (!f->used) { + FuncSlot *f = &func_table[i]; + if (!f->used || strcmp(f->name, name) == 0) { f->used = 1; - f->name = strdup(name); - f->def = def; - return; - } - - if (strcmp(f->name, name) == 0) { - f->def = def; // overwrite allowed + f->name = name; + f->body = body; return; } - - idx = (idx + 1) & (FUNC_TABLE_SIZE - 1); + i = (i + 1) & (FUNC_TABLE_SIZE - 1); } } +static ASTNode *get_func(const char *name) { + unsigned i = hash_str(name) & (FUNC_TABLE_SIZE - 1); + for (;;) { + FuncSlot *f = &func_table[i]; + if (!f->used) return NULL; + if (strcmp(f->name, name) == 0) return f->body; + i = (i + 1) & (FUNC_TABLE_SIZE - 1); + } +} +/* ================= SPC LOADER ================= */ -static inline ASTNode *get_func(const char *name) { - if (!name) return NULL; - - unsigned idx = hash_str(name) & (FUNC_TABLE_SIZE - 1); - - for (unsigned i = 0; i < FUNC_TABLE_SIZE; i++) { - FuncSlot *f = &func_table[idx]; - - if (!f->used) - return NULL; - - if (strcmp(f->name, name) == 0) - return f->def; +#define SPC_MAGIC "SPC\0" +#define SPC_VERSION 1 - idx = (idx + 1) & (FUNC_TABLE_SIZE - 1); - } +typedef struct { + const unsigned char *data; + size_t size; + size_t pos; +} Reader; - return NULL; +static unsigned char rd_u8(Reader *r) { + if (r->pos >= r->size) SPLICE_FAIL("SPC_EOF"); + return r->data[r->pos++]; } +static uint32_t rd_u32(Reader *r) { + uint32_t v = 0; + v |= rd_u8(r); + v |= rd_u8(r) << 8; + v |= rd_u8(r) << 16; + v |= rd_u8(r) << 24; + return v; +} +static const char *rd_str(Reader *r) { + uint32_t len = rd_u32(r); + if (r->pos + len > r->size) SPLICE_FAIL("SPC_STR"); + char *s = (char *)arena_alloc(len + 1); + memcpy(s, r->data + r->pos, len); + s[len] = 0; + r->pos += len; + return s; +} +static ASTNode *read_node(Reader *r); -static ASTNode *clone_ast(const ASTNode *n) { - if (!n) return NULL; - - ASTNode *c = ast_new(n->type); +static ASTNode *read_node(Reader *r) { + ASTNode *n = (ASTNode *)arena_alloc(sizeof(ASTNode)); + n->type = (ASTNodeType)rd_u8(r); switch (n->type) { - - case AST_NUMBER: - c->number = n->number; + case AST_NUMBER: { + double tmp; + memcpy(&tmp, r->data + r->pos, 8); + r->pos += 8; + n->number = tmp; break; - + } case AST_STRING: case AST_IDENTIFIER: - c->string = n->string ? strdup(n->string) : NULL; + n->string = rd_str(r); break; - - case AST_BINARY_OP: - c->binop.op = strdup(n->binop.op); - c->binop.left = clone_ast(n->binop.left); - c->binop.right = clone_ast(n->binop.right); + case AST_PRINT: + n->print.expr = read_node(r); break; - case AST_LET: case AST_ASSIGN: - c->var.varname = strdup(n->var.varname); - c->var.value = clone_ast(n->var.value); - break; - - case AST_PRINT: - c->print.expr = clone_ast(n->print.expr); - break; - - case AST_INPUT: - c->input.prompt = clone_ast(n->input.prompt); - break; - - case AST_RAISE: - c->raise.expr = clone_ast(n->raise.expr); - break; - - case AST_WARN: - c->warn.expr = clone_ast(n->warn.expr); + n->var.name = rd_str(r); + n->var.value = read_node(r); break; - - case AST_INFO: - c->info.expr = clone_ast(n->info.expr); - break; - - case AST_READ: - c->read.expr = clone_ast(n->read.expr); - break; - - case AST_WRITE: - c->write.path = clone_ast(n->write.path); - c->write.value = clone_ast(n->write.value); - break; - - case AST_WHILE: - c->whilestmt.cond = clone_ast(n->whilestmt.cond); - c->whilestmt.body = clone_ast(n->whilestmt.body); - break; - - case AST_IF: - c->ifstmt.condition = clone_ast(n->ifstmt.condition); - c->ifstmt.then_branch = clone_ast(n->ifstmt.then_branch); - c->ifstmt.else_branch = clone_ast(n->ifstmt.else_branch); + case AST_BINARY_OP: + n->binop.op = rd_str(r); + n->binop.left = read_node(r); + n->binop.right = read_node(r); break; - - case AST_STATEMENTS: - c->statements.count = n->statements.count; - c->statements.stmts = - (ASTNode**)calloc(c->statements.count, sizeof(ASTNode*)); - for (int i = 0; i < c->statements.count; i++) - c->statements.stmts[i] = - clone_ast(n->statements.stmts[i]); + case AST_STATEMENTS: { + int c = (int)rd_u32(r); + n->statements.count = c; + n->statements.stmts = (ASTNode **)arena_alloc(sizeof(ASTNode *) * c); + for (int i = 0; i < c; i++) n->statements.stmts[i] = read_node(r); break; - + } case AST_FUNC_DEF: - c->funcdef.funcname = strdup(n->funcdef.funcname); - c->funcdef.param_count = n->funcdef.param_count; - c->funcdef.params = - (char**)calloc(c->funcdef.param_count, sizeof(char*)); - for (int i = 0; i < c->funcdef.param_count; i++) - c->funcdef.params[i] = - strdup(n->funcdef.params[i]); - c->funcdef.body = clone_ast(n->funcdef.body); + n->funcdef.name = rd_str(r); + n->funcdef.body = read_node(r); break; - case AST_FUNCTION_CALL: - c->funccall.funcname = strdup(n->funccall.funcname); - c->funccall.arg_count = n->funccall.arg_count; - c->funccall.args = - (ASTNode**)calloc(c->funccall.arg_count, sizeof(ASTNode*)); - for (int i = 0; i < c->funccall.arg_count; i++) - c->funccall.args[i] = - clone_ast(n->funccall.args[i]); + n->funccall.name = rd_str(r); break; - case AST_RETURN: - c->retstmt.expr = clone_ast(n->retstmt.expr); + n->retstmt.expr = read_node(r); break; - - case AST_IMPORT: - c->importstmt.filename = strdup(n->importstmt.filename); + case AST_WHILE: + n->whilestmt.cond = read_node(r); + n->whilestmt.body = read_node(r); + break; + case AST_IF: + n->ifstmt.cond = read_node(r); + n->ifstmt.then_b = read_node(r); + n->ifstmt.else_b = read_node(r); break; - case AST_FOR: - c->forstmt.for_var = strdup(n->forstmt.for_var); - c->forstmt.for_start = clone_ast(n->forstmt.for_start); - c->forstmt.for_end = clone_ast(n->forstmt.for_end); - c->forstmt.for_body = clone_ast(n->forstmt.for_body); + n->forstmt.var = rd_str(r); + n->forstmt.start = read_node(r); + n->forstmt.end = read_node(r); + n->forstmt.body = read_node(r); break; - - case AST_ARRAY_LITERAL: - c->arraylit.count = n->arraylit.count; - c->arraylit.elements = - (ASTNode**)calloc(c->arraylit.count, sizeof(ASTNode*)); - for (int i = 0; i < c->arraylit.count; i++) - c->arraylit.elements[i] = - clone_ast(n->arraylit.elements[i]); + default: break; + } + return n; +} - case AST_INDEX_EXPR: - c->indexexpr.target = clone_ast(n->indexexpr.target); - c->indexexpr.index = clone_ast(n->indexexpr.index); - break; +static ASTNode *read_ast_from_spc_mem(const unsigned char *data, size_t size) { + if (size < 5) SPLICE_FAIL("SPC_SHORT"); + if (memcmp(data, SPC_MAGIC, 4) != 0) SPLICE_FAIL("SPC_MAGIC"); + if (data[4] != SPC_VERSION) SPLICE_FAIL("SPC_VERSION"); + Reader r = { data, size, 5 }; + return read_node(&r); +} - case AST_INDEX_ASSIGN: - c->indexassign.target = clone_ast(n->indexassign.target); - c->indexassign.index = clone_ast(n->indexassign.index); - c->indexassign.value = clone_ast(n->indexassign.value); - break; +/* ================= EVAL ================= */ +static Value eval(ASTNode *n) { + switch (n->type) { + case AST_NUMBER: return (Value){ VAL_NUMBER, n->number, NULL, NULL }; + case AST_STRING: return (Value){ VAL_STRING, 0, n->string, NULL }; + case AST_IDENTIFIER: { + VarSlot *v = get_var(n->string); + return v ? v->value : (Value){ VAL_NUMBER, 0, NULL, NULL }; + } + case AST_BINARY_OP: { + Value a = eval(n->binop.left); + Value b = eval(n->binop.right); + if (!strcmp(n->binop.op, "+")) return (Value){ VAL_NUMBER, a.number + b.number, NULL, NULL }; + if (!strcmp(n->binop.op, "-")) return (Value){ VAL_NUMBER, a.number - b.number, NULL, NULL }; + if (!strcmp(n->binop.op, "*")) return (Value){ VAL_NUMBER, a.number * b.number, NULL, NULL }; + if (!strcmp(n->binop.op, "/")) return (Value){ VAL_NUMBER, a.number / b.number, NULL, NULL }; + return (Value){ VAL_NUMBER, 0, NULL, NULL }; + } default: - error(0, "Splice/VMError clone_ast: unsupported AST node %d", n->type); + return (Value){ VAL_NUMBER, 0, NULL, NULL }; } - - return c; -} -static int strcmp_ptr(const void *a, const void *b) { - return strcmp(*(char**)a, *(char**)b); -} - -static int already_imported(const char *path) { - return bsearch( - &path, - imported_files, - imported_count, - sizeof(char*), - strcmp_ptr - ) != NULL; } +/* ================= INTERPRET ================= */ -/* ========================= - AST alloc/free - ========================= */ -static inline ASTNode *ast_new(ASTNodeType t) { - ASTNode *n = (ASTNode*)calloc(1, sizeof(ASTNode)); - if (!n) error(0, "Splice/SystemError OOM allocating ASTNode"); - n->type = t; - return n; -} +static ExecResult interpret(ASTNode *n) { + switch (n->type) { -static inline void free_ast(ASTNode *node) { - if (!node) return; - switch (node->type) { - case AST_STRING: - case AST_IDENTIFIER: - free(node->string); - break; - case AST_TUPLE: - for (int i = 0; i < node->tuple.count; i++) - free_ast(node->tuple.items[i]); - free(node->tuple.items); - break; + case AST_STATEMENTS: + for (int i = 0; i < n->statements.count; i++) { + ExecResult r = interpret(n->statements.stmts[i]); + if (r != EXEC_OK) return r; + } + return EXEC_OK; - case AST_BINARY_OP: - free(node->binop.op); - free_ast(node->binop.left); - free_ast(node->binop.right); - break; + case AST_PRINT: { + Value v = eval(n->print.expr); + if (v.type == VAL_STRING) SPLICE_PRINTLN(v.string); + else { + char buf[32]; + snprintf(buf, sizeof(buf), "%g", v.number); + SPLICE_PRINTLN(buf); + } + return EXEC_OK; + } case AST_LET: case AST_ASSIGN: - free(node->var.varname); - free_ast(node->var.value); - break; - - case AST_PRINT: free_ast(node->print.expr); break; - case AST_INPUT: free_ast(node->input.prompt); break; - case AST_RAISE: free_ast(node->raise.expr); break; - case AST_WARN: free_ast(node->warn.expr); break; - case AST_INFO: free_ast(node->info.expr); break; - - case AST_WHILE: - free_ast(node->whilestmt.cond); - free_ast(node->whilestmt.body); - break; + set_var(n->var.name, eval(n->var.value)); + return EXEC_OK; case AST_IF: - free_ast(node->ifstmt.condition); - free_ast(node->ifstmt.then_branch); - free_ast(node->ifstmt.else_branch); - break; + return eval(n->ifstmt.cond).number + ? interpret(n->ifstmt.then_b) + : interpret(n->ifstmt.else_b); - case AST_STATEMENTS: - for (int j = 0; j < node->statements.count; ++j) - free_ast(node->statements.stmts[j]); - free(node->statements.stmts); - break; + case AST_WHILE: + while (eval(n->whilestmt.cond).number) { + ExecResult r = interpret(n->whilestmt.body); + if (r == EXEC_BREAK) break; + if (r == EXEC_CONTINUE) continue; + if (r != EXEC_OK) return r; + } + return EXEC_OK; - case AST_FUNC_DEF: - free(node->funcdef.funcname); - for (int j = 0; j < node->funcdef.param_count; ++j) free(node->funcdef.params[j]); - free(node->funcdef.params); - free_ast(node->funcdef.body); - break; + case AST_FOR: { + int s = (int)eval(n->forstmt.start).number; + int e = (int)eval(n->forstmt.end).number; + for (int i = s; i <= e; i++) { + set_var(n->forstmt.var, (Value){ VAL_NUMBER, i, NULL, NULL }); + ExecResult r = interpret(n->forstmt.body); + if (r == EXEC_BREAK) break; + if (r == EXEC_CONTINUE) continue; + if (r != EXEC_OK) return r; + } + return EXEC_OK; + } - case AST_FUNCTION_CALL: - free(node->funccall.funcname); - for (int j = 0; j < node->funccall.arg_count; ++j) free_ast(node->funccall.args[j]); - free(node->funccall.args); - break; + case AST_BREAK: return EXEC_BREAK; + case AST_CONTINUE: return EXEC_CONTINUE; case AST_RETURN: - free_ast(node->retstmt.expr); - break; - - case AST_IMPORT: - free(node->importstmt.filename); - break; - - case AST_FOR: - free(node->forstmt.for_var); - free_ast(node->forstmt.for_start); - free_ast(node->forstmt.for_end); - free_ast(node->forstmt.for_body); - break; - - case AST_ARRAY_LITERAL: - for (int j = 0; j < node->arraylit.count; ++j) free_ast(node->arraylit.elements[j]); - free(node->arraylit.elements); - break; + splice_return_value = eval(n->retstmt.expr); + return EXEC_RETURN; - case AST_INDEX_EXPR: - free_ast(node->indexexpr.target); - free_ast(node->indexexpr.index); - break; + case AST_FUNC_DEF: + add_func(n->funcdef.name, n->funcdef.body); + return EXEC_OK; - case AST_INDEX_ASSIGN: - free_ast(node->indexassign.target); - free_ast(node->indexassign.index); - free_ast(node->indexassign.value); - break; + case AST_FUNCTION_CALL: { + ASTNode *body = get_func(n->funccall.name); + if (!body) SPLICE_FAIL("UNDEF_FUNC"); + ExecResult r = interpret(body); + if (r == EXEC_RETURN) return EXEC_OK; + return r; + } default: - break; + eval(n); + return EXEC_OK; } - free(node); } -/* ========================= - AST serialization helpers - ========================= */ -#define SPC_MAGIC "SPC" -#define SPC_VERSION 1 -#define AST_NULL_SENTINEL 0xFF - -static inline void w_u8(FILE *f, unsigned char v) { - if (fputc(v, f) == EOF) error(0, "Splice/SystemError write u8 failed"); -} -static inline void w_u32(FILE *f, unsigned int v) { - if (fwrite(&v, 4, 1, f) != 1) error(0, "Splice/SystemError write u32 failed"); -} -static inline void w_u16(FILE *f, unsigned short v) { - if (fwrite(&v, 2, 1, f) != 1) error(0, "Splice/SystemError write u16 failed"); -} -static inline void w_double(FILE *f, double d) { - if (fwrite(&d, sizeof(double), 1, f) != 1) error(0, "Splice/SystemError write double failed"); -} -static inline void w_str(FILE *f, const char *s) { - if (!s) s = ""; - unsigned short len = (unsigned short)strlen(s); - w_u16(f, len); - if (len && fwrite(s, 1, len, f) != len) error(0, "Splice/SystemError write string failed"); -} +/* ================= RESET ================= */ -static inline unsigned char r_u8(FILE *f) { - int c = fgetc(f); - if (c == EOF) error(0, "Splice/SyntaxError Unexpected EOF (u8)"); - return (unsigned char)c; -} -static inline unsigned int r_u32(FILE *f) { - unsigned int v; - if (fread(&v, 4, 1, f) != 1) error(0, "Splice/SyntaxError Unexpected EOF (u32)"); - return v; -} -static inline unsigned short r_u16(FILE *f) { - unsigned short v; - if (fread(&v, 2, 1, f) != 1) error(0, "Splice/SyntaxError Unexpected EOF (u16)"); - return v; -} -static inline double r_double(FILE *f) { - double d; - if (fread(&d, sizeof(double), 1, f) != 1) error(0, "Splice/SyntaxError Unexpected EOF (double)"); - return d; -} -static inline char *r_str(FILE *f) { - unsigned short len = r_u16(f); - char *s = (char*)malloc((size_t)len + 1); - if (!s) error(0, "OOM reading string"); - if (len && fread(s, 1, len, f) != len) error(0, "Splice/SyntaxError Unexpected EOF (string)"); - s[len] = 0; - return s; +static void splice_reset_vm(void) { + memset(var_table, 0, sizeof(var_table)); + memset(func_table, 0, sizeof(func_table)); + splice_return_value = (Value){ VAL_NUMBER, 0, NULL, NULL }; } -static void write_ast_node(FILE *f, const ASTNode *n); - -static void write_ast_node(FILE *f, const ASTNode *n) { - if (!n) { w_u8(f, AST_NULL_SENTINEL); return; } - w_u8(f, (unsigned char)n->type); - - switch (n->type) { - case AST_NUMBER: w_double(f, n->number); break; - - case AST_STRING: - case AST_IDENTIFIER: - w_str(f, n->string); - break; - case AST_BREAK: - /* nothing to write */ - break; - case AST_CONTINUE: - break; - - case AST_BINARY_OP: - w_str(f, n->binop.op); - write_ast_node(f, n->binop.left); - write_ast_node(f, n->binop.right); - break; - case AST_READ: - write_ast_node(f, n->read.expr); - break; - case AST_TUPLE: - w_u32(f, (unsigned int)n->tuple.count); - for (int i = 0; i < n->tuple.count; i++) - write_ast_node(f, n->tuple.items[i]); - break; - - case AST_WRITE: - write_ast_node(f, n->write.path); - write_ast_node(f, n->write.value); - break; - - case AST_PRINT: write_ast_node(f, n->print.expr); break; - case AST_INPUT: write_ast_node(f, n->input.prompt); break; - case AST_RAISE: write_ast_node(f, n->raise.expr); break; - case AST_WARN: write_ast_node(f, n->warn.expr); break; - case AST_INFO: write_ast_node(f, n->info.expr); break; - - case AST_LET: - case AST_ASSIGN: - w_str(f, n->var.varname); - write_ast_node(f, n->var.value); - break; - - case AST_RETURN: - write_ast_node(f, n->retstmt.expr); - break; - - case AST_WHILE: - write_ast_node(f, n->whilestmt.cond); - write_ast_node(f, n->whilestmt.body); - break; - - case AST_IF: - write_ast_node(f, n->ifstmt.condition); - write_ast_node(f, n->ifstmt.then_branch); - write_ast_node(f, n->ifstmt.else_branch); - break; - - case AST_FOR: - w_str(f, n->forstmt.for_var); - write_ast_node(f, n->forstmt.for_start); - write_ast_node(f, n->forstmt.for_end); - write_ast_node(f, n->forstmt.for_body); - break; - - case AST_ARRAY_LITERAL: - w_u32(f, (unsigned int)n->arraylit.count); - for (int i = 0; i < n->arraylit.count; ++i) write_ast_node(f, n->arraylit.elements[i]); - break; - - case AST_INDEX_EXPR: - write_ast_node(f, n->indexexpr.target); - write_ast_node(f, n->indexexpr.index); - break; - - case AST_INDEX_ASSIGN: - write_ast_node(f, n->indexassign.target); - write_ast_node(f, n->indexassign.index); - write_ast_node(f, n->indexassign.value); - break; - - case AST_FUNC_DEF: - w_str(f, n->funcdef.funcname); - w_u32(f, (unsigned int)n->funcdef.param_count); - for (int i = 0; i < n->funcdef.param_count; ++i) w_str(f, n->funcdef.params[i]); - write_ast_node(f, n->funcdef.body); - break; - - case AST_FUNCTION_CALL: - w_str(f, n->funccall.funcname); - w_u32(f, (unsigned int)n->funccall.arg_count); - for (int i = 0; i < n->funccall.arg_count; ++i) write_ast_node(f, n->funccall.args[i]); - break; - - case AST_STATEMENTS: - w_u32(f, (unsigned int)n->statements.count); - for (int i = 0; i < n->statements.count; ++i) write_ast_node(f, n->statements.stmts[i]); - break; - - case AST_IMPORT: - w_str(f, n->importstmt.filename); - break; - - default: - error(0, "Splice/SystemError write_ast_node: unsupported type %d", (int)n->type); - } -} - -static ASTNode *read_ast_node(FILE *f); - -static ASTNode *read_ast_node(FILE *f) { - unsigned char t = r_u8(f); - if (t == AST_NULL_SENTINEL) return NULL; - - ASTNodeType type = (ASTNodeType)t; - ASTNode *n = ast_new(type); - - switch (type) { - case AST_READ: - n->read.expr = read_ast_node(f); - break; - case AST_BREAK: - /* nothing to read */ - break; - - case AST_WRITE: - n->write.path = read_ast_node(f); - n->write.value = read_ast_node(f); - break; - - case AST_NUMBER: - n->number = r_double(f); - break; - case AST_TUPLE: { - unsigned int c = r_u32(f); - n->tuple.count = (int)c; - n->tuple.items = - (ASTNode**)calloc(c ? c : 1, sizeof(ASTNode*)); - for (unsigned int i = 0; i < c; i++) - n->tuple.items[i] = read_ast_node(f); - break; - } - case AST_CONTINUE: - break; - - case AST_STRING: - case AST_IDENTIFIER: - n->string = r_str(f); - break; - - case AST_BINARY_OP: - n->binop.op = r_str(f); - n->binop.left = read_ast_node(f); - n->binop.right = read_ast_node(f); - break; - - case AST_PRINT: n->print.expr = read_ast_node(f); break; - case AST_INPUT: n->input.prompt = read_ast_node(f); break; - case AST_RAISE: n->raise.expr = read_ast_node(f); break; - case AST_WARN: n->warn.expr = read_ast_node(f); break; - case AST_INFO: n->info.expr = read_ast_node(f); break; - - case AST_LET: - case AST_ASSIGN: - n->var.varname = r_str(f); - n->var.value = read_ast_node(f); - break; - - case AST_RETURN: - n->retstmt.expr = read_ast_node(f); - break; - - case AST_WHILE: - n->whilestmt.cond = read_ast_node(f); - n->whilestmt.body = read_ast_node(f); - break; - - case AST_IF: - n->ifstmt.condition = read_ast_node(f); - n->ifstmt.then_branch = read_ast_node(f); - n->ifstmt.else_branch = read_ast_node(f); - break; - - case AST_FOR: - n->forstmt.for_var = r_str(f); - n->forstmt.for_start = read_ast_node(f); - n->forstmt.for_end = read_ast_node(f); - n->forstmt.for_body = read_ast_node(f); - break; - - case AST_ARRAY_LITERAL: { - unsigned int count = r_u32(f); - n->arraylit.count = (int)count; - n->arraylit.elements = (ASTNode**)calloc(count ? count : 1, sizeof(ASTNode*)); - if (!n->arraylit.elements) error(0, "Splice/SystemError OOM arraylit elements"); - for (unsigned int i = 0; i < count; ++i) n->arraylit.elements[i] = read_ast_node(f); - break; - } - - case AST_INDEX_EXPR: - n->indexexpr.target = read_ast_node(f); - n->indexexpr.index = read_ast_node(f); - break; - - case AST_INDEX_ASSIGN: - n->indexassign.target = read_ast_node(f); - n->indexassign.index = read_ast_node(f); - n->indexassign.value = read_ast_node(f); - break; - - case AST_FUNC_DEF: { - n->funcdef.funcname = r_str(f); - unsigned int pc = r_u32(f); - n->funcdef.param_count = (int)pc; - n->funcdef.params = (char**)calloc(pc ? pc : 1, sizeof(char*)); - if (!n->funcdef.params) error(0, "Splice/SystemError OOM func params"); - for (unsigned int i = 0; i < pc; ++i) n->funcdef.params[i] = r_str(f); - n->funcdef.body = read_ast_node(f); - break; - } - - case AST_FUNCTION_CALL: { - n->funccall.funcname = r_str(f); - unsigned int ac = r_u32(f); - n->funccall.arg_count = (int)ac; - n->funccall.args = (ASTNode**)calloc(ac ? ac : 1, sizeof(ASTNode*)); - if (!n->funccall.args) error(0, "Splice/SystemError OOM funccall args"); - for (unsigned int i = 0; i < ac; ++i) n->funccall.args[i] = read_ast_node(f); - break; - } - - case AST_STATEMENTS: { - unsigned int c = r_u32(f); - n->statements.count = (int)c; - n->statements.stmts = (ASTNode**)calloc(c ? c : 1, sizeof(ASTNode*)); - if (!n->statements.stmts) error(0, "Splice/SystemError OOM statements"); - for (unsigned int i = 0; i < c; ++i) n->statements.stmts[i] = read_ast_node(f); - break; - } - - case AST_IMPORT: - n->importstmt.filename = r_str(f); - break; - - default: - error(0, "Splice/SystemError read_ast_node: unknown type %d", (int)type); - } - return n; -} - - - -/* Public: VM helper */ -#if !SPLICE_EMBED -static inline ASTNode *read_ast_from_spc(const char *filename) { - FILE *f = fopen(filename, "rb"); - if (!f) { error(0, "Splice/SPCError Could not open bytecode file: %s", filename); return NULL; } - - char magic[5] = {0}; - if (fread(magic, 1, 4, f) != 4) error(0, "Splice/SPCError Invalid SPC (short)"); - if (memcmp(magic, SPC_MAGIC, 4) != 0) error(0, "Splice/SPCError Invalid SPC magic"); - - unsigned char ver = r_u8(f); - if (ver != SPC_VERSION) error(0, "Splice/SPCError Unsupported SPC version: %u", ver); - ASTNode *root = read_ast_node(f); - fclose(f); - return root; -} #endif - -/* ========================= - Runtime eval/interpret - ========================= */ -static inline void print_value(Value v) { - if (v.type == VAL_STRING) { - printf("%s\n", v.string ? v.string : ""); - } else if (v.type == VAL_NUMBER) { - printf("%g\n", v.number); - } else { - printf("\n"); - } -} - -static inline char *eval_to_string(ASTNode *node); - -static inline Value eval(ASTNode *node) { - if (!node) { - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = 0; - return tmp; - } - - switch (node->type) { - case AST_READ: { - char *path = eval_to_string(node->read.expr); -#ifndef SPLICE_EMBED - FILE *f = fopen(path, "rb"); - if (!f) { - free(path); - Value tmp; - tmp.type = VAL_STRING; - tmp.string = strdup(""); - return tmp; - } - - fseek(f, 0, SEEK_END); - long size = ftell(f); - rewind(f); - - char *buf = (char*)malloc(size + 1); - if (!buf) error(0, "Splice/SystemError OOM in read()"); - fread(buf, 1, size, f); - buf[size] = 0; - - fclose(f); - free(path); - - Value tmp; - tmp.type = VAL_STRING; - tmp.string = buf; - return tmp; -#else - free(path); - Value tmp; - tmp.type = VAL_STRING; - tmp.string = strdup(""); - return tmp; -#endif - } - - case AST_NUMBER: { - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = node->number; - return tmp; - } - case AST_TUPLE: { - ObjArray *oa = (ObjArray*)calloc(1, sizeof(ObjArray)); - if (!oa) error(0, "Splice/SystemError OOM tuple"); - - oa->type = OBJ_TUPLE; - oa->count = node->tuple.count; - oa->capacity = node->tuple.count; - oa->items = - (Value*)calloc((size_t)(oa->capacity ? oa->capacity : 1), sizeof(Value)); - - for (int i = 0; i < node->tuple.count; i++) - oa->items[i] = eval(node->tuple.items[i]); - - Value v; - v.type = VAL_OBJECT; - v.object = oa; - return v; - } - - case AST_STRING: { - Value tmp; - tmp.type = VAL_STRING; - tmp.string = strdup(node->string ? node->string : ""); - return tmp; - } - - case AST_IDENTIFIER: { - VarSlot *slot = get_var(node->string); - - if (!slot) { - Value tmp = { .type = VAL_NUMBER, .number = 0 }; - return tmp; - } - - if (slot->type == VAR_STRING) { - Value tmp; - tmp.type = VAL_STRING; - tmp.string = strdup(slot->str ? slot->str : ""); - return tmp; - } - - if (slot->type == VAR_OBJECT) { - Value tmp; - tmp.type = VAL_OBJECT; - tmp.object = slot->obj; - return tmp; - } - - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = slot->value; - return tmp; - } - - - case AST_ARRAY_LITERAL: { - ObjArray *oa = (ObjArray*)calloc(1, sizeof(ObjArray)); - if (!oa) error(0, "Splice/SystemError OOM array"); - oa->type = OBJ_ARRAY; - oa->count = node->arraylit.count; - oa->capacity = node->arraylit.count; - oa->items = (Value*)calloc((size_t)(oa->capacity ? oa->capacity : 1), sizeof(Value)); - if (!oa->items) error(0, "Splice/SystemError OOM array items"); - for (int j = 0; j < node->arraylit.count; ++j) oa->items[j] = eval(node->arraylit.elements[j]); - Value tmp; - tmp.type = VAL_OBJECT; - tmp.object = oa; - return tmp; - } - - case AST_INDEX_EXPR: { - Value target = eval(node->indexexpr.target); - Value idxv = eval(node->indexexpr.index); - int idx = (int)idxv.number; - - if (target.type != VAL_OBJECT) error(0, "Splice/IndexError index: target is not array"); - ObjArray *oa = (ObjArray*)target.object; - if (!oa || oa->type != OBJ_ARRAY) error(0, "Splice/IndexError index: not an array"); - - if (idx < 0 || idx >= oa->count) { - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = 0; - return tmp; - } - return oa->items[idx]; - } - case AST_INPUT: { - char *prompt = eval_to_string(node->input.prompt); - - #if SPLICE_HAS_STDIO - printf("%s", prompt); - fflush(stdout); - - char buf[1024]; - if (fgets(buf, sizeof(buf), stdin)) { - size_t len = strlen(buf); - if (len && buf[len - 1] == '\n') buf[len - 1] = 0; - } else { - buf[0] = 0; - } - - free(prompt); - - Value tmp; - tmp.type = VAL_STRING; - tmp.string = strdup(buf); - return tmp; - #else - free(prompt); - Value tmp; - tmp.type = VAL_STRING; - tmp.string = strdup(""); - return tmp; - #endif - } - - case AST_INDEX_ASSIGN: { - if (!node->indexassign.target || node->indexassign.target->type != AST_IDENTIFIER) - error(0, "Splice/IndexError index assign: target must be identifier"); - - VarSlot *slot = get_var(node->indexassign.target->string); - double d = slot ? slot->value : 0.0; - - if (!slot || slot->type != VAR_OBJECT) - error(0, "Splice/IndexError index assign: variable is not array"); - - - ObjArray *oa = (ObjArray*)slot->obj; - if (oa->type == OBJ_TUPLE) { - error(0, "Splice/TypeError cannot assign to tuple (immutable)"); - } - - int idx = (int)eval(node->indexassign.index).number; - Value val = eval(node->indexassign.value); - - if (idx < 0) error(0, "Splice/IndexError index assign: negative index"); - if (idx >= oa->count) { - while (idx >= oa->capacity) { - int newcap = oa->capacity ? oa->capacity * 2 : 4; - Value *ni = (Value*)realloc(oa->items, sizeof(Value) * (size_t)newcap); - if (!ni) error(0, "Splice/SystemError OOM realloc array"); - oa->items = ni; - oa->capacity = newcap; - } - for (int k = oa->count; k <= idx; ++k) { - oa->items[k].type = VAL_NUMBER; - oa->items[k].number = 0; - } - oa->count = idx + 1; - } - if (oa->items[idx].type == VAL_STRING) free(oa->items[idx].string); - oa->items[idx] = val; - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = 1; - return tmp; - } - - case AST_BINARY_OP: { - Value left = eval(node->binop.left); - Value right; - if (node->binop.right) { - right = eval(node->binop.right); - } else { - right.type = VAL_NUMBER; - right.number = 0; - right.string = NULL; - right.object = NULL; - } - - /* string concat on + */ - if (node->binop.op && strcmp(node->binop.op, "+") == 0 && - (left.type == VAL_STRING || right.type == VAL_STRING)) { - - char lb[64], rb[64]; - const char *ls = (left.type == VAL_STRING) ? left.string : (snprintf(lb, sizeof(lb), "%g", left.number), lb); - const char *rs = (right.type == VAL_STRING) ? right.string : (snprintf(rb, sizeof(rb), "%g", right.number), rb); - - char *out = (char*)malloc(strlen(ls) + strlen(rs) + 1); - if (!out) error(0, "Splice/SystemError OOM concat"); - strcpy(out, ls); - strcat(out, rs); - - if (left.type == VAL_STRING) free(left.string); - if (right.type == VAL_STRING) free(right.string); - - Value tmp; - tmp.type = VAL_STRING; - tmp.string = out; - return tmp; - } - - double lnum = (left.type == VAL_NUMBER) ? left.number : strtod(left.string ? left.string : "0", NULL); - double rnum = (right.type == VAL_NUMBER) ? right.number : strtod(right.string ? right.string : "0", NULL); - - double result = 0; - const char *op = node->binop.op ? node->binop.op : ""; - - if (strcmp(op, "==") == 0) { - if (left.type == VAL_STRING || right.type == VAL_STRING) { - const char *ls = (left.type == VAL_STRING) ? left.string : ""; - const char *rs = (right.type == VAL_STRING) ? right.string : ""; - result = (strcmp(ls, rs) == 0); - } else { - result = (lnum == rnum); - } - } - else if (strcmp(op, "!=") == 0) { - if (left.type == VAL_STRING || right.type == VAL_STRING) { - const char *ls = (left.type == VAL_STRING) ? left.string : ""; - const char *rs = (right.type == VAL_STRING) ? right.string : ""; - result = (strcmp(ls, rs) != 0); - } else { - result = (lnum != rnum); - } - } - else if (strcmp(op, "+") == 0) result = lnum + rnum; - else if (strcmp(op, "-") == 0) result = lnum - rnum; - else if (strcmp(op, "*") == 0) result = lnum * rnum; - else if (strcmp(op, "/") == 0) result = lnum / rnum; - else if (strcmp(op, "<") == 0) result = (lnum < rnum); - else if (strcmp(op, ">") == 0) result = (lnum > rnum); - else if (strcmp(op, "<=") == 0) result = (lnum <= rnum); - else if (strcmp(op, ">=") == 0) result = (lnum >= rnum); - else if (strcmp(op, "&&") == 0) result = ((lnum != 0) && (rnum != 0)); - else if (strcmp(op, "||") == 0) result = ((lnum != 0) || (rnum != 0)); - else if (strcmp(op, "!") == 0) result = (lnum == 0); - - if (left.type == VAL_STRING) free(left.string); - if (right.type == VAL_STRING) free(right.string); - - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = result; - return tmp; - } - - case AST_FUNCTION_CALL: { - /* builtins */ - if (strcmp(node->funccall.funcname, "len") == 0 && node->funccall.arg_count == 1) { - Value a = eval(node->funccall.args[0]); - if (a.type != VAL_OBJECT) { - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = 0; - return tmp; - } - ObjArray *oa = (ObjArray*)a.object; - if (!oa || oa->type != OBJ_ARRAY) { - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = 0; - return tmp; - } - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = (double)oa->count; - return tmp; - } - - if (strcmp(node->funccall.funcname, "append") == 0 && node->funccall.arg_count == 2) { - Value a = eval(node->funccall.args[0]); - Value v = eval(node->funccall.args[1]); - if (a.type != VAL_OBJECT) error(0, "Splice/ArrayError append: first arg must be array"); - ObjArray *oa = (ObjArray*)a.object; - if (!oa) error(0, "Splice/ArrayError append: invalid object"); - if (oa->type == OBJ_TUPLE) - error(0, "Splice/ArrayError append: cannot modify tuple (immutable)"); - if (oa->type != OBJ_ARRAY) - error(0, "Splice/ArrayError append: not an array"); - - if (oa->count >= oa->capacity) { - int newcap = oa->capacity ? oa->capacity * 2 : 4; - Value *ni = (Value*)realloc(oa->items, sizeof(Value) * (size_t)newcap); - if (!ni) error(0, "Splice/SystemError OOM append realloc"); - oa->items = ni; - oa->capacity = newcap; - } - oa->items[oa->count++] = v; - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = 1; - return tmp; - } - - /* native */ - SpliceCFunc native = Splice_get_native(node->funccall.funcname); - if (native) { - Value *args = (Value*)malloc(sizeof(Value) * (size_t)node->funccall.arg_count); - if (!args) error(0, "Splice/SystemError OOM native args"); - for (int j = 0; j < node->funccall.arg_count; ++j) args[j] = eval(node->funccall.args[j]); - Value r = native(node->funccall.arg_count, args); - for (int j = 0; j < node->funccall.arg_count; ++j) - if (args[j].type == VAL_STRING) free(args[j].string); - free(args); - return r; - } - - /* user-defined */ - ASTNode *func = get_func(node->funccall.funcname); - if (!func) error(0, "Splice/SyntaxError Undefined function: %s", node->funccall.funcname); - - for (int j = 0; j < func->funcdef.param_count; ++j) { - Value av; - if (j < node->funccall.arg_count) { - av = eval(node->funccall.args[j]); - } else { - av.type = VAL_NUMBER; - av.number = 0; - av.string = NULL; - av.object = NULL; - } - - if (av.type == VAL_STRING) { - - - - set_var(func->funcdef.params[j], VAR_STRING, 0, av.string); - free(av.string); - } else if (av.type == VAL_OBJECT) { - set_var_object(func->funcdef.params[j], av.object); - } else { - - - set_var(func->funcdef.params[j], VAR_NUMBER, av.number, NULL); - } - } - - Value result; - result.type = VAL_NUMBER; - result.number = 0; - if (setjmp(return_buf) == 0) { - interpret(func->funcdef.body); - } else { - result = return_value; - } - - return result; - } - - default: { - Value tmp; - tmp.type = VAL_NUMBER; - tmp.number = 0; - return tmp; - } - } -} - -static inline void sb_ensure(char **buf, size_t *cap, size_t need) { - if (need <= *cap) return; - while (*cap < need) *cap *= 2; - *buf = (char*)realloc(*buf, *cap); - if (!*buf) error(0, "Splice/SystemError OOM stringify"); -} - -static inline char *value_item_to_tmp(Value v, char tmp[128]) { - if (v.type == VAL_STRING) { - /* quoted strings */ - snprintf(tmp, 128, "\"%s\"", v.string ? v.string : ""); - return tmp; - } - if (v.type == VAL_NUMBER) { - snprintf(tmp, 128, "%g", v.number); - return tmp; - } - return ""; -} - -static inline char *eval_to_string(ASTNode *node) { - Value v = eval(node); - - if (v.type == VAL_STRING) { - /* caller owns it */ - return v.string; - } - - if (v.type == VAL_OBJECT) { - ObjArray *oa = (ObjArray*)v.object; - if (!oa) return strdup(""); - - if (oa->type == OBJ_ARRAY || oa->type == OBJ_TUPLE) { - /* build string */ - size_t cap = 128; - size_t len = 0; - char *out = (char*)malloc(cap); - if (!out) error(0, "Splice/SystemError OOM stringify"); - - char open = (oa->type == OBJ_TUPLE) ? '(' : '['; - char close = (oa->type == OBJ_TUPLE) ? ')' : ']'; - - out[len++] = open; - - for (int i = 0; i < oa->count; i++) { - char tmp[128]; - const char *s = value_item_to_tmp(oa->items[i], tmp); - - size_t sl = strlen(s); - sb_ensure(&out, &cap, len + sl + 4); - memcpy(out + len, s, sl); - len += sl; - - if (i + 1 < oa->count) { - out[len++] = ','; - out[len++] = ' '; - } - } - - out[len++] = close; - out[len] = 0; - return out; - } - - return strdup(""); - } - - /* number */ - char buf[64]; - snprintf(buf, sizeof(buf), "%g", v.number); - return strdup(buf); -} - - - -static inline void interpret(ASTNode *node); - -static inline void interpret(ASTNode *node) { - if (!node) return; - - switch (node->type) { - case AST_STATEMENTS: { - for (int j = 0; j < node->statements.count; ++j) { - ASTNode *s = node->statements.stmts[j]; - if (s && s->type == AST_FUNC_DEF) - add_func(s->funcdef.funcname, s); - - - } - for (int j = 0; j < node->statements.count; ++j) { - ASTNode *s = node->statements.stmts[j]; - if (!s || s->type == AST_FUNC_DEF) continue; - interpret(s); - } - break; - } - case AST_CONTINUE: - longjmp(*continue_buf, 1); - break; - - case AST_BREAK: - if (!break_buf) - error(0, "Splice/SyntaxError 'break' outside loop"); - longjmp(*break_buf, 1); - break; - - case AST_IMPORT: { - #if !SPLICE_EMBED - const char *path = node->importstmt.filename; - - if (already_imported(path)) { - break; // already loaded - } - - ASTNode *mod = read_ast_from_spc(path); - if (!mod) error(0, "Splice/ImportError import failed: %s", path); - - imported_files[imported_count++] = strdup(path); - - interpret(mod); // executes + registers funcs - free_ast(mod); - #else - error(0, "Splice/ImportError import not supported on embedded builds"); - #endif - break; - } - - case AST_PRINT: { - Value v = eval(node->print.expr); - print_value(v); - if (v.type == VAL_STRING) free(v.string); - - } - - - case AST_FUNC_DEF: - add_func(node->funcdef.funcname, clone_ast(node)); - - break; - - case AST_RETURN: - return_value = eval(node->retstmt.expr); - longjmp(return_buf, 1); - break; - - case AST_LET: - case AST_ASSIGN: { - Value val = eval(node->var.value); - if (val.type == VAL_STRING) { - - - set_var(node->var.varname, VAR_STRING, 0, val.string); - free(val.string); - } else if (val.type == VAL_OBJECT) { - set_var_object(node->var.varname, val.object); - } else { - - - set_var(node->var.varname, VAR_NUMBER, val.number, NULL); - } - break; - } - - case AST_IF: - if (eval(node->ifstmt.condition).number) - interpret(node->ifstmt.then_branch); - else - interpret(node->ifstmt.else_branch); - break; - case AST_WRITE: { - Value path = eval(node->write.path); - Value val = eval(node->write.value); - - if (path.type != VAL_STRING) { - error(0, "Splice/IOError write(): path must be string"); - } - - char *out; - if (val.type == VAL_STRING) { - out = val.string; - } else { - char buf[64]; - snprintf(buf, sizeof(buf), "%g", val.number); - out = strdup(buf); - } - -#ifndef SPLICE_EMBED - FILE *f = fopen(path.string, "wb"); - if (!f) { - free(path.string); - if (val.type == VAL_STRING) free(val.string); - error(0, "Splice/IOError write(): cannot open file"); - } - - fwrite(out, 1, strlen(out), f); - fclose(f); -#endif - - free(path.string); - if (val.type == VAL_STRING) free(val.string); - else free(out); - - break; - } - - case AST_WHILE: { - jmp_buf jb, jc; - - jmp_buf *prev_break = break_buf; - jmp_buf *prev_cont = continue_buf; - - break_buf = &jb; - continue_buf = &jc; - - if (setjmp(jb) == 0) { - while (eval(node->whilestmt.cond).number) { - if (setjmp(jc) == 0) { - interpret(node->whilestmt.body); - } - // continue lands here - } - } - - // restore outer loop context - break_buf = prev_break; - continue_buf = prev_cont; - - break; - } - - - - case AST_FOR: { - if (!node->forstmt.for_var) { - error(0, "Splice/NULLError for-loop variable name is NULL"); - } - - int start = (int)eval(node->forstmt.for_start).number; - int end = (int)eval(node->forstmt.for_end).number; - - jmp_buf jb, jc; - - jmp_buf *prev_break = break_buf; - jmp_buf *prev_cont = continue_buf; - - break_buf = &jb; - continue_buf = &jc; - - if (setjmp(jb) == 0) { - for (int k = start; k <= end; ++k) { - - set_var(node->forstmt.for_var, VAR_NUMBER, k, NULL); - - if (setjmp(jc) == 0) { - interpret(node->forstmt.for_body); - } - // continue jumps land here - } - } - - // restore outer context - break_buf = prev_break; - continue_buf = prev_cont; - - - - - break; - } - - - case AST_FUNCTION_CALL: - case AST_ARRAY_LITERAL: - case AST_INDEX_EXPR: - case AST_INDEX_ASSIGN: - (void)eval(node); - break; - - case AST_RAISE: { - char *msg = eval_to_string(node->raise.expr); - error(0, "%s", msg); - break; - } - - - case AST_WARN: { - char *msg = eval_to_string(node->warn.expr); - warn(0, "%s", msg); - free(msg); - break; - } - case AST_INFO: { - char *msg = eval_to_string(node->info.expr); - info(0, "%s", msg); - free(msg); - break; - } - - default: - break; - } -} -typedef struct { - const unsigned char *data; - size_t size; - size_t pos; -} SpcMemReader; - -static inline unsigned char m_u8(SpcMemReader *r) { - if (r->pos >= r->size) error(0, "Splice/IOError Unexpected EOF (mem u8)"); - return r->data[r->pos++]; -} - -static inline unsigned short m_u16(SpcMemReader *r) { - if (r->pos + 2 > r->size) error(0, "Splice/IOError Unexpected EOF (mem u16)"); - unsigned short v; - memcpy(&v, r->data + r->pos, 2); - r->pos += 2; - return v; -} - -static inline unsigned int m_u32(SpcMemReader *r) { - if (r->pos + 4 > r->size) error(0, "Splice/IOError Unexpected EOF (mem u32)"); - unsigned int v; - memcpy(&v, r->data + r->pos, 4); - r->pos += 4; - return v; -} - -static inline double m_double(SpcMemReader *r) { - if (r->pos + sizeof(double) > r->size) - error(0, "Splice/IOError Unexpected EOF (mem double)"); - double d; - memcpy(&d, r->data + r->pos, sizeof(double)); - r->pos += sizeof(double); - return d; -} - -static inline char *m_str(SpcMemReader *r) { - unsigned short len = m_u16(r); - if (r->pos + len > r->size) error(0, "Splice/IOError Unexpected EOF (mem string)"); - char *s = (char*)malloc(len + 1); - memcpy(s, r->data + r->pos, len); - r->pos += len; - s[len] = 0; - return s; -} -static ASTNode *read_ast_node_mem(SpcMemReader *r) { - unsigned char tag = m_u8(r); - - ASTNodeType type = (ASTNodeType)tag; - ASTNode *n = ast_new(type); - - - switch (n->type) { - case AST_NUMBER: - n->number = m_double(r); - break; - - case AST_STRING: - case AST_IDENTIFIER: - n->string = m_str(r); - break; - - case AST_BINARY_OP: - n->binop.op = m_str(r); - n->binop.left = read_ast_node_mem(r); - n->binop.right = read_ast_node_mem(r); - break; - - case AST_PRINT: - n->print.expr = read_ast_node_mem(r); - break; - - case AST_LET: - case AST_ASSIGN: - n->var.varname = m_str(r); - n->var.value = read_ast_node_mem(r); - break; - - case AST_STATEMENTS: { - unsigned int c = m_u32(r); - n->statements.count = (int)c; - n->statements.stmts = (ASTNode**)calloc(c ? c : 1, sizeof(ASTNode*)); - for (unsigned int i = 0; i < c; i++) - n->statements.stmts[i] = read_ast_node_mem(r); - break; - } - - case AST_FUNCTION_CALL: { - n->funccall.funcname = m_str(r); - unsigned int ac = m_u32(r); - n->funccall.arg_count = (int)ac; - n->funccall.args = (ASTNode**)calloc(ac ? ac : 1, sizeof(ASTNode*)); - for (unsigned int i = 0; i < ac; i++) - n->funccall.args[i] = read_ast_node_mem(r); - break; - } - - default: - error(0, "Splice/IOError Unsupported AST type in mem reader: %d", n->type); - } - - return n; -} -static inline ASTNode *read_ast_from_spc_mem( - const unsigned char *data, - size_t size -) { - if (size < 5) error(0, "Splice/SPCrror Invalid SPC (too small)"); - if (memcmp(data, SPC_MAGIC, 4) != 0) - error(0, "Splice/SPCrror Invalid SPC magic"); - - if (data[4] != SPC_VERSION) - error(0, "Splice/SPCrror Unsupported SPC version"); - - SpcMemReader r; - r.data = data; - r.size = size; - r.pos = 5; - - return read_ast_node_mem(&r); -} - - -#endif /* Splice_H */ From fbd9db56ddba6531fefdc58ff6f04a5fe3eb1120 Mon Sep 17 00:00:00 2001 From: Reboy2000 Date: Fri, 6 Feb 2026 15:28:56 -0500 Subject: [PATCH 2/7] Added embedded functiality and fixed adding strings together --- bin/Splice | Bin 35464 -> 35464 bytes src/splice.h | 52 ++++++++++++++++++++++++++++++++++++++++++++------ test/main.spc | Bin 108 -> 122 bytes test/main.spl | 5 ++--- 4 files changed, 48 insertions(+), 9 deletions(-) diff --git a/bin/Splice b/bin/Splice index 21ccd7cab1d08c06b5187363195650b8daff8601..30447f6ca8d6f6ac64e70de9c46b1314e06c3654 100755 GIT binary patch delta 2778 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 diff --git a/src/splice.h b/src/splice.h index 06e88e4..c96d623 100644 --- a/src/splice.h +++ b/src/splice.h @@ -312,6 +312,20 @@ static Value eval(ASTNode *n) { case AST_BINARY_OP: { Value a = eval(n->binop.left); Value b = eval(n->binop.right); + if (!strcmp(n->binop.op, "+")) { + if (a.type == VAL_STRING && b.type == VAL_STRING) { + size_t la = strlen(a.string); + size_t lb = strlen(b.string); + char *s = arena_alloc(la + lb + 1); + memcpy(s, a.string, la); + memcpy(s + la, b.string, lb); + s[la + lb] = 0; + return (Value){ VAL_STRING, 0, s, NULL }; + } + // fallback numeric + + return (Value){ VAL_NUMBER, a.number + b.number, NULL, NULL }; + } + if (!strcmp(n->binop.op, "+")) return (Value){ VAL_NUMBER, a.number + b.number, NULL, NULL }; if (!strcmp(n->binop.op, "-")) return (Value){ VAL_NUMBER, a.number - b.number, NULL, NULL }; if (!strcmp(n->binop.op, "*")) return (Value){ VAL_NUMBER, a.number * b.number, NULL, NULL }; @@ -322,6 +336,36 @@ static Value eval(ASTNode *n) { return (Value){ VAL_NUMBER, 0, NULL, NULL }; } } +static void splice_print_value(Value v) { + char buf[32]; + + switch (v.type) { + + case VAL_STRING: + if (v.string) { + SPLICE_PRINTLN(v.string); + } else { + SPLICE_PRINTLN("(null)"); + } + break; + + case VAL_NUMBER: + // portable, works everywhere + snprintf(buf, sizeof(buf), "%g", v.number); + SPLICE_PRINTLN(buf); + break; + + case VAL_OBJECT: + // placeholder until objects exist + SPLICE_PRINTLN(""); + break; + + default: + SPLICE_PRINTLN(""); + break; + } +} + /* ================= INTERPRET ================= */ @@ -337,14 +381,10 @@ static ExecResult interpret(ASTNode *n) { case AST_PRINT: { Value v = eval(n->print.expr); - if (v.type == VAL_STRING) SPLICE_PRINTLN(v.string); - else { - char buf[32]; - snprintf(buf, sizeof(buf), "%g", v.number); - SPLICE_PRINTLN(buf); - } + splice_print_value(v); return EXEC_OK; } + case AST_LET: case AST_ASSIGN: diff --git a/test/main.spc b/test/main.spc index 425dea920fb9b38aff7c5e7c350aeb935419e62d..041024afac1b65ce4437e5060c4cf3303057c7d0 100644 GIT binary patch delta 87 zcmd0)(hm-BW?h0J1w V^8BLg%)E4k Date: Fri, 6 Feb 2026 16:08:57 -0500 Subject: [PATCH 3/7] Added embedded functiality and fixed CICD issue --- bin/Splice | Bin 35464 -> 35464 bytes bin/spbuild | Bin 34824 -> 34952 bytes src/build.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++-- src/splice.c | 44 ++++++++++++++++++++++++++++++++++++ src/splice.h | 6 +++-- 5 files changed, 108 insertions(+), 4 deletions(-) diff --git a/bin/Splice b/bin/Splice index 30447f6ca8d6f6ac64e70de9c46b1314e06c3654..ba7557450a81e4e7108c2ba57f7ad80cfba8f62e 100755 GIT binary patch delta 2098 zcmZ`)eN0nV6hHU9wzQ={OAGSR(w2z>`Do=sWnu8dFZ_sN#fhQx%_8emN9AX;okbb^*Zm25~fB?WO;SYx^lBSaE(C#QPEEA*99YyOf+#`jbN$j2| zdW&R-bs9x>r^FU8L%U+4DaTlKCNaq|V%BByY-Q#!+EufQqX*`vL;JZ&zi!%bY?h}- zMo5h%k(bM%F%4XfB>;vr88=$aaG5;}q?>;Pr;C9C^Eq&q5-6xpaG5#1#5sdSU(IEz zSjfO7TA|#|hhhyq*5I&)YG+hvf}AYLDFbKEt9b)rjd*|trH&hA2{$Utt%2%9URd19 zx*V}+M`X}w!y>0yNFQMJZrp?+-o#B8#B}<61!gC4y3h`?O7}xnJ)TJ+=PT&55|@xr&Lpq~wN53p)?;FvGNiwkzi zMG;u64ZGfI^(OKUP!!-oud9aKyI zF(%~BqJ4sK!M}|DB?g9XU|YRxm|NhK$e?Wm^H!X54`cU{cLd{R2phD@1)ko$A2EmR z1OT46Y8@X-I2pjslMjWQ9b2;R#ZGM#weKQFRAlMr^c5GlR^hM>RzxjgD#- z6EmF`WKTERc@6f%Tf~idSs25+*qW=g^WHaDp5T!yS?mM)EOPvcIXnNHeixK){AK*I z-HfQQ9qkz!8GKpSiJou)p6a<#FB|UeppSx=>psOOuwncAFmjKb;NC}>ieD(yEBQ~s^pV8#7MNVS9L73~Ys?CH`Kk{aYFP!t~KknpqtK5ghXC|8W1`#uqM zRigE=$raHb3jj^}dDpV01&1zlps zM+XydoykpeN0ZRjpZqYBHtG$GQbyhSShA7c&}U`uwD~*vA3;t3BXs;Gsm*Zso!7Jn zO4{*l+BmlBQVSkH19afcM{Cta@FA$(AG;~VEGAz4D(019oY@l{_5oqW?4HlV6@sP5 zC)CAUn8KsdMl1gOia1<|`y@(D*{YE>Y z<5j|^K^Poe!s-tiHK`UBNFxhOmJHZD705;cL0%mUirpa)5U&Mgr504xT2Qy@AaHsp zXzKMe+n7yVMw9A+&p#|IZ8pX-Ll5aS<0O(rhmB^Ydys~jrjjq{JkvCyp|vKPy5xyS z*ARNq6hmgwUXz*h&{30_`THr2ib)|r=f%89N~kl&Ms|Bn#*|q+3Qm(7bV9NHG^tmg zk?)q*Gt^ucS#tq9hfRYy_!o@7B$bF)OZc{g%O&g$7W11WT(1-HP6=!A(Lxt$C2Yio z3wW=Dg#!@qDG|>ES-2Qzl?Yl18ITVUVR& zl@)8t;ALK5ac4sQjhb(DhulXdmd^d7XWg^ahpRIlngiQyPc&T& NQ+~+gWzT`%z=mjl!=28kZc120?E1o zaSJ5$dIv&cuu37UAP!m*B_Lb&hr@u`2!A+5EWfmI8V78^By*nE_b{39C9n5<-~H}) z?mhSPwe06w_H*IyiH`QaoE>oh5CE7b{ooUEuxX?;`;JqlRWb{AoU9q?ltT9@>XegR zC8hTBxw5)bQA?O(T|zDsIiJNPPl}v88*;@PxcQuU){SGSv$KjH)5gP7zxgoV9Pd#P zvLG^rUQt2YbO=?UiEX+{9I55R*}_?deA{8TdQQiZDWaO`Gwt+(;GpGvEuxS}%?5)XKY5eQu^L%}KJ zq`5C}97aCvj`2kf?!Bji#Yx^ClQf`)#1PK=aLp~`ZsVFm$O~W}=u%5MYpfNq2oT1D zQxa;b+M(b%0L7H73{`DKVlGGtZLynZvI65yG~TUnEjW)?U_*lrs0>mmffW;8VmGxm z?y{df(t5nX0y+aZ!pkSzh8ijPN*o`cZ$i@g!v*wt6jduCJV`p~JBs+VwAdayDv1|B zAYHR2Mf{c`UWDVJv9X-=ZW4Z4jaaH}8**da&^NS@w2yA*+9l45uMZ+s&#^w8=-3^g zDIP}BH?t`~3vi(%@UQ~?%JNGAnuTZdW1(~waRi7sqQmp*?ggmk|9EHNII}TcHXDlf z>Gu)R9k0$jPq^!aOfbwLb6K@v840orhK0nz494Oas$e=>a+x@qvZL&AAx%!= zDZw5g=qAPd)2jP}^Z~0kR=Q4)PkVur)1JZcDErCSZTSi#sbd~m#gQaGA$FFXHoal_ z09oMedu}3gi_eMeWKJ_vEy2UG4zr8YvMc7OqvBuhun+5FQM@&*)$)XV65pM@lyDV= z>}Tr)NBwwd9g(+D!wj~ZyqVZH*pz{_pNZ`m#J1;cq95>M)3$2BQs4$!xS73T%je2@ z)?mvcHEh4FgrjL+HS7!9s0ov&xi1wi z;-nc*xgLe<%#A;>ZzJ5@V0=rCMjBb8quCRAmg1ea`S4BKrGgauUXII)nBXkpI*Z~9 zotudxiT{SzH&(BydC@z0qqk)B+VwR>8|zk;vWF7~+5Oz_*w?NAho7@6v&)I1|(f18AZ~(3YD(x7P&vgC@x6HACi73uK+lW@YYDw!`hyr^eFHa*iEy z=W)RS_N_agY-RV{0tdgdY|jhi8msWkB;~BhGd1JmVOc&1?3~9#-evtBfuz`oN8lWf z*hHa-cv*!og@jnWFqK5(p9(eJBfSENYs9QF!*$iB<798fN%b!EAYnqYvvEi(e>CS~ zHH`SyVs&tU6$&=%Wn8OZmr=$W6x?H$@%sv1V3l!`f=jT77(%OprJ_psh>YjLGj^Hi zPz1#a?on{<1bP0Nf(I3RTfy@j@xPki)KucW(+^oxw|?!a8h99wYXrBo|Lw zqkr{GgL=z>% diff --git a/bin/spbuild b/bin/spbuild index 57c3526226e8d1e7df91840dc2ea7fa9a0da5af1..c1824466183d6f44d6e47b28c442e153b2bd760b 100755 GIT binary patch delta 6680 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 delta 5996 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 #include #include +#include +#include +#include /* ========================================================= This builder emits SPC compatible with the simplified VM @@ -727,8 +730,13 @@ static void write_node(FILE *f, ASTNode *n) { } static int write_spc(const char *out_path, ASTNode *root) { - FILE *f = fopen(out_path, "wb"); - if (!f) return 0; + int fd = open(out_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd < 0) return 0; + FILE *f = fdopen(fd, "wb"); + if (!f) { + close(fd); + return 0; + } fwrite(SPC_MAGIC, 1, 4, f); wr_u8(f, (uint8_t)SPC_VERSION); write_node(f, root); @@ -754,6 +762,46 @@ static char *read_file(const char *path) { return buf; } +/* Basic validation to reduce path traversal risk. + Allows only non-empty relative paths and rejects any ".." component. */ +static int is_safe_relative_path(const char *path) { + if (path == NULL || *path == '\0') { + return 0; + } + + /* Reject absolute POSIX-style paths. */ + if (path[0] == '/') { + return 0; + } + + /* Rudimentary check against Windows drive letters like "C:" or "C:\". */ + if (((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && + path[1] == ':') { + return 0; + } + + /* Scan components separated by '/' or '\\' and reject any that are exactly "..". */ + const char *p = path; + while (*p) { + while (*p == '/' || *p == '\\') { + p++; + } + if (!*p) { + break; + } + const char *start = p; + while (*p && *p != '/' && *p != '\\') { + p++; + } + size_t len = (size_t)(p - start); + if (len == 2 && start[0] == '.' && start[1] == '.') { + return 0; + } + } + + return 1; +} + int main(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Usage: %s \n", argv[0]); @@ -763,6 +811,16 @@ int main(int argc, char **argv) { const char *in = argv[1]; const char *out = argv[2]; + if (!is_safe_relative_path(in)) { + fprintf(stderr, "spbuild: unsafe input path '%s'\n", in); + return 1; + } + + if (!is_safe_relative_path(out)) { + fprintf(stderr, "spbuild: unsafe output path '%s'\n", out); + return 1; + } + char *src = read_file(in); if (!src) { fprintf(stderr, "spbuild: cannot read %s\n", in); diff --git a/src/splice.c b/src/splice.c index a94f8e2..9437f93 100644 --- a/src/splice.c +++ b/src/splice.c @@ -26,6 +26,46 @@ static void success(int ln, const char *fmt, ...) { CLI VM entry ========================= */ +/* Basic validation to reduce path traversal risk. + Allows only non-empty relative paths and rejects any ".." component. */ +static int is_safe_relative_path(const char *path) { + if (path == NULL || *path == '\0') { + return 0; + } + + /* Reject absolute POSIX-style paths. */ + if (path[0] == '/') { + return 0; + } + + /* Rudimentary check against Windows drive letters like "C:" or "C:\". */ + if (((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && + path[1] == ':') { + return 0; + } + + /* Scan components separated by '/' or '\\' and reject any that are exactly "..". */ + const char *p = path; + while (*p) { + while (*p == '/' || *p == '\\') { + p++; + } + if (!*p) { + break; + } + const char *start = p; + while (*p && *p != '/' && *p != '\\') { + p++; + } + size_t len = (size_t)(p - start); + if (len == 2 && start[0] == '.' && start[1] == '.') { + return 0; + } + } + + return 1; +} + int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: %s \n", argv[0]); @@ -38,6 +78,10 @@ int main(int argc, char **argv) { } const char *path = argv[1]; + if (!is_safe_relative_path(path)) { + fprintf(stderr, "[ERROR] invalid SPC path\n"); + return 1; + } const char *ext = strrchr(path, '.'); if (!ext || strcmp(ext, ".spc") != 0) { fprintf(stderr, "[ERROR] only .spc supported by VM now\n"); diff --git a/src/splice.h b/src/splice.h index c96d623..6820107 100644 --- a/src/splice.h +++ b/src/splice.h @@ -191,6 +191,7 @@ static ASTNode *get_func(const char *name) { #define SPC_MAGIC "SPC\0" #define SPC_VERSION 1 +#define SPLICE_MAX_STRING_LEN (64 * 1024 - 1) typedef struct { const unsigned char *data; @@ -214,8 +215,9 @@ static uint32_t rd_u32(Reader *r) { static const char *rd_str(Reader *r) { uint32_t len = rd_u32(r); - if (r->pos + len > r->size) SPLICE_FAIL("SPC_STR"); - char *s = (char *)arena_alloc(len + 1); + if (len > r->size - r->pos) SPLICE_FAIL("SPC_STR"); + if (len > SPLICE_MAX_STRING_LEN) SPLICE_FAIL("SPC_STR_LEN"); + char *s = (char *)arena_alloc((size_t)len + 1); memcpy(s, r->data + r->pos, len); s[len] = 0; r->pos += len; From 4c7fcf45512a7e1e425a4a7f7ad66cdec1c1ebe7 Mon Sep 17 00:00:00 2001 From: Reboy2000 Date: Fri, 6 Feb 2026 16:32:54 -0500 Subject: [PATCH 4/7] Added embedded functiality and fixed CICD issue --- src/build.c | 137 +++++++++++++++++++++++++++++++++++++++++++++------ src/splice.c | 71 +++++++++++++++++++++++--- 2 files changed, 184 insertions(+), 24 deletions(-) diff --git a/src/build.c b/src/build.c index 8a6f198..2516560 100644 --- a/src/build.c +++ b/src/build.c @@ -6,6 +6,20 @@ #include #include #include +#include +#ifdef _WIN32 +#include +#endif + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#ifdef _WIN32 +#define splice_getcwd _getcwd +#else +#define splice_getcwd getcwd +#endif /* ========================================================= This builder emits SPC compatible with the simplified VM @@ -764,24 +778,24 @@ static char *read_file(const char *path) { /* Basic validation to reduce path traversal risk. Allows only non-empty relative paths and rejects any ".." component. */ -static int is_safe_relative_path(const char *path) { - if (path == NULL || *path == '\0') { +static int is_safe_relative_path(const char *arg) { + if (arg == NULL || *arg == '\0') { return 0; } /* Reject absolute POSIX-style paths. */ - if (path[0] == '/') { + if (arg[0] == '/') { return 0; } /* Rudimentary check against Windows drive letters like "C:" or "C:\". */ - if (((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && - path[1] == ':') { + if (((arg[0] >= 'A' && arg[0] <= 'Z') || (arg[0] >= 'a' && arg[0] <= 'z')) && + arg[1] == ':') { return 0; } /* Scan components separated by '/' or '\\' and reject any that are exactly "..". */ - const char *p = path; + const char *p = arg; while (*p) { while (*p == '/' || *p == '\\') { p++; @@ -802,28 +816,119 @@ static int is_safe_relative_path(const char *path) { return 1; } +static int path_within_base(const char *path, const char *base) { + size_t base_len = strlen(base); + if (strncmp(path, base, base_len) != 0) { + return 0; + } + return path[base_len] == '\0' || path[base_len] == '/'; +} + +static int fullpath_buf(const char *path, char *out, size_t out_sz) { +#ifdef _WIN32 + return _fullpath(out, path, out_sz) != NULL; +#else + (void)out_sz; + return realpath(path, out) != NULL; +#endif +} + +static int resolve_input_path(const char *arg, char *dst, size_t dst_len) { + if (!is_safe_relative_path(arg)) { + return 0; + } + + char cwd[PATH_MAX]; + char resolved[PATH_MAX]; + if (!splice_getcwd(cwd, sizeof(cwd))) { + return 0; + } + if (!fullpath_buf(arg, resolved, sizeof(resolved))) { + return 0; + } + if (!path_within_base(resolved, cwd)) { + return 0; + } + if (snprintf(dst, dst_len, "%s", resolved) >= (int)dst_len) { + return 0; + } + return 1; +} + +static int resolve_output_path(const char *arg, char *dst, size_t dst_len) { + if (!is_safe_relative_path(arg)) { + return 0; + } + + const char *slash = strrchr(arg, '/'); + const char *bslash = strrchr(arg, '\\'); + if (bslash && (!slash || bslash > slash)) { + slash = bslash; + } + + char dir[PATH_MAX]; + const char *base = arg; + if (slash) { + size_t dlen = (size_t)(slash - arg); + if (dlen == 0 || dlen >= sizeof(dir)) { + return 0; + } + memcpy(dir, arg, dlen); + dir[dlen] = '\0'; + base = slash + 1; + } else { + strcpy(dir, "."); + } + + if (*base == '\0' || strcmp(base, ".") == 0 || strcmp(base, "..") == 0) { + return 0; + } + if (strchr(base, '/') != NULL || strchr(base, '\\') != NULL) { + return 0; + } + + char cwd[PATH_MAX]; + char dir_resolved[PATH_MAX]; + if (!splice_getcwd(cwd, sizeof(cwd))) { + return 0; + } + if (!fullpath_buf(dir, dir_resolved, sizeof(dir_resolved))) { + return 0; + } + if (!path_within_base(dir_resolved, cwd)) { + return 0; + } + + if (snprintf(dst, dst_len, "%s/%s", dir_resolved, base) >= (int)dst_len) { + return 0; + } + return 1; +} + int main(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } - const char *in = argv[1]; - const char *out = argv[2]; + const char *in_arg = argv[1]; + const char *out_arg = argv[2]; + char in_path[PATH_MAX]; + char out_path[PATH_MAX]; - if (!is_safe_relative_path(in)) { - fprintf(stderr, "spbuild: unsafe input path '%s'\n", in); + if (!resolve_input_path(in_arg, in_path, sizeof(in_path))) { + fprintf(stderr, "spbuild: unsafe input path '%s'\n", in_arg); return 1; } - if (!is_safe_relative_path(out)) { - fprintf(stderr, "spbuild: unsafe output path '%s'\n", out); + if (!resolve_output_path(out_arg, out_path, sizeof(out_path))) { + fprintf(stderr, "spbuild: unsafe output path '%s'\n", out_arg); return 1; } - char *src = read_file(in); + char *src = read_file(in_path); if (!src) { - fprintf(stderr, "spbuild: cannot read %s\n", in); + fprintf(stderr, "spbuild: cannot read %s\n", in_arg); return 1; } @@ -832,8 +937,8 @@ int main(int argc, char **argv) { ASTNode *root = parse_program(&tv); - if (!write_spc(out, root)) { - fprintf(stderr, "spbuild: failed to write %s\n", out); + if (!write_spc(out_path, root)) { + fprintf(stderr, "spbuild: failed to write %s\n", out_arg); free_ast(root); tv_free(&tv); free(src); diff --git a/src/splice.c b/src/splice.c index 9437f93..6bac25a 100644 --- a/src/splice.c +++ b/src/splice.c @@ -4,6 +4,21 @@ #include #include #include +#include +#include +#ifdef _WIN32 +#include +#endif + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +#ifdef _WIN32 +#define splice_getcwd _getcwd +#else +#define splice_getcwd getcwd +#endif #include "splice.h" #include "sdk.h" @@ -28,24 +43,24 @@ static void success(int ln, const char *fmt, ...) { /* Basic validation to reduce path traversal risk. Allows only non-empty relative paths and rejects any ".." component. */ -static int is_safe_relative_path(const char *path) { - if (path == NULL || *path == '\0') { +static int is_safe_relative_path(const char *arg) { + if (arg == NULL || *arg == '\0') { return 0; } /* Reject absolute POSIX-style paths. */ - if (path[0] == '/') { + if (arg[0] == '/') { return 0; } /* Rudimentary check against Windows drive letters like "C:" or "C:\". */ - if (((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && - path[1] == ':') { + if (((arg[0] >= 'A' && arg[0] <= 'Z') || (arg[0] >= 'a' && arg[0] <= 'z')) && + arg[1] == ':') { return 0; } /* Scan components separated by '/' or '\\' and reject any that are exactly "..". */ - const char *p = path; + const char *p = arg; while (*p) { while (*p == '/' || *p == '\\') { p++; @@ -66,6 +81,45 @@ static int is_safe_relative_path(const char *path) { return 1; } +static int path_within_base(const char *path, const char *base) { + size_t base_len = strlen(base); + if (strncmp(path, base, base_len) != 0) { + return 0; + } + return path[base_len] == '\0' || path[base_len] == '/'; +} + +static int fullpath_buf(const char *path, char *out, size_t out_sz) { +#ifdef _WIN32 + return _fullpath(out, path, out_sz) != NULL; +#else + (void)out_sz; + return realpath(path, out) != NULL; +#endif +} + +static int resolve_input_path(const char *arg, char *dst, size_t dst_len) { + if (!is_safe_relative_path(arg)) { + return 0; + } + + char cwd[PATH_MAX]; + char resolved[PATH_MAX]; + if (!splice_getcwd(cwd, sizeof(cwd))) { + return 0; + } + if (!fullpath_buf(arg, resolved, sizeof(resolved))) { + return 0; + } + if (!path_within_base(resolved, cwd)) { + return 0; + } + if (snprintf(dst, dst_len, "%s", resolved) >= (int)dst_len) { + return 0; + } + return 1; +} + int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: %s \n", argv[0]); @@ -77,11 +131,12 @@ int main(int argc, char **argv) { return 0; } - const char *path = argv[1]; - if (!is_safe_relative_path(path)) { + char path_buf[PATH_MAX]; + if (!resolve_input_path(argv[1], path_buf, sizeof(path_buf))) { fprintf(stderr, "[ERROR] invalid SPC path\n"); return 1; } + const char *path = path_buf; const char *ext = strrchr(path, '.'); if (!ext || strcmp(ext, ".spc") != 0) { fprintf(stderr, "[ERROR] only .spc supported by VM now\n"); From e3f1d7a672c568b76bd76f0c2dc12a701f97673c Mon Sep 17 00:00:00 2001 From: Reboy2000 Date: Fri, 6 Feb 2026 16:45:38 -0500 Subject: [PATCH 5/7] Added embedded functiality and fixed CICD issue --- src/build.c | 70 ++++++++++++++++++++++------------------------------- 1 file changed, 29 insertions(+), 41 deletions(-) diff --git a/src/build.c b/src/build.c index 2516560..a7fb294 100644 --- a/src/build.c +++ b/src/build.c @@ -855,54 +855,31 @@ static int resolve_input_path(const char *arg, char *dst, size_t dst_len) { return 1; } -static int resolve_output_path(const char *arg, char *dst, size_t dst_len) { - if (!is_safe_relative_path(arg)) { +static int is_safe_filename(const char *name) { + if (name == NULL || *name == '\0') { return 0; } - - const char *slash = strrchr(arg, '/'); - const char *bslash = strrchr(arg, '\\'); - if (bslash && (!slash || bslash > slash)) { - slash = bslash; - } - - char dir[PATH_MAX]; - const char *base = arg; - if (slash) { - size_t dlen = (size_t)(slash - arg); - if (dlen == 0 || dlen >= sizeof(dir)) { + for (const char *p = name; *p; p++) { + unsigned char c = (unsigned char)*p; + if (!(c == '.' || c == '_' || c == '-' || + (c >= '0' && c <= '9') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z'))) { return 0; } - memcpy(dir, arg, dlen); - dir[dlen] = '\0'; - base = slash + 1; - } else { - strcpy(dir, "."); - } - - if (*base == '\0' || strcmp(base, ".") == 0 || strcmp(base, "..") == 0) { - return 0; - } - if (strchr(base, '/') != NULL || strchr(base, '\\') != NULL) { - return 0; - } - - char cwd[PATH_MAX]; - char dir_resolved[PATH_MAX]; - if (!splice_getcwd(cwd, sizeof(cwd))) { - return 0; - } - if (!fullpath_buf(dir, dir_resolved, sizeof(dir_resolved))) { - return 0; } - if (!path_within_base(dir_resolved, cwd)) { + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { return 0; } + return 1; +} - if (snprintf(dst, dst_len, "%s/%s", dir_resolved, base) >= (int)dst_len) { - return 0; +static int ensure_out_dir(const char *dir) { + struct stat st; + if (stat(dir, &st) == 0) { + return S_ISDIR(st.st_mode); } - return 1; + return mkdir(dir, 0755) == 0; } int main(int argc, char **argv) { @@ -915,14 +892,25 @@ int main(int argc, char **argv) { const char *out_arg = argv[2]; char in_path[PATH_MAX]; char out_path[PATH_MAX]; + const char *out_dir = "./out"; if (!resolve_input_path(in_arg, in_path, sizeof(in_path))) { fprintf(stderr, "spbuild: unsafe input path '%s'\n", in_arg); return 1; } - if (!resolve_output_path(out_arg, out_path, sizeof(out_path))) { - fprintf(stderr, "spbuild: unsafe output path '%s'\n", out_arg); + if (!is_safe_filename(out_arg)) { + fprintf(stderr, "spbuild: unsafe output filename '%s'\n", out_arg); + return 1; + } + + if (!ensure_out_dir(out_dir)) { + fprintf(stderr, "spbuild: cannot create output dir %s\n", out_dir); + return 1; + } + + if (snprintf(out_path, sizeof(out_path), "%s/%s", out_dir, out_arg) >= (int)sizeof(out_path)) { + fprintf(stderr, "spbuild: output path too long\n"); return 1; } From 95a30a152b057c8fe1fa06ca170bc85f65a40ab5 Mon Sep 17 00:00:00 2001 From: Reboy2000 Date: Fri, 6 Feb 2026 17:11:41 -0500 Subject: [PATCH 6/7] Added embedded functiality and fixed CICD issue --- test/out/main.spc | Bin 0 -> 122 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/out/main.spc diff --git a/test/out/main.spc b/test/out/main.spc new file mode 100644 index 0000000000000000000000000000000000000000..041024afac1b65ce4437e5060c4cf3303057c7d0 GIT binary patch literal 122 zcmWFzaAsiSW@2DqU||IkWw~XkMT{Vpp`L-B0Xs7zkf{wJ8P$Mn&oqUSjLc#MASx=& z%gfA5*HH*A$jMAj4f9n1>MhR9&$CisLYO5A)C5vpo?ny=RIQMlUsRNuT#{1>0A^zx AfdBvi literal 0 HcmV?d00001 From 060432023e7bfb97715226db9710b0bc0ee64d19 Mon Sep 17 00:00:00 2001 From: Reboy2000 Date: Fri, 6 Feb 2026 17:54:01 -0500 Subject: [PATCH 7/7] Added embedded functiality and fixed CICD issue --- bin/Splice | Bin 35464 -> 35640 bytes bin/spbuild | Bin 34952 -> 35272 bytes src/build.c | 16 +--------------- src/splice.c | 44 ++------------------------------------------ 4 files changed, 3 insertions(+), 57 deletions(-) diff --git a/bin/Splice b/bin/Splice index ba7557450a81e4e7108c2ba57f7ad80cfba8f62e..c98b0b58499af85f0d0e1713c05d8efd844490b4 100755 GIT binary patch delta 2922 zcmZ`*3s6+o8UD|``{K1Muplh#-d!aG#PD1JI|>UGYcf_4NyJ)%s8Fy(Swh-GXm=s5 zLRcfW2PxVl;*2)1u^n`2>a=l$#F~)A1`WduTq$obF)SK4#o{{*zUqu?7GF0F}ZY9I*zPMzTZQ(GsnYSa3(vCZyYB`aiOD zTQvQUWQV(rl6C)mYs3s4;G(IGF^3kKq*WUuEf<<-E%TyLBgX(604qBRlM2N|jqFEE zRsg-lE=-6-zjOen4i_Su08(%kLPerfB3_2`S9<}v=n4f#77BTlU%3Es_;q2lO%bo7 z?%Hc%hrT!c{NSQ%?D5ww(svZiajVhOjGhT7LS!M5FtBtHceMa?5)8J`JiXye&z%C& zGgHs>%zi&*I3A!)6J+ygh|M>lR{3bR(&Dfs@r#wvo(|5ULI8cb!se@FY|FSmfW3@B zah1xpEaxJzmocdO+m-mijUzvm302}-gqryT6f zAjg;l`XOR-)_Y>$F zU!>#5&?gMB4Y9D!p?;eM&mCb6WgNLHXcHs&(3>i~qce#YHf3R*MGJAhs(|(h^(r5p zTU!DiLINH_4$)jCAy6y?#{=SL>htixa(kvXyw%;$mX0>>csi>Rq>Yi8+2RRIG{J%o zi|@^ANX5h;7J40HbD*<-@%3!6zzry!MGha5T>yP}U3x8$%ugcTLcl6^A;jimu>kQT z^~qwP0}83o0kj*Pon0XM^(Ko+UPaCv0DZ!a2{=E@!wsE8PAL2o&R-%g=OJEaVU1&3*e2V|7z;Dv9a^4_ z?PYm_t=F=6D5&F4ie4G_2 z<_F2zEX}E2N63>5vpJ1k4vWyb<}6WG&y$_>pnkdHcp4u_$HVl@UnIl1F=%jj#}jjX z`2d2~2m1mz&(xpo!40>;L^_W>X|r@0_+Y$NB6m;bdO6u?v*(#D7a za)}N{q>~S*azTwQ!OG_lxM?FThi|hQ&jspQ@DH+wmPN({;?fcPK7h0zZlNua&zk;i z<+0Q478Ei05-X2yhorlgQZ6cqQDo49s3@}8TNSm5#Nlu54c%_MBMdhx>c9M!-X!VU zBj|a8W> zfPZl)1Y8P*z!W{rGiK8Uqxq2~pU;W|Lr9}g+aLJ~d6}VXyW*g-LwI~*QC6ayql3nW ziH_bha-@@nm@-K4*(MA+C%%I?bWTp5&EgDB?*Nx?_6~7>GzAG1J|8fGVxb|<&V=6*Ag55y zq1-}Y7$6ZSaVQxmt58Z%cF^V%urnYKlT?Hlt`maW-i=mxz-x-X0<0_hj5H@rVZ0tOoG!JLY!`bujs(H9z9xj=S=>b&Y)$bMN*#4L{oI`SdsS4XX_o1{>bY@7+9hOe>-=5 zf6jFhb%(MqY*$(GxpqEGc#pQ?DhV)|IZotbl~_OXD9{+ delta 2470 zcmZ8iYj9K75#DpJBh$&&oS(!C-)Z2XY?Am9!$*OUeq==f1j3W0z$q|-*JEh?=whD(&8GU-2gRelMADU zBFDDx?N~Uyp&HJ-we-EmesN+QCJa4G9W+y>nZ+cUXU&t=DA_c=-UD!qk+VgPd69i^ z<40_0wm}%0+b<5`sL|gkm#9V~^jAT+xe35sWl&PP1*Ik-K(%xZLSX?~q_-f{#-L@7 zNvTs@dejMLrbrVcA!ZU2J4cYj!~>aeLmh?xP4GYIZ6q zktr!{ZXR@$s9JEp7;auf{j34{1LOqjO}ifW&wR_2Xp2pY)|hQ>ennDr#m*ab&LF@D zE$Fa6S)zVUb7(26?{*Hey6^J_-S<1hBOln{@pPu=MoU%A=WC>wZ7?z;LX1K9(pt)6 zSm(x0QU5jRHQqRl-xlDj_h|u=q9>+tS`9EVOLjlaxkkFri1z~NCGdRdH)=drtd}q? zOq4!YW#LHkQwFHyilE9`!_B`WLM>>?xnq;$WHXIrBaDQ(?hx6Z!-&}}jFjSU%|6*( zrdCrx#uqI@b}i6PsGlJpXEbh6N13{UNNt?-voy&a+r!D5(_~L9rje_r%F<wKd_f)CfJXaPR)uZ$2HNi1XG^PXRPN${>)qkTWtI_`M9l`L)tPeWJ1igeeTuLe_=ETkt z&IeEL@|j=%rk=`T8CaiM&aAx~{Ge_m-2URPYG{9iN?@?L-Ffo(4rcpu$_6brsmy7+ zx<1vxEWOr&4Dy#p6?4b5(jO+jAbNTyDaJK1H+G1y)Lw(*jyKuk7;?U1ulP2(sDE2| z{GROXf>05_=RG<0Eq9pkb+^jevBtC7urHuyqUv!A13K|e9~k!>ec;Zys*Ta^p&HSJ z*gv=b?IBvAD4eC8M!nyWr)JTvO6w-LPWGzS+qxvzDH5K{>1JkJlH0(p;?Hxd*n2pX zyVo%nRM#;0N?`|&&xq#OMJ&$yq&Py;2Wi6&S|K%Rg)^U;AT$ZkVy2L0nsJg&4Y2;> zPh(krhi|k(B}2cTB%KpfsB}T=uoG;nvLUn2g?3*pHu?(7zWIDU-N=RB8tuvn_Im-ve!~&tQSEMr#U$%U zuH%4j8C#9#eG*u63AO1lmAAN1c?uw;bY#H7%;l{*aea+BvT}}NX!B- zdd0%0F&ZhxiAV_=x}2EbRUr(Ta8p-d=XYqS?IfeL^wh!q9G6;j{1C?#SJa?c3_a4~9N3#bm+R zK6JEW#TQaW-<9A-YwZ(N+yAopjgiuoOFrEm*rdF+cXjB}^1fBiwNIWdy?PqshZH&S z;zxZm_qCmS)77^ADc_%O+-S+U`Q)*gOL_0$<86N5Cq!yp@<%C=v!?sU3m(~dalhlA a8UBm^7j82Hl;E<>-Is~1WhwDTi;x;}Lw_gJCCSgqv#Gdcm?mPqd-hXqG({-v& zovJ!jb?TgZ+a&QQ6FWW;H>63QEr=|eP9zY~D*l6~Y&y#k<``eg_LKkSr;?1bqWoC4 zSFlF5&+w0KQ22-2XRr@3Ntx#-zY>_laZ#RR7p;+ffzdY6Kirqc8pU75%i1}`P81L) zkE`(;&*BxCMw;grYnAEv0j`k6hAWlqUd5lqqd{!GGI5a>l5>gN;o#A3CxVs4m6^~= z0{GG$L<3lBFlbBB_!AmvoR`nFa^b3y`-uvrq9`_?Obb8#?0uJ7q6+@!_vy~9AI$q( zJ2R=;rWQb^75wRed0Yp7;?cOA8Er(52oRR298U7AXI7i(MxWxE57LM_v=Mf%R*Ta4saWPW+Pwt+GYay;{1IEf$Zk=`O&hUg ziBhzVI&;bG7_uzz!kCV92u}y|TtRzs@NOy|P(*b@vBU;!rtIOQ|E%p(lpdd@wR?j$ znj_OyLebm`a<&l_SE=o@<_?q7deK+hrJ%fFMHKs7eTQq{g0FZ;5!^kj&~*oo`|=0A zwn~EA?HCE7PP*Xh8T>k2;!1u1?mqA9;o~|Obyh*$=lB@fAZTAH4BZ;g%g*`oyQ-TD zCRRCk7V^O}gw-JmBlAa8m_xFTe*}@M(?a3Sx6k;Bmn*0zO<|vmUv@rhWsqlVlc zw$s4rEa0aU+HPc#VOmXX!A=!PFZ5E;mv8wOH{JIB?k|Pl6E>f(_>@3-FC4*GIyv|K z;46M!(CMCsBM!q6>A)wu+^cW}d|s@O+I_xgi-(+p@K&0}KI`ZYzC4bXb3E8SYawt0 zR?zvXD6e<`D}DoB*$ah8i4l55l%6bfJD%m90&TJ)c*`H#?-i00dWJSH-KfFbU_gJP7M-hLYM-vmz6!Hlz3hoQMet6fZM@)2L#Irbjk1H z8qlbOdU&V=cyd1Y8bRZRHvygly@n(0Kz7|W_VQjeNq2LGDDn=JQBMIToQbk~dHikY zf2A;Xe+~YX;Bi%33;5h6==1n$g{^-a3L|f)=ypU~-HvOKtj9&W*XI-W$L)1H&LQ9S zXyaW5fjpKYG-lFJL@)GN=FymCP)xVPljjh2hbxnu1Mqn4BFdXB7R)@Vi*45y+C3yh zMq=j~hEf-#4@BF&79pr6a~$E^XjwK_Am@oW2&E0)iG@o1|xE)%= z!Wu-yIzSDi)KsSx@a?BXR%f7<3b7{>wN!j#O?83GDL5B_mMEqS0KT9 zbtFxPGVUBL1luhXDIhr)gSiQ@E9Ih1_DwtiH>usT%s$`CM|I{v0>T$Eq6Z z5`!l(uF^>BKyy!~HEX0hL9>E(_!&WVStW{W|>5LL?ab} z)@S>XX0`Qbq$Qkj64CaqM*2BucF+-?^v$Gg}m{pC>$hEQt`7s{>=)h^rx!N(A% zUce6l&jI!W8nKJs2h0b27jP&H1z`*Aw?vTV>};jS1Dd?AQND$1sQ4=5zth02(_}sU zp<1DfeHwbXA^l+J%V42l)29rfqCIehk~9T#giW_j)~f|u(}do0s3##mq4!CM9Z2@; z4O1p-;H=3q(jcQ`LdK}zoR{(wGIns=ZP0BQ`Ft%-gTjgVkK&4v_!IMMFyERW z+wfYrL0qU{ABImAD@FFtaI^j+c-RUFE%0mtvqconS9->lDLua1m7alpgo7_!`kbhf zSGteKebKwX$17nkpc3#_K)z)^0^|uF4rNz)-^RUQ@LQnOMoc~eco}(#c_T8_S}^rR za3+@=+92|pwkXnvUNKpT-=NlM(IZH@SYBNhNh$@vKC`duN|&Y$iXfak#JUw>5-GJ3$;!`i&%PE;5Ny zK7{i_dOO)zYH0=HMC#m->GIA<2+#rTVz}idzC1oUz9_m zX5GeTgpXO#lr_Quw#{T>?@ft{Fe&`$FcFqNu+b^^Ek6Uh`Dr~teNct%mOwpi2-F48 zd9eI;he~@y8Z`)=yLl4)1$;b6q$KHg=nuqE&w1caA^EtkpJMMtmkJIx&9qD?gbkVH zIncz@aUUR`Iic}7uF*g}L(m8hU#8IqjZ=h&p>axg3;4K2Tw^iU7)d>2z`v3;PU&_q zeN46(DzZ5-QX| zl}`pq3)qXXCgrF`nk#p{4VDUIj!(7B5@%MP(@7cfz<7|9#0QjBS}9KEC<j`y{2PVk*GTGSu4lA_$>1y)BOm8g<4~UrH-ORRFTSK>mO>C(H*itOa zHYb$nrH$B)W}*Wt_UPG4+j1d{_1ad1ADFZOWo}F2;u(;77@1}icEK=X_a$Tk&t;xz zpQDdgo*tfgZ^)Tu?EaKJnwWF7q|Xzd7FyW0q$FVvdpfBjrx0fO<8#1<_`n4Qc_#(R zCi+(yvo1MN3<_iI$t%pmddae!oO^LMv)qBZJ8sC3a3FnD&wR;M$l0otCPBr%NGVAW zl#+3I)fm5AF$ zk?j+E?LqcdYOJt>eVbZ+^_ol5b_sLYxwP$94?U3HC#S9(c8R1I=M!IM_JA!=jn9@#fH z1+%EE#EjZdNq2>_(o1`w;!fb!H*^BJ0|# zmU}R1noV%wm90O#8v91H9GClbllqO5`gc$2Z<^HK>hI&*iNOC}qP^%Hq6+#g{e}9d zh*Idbz?h%o$6qVWcF{_z{y&C)Wc+va+$7x3E>54Vnx0+X(#-O+XO-MlRa;S4zP`em zn$nc2qo#&CTdHc;UT0lXURPJ&Y+ch>QQll(1$RSBvvqA%qcx>TM=4F&DNR4}21Xmo zn=7r&_4U@;`nq-Op6miKrRm@c*)ND9zVxlmiMviucGBZ|Ky)dLgvY{3{63l#1u>-T zh#}S5I8ryqlV(N&X+N=0P)`!+{+>+1J*gDJ4&<0(tO6-Y(Av;mK^s9!6G3BN4o6~;rX>2q%Q=Ns=>)07~8hoqJz4!dV#QblxOl^OAF?J1}aCd9~ z#swh%$j9jk0cZ)p>Rg#lwE{5qxKVX^i~WnMZ+5kJ~T$SJ2RdTNO%qn?i1cd z8$-jVOd$>pt`rK;a4-uiME0`VvvOTU^O}3sQtA5g+S>XxfEDZ4G;9Q{K_*aXeM3bZ zl_Ep@qrAg&Ao+%Qj@uXAT>7(;;$=7U@q4Q3Af&0Tp|Ps2`7ZRD%bVHK9Y%fKn)MAF zZEUVzOYHe=`L0Wry+VKJyJE>PRfqVR(53jhrc3-pg$3|uDZClVJ!`IDl&d_`&||4T5lj&1<#bOM=V;j=+PS|KE>C zUxDM`zHB`G3OxG?eC8E6z8OuPZ>EKp6DR_DBhjMRXtFfYjJ+Nbsx^0G4L?tQ8pXy5607LmB1nM0< zT&m0ZQ|L!&>Q#SyHLG(@%S#1kKDl}G$hx}q7rm>BSL-&^rU-+pcip<7Zoyc^>ExF_ ze97?U@3!8oTzaA8!`C?#kz?x0UT1%z2`v zXvy&>ZnXUM{4YDA3UhZqF#O&#$91=PZvDm1l`EX#&wut*q4LP^vDiI3e*NA@o)fzL zd(Yn2`q#YPkmaj?H9L0Kg`Js9*;baE-FR3RVkn8bX|-Ww!@ji0Bkw)*UM;{ z6qWovXz8^Z-m)v)+6NO$9<8w4XW_a%Vj0_7J!0n5nAM|c%B1Yv-}%qrgRr~LeeTgS z=bZPv=lwqKd-!*8sa}>EKafxcA~J{{w48A~mlYeQXZyit{7_QXydpn= zH?x!&{doUwipsyOAJ0F=A$zVL+!i<_){=6_A*IB40=w0cf7_GG*GexW^#qW~K@=dT zK(zRc<4LMaV-I$ObSdbfiM2{08>LqB->XhZ^*TOHojS7(SQCldQ9x?j0$nf)W+rsn zN@TVbOlerL$oAxvA*`S;00o-%Y7fJ&t6FjTXlDJc+1brBU<{aB3o`f z7}avKJi29IQ%p++QKK%};ngvR*HcAaOBlIMGIHA04sSbAd%ff+tRb>n-X#}PlT*Pr zI=lq@n=0}`qaN#LQ+c^Iv98BDd#Zdon3__^UFoTEcp+b-4a99gE@8T>)-~iD*i;GT z6%b-Bgeiwmb6W1MexT(_+U!*jR_;_)^-k0|yulmHF*XgWC|OFbdLrjDT1R2Y6>>T5 z_?(ASWWS;^w8CzNj3h^a5U$h5f6&fxP`}UVQH8c%QH8XI4*2qpK!=*4IzYFfI@}o4 z;WtI{`#xVBKcO{A)qmr?+GTYqU^SGMqU(ubxeu(Xpl0)f)c%u-S{b=No(7ed0oJE7 zwl2qb3ve-i0`p1p``_u*k}TkkN2wilYb^x675yt!Z0jZ9RbcOx$5IN!nQ54NF3~ty z$NudtPb7JFOYTZjin}r&_Pi}Qygr}QlGyC7ys0PeEeI0-^Q0(F_>N`dyDmyvywRFLlpNodd&IXZ~N#NcY!#hF{MfG2&1tuM~u zwXon7SMU0q85=C~N*K9xs(Q00pE}s)O}qdScHZ$F{i{TVK2-uNP+vKqgZ2Zyg?V-F zldBYry5KS4nTzmV7X+|?h&q1O#%7pRFuMb0ieL@E*O2qP5I|*a#RW8YV3uHbRxs|B z>h}r!E`A^+uK07we7ZV_{xgeKfa zw;gmlXYn7x%%!yf!iE8A5kwVh8ez?6V9l8NeGh@baT*!|rWiE{E3QPGE#n8m;#e2& z2ur(<#j{}H{v}vs)&&BqH-zNzpeD?w0|keLVi+RR0n8@&tg4Q=$mH1A2b1s9x(m%d z-#eZVb0F{%LvG`&FsuU}t;U(^Qfj}0>oI8DM*yo^0i!BGhE&s6A}<9jsX3hN_kh<* zl*10do=`aoFYu$`qwG48=K-@X^d90hYa+>W0CNw8*-4%WSPEblcz8sdvnFlGiRmO4 zVYk{c1ojZg1%NeMhrrg8JP|O*P}p-MXA8=J4G)4O!29xrv-pXKu!5N|gSaBW+o__& z#ax~Oa)u$ZJ{MgmKZ~5+u%Y(JS;3(V4rb|9uGLS-(q?iFCwnIdVoele=LW0Yl`fFo zgIM?ko_QPNS&X+ZqPQAjaRENUI0@rvjGjnb`OVZ~iKdR9rcg%*U<#`aa&5^S$*LEu zXgq%}G8KVxG4lRE>BOcZOHnT;$`G5Oa#!vc#=|4=g;CjIgW7BfAIV~mV397U7`j0r zOc+Vzlnqrl?~BJhaCE=VKQU@HVrUd9c}PSvX@*3 z(nd=1Fi1CB6n#!b8>AYEpN}46D}zud@%Ndo3ncY`^}@^+j8DM4N!%7wR7@S-x)596 ztH>P45FVl?k<|V=02cvAB)zvWifcF@qbO~?7)6W+f%|Yz#{lMz6%xhAiWG7Z$$m(} z7ir`e*XT2~UM-?muZTWZud9pStg?9^14_v?k3`-AjFwQO2xNS`0XjSyvd1F77|`RP zgBkkZbz~5-&ca?7gIpZ+vM15uc_qF;hCF97GpB`OqYUBFWV@Or?7vKJxz0m(mKp}>r- zv-p<=3tPhn3~>|mz&FEyeJB_3@_kAM9v^MivSN8*;B1a^cD^`UC1eq$c#g6c*E(An z^{ty=knCZ!pnRLIyI2-VL&-Ml-C}t%aD>6K{vK|43IA{7*pTJaqM*+%;TMdhEQecT zXC#cEmUDxofv9Gg}+!nW(?c>kKWkyRXY7#G+LC@^v-EmJve+iR7y?fC0{I@1k zVz*X)MWX8l*0fL9t{GHCFp-+%1}>ZQ>=}O0RK=!pP5eBT4)P}Mir2>{f|y|OJVt>( z$Z8aSIbI>^1JPLaB8bMe&IQri0isj**?2uG54YV0S_k3*&M^CS&~Fgt&2g)F`tOwU-3~PTxZ< z#xU}Rz{gwI{Yp1*mRKA$+?c#P(X0+v%T7Ka$)qk-%Mb9Ai6-?TNuD7l(qZE71LL4t zE>JM`GI@eBIjWM!Dj4rde0-8wZDVqxvJtJ3jl2Rh2ACY7KqjcTr`tWtKv6HRGyVitv^T z?A=uH_mUss>n;D1-DdMnQHoS%ZB4;ok*de}U=%PLJdStYbvvt9JpKj$-tu_NJ#d}^ zHnyJNt5V(@3C2UrS{)T2F^Id8ziHhTQKTD;^hQKFFG($qn5UID_%B=A!YVCqNL>KO zT~1vX)i~r{DA?s`vvNT7eMF6s?S^2+*6$HoA|;4W{0ws_+%BT%1LQq%#d~>oTJA_n zp`#A627Y){8r#c19o3jS5Hx7+HeqgI<( zF7vZldbX2a&dSXiZX{V`P$iyF9#T+Bbn%W3Mb~gO|HZh}u~|W~xPU`#oiT{=k#?w0 z_|9>2g!3;bDPxb4GJXRw235x!p{Wzj?}u1lDb9~={Us02o-{(+BlEJYtd6LYDgUS$ zv*RSwztbP-ZJJM`sbX-?k8$reg{IKMwDceB2I$?x{-^7L{~OvctUa`eugjUj+j9zn z*DP9JlafAqT{@pWq2Nf}gf}G#-(s@uiHpN%D&ZB1HY$?XsYsFxaipp?k=mX>n(2w8 z#TSR5L&>DumP)~Iq*2JWF%)XRM-qJCP(6u$2)zfrA3cZREP5k)7dpO>sB-vGd%E%K zk9|r57k{gcXp{JLdj`A6&3WnUD?Tl6JiEfzV*V}vATL#W7kKvX`L}twT05#&*bh8) z(pc>hOketeS7O?PY1B2oby9BlpD}H};pdRSujgfI)p%UJc9Z`IJUgbdZy!m`|62EI zb-u*4j{h<5n;@i!qWyvKKLX={2}i7@mx_6HetvigFrXwILpSqicT>zhM(hpr6X;;c zzF_=}yQ65;qLnLGFQ%$hOIIykvw^BsuUYyyRjpkb0Iyy8ueFaap{jN3*DhYQMj2NM z1gzJuUcxn7^XgtpZe~3Zf0oW^8l@BLkm`$|L(*p&@Kstgw2MFwGy1<(nFH%L0_!n> z_1#4OKD?mdm*EddOlfLhooMy1%L41HRR6jHE>jM80)R&X>&8)jgxbJ*WrlyfKCo`G z`PZn4%K4R9{`GTWjjKy;|9a;U+f#Lnd3eh~_*6oyY|W9rh&{K61hx8~8`)vjDZ zmL*?)uKTaUzxdO%Z}-!t$vwM&d})U2qtl;!KkfAgwpZT0x<4!G;J6d<=hkh0eb=cE z$D1D6bM^P?pZ~Ng@(&OHdP{ZUhuUS&7hQU8R&MT&%WtkY=YB0dx$HlS+mgx;cctl~ zKK=6Nw;FTDl@~q!&Bg`a*iTI?|71%|$g&&P&o=&gbLfL7dk-9c>&8^=?&|(sJGPxV z98>!{*B4JN`Dn)#ihSTl%@$S4z#0{w{!I0#RTma5tZ)0^RnI%?bIvquoP2$(`gFz4 S \n", argv[0]); @@ -892,7 +884,6 @@ int main(int argc, char **argv) { const char *out_arg = argv[2]; char in_path[PATH_MAX]; char out_path[PATH_MAX]; - const char *out_dir = "./out"; if (!resolve_input_path(in_arg, in_path, sizeof(in_path))) { fprintf(stderr, "spbuild: unsafe input path '%s'\n", in_arg); @@ -904,12 +895,7 @@ int main(int argc, char **argv) { return 1; } - if (!ensure_out_dir(out_dir)) { - fprintf(stderr, "spbuild: cannot create output dir %s\n", out_dir); - return 1; - } - - if (snprintf(out_path, sizeof(out_path), "%s/%s", out_dir, out_arg) >= (int)sizeof(out_path)) { + if (snprintf(out_path, sizeof(out_path), "./%s", out_arg) >= (int)sizeof(out_path)) { fprintf(stderr, "spbuild: output path too long\n"); return 1; } diff --git a/src/splice.c b/src/splice.c index 6bac25a..9a6b476 100644 --- a/src/splice.c +++ b/src/splice.c @@ -81,45 +81,6 @@ static int is_safe_relative_path(const char *arg) { return 1; } -static int path_within_base(const char *path, const char *base) { - size_t base_len = strlen(base); - if (strncmp(path, base, base_len) != 0) { - return 0; - } - return path[base_len] == '\0' || path[base_len] == '/'; -} - -static int fullpath_buf(const char *path, char *out, size_t out_sz) { -#ifdef _WIN32 - return _fullpath(out, path, out_sz) != NULL; -#else - (void)out_sz; - return realpath(path, out) != NULL; -#endif -} - -static int resolve_input_path(const char *arg, char *dst, size_t dst_len) { - if (!is_safe_relative_path(arg)) { - return 0; - } - - char cwd[PATH_MAX]; - char resolved[PATH_MAX]; - if (!splice_getcwd(cwd, sizeof(cwd))) { - return 0; - } - if (!fullpath_buf(arg, resolved, sizeof(resolved))) { - return 0; - } - if (!path_within_base(resolved, cwd)) { - return 0; - } - if (snprintf(dst, dst_len, "%s", resolved) >= (int)dst_len) { - return 0; - } - return 1; -} - int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: %s \n", argv[0]); @@ -131,12 +92,11 @@ int main(int argc, char **argv) { return 0; } - char path_buf[PATH_MAX]; - if (!resolve_input_path(argv[1], path_buf, sizeof(path_buf))) { + if (!is_safe_relative_path(argv[1])) { fprintf(stderr, "[ERROR] invalid SPC path\n"); return 1; } - const char *path = path_buf; + const char *path = argv[1]; const char *ext = strrchr(path, '.'); if (!ext || strcmp(ext, ".spc") != 0) { fprintf(stderr, "[ERROR] only .spc supported by VM now\n");