mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-08-07 07:15:47 +00:00
600 lines
19 KiB
C
600 lines
19 KiB
C
/*
|
|
* Tardygrada — Compiler Implementation
|
|
*
|
|
* Simple recursive descent parser.
|
|
* Emits spawn instructions for the VM.
|
|
*/
|
|
|
|
#include "compiler.h"
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
|
|
/* ============================================
|
|
* Parser State
|
|
* ============================================ */
|
|
|
|
typedef struct {
|
|
tardy_lexer_t *lex;
|
|
int pos; /* current token index */
|
|
tardy_program_t *prog;
|
|
} tardy_parser_t;
|
|
|
|
static tardy_token_t *current(tardy_parser_t *p)
|
|
{
|
|
if (p->pos >= p->lex->count)
|
|
return &p->lex->tokens[p->lex->count - 1]; /* EOF */
|
|
return &p->lex->tokens[p->pos];
|
|
}
|
|
|
|
static tardy_token_t *advance_tok(tardy_parser_t *p)
|
|
{
|
|
tardy_token_t *tok = current(p);
|
|
if (tok->type != TOK_EOF)
|
|
p->pos++;
|
|
return tok;
|
|
}
|
|
|
|
static bool check(tardy_parser_t *p, tardy_tok_type_t type)
|
|
{
|
|
return current(p)->type == type;
|
|
}
|
|
|
|
static bool match(tardy_parser_t *p, tardy_tok_type_t type)
|
|
{
|
|
if (check(p, type)) {
|
|
advance_tok(p);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void error(tardy_parser_t *p, const char *msg)
|
|
{
|
|
tardy_token_t *tok = current(p);
|
|
snprintf(p->prog->error, sizeof(p->prog->error),
|
|
"line %d col %d: %s (got '%s')",
|
|
tok->line, tok->col, msg, tok->text);
|
|
p->prog->has_error = true;
|
|
}
|
|
|
|
static bool expect(tardy_parser_t *p, tardy_tok_type_t type, const char *msg)
|
|
{
|
|
if (check(p, type)) {
|
|
advance_tok(p);
|
|
return true;
|
|
}
|
|
error(p, msg);
|
|
return false;
|
|
}
|
|
|
|
static void emit_inst(tardy_parser_t *p, tardy_instruction_t inst)
|
|
{
|
|
if (p->prog->count < TARDY_MAX_INSTRUCTIONS)
|
|
p->prog->instructions[p->prog->count++] = inst;
|
|
}
|
|
|
|
/* ============================================
|
|
* Parse type annotation
|
|
* ============================================ */
|
|
|
|
static tardy_type_t parse_type(tardy_parser_t *p)
|
|
{
|
|
tardy_token_t *tok = current(p);
|
|
tardy_type_t type = TARDY_TYPE_UNIT;
|
|
|
|
switch (tok->type) {
|
|
case TOK_INT: type = TARDY_TYPE_INT; break;
|
|
case TOK_FLOAT: type = TARDY_TYPE_FLOAT; break;
|
|
case TOK_STR: type = TARDY_TYPE_STR; break;
|
|
case TOK_BOOL: type = TARDY_TYPE_BOOL; break;
|
|
case TOK_FACT: type = TARDY_TYPE_FACT; break;
|
|
default:
|
|
error(p, "expected type (int, float, str, bool, Fact)");
|
|
return TARDY_TYPE_UNIT;
|
|
}
|
|
|
|
advance_tok(p);
|
|
return type;
|
|
}
|
|
|
|
/* ============================================
|
|
* Parse trust annotation (@verified, @sovereign, etc.)
|
|
* ============================================ */
|
|
|
|
static tardy_trust_t parse_trust(tardy_parser_t *p)
|
|
{
|
|
if (check(p, TOK_AT_VERIFIED)) { advance_tok(p); return TARDY_TRUST_VERIFIED; }
|
|
if (check(p, TOK_AT_HARDENED)) { advance_tok(p); return TARDY_TRUST_HARDENED; }
|
|
if (check(p, TOK_AT_SOVEREIGN)) { advance_tok(p); return TARDY_TRUST_SOVEREIGN; }
|
|
return TARDY_TRUST_DEFAULT; /* no annotation = default immutable */
|
|
}
|
|
|
|
/* ============================================
|
|
* Parse value binding
|
|
*
|
|
* let x: int = 5 @verified (immutable)
|
|
* x: int = 5 (mutable)
|
|
* ============================================ */
|
|
|
|
static void parse_binding(tardy_parser_t *p, bool immutable)
|
|
{
|
|
tardy_instruction_t inst = {0};
|
|
inst.opcode = OP_SPAWN_VALUE;
|
|
|
|
/* Name */
|
|
if (!check(p, TOK_IDENT)) {
|
|
error(p, "expected identifier");
|
|
return;
|
|
}
|
|
strncpy(inst.name, current(p)->text, sizeof(inst.name) - 1);
|
|
advance_tok(p);
|
|
|
|
/* : type */
|
|
if (!expect(p, TOK_COLON, "expected ':'"))
|
|
return;
|
|
|
|
inst.type = parse_type(p);
|
|
|
|
/* = value */
|
|
if (!expect(p, TOK_EQUALS, "expected '='"))
|
|
return;
|
|
|
|
tardy_token_t *val = current(p);
|
|
switch (val->type) {
|
|
case TOK_INT_LIT:
|
|
inst.int_val = 0;
|
|
{
|
|
const char *s = val->text;
|
|
int neg = 0;
|
|
int i = 0;
|
|
if (s[0] == '-') { neg = 1; i = 1; }
|
|
for (; s[i]; i++)
|
|
inst.int_val = inst.int_val * 10 + (s[i] - '0');
|
|
if (neg) inst.int_val = -inst.int_val;
|
|
}
|
|
advance_tok(p);
|
|
break;
|
|
|
|
case TOK_FLOAT_LIT:
|
|
/* Simple float parse */
|
|
{
|
|
const char *s = val->text;
|
|
double v = 0.0;
|
|
int neg = 0;
|
|
int i = 0;
|
|
if (s[0] == '-') { neg = 1; i = 1; }
|
|
for (; s[i] && s[i] != '.'; i++)
|
|
v = v * 10.0 + (s[i] - '0');
|
|
if (s[i] == '.') {
|
|
i++;
|
|
double frac = 0.1;
|
|
for (; s[i]; i++) {
|
|
v += (s[i] - '0') * frac;
|
|
frac *= 0.1;
|
|
}
|
|
}
|
|
inst.float_val = neg ? -v : v;
|
|
}
|
|
advance_tok(p);
|
|
break;
|
|
|
|
case TOK_STR_LIT:
|
|
strncpy(inst.str_val, val->text, sizeof(inst.str_val) - 1);
|
|
advance_tok(p);
|
|
break;
|
|
|
|
case TOK_BOOL_LIT:
|
|
inst.bool_val = (strcmp(val->text, "true") == 0);
|
|
advance_tok(p);
|
|
break;
|
|
|
|
case TOK_RECEIVE:
|
|
/* receive("prompt") — pending slot, filled via MCP */
|
|
inst.opcode = OP_RECEIVE;
|
|
advance_tok(p); /* skip 'receive' */
|
|
if (!expect(p, TOK_LPAREN, "expected '(' after receive"))
|
|
return;
|
|
if (!check(p, TOK_STR_LIT)) {
|
|
error(p, "expected string prompt for receive()");
|
|
return;
|
|
}
|
|
strncpy(inst.str_val, current(p)->text, sizeof(inst.str_val) - 1);
|
|
advance_tok(p);
|
|
if (!expect(p, TOK_RPAREN, "expected ')' after prompt"))
|
|
return;
|
|
|
|
/* Optional: grounded_in(ontology) */
|
|
if (check(p, TOK_GROUNDED_IN)) {
|
|
advance_tok(p);
|
|
if (!expect(p, TOK_LPAREN, "expected '(' after grounded_in"))
|
|
return;
|
|
if (check(p, TOK_IDENT) || check(p, TOK_STR_LIT)) {
|
|
strncpy(inst.ontology, current(p)->text,
|
|
sizeof(inst.ontology) - 1);
|
|
advance_tok(p);
|
|
}
|
|
if (!expect(p, TOK_RPAREN, "expected ')' after ontology"))
|
|
return;
|
|
inst.grounded = true;
|
|
}
|
|
break;
|
|
|
|
case TOK_EXEC:
|
|
/* exec("command") — fork/exec shell command, capture stdout */
|
|
inst.opcode = OP_EXEC;
|
|
advance_tok(p); /* skip 'exec' */
|
|
if (!expect(p, TOK_LPAREN, "expected '(' after exec"))
|
|
return;
|
|
if (!check(p, TOK_STR_LIT)) {
|
|
error(p, "expected command string for exec()");
|
|
return;
|
|
}
|
|
strncpy(inst.str_val, current(p)->text, sizeof(inst.str_val) - 1);
|
|
advance_tok(p);
|
|
if (!expect(p, TOK_RPAREN, "expected ')' after command"))
|
|
return;
|
|
/* Optional: grounded_in(ontology) */
|
|
if (check(p, TOK_GROUNDED_IN)) {
|
|
advance_tok(p);
|
|
if (!expect(p, TOK_LPAREN, "expected '(' after grounded_in"))
|
|
return;
|
|
if (check(p, TOK_IDENT) || check(p, TOK_STR_LIT)) {
|
|
strncpy(inst.ontology, current(p)->text,
|
|
sizeof(inst.ontology) - 1);
|
|
advance_tok(p);
|
|
}
|
|
if (!expect(p, TOK_RPAREN, "expected ')' after ontology"))
|
|
return;
|
|
inst.grounded = true;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
error(p, "expected value (int, float, string, bool, receive(), exec())");
|
|
return;
|
|
}
|
|
|
|
/* Optional trust annotation */
|
|
if (immutable)
|
|
inst.trust = parse_trust(p);
|
|
else
|
|
inst.trust = TARDY_TRUST_MUTABLE;
|
|
|
|
emit_inst(p, inst);
|
|
}
|
|
|
|
/* ============================================
|
|
* Parse agent body
|
|
*
|
|
* agent Name {
|
|
* let x: int = 5
|
|
* y: str = "hello"
|
|
* }
|
|
* ============================================ */
|
|
|
|
static void parse_agent(tardy_parser_t *p)
|
|
{
|
|
/* agent keyword already consumed */
|
|
|
|
/* Name */
|
|
if (!check(p, TOK_IDENT)) {
|
|
error(p, "expected agent name");
|
|
return;
|
|
}
|
|
|
|
tardy_instruction_t agent_inst = {0};
|
|
agent_inst.opcode = OP_SPAWN_AGENT;
|
|
strncpy(agent_inst.name, current(p)->text, sizeof(agent_inst.name) - 1);
|
|
strncpy(p->prog->agent_name, current(p)->text,
|
|
sizeof(p->prog->agent_name) - 1);
|
|
advance_tok(p);
|
|
|
|
/* Optional trust annotation on the agent itself */
|
|
agent_inst.trust = parse_trust(p);
|
|
|
|
emit_inst(p, agent_inst);
|
|
|
|
/* Optional @semantics(key: value, ...) block */
|
|
if (check(p, TOK_AT_SEMANTICS)) {
|
|
advance_tok(p); /* skip @semantics */
|
|
if (!expect(p, TOK_LPAREN, "expected '(' after @semantics"))
|
|
return;
|
|
while (!check(p, TOK_RPAREN) && !check(p, TOK_EOF) && !p->prog->has_error) {
|
|
/* Parse key: value pairs */
|
|
tardy_instruction_t sem_inst = {0};
|
|
sem_inst.opcode = OP_SET_SEMANTICS;
|
|
|
|
/* Key is like truth.min_confidence — idents + dots */
|
|
char key[64];
|
|
int klen = 0;
|
|
while ((check(p, TOK_IDENT) || check(p, TOK_DOT)) &&
|
|
klen < 62 && !p->prog->has_error) {
|
|
const char *t = current(p)->text;
|
|
int tlen = (int)strlen(t);
|
|
if (klen + tlen < 63) {
|
|
memcpy(key + klen, t, tlen);
|
|
klen += tlen;
|
|
}
|
|
advance_tok(p);
|
|
}
|
|
key[klen] = '\0';
|
|
strncpy(sem_inst.sem_key, key, sizeof(sem_inst.sem_key) - 1);
|
|
|
|
if (!expect(p, TOK_COLON, "expected ':' in @semantics"))
|
|
return;
|
|
|
|
/* Value: number or ident */
|
|
if (check(p, TOK_INT_LIT) || check(p, TOK_FLOAT_LIT)) {
|
|
strncpy(sem_inst.sem_value, current(p)->text,
|
|
sizeof(sem_inst.sem_value) - 1);
|
|
advance_tok(p);
|
|
} else {
|
|
error(p, "expected number value in @semantics");
|
|
return;
|
|
}
|
|
|
|
emit_inst(p, sem_inst);
|
|
|
|
/* Optional comma */
|
|
if (check(p, TOK_COMMA))
|
|
advance_tok(p);
|
|
}
|
|
if (!expect(p, TOK_RPAREN, "expected ')' after @semantics"))
|
|
return;
|
|
}
|
|
|
|
/* Body */
|
|
if (!expect(p, TOK_LBRACE, "expected '{'"))
|
|
return;
|
|
|
|
while (!check(p, TOK_RBRACE) && !check(p, TOK_EOF) && !p->prog->has_error) {
|
|
if (match(p, TOK_LET)) {
|
|
/* Immutable binding */
|
|
parse_binding(p, true);
|
|
} else if (match(p, TOK_FORK)) {
|
|
/* fork "path/to/module.tardy" as ModuleName */
|
|
tardy_instruction_t finst = {0};
|
|
finst.opcode = OP_FORK;
|
|
if (!check(p, TOK_STR_LIT)) {
|
|
error(p, "expected file path string after fork");
|
|
return;
|
|
}
|
|
strncpy(finst.str_val, current(p)->text,
|
|
sizeof(finst.str_val) - 1);
|
|
advance_tok(p);
|
|
/* Optional: as Name */
|
|
if (check(p, TOK_IDENT) &&
|
|
strcmp(current(p)->text, "as") == 0) {
|
|
advance_tok(p);
|
|
if (check(p, TOK_IDENT)) {
|
|
strncpy(finst.name, current(p)->text,
|
|
sizeof(finst.name) - 1);
|
|
advance_tok(p);
|
|
}
|
|
}
|
|
emit_inst(p, finst);
|
|
} else if (match(p, TOK_COORDINATE)) {
|
|
/* coordinate [a, b, c] on("task") consensus(ProofWeight) */
|
|
tardy_instruction_t cinst = {0};
|
|
cinst.opcode = OP_COORDINATE;
|
|
|
|
/* [agent_list] */
|
|
if (!expect(p, TOK_LBRACE, "expected '[' or '{' after coordinate"))
|
|
return;
|
|
char agents[256];
|
|
int alen = 0;
|
|
while (!check(p, TOK_RBRACE) && !check(p, TOK_EOF)) {
|
|
if (check(p, TOK_IDENT)) {
|
|
const char *name = current(p)->text;
|
|
int nlen = (int)strlen(name);
|
|
if (alen > 0 && alen < 254) agents[alen++] = ',';
|
|
if (alen + nlen < 255) {
|
|
memcpy(agents + alen, name, nlen);
|
|
alen += nlen;
|
|
}
|
|
}
|
|
advance_tok(p);
|
|
if (check(p, TOK_COMMA)) advance_tok(p);
|
|
}
|
|
agents[alen] = '\0';
|
|
strncpy(cinst.coord_agents, agents, sizeof(cinst.coord_agents) - 1);
|
|
if (!expect(p, TOK_RBRACE, "expected ']' or '}'"))
|
|
return;
|
|
|
|
/* on("task description") */
|
|
if (check(p, TOK_ON)) {
|
|
advance_tok(p);
|
|
if (!expect(p, TOK_LPAREN, "expected '(' after on"))
|
|
return;
|
|
if (check(p, TOK_STR_LIT)) {
|
|
strncpy(cinst.coord_task, current(p)->text,
|
|
sizeof(cinst.coord_task) - 1);
|
|
advance_tok(p);
|
|
}
|
|
if (!expect(p, TOK_RPAREN, "expected ')' after task"))
|
|
return;
|
|
}
|
|
|
|
/* Optional: consensus(method) — just consume for now */
|
|
if (check(p, TOK_CONSENSUS)) {
|
|
advance_tok(p);
|
|
if (check(p, TOK_LPAREN)) {
|
|
advance_tok(p);
|
|
if (check(p, TOK_IDENT)) advance_tok(p);
|
|
if (check(p, TOK_RPAREN)) advance_tok(p);
|
|
}
|
|
}
|
|
|
|
emit_inst(p, cinst);
|
|
} else if (check(p, TOK_INVARIANT)) {
|
|
advance_tok(p);
|
|
tardy_instruction_t iinst = {0};
|
|
iinst.opcode = OP_ADD_INVARIANT;
|
|
|
|
if (!expect(p, TOK_LPAREN, "expected '(' after invariant"))
|
|
return;
|
|
|
|
/* Parse invariant type */
|
|
if (check(p, TOK_IDENT)) {
|
|
const char *itype = current(p)->text;
|
|
if (strcmp(itype, "trust_min") == 0) {
|
|
iinst.invariant_type = 3; /* TARDY_INVARIANT_TRUST_MIN */
|
|
advance_tok(p);
|
|
if (!expect(p, TOK_COLON, "expected ':'"))
|
|
return;
|
|
/* Parse trust level */
|
|
if (check(p, TOK_AT_VERIFIED)) {
|
|
iinst.inv_trust = TARDY_TRUST_VERIFIED;
|
|
advance_tok(p);
|
|
} else if (check(p, TOK_AT_HARDENED)) {
|
|
iinst.inv_trust = TARDY_TRUST_HARDENED;
|
|
advance_tok(p);
|
|
} else if (check(p, TOK_AT_SOVEREIGN)) {
|
|
iinst.inv_trust = TARDY_TRUST_SOVEREIGN;
|
|
advance_tok(p);
|
|
} else {
|
|
error(p, "expected trust level (@verified, @hardened, @sovereign)");
|
|
return;
|
|
}
|
|
} else if (strcmp(itype, "non_empty") == 0) {
|
|
iinst.invariant_type = 2; /* TARDY_INVARIANT_NON_EMPTY */
|
|
advance_tok(p);
|
|
} else if (strcmp(itype, "range") == 0) {
|
|
iinst.invariant_type = 1; /* TARDY_INVARIANT_RANGE */
|
|
advance_tok(p);
|
|
if (!expect(p, TOK_COLON, "expected ':'"))
|
|
return;
|
|
if (check(p, TOK_INT_LIT)) {
|
|
/* parse min */
|
|
iinst.inv_min = 0;
|
|
const char *s = current(p)->text;
|
|
for (int ii = 0; s[ii]; ii++)
|
|
iinst.inv_min = iinst.inv_min * 10 + (s[ii] - '0');
|
|
advance_tok(p);
|
|
}
|
|
if (check(p, TOK_COMMA)) advance_tok(p);
|
|
if (check(p, TOK_INT_LIT)) {
|
|
/* parse max */
|
|
iinst.inv_max = 0;
|
|
const char *s = current(p)->text;
|
|
for (int ii = 0; s[ii]; ii++)
|
|
iinst.inv_max = iinst.inv_max * 10 + (s[ii] - '0');
|
|
advance_tok(p);
|
|
}
|
|
} else {
|
|
error(p, "unknown invariant type");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!expect(p, TOK_RPAREN, "expected ')'"))
|
|
return;
|
|
|
|
emit_inst(p, iinst);
|
|
} else if (match(p, TOK_FREEZE)) {
|
|
tardy_instruction_t finst = {0};
|
|
finst.opcode = OP_FREEZE;
|
|
if (!check(p, TOK_IDENT)) {
|
|
error(p, "expected agent name after freeze");
|
|
return;
|
|
}
|
|
strncpy(finst.name, current(p)->text, sizeof(finst.name) - 1);
|
|
advance_tok(p);
|
|
finst.trust = parse_trust(p);
|
|
if (finst.trust < TARDY_TRUST_DEFAULT)
|
|
finst.trust = TARDY_TRUST_VERIFIED; /* default freeze to @verified */
|
|
emit_inst(p, finst);
|
|
} else if (check(p, TOK_IDENT)) {
|
|
/* Mutable binding */
|
|
parse_binding(p, false);
|
|
} else {
|
|
error(p, "expected 'let', 'fork', 'coordinate', 'invariant', 'freeze', or identifier");
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (!expect(p, TOK_RBRACE, "expected '}'"))
|
|
return;
|
|
|
|
/* Emit halt */
|
|
tardy_instruction_t halt = {0};
|
|
halt.opcode = OP_HALT;
|
|
emit_inst(p, halt);
|
|
}
|
|
|
|
/* ============================================
|
|
* Compiler Entry Point
|
|
* ============================================ */
|
|
|
|
int tardy_compile(tardy_program_t *prog, const char *src, int len)
|
|
{
|
|
if (!prog || !src)
|
|
return -1;
|
|
|
|
memset(prog, 0, sizeof(tardy_program_t));
|
|
|
|
/* Lex */
|
|
tardy_lexer_t lex;
|
|
if (tardy_lex(&lex, src, len) != 0) {
|
|
snprintf(prog->error, sizeof(prog->error), "lexer error");
|
|
prog->has_error = true;
|
|
return -1;
|
|
}
|
|
|
|
/* Parse */
|
|
tardy_parser_t parser = {0};
|
|
parser.lex = &lex;
|
|
parser.pos = 0;
|
|
parser.prog = prog;
|
|
|
|
while (!check(&parser, TOK_EOF) && !prog->has_error) {
|
|
if (match(&parser, TOK_AGENT)) {
|
|
parse_agent(&parser);
|
|
} else {
|
|
error(&parser, "expected 'agent'");
|
|
break;
|
|
}
|
|
}
|
|
|
|
return prog->has_error ? -1 : 0;
|
|
}
|
|
|
|
/* ============================================
|
|
* Compile from file
|
|
* ============================================ */
|
|
|
|
int tardy_compile_file(tardy_program_t *prog, const char *path)
|
|
{
|
|
int fd = open(path, O_RDONLY);
|
|
if (fd < 0) {
|
|
snprintf(prog->error, sizeof(prog->error),
|
|
"cannot open file: %s", path);
|
|
prog->has_error = true;
|
|
return -1;
|
|
}
|
|
|
|
struct stat st;
|
|
if (fstat(fd, &st) < 0) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
if (st.st_size > 1024 * 1024) { /* 1MB max source file */
|
|
close(fd);
|
|
snprintf(prog->error, sizeof(prog->error), "file too large");
|
|
prog->has_error = true;
|
|
return -1;
|
|
}
|
|
|
|
char buf[1024 * 1024];
|
|
ssize_t n = read(fd, buf, st.st_size);
|
|
close(fd);
|
|
|
|
if (n <= 0)
|
|
return -1;
|
|
|
|
return tardy_compile(prog, buf, (int)n);
|
|
}
|