Add web version

This commit is contained in:
zongor 2026-06-29 16:39:01 -07:00
parent 0449d9b84e
commit 95f3e0aeed
10 changed files with 229 additions and 64 deletions

View File

@ -1 +1,63 @@
// wip #include "../../libc.h"
#include "../../compiler.h"
#include <stdlib.h>
#include <string.h>
#include <emscripten.h>
#define MEM_SIZE (1 << 16)
static size_t List_size(List *list) {
size_t total = 0;
Node *curr = list->head;
while (curr) {
total += curr->size;
curr = curr->next;
}
return total;
}
static void list2buffer(List *list, char *buffer, size_t *offset) {
Node *curr = list->head;
while (curr) {
char *tmp_str = node_value(curr);
memcpy(buffer + *offset, tmp_str, curr->size);
*offset += curr->size;
curr = curr->next;
}
}
EMSCRIPTEN_KEEPALIVE
char *undar_compile(int emitter_type, char *source) {
Emitter e;
if (emitter_type == 0) {
e = rer_emitter();
} else if (emitter_type == 1) {
e = uxn_emitter();
} else {
return NULL;
}
u8 tape[MEM_SIZE];
Arena a = {tape, 0, MEM_SIZE};
List *code = List_init(&a);
List *memory = List_init(&a);
compile(&a, code, memory, e, source);
size_t code_size = List_size(code);
size_t mem_size = List_size(memory);
size_t total_size = code_size + mem_size + 1;
char *result = (char *)malloc(total_size);
if (result == NULL) return NULL;
size_t offset = 0;
list2buffer(code, result, &offset);
list2buffer(memory, result, &offset);
result[offset] = '\0';
afree(&a);
return result;
}

8
build
View File

@ -93,7 +93,13 @@ case $ARCH in
BUILD_CMD="$CC -o $BUILD_DIR/undar $SRC_DIR/main.c emit/rer/emit.c emit/uxn/emit.c $LINK_FLAGS $BUILD_DIR/libc.o $BUILD_DIR/list.o $BUILD_DIR/lexer.o $BUILD_DIR/compiler.o $BUILD_DIR/strbuf.o $BUILD_FLAGS $LINK_FLAGS" BUILD_CMD="$CC -o $BUILD_DIR/undar $SRC_DIR/main.c emit/rer/emit.c emit/uxn/emit.c $LINK_FLAGS $BUILD_DIR/libc.o $BUILD_DIR/list.o $BUILD_DIR/lexer.o $BUILD_DIR/compiler.o $BUILD_DIR/strbuf.o $BUILD_FLAGS $LINK_FLAGS"
;; ;;
"web") "web")
BUILD_CMD="$CC $SRC_DIR/main.c emit/rer/emit.c emit/uxn/emit.c $BUILD_DIR/libc.o $BUILD_DIR/list.o $BUILD_DIR/lexer.o $BUILD_DIR/compiler.o -o $BUILD_DIR/undar.html $BUILD_FLAGS $LINK_FLAGS" WEB_BUILD_FLAGS="-O3 -s STACK_SIZE=1048576"
WEB_EXPORTS="-s EXPORTED_FUNCTIONS=[\"_undar_compile\",\"_malloc\",\"_free\"] -s EXPORTED_RUNTIME_METHODS=[\"cwrap\"]"
BUILD_CMD="$CC $SRC_DIR/main.c emit/rer/emit.c emit/uxn/emit.c $BUILD_DIR/libc.o $BUILD_DIR/list.o $BUILD_DIR/lexer.o $BUILD_DIR/compiler.o $BUILD_DIR/strbuf.o -o $BUILD_DIR/undar.js $WEB_BUILD_FLAGS $WEB_EXPORTS"
cp ./tools/index.html $BUILD_DIR/
;; ;;
esac esac

View File

@ -15,7 +15,7 @@ List *memory;
* Scope * Scope
***************************************************/ ***************************************************/
void void
scopes_init() scopes_init(void)
{ {
Scope *current; Scope *current;
Scope child = {0}; Scope child = {0};
@ -27,7 +27,7 @@ scopes_init()
} }
Scope * Scope *
scope_new() scope_new(void)
{ {
Scope *current; Scope *current;
Scope child = {0}; Scope child = {0};
@ -43,7 +43,7 @@ scope_new()
} }
void void
scope_pop() scope_pop(void)
{ {
if(parser.current_scope) { if(parser.current_scope) {
parser.current_scope = parser.current_scope->parent; parser.current_scope = parser.current_scope->parent;
@ -52,7 +52,7 @@ scope_pop()
} }
void void
scope_push() scope_push(void)
{ {
Scope *scope = List_get(parser.scopes, parser.scope_idx); Scope *scope = List_get(parser.scopes, parser.scope_idx);
parser.current_scope = scope; parser.current_scope = scope;
@ -109,13 +109,13 @@ scope_add_symbol(const char *name, u32 name_length, SymbolType type, u32 size)
} }
bool bool
is_global() is_global(void)
{ {
return parser.current_scope != nil && parser.current_scope->parent == nil; return parser.current_scope != nil && parser.current_scope->parent == nil;
} }
bool bool
is_type() is_type(void)
{ {
return parser.previous.type >= TOKEN_TYPE_I8 && return parser.previous.type >= TOKEN_TYPE_I8 &&
parser.previous.type <= TOKEN_TYPE_PTR; parser.previous.type <= TOKEN_TYPE_PTR;
@ -156,7 +156,7 @@ token_type_to_sym_type(TokenType t)
***************************************************/ ***************************************************/
bool bool
advance() advance(void)
{ {
parser.previous = parser.current; parser.previous = parser.current;
@ -174,7 +174,7 @@ check(TokenType type)
} }
bool bool
consume_is_type() consume_is_type(void)
{ {
if(is_type()) { if(is_type()) {
advance(); advance();
@ -215,17 +215,17 @@ expect(TokenType type)
u32 variable_declaration(Symbol *def); u32 variable_declaration(Symbol *def);
void void
define_plex() define_plex(void)
{ {
} }
void void
define_trait() define_trait(void)
{ {
} }
void void
define_function() define_function(void)
{ {
Symbol *fn; Symbol *fn;
u32 size = 0; u32 size = 0;
@ -298,7 +298,7 @@ calculate_strides(List *dims, List *strides)
} }
void void
define_array() define_array(void)
{ {
i32 size = 0, n = 0, flat_array_length = 1; i32 size = 0, n = 0, flat_array_length = 1;
TokenType tt = parser.previous.type; TokenType tt = parser.previous.type;
@ -365,9 +365,9 @@ build_symbol_table(char *source)
} }
} }
void expression(); void expression(void);
void statement(); void statement(void);
void declaration(); void declaration(void);
ParseRule *get_rule(TokenType type); ParseRule *get_rule(TokenType type);
void parse_precedence(Precedence precedence); void parse_precedence(Precedence precedence);
@ -459,7 +459,7 @@ variable_declaration(Symbol *def)
} }
void void
binary() binary(void)
{ {
TokenType operatorType = parser.previous.type; TokenType operatorType = parser.previous.type;
ParseRule *rule = get_rule(operatorType); ParseRule *rule = get_rule(operatorType);
@ -521,7 +521,7 @@ binary()
} }
void void
literal() literal(void)
{ {
switch(parser.previous.type) { switch(parser.previous.type) {
case TOKEN_KEYWORD_FALSE: case TOKEN_KEYWORD_FALSE:
@ -539,13 +539,13 @@ literal()
} }
void void
expression() expression(void)
{ {
parse_precedence(PREC_ASSIGNMENT); parse_precedence(PREC_ASSIGNMENT);
} }
void void
variable() variable(void)
{ {
Symbol *sym = scope_get_symbol(parser.current_scope, parser.previous.start, Symbol *sym = scope_get_symbol(parser.current_scope, parser.previous.start,
parser.previous.length); parser.previous.length);
@ -594,7 +594,7 @@ variable()
} }
void void
cast_type() cast_type(void)
{ {
SymbolType st = parser.current_type; SymbolType st = parser.current_type;
TokenType cast_type; TokenType cast_type;
@ -691,7 +691,7 @@ cast_type()
} }
void void
declaration() declaration(void)
{ {
if(is_type() && (parser.current.type == TOKEN_IDENTIFIER || parser.current.type == TOKEN_LBRACKET) ) if(is_type() && (parser.current.type == TOKEN_IDENTIFIER || parser.current.type == TOKEN_LBRACKET) )
variable_declaration(nil); variable_declaration(nil);
@ -700,7 +700,7 @@ declaration()
} }
void void
print_statement() print_statement(void)
{ {
expression(); expression();
consume(TOKEN_SEMICOLON); consume(TOKEN_SEMICOLON);
@ -709,7 +709,7 @@ print_statement()
} }
void void
if_statement() if_statement(void)
{ {
consume(TOKEN_LPAREN); consume(TOKEN_LPAREN);
expression(); expression();
@ -727,7 +727,7 @@ if_statement()
} }
void void
while_statement() while_statement(void)
{ {
u32 this_while = emitter.loops++; u32 this_while = emitter.loops++;
emitter.emit_while(arena, code, this_while); emitter.emit_while(arena, code, this_while);
@ -740,7 +740,7 @@ while_statement()
} }
void void
block() block(void)
{ {
while(!check(TOKEN_RBRACE) && !check(TOKEN_EOF)) declaration(); while(!check(TOKEN_RBRACE) && !check(TOKEN_EOF)) declaration();
@ -748,7 +748,7 @@ block()
} }
void void
function() function(void)
{ {
Symbol *sym = scope_get_symbol(parser.current_scope, parser.previous.start, Symbol *sym = scope_get_symbol(parser.current_scope, parser.previous.start,
parser.previous.length); parser.previous.length);
@ -762,7 +762,7 @@ function()
} }
void void
call() call(void)
{ {
if(!check(TOKEN_RPAREN)) { if(!check(TOKEN_RPAREN)) {
do { do {
@ -777,7 +777,7 @@ call()
} }
void void
return_statement() return_statement(void)
{ {
if(match(TOKEN_SEMICOLON)) { if(match(TOKEN_SEMICOLON)) {
emitter.emit_early_return(arena, code); emitter.emit_early_return(arena, code);
@ -789,7 +789,7 @@ return_statement()
} }
void void
statement() statement(void)
{ {
if(match(TOKEN_LBRACE)) { if(match(TOKEN_LBRACE)) {
scope_push(); scope_push();
@ -814,7 +814,7 @@ statement()
} }
void void
grouping() grouping(void)
{ {
emitter.emit_open_paren(arena, code); emitter.emit_open_paren(arena, code);
@ -825,25 +825,25 @@ grouping()
} }
void void
number() number(void)
{ {
emitter.emit_int(arena, code, parser.previous.start, parser.previous.length); emitter.emit_int(arena, code, parser.previous.start, parser.previous.length);
} }
void void
string() string(void)
{ {
emitter.emit_str(arena, code, parser.previous.start, parser.previous.length); emitter.emit_str(arena, code, parser.previous.start, parser.previous.length);
} }
void void
character() character(void)
{ {
emitter.emit_char(arena, code, parser.previous.start, parser.previous.length); emitter.emit_char(arena, code, parser.previous.start, parser.previous.length);
} }
void void
unary() unary(void)
{ {
TokenType operatorType = parser.previous.type; TokenType operatorType = parser.previous.type;
parse_precedence(PREC_UNARY); parse_precedence(PREC_UNARY);
@ -861,13 +861,13 @@ unary()
} }
void void
read() read(void)
{ {
emitter.emit_read(arena, code); emitter.emit_read(arena, code);
} }
void void
write() write(void)
{ {
consume(TOKEN_EQ); consume(TOKEN_EQ);
advance(); advance();

View File

@ -31,7 +31,7 @@ typedef enum {
PREC_PRIMARY PREC_PRIMARY
} Precedence; } Precedence;
typedef void (*ParseFn)(); typedef void (*ParseFn)(void);
struct parse_rule_s { struct parse_rule_s {
ParseFn prefix; ParseFn prefix;

4
emit.h
View File

@ -109,7 +109,7 @@ struct emitter_s {
StrArgEmit emit_char; StrArgEmit emit_char;
}; };
Emitter rer_emitter(); Emitter rer_emitter(void);
Emitter uxn_emitter(); Emitter uxn_emitter(void);
#endif #endif

22
lexer.c
View File

@ -30,26 +30,26 @@ is_digit(char c)
} }
static bool static bool
is_at_end() is_at_end(void)
{ {
return *lexer.current == '\0'; return *lexer.current == '\0';
} }
static char static char
advance() advance(void)
{ {
lexer.current++; lexer.current++;
return lexer.current[-1]; return lexer.current[-1];
} }
char char
peek() peek(void)
{ {
return *lexer.current; return *lexer.current;
} }
static char static char
peek_next() peek_next(void)
{ {
if(is_at_end()) return '\0'; if(is_at_end()) return '\0';
return lexer.current[1]; return lexer.current[1];
@ -87,7 +87,7 @@ error_token(const char *message)
} }
static void static void
skip_whitespace() skip_whitespace(void)
{ {
for(;;) { for(;;) {
char c = peek(); char c = peek();
@ -140,7 +140,7 @@ check_keyword(i32 start, i32 length, const char *rest, TokenType type)
} }
static TokenType static TokenType
identifierType() identifierType(void)
{ {
switch(lexer.start[0]) { switch(lexer.start[0]) {
case 'a': case 'a':
@ -343,14 +343,14 @@ identifierType()
} }
static Token static Token
identifier() identifier(void)
{ {
while(is_alpha(peek()) || is_digit(peek())) advance(); while(is_alpha(peek()) || is_digit(peek())) advance();
return make_token(identifierType()); return make_token(identifierType());
} }
static Token static Token
number() number(void)
{ {
while(is_digit(peek())) advance(); while(is_digit(peek())) advance();
@ -368,7 +368,7 @@ number()
} }
static Token static Token
string() string(void)
{ {
while(peek() != '"' && !is_at_end()) { while(peek() != '"' && !is_at_end()) {
if(peek() == '\n') lexer.line++; if(peek() == '\n') lexer.line++;
@ -383,7 +383,7 @@ string()
} }
static Token static Token
character() character(void)
{ {
advance(); advance();
if(peek() != '\'' && !is_at_end()) { if(peek() != '\'' && !is_at_end()) {
@ -398,7 +398,7 @@ character()
} }
Token Token
next_token() next_token(void)
{ {
char c; char c;
char next; char next;

View File

@ -101,8 +101,8 @@ struct token_s {
}; };
void init_lexer(const char *source); void init_lexer(const char *source);
Token next_token(); Token next_token(void);
const char *token_type_to_string(TokenType type); const char *token_type_to_string(TokenType type);
char peek(); char peek(void);
#endif #endif

4
libc.c
View File

@ -367,7 +367,7 @@ stoi(const char *str, u32 length)
} }
while (is_digit(*str) && length-- > 0) { while (is_digit(*str) && length-- > 0) {
int digit = *str++ - '0'; i32 digit = *str++ - '0';
if (neg) { if (neg) {
if (n < (I32_MIN / 10) || (n == (I32_MIN / 10) && digit > -(I32_MIN % 10))) { if (n < (I32_MIN / 10) || (n == (I32_MIN / 10) && digit > -(I32_MIN % 10))) {
@ -396,7 +396,7 @@ ston(const char *str, u32 length)
} }
while (is_digit(*str) && length-- > 0) { while (is_digit(*str) && length-- > 0) {
int digit = *str++ - '0'; u32 digit = *str++ - '0';
if (n > (U32_MAX / 10) || (n == (U32_MAX / 10) && digit > (U32_MAX % 10))) { if (n > (U32_MAX / 10) || (n == (U32_MAX / 10) && digit > (U32_MAX % 10))) {
return U32_MAX; return U32_MAX;

9
libc.h
View File

@ -5,9 +5,6 @@
#if __has_include(<stdint.h>) #if __has_include(<stdint.h>)
#define HAVE_STDINT 1 #define HAVE_STDINT 1
#endif #endif
#if __has_include(<stdbool.h>)
#define HAVE_STDBOOL 1
#endif
#if __has_include(<stddef.h>) #if __has_include(<stddef.h>)
#define HAVE_STDDEF 1 #define HAVE_STDDEF 1
#endif #endif
@ -34,13 +31,9 @@ typedef signed int r32;
typedef float f32; typedef float f32;
#endif #endif
#ifdef HAVE_STDBOOL
#include <stdbool.h>
#else
#define true 1 #define true 1
#define false 0 #define false 0
typedef u8 bool; typedef u8 bool;
#endif
#ifdef HAVE_STDDEF #ifdef HAVE_STDDEF
#include <stddef.h> #include <stddef.h>
@ -59,7 +52,7 @@ typedef u8 bool;
#define I32_MIN -2147483647 #define I32_MIN -2147483647
#define I32_MAX 2147483647 #define I32_MAX 2147483647
#define U32_MAX 4294967295 #define U32_MAX 4294967295UL
#define FIXED_CONST 65536.0f #define FIXED_CONST 65536.0f

104
tools/index.html Normal file
View File

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Undâr Compiler</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet" type="text/css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.2/ace.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.2/mode-java.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.2/mode-forth.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.2/theme-one_dark.min.js"></script>
</head>
<body class="min-h-screen bg-base-100 text-base-content">
<div class="navbar bg-base-300 shadow-lg">
<div class="flex-1">
<span class="text-xl font-bold px-4">Undâr Compiler</span>
</div>
<div class="flex-none gap-2 px-4">
<select id="emitter" class="select select-bordered w-full max-w-xs">
<option value="1">Uxntal Emitter</option>
<option value="0" disabled>Reality Engine Emitter</option>
</select>
<button id="compile-btn" class="btn btn-primary" disabled>
<span id="btn-text">Loading...</span>
<span id="btn-loading" class="loading loading-spinner loading-sm hidden"></span>
</button>
</div>
</div>
<div class="container mx-auto p-4">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 h-[calc(100vh-100px)]">
<div class="card bg-base-200 shadow-xl flex flex-col">
<div class="card-title p-4 pb-0">Source</div>
<div class="card-body flex-1 p-4 pt-2">
<div id="input" class="w-full h-full rounded-lg overflow-hidden border border-base-300"></div>
</div>
</div>
<div class="card bg-base-200 shadow-xl flex flex-col">
<div class="card-title p-4 pb-0">Output</div>
<div class="card-body flex-1 p-4 pt-2">
<div id="output" class="w-full h-full rounded-lg overflow-hidden border border-base-300"></div>
</div>
</div>
</div>
</div>
<script>
var inputEditor;
var outputEditor;
var Module = {
onRuntimeInitialized: function() {
const emitterEl = document.getElementById('emitter');
const compileBtn = document.getElementById('compile-btn');
const btnText = document.getElementById('btn-text');
const btnLoading = document.getElementById('btn-loading');
inputEditor = ace.edit("input");
inputEditor.session.setMode("ace/mode/java");
inputEditor.setTheme("ace/theme/one_dark");
inputEditor.setOptions({ fontSize: "14px" });
outputEditor = ace.edit("output");
outputEditor.session.setMode("ace/mode/forth");
outputEditor.setTheme("ace/theme/one_dark");
outputEditor.setReadOnly(true);
outputEditor.setOptions({ fontSize: "14px" });
btnText.innerText = "Compile";
compileBtn.disabled = false;
const compile = Module.cwrap('undar_compile', 'string', ['number', 'string']);
compileBtn.addEventListener('click', function() {
const sourceCode = inputEditor.getValue();
const emitterType = parseInt(emitterEl.value, 10);
btnText.classList.add("hidden");
btnLoading.classList.remove("hidden");
compileBtn.disabled = true;
outputEditor.setValue("Compiling...", -1);
setTimeout(() => {
try {
const result = compile(emitterType, sourceCode);
outputEditor.setValue((result === null) ? "Compilation Error" : result, -1);
} catch (e) {
outputEditor.setValue("WASM Runtime Error:\n" + e.message, -1);
} finally {
btnLoading.classList.add("hidden");
btnText.classList.remove("hidden");
compileBtn.disabled = false;
}
}, 50);
});
}
};
</script>
<script src="undar.js"></script>
</body>
</html>