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"
;;
"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

View File

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

View File

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

4
emit.h
View File

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

22
lexer.c
View File

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

View File

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

4
libc.c
View File

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

9
libc.h
View File

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