This commit is contained in:
2025-08-24 14:41:13 +02:00
parent b2d3131cd4
commit 7ffbe39bca
34 changed files with 4112 additions and 10755 deletions
+7 -1
View File
@@ -1,2 +1,8 @@
a.out
coverage_html/
test
test.exe
test_cov
wl
*.exe
wl.exe
wl_cov
+28 -4
View File
@@ -1,5 +1,29 @@
all: WL.c
gcc WL.c main.c -o wl -Wall -Wextra -O0 -g3
WL.c: $(wildcard src/*.c src/*.h)
python amalg.py
.PHONY: all coverage coverage_report install uninstall
all: wl
wl: wl.c wl.h main.c
gcc main.c wl.c -o wl -g3 -O0
test: wl.c wl.h test.c
gcc test.c wl.c -o test -g3 -O0
coverage:
gcc main.c wl.c -o wl_cov -g3 -O0 --coverage -fprofile-arcs -ftest-coverage
gcc test.c wl.c -o test_cov -g3 -O0 --coverage -fprofile-arcs -ftest-coverage
coverage_report:
gcov wl.c main.c test.c
lcov --capture --directory . --output-file coverage.info
lcov --remove coverage.info '/usr/*' --output-file coverage_filtered.info
genhtml coverage_filtered.info --output-directory coverage_html --title "WL Template Engine Coverage"
install: wl
sudo cp wl /usr/local/bin
uninstall:
sudo rm /usr/local/bin/wl
clean:
rm *.gcda *.gcno *.info
+8 -57
View File
@@ -1,59 +1,10 @@
# WL
WL is the proof of concept for a powerful and flexible templating language for the web. All functionality is implemented, but is still missing some polish.
WL is an HTML templating engine written in and for C.
Here's an example template in WL with a recursive comment view element:
```html
let posts = [
{
author: "UserA",
title: "I'm the first post",
content: "Sup everyone!",
date: "1 Jan 2025",
comments: [
{
author: "UserB",
content: "Hello! This is a comment!",
comments: [
{
author: "UserA",
content: "Hello to you!",
comments: []
}
]
}
]
}
]
fun comment(c)
<div>
<a>\c.author</a>
<p>\c.content</p>
\for subc in c.comments:
comment(subc)
</div>
<html>
<head>
<title>List of posts</title>
</head>
<body>
\for post in posts:
<div class="post">
<h2>
\post.title
</h2>
<span>
Posted on \post.date by \post.author
</span>
<p>
\post.content
</p>
<div class="comments">
\for c in post.comments:
comment(c)
</div>
</div>
</body>
</html>
```
It supports:
1. Zero dependencies
2. Single-file implementation
3. HTML-first design
4. Complete scripting language
5. Easy integration
6. Performs no I/O or dynamic allocations
+3401 -3966
View File
File diff suppressed because it is too large Load Diff
+67 -71
View File
@@ -1,20 +1,7 @@
#ifndef WL_AMALGAMATION
#define WL_AMALGAMATION
#include <stdint.h>
// This file was generated automatically. Do not modify directly!
////////////////////////////////////////////////////////////////////////////////////////
// src/compile.h
////////////////////////////////////////////////////////////////////////////////////////
#ifndef WL_PUBLIC_INCLUDED
#define WL_PUBLIC_INCLUDED
#ifndef WL_AMALGAMATION
#include "includes.h"
#endif
typedef struct WL_State WL_State;
typedef struct WL_Runtime WL_Runtime;
typedef struct WL_Compiler WL_Compiler;
typedef struct {
char *ptr;
@@ -24,70 +11,79 @@ typedef struct {
typedef struct {
char *ptr;
int len;
} WL_Program;
typedef enum {
WL_DONE,
WL_ERROR,
WL_VAR,
WL_CALL,
WL_OUTPUT,
} WL_ResultType;
typedef struct {
WL_ResultType type;
WL_String str;
} WL_Result;
int cur;
} WL_Arena;
typedef struct {
char *ptr;
int len;
int cur;
} WL_Arena;
typedef struct WL_Compiler WL_Compiler;
} WL_Program;
typedef enum {
WL_COMPILE_RESULT_DONE,
WL_COMPILE_RESULT_FILE,
WL_COMPILE_RESULT_ERROR,
} WL_CompileResultType;
WL_ADD_ERROR,
WL_ADD_AGAIN,
WL_ADD_LINK,
} WL_AddResultType;
typedef struct {
WL_CompileResultType type;
WL_Program program;
WL_String path;
} WL_CompileResult;
WL_AddResultType type;
WL_String path;
} WL_AddResult;
WL_Compiler* WL_Compiler_init (WL_Arena *arena);
void WL_Compiler_free (WL_Compiler *compiler);
WL_CompileResult WL_compile (WL_Compiler *compiler, WL_String file, WL_String content);
WL_State* WL_State_init (WL_Arena *a, WL_Program p, char *err, int errmax);
void WL_State_free (WL_State *state);
void WL_State_trace (WL_State *state, int trace);
WL_Result WL_eval (WL_State *state);
typedef enum {
WL_EVAL_NONE,
WL_EVAL_DONE,
WL_EVAL_ERROR,
WL_EVAL_OUTPUT,
WL_EVAL_SYSVAR,
WL_EVAL_SYSCALL,
} WL_EvalResultType;
void WL_dump_program(WL_Program program);
typedef struct {
WL_EvalResultType type;
WL_String str;
} WL_EvalResult;
int WL_streq (WL_String a, char *b, int blen);
int WL_peeknone (WL_State *state, int off);
int WL_peekint (WL_State *state, int off, long long *x);
int WL_peekfloat (WL_State *state, int off, float *x);
int WL_peekstr (WL_State *state, int off, WL_String *str);
int WL_popnone (WL_State *state);
int WL_popint (WL_State *state, long long *x);
int WL_popfloat (WL_State *state, float *x);
int WL_popstr (WL_State *state, WL_String *str);
int WL_popany (WL_State *state);
void WL_select (WL_State *state);
void WL_pushnone (WL_State *state);
void WL_pushint (WL_State *state, long long x);
void WL_pushfloat (WL_State *state, float x);
void WL_pushstr (WL_State *state, WL_String str);
void WL_pusharray (WL_State *state, int cap);
void WL_pushmap (WL_State *state, int cap);
void WL_insert (WL_State *state);
void WL_append (WL_State *state);
WL_Compiler* wl_compiler_init (WL_Arena *arena);
WL_AddResult wl_compiler_add (WL_Compiler *compiler, WL_String content);
int wl_compiler_link (WL_Compiler *compiler, WL_Program *program);
WL_String wl_compiler_error (WL_Compiler *compiler);
int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap);
void wl_dump_program (WL_Program program);
#endif // WL_PUBLIC_INCLUDED
#endif // WL_AMALGAMATION
WL_Runtime* wl_runtime_init (WL_Arena *arena, WL_Program program);
WL_EvalResult wl_runtime_eval (WL_Runtime *rt);
WL_String wl_runtime_error (WL_Runtime *rt);
void wl_runtime_dump (WL_Runtime *rt);
bool wl_streq (WL_String a, char *b, int blen);
int wl_arg_count (WL_Runtime *rt);
bool wl_arg_none (WL_Runtime *rt, int idx);
bool wl_arg_bool (WL_Runtime *rt, int idx, bool *x);
bool wl_arg_s64 (WL_Runtime *rt, int idx, int64_t *x);
bool wl_arg_f64 (WL_Runtime *rt, int idx, double *x);
bool wl_arg_str (WL_Runtime *rt, int idx, WL_String *x);
bool wl_arg_array (WL_Runtime *rt, int idx);
bool wl_arg_map (WL_Runtime *rt, int idx);
bool wl_peek_none (WL_Runtime *rt, int off);
bool wl_peek_bool (WL_Runtime *rt, int off, bool *x);
bool wl_peek_s64 (WL_Runtime *rt, int off, int64_t *x);
bool wl_peek_f64 (WL_Runtime *rt, int off, double *x);
bool wl_peek_str (WL_Runtime *rt, int off, WL_String *x);
bool wl_pop_any (WL_Runtime *rt);
bool wl_pop_none (WL_Runtime *rt);
bool wl_pop_bool (WL_Runtime *rt, bool *x);
bool wl_pop_s64 (WL_Runtime *rt, int64_t *x);
bool wl_pop_f64 (WL_Runtime *rt, double *x);
bool wl_pop_str (WL_Runtime *rt, WL_String *x);
void wl_push_none (WL_Runtime *rt);
void wl_push_true (WL_Runtime *rt);
void wl_push_false (WL_Runtime *rt);
void wl_push_s64 (WL_Runtime *rt, int64_t x);
void wl_push_f64 (WL_Runtime *rt, double x);
void wl_push_str (WL_Runtime *rt, WL_String x);
void wl_push_array (WL_Runtime *rt, int cap);
void wl_push_map (WL_Runtime *rt, int cap);
void wl_push_arg (WL_Runtime *rt, int idx);
void wl_insert (WL_Runtime *rt);
void wl_append (WL_Runtime *rt);
-53
View File
@@ -1,53 +0,0 @@
class Amalgamator:
def __init__(self):
self.out = ""
def append_text(self, text):
self.out += text
def append_file(self, file):
self.out += "\n"
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
self.out += "// " + file + "\n"
self.out += "////////////////////////////////////////////////////////////////////////////////////////\n"
self.out += "\n"
# self.out += "#line 1 \"" + file + "\"\n"
self.out += open(file).read()
if len(self.out) > 0 and self.out[len(self.out)-1] != '\n':
self.out += "\n"
def save(self, file):
open(file, 'w').write(self.out)
desc = """
// This file was generated automatically. Do not modify directly!
"""
header = Amalgamator()
header.append_text("#ifndef WL_AMALGAMATION\n")
header.append_text("#define WL_AMALGAMATION\n")
header.append_text(desc)
header.append_file("src/compile.h")
header.append_text("#endif // WL_AMALGAMATION\n")
header.save("WL.h")
source = Amalgamator()
source.append_text("#include \"WL.h\"\n")
source.append_file("src/includes.h")
source.append_file("src/basic.h")
source.append_file("src/basic.c")
source.append_file("src/file.h")
source.append_file("src/file.c")
source.append_file("src/parse.h")
source.append_file("src/parse.c")
source.append_file("src/assemble.h")
source.append_file("src/assemble.c")
source.append_file("src/value.h")
source.append_file("src/value.c")
source.append_file("src/eval.h")
source.append_file("src/eval.c")
source.append_file("src/compile.c")
source.save("WL.c")
-1
View File
@@ -1 +0,0 @@
let ugu = 78
-1
View File
@@ -1 +0,0 @@
<!-- This is a comment. Comments have no effect! -->
-45
View File
@@ -1,45 +0,0 @@
<!--
You can have regular HTML elements in WL.
-->
<a head="some_page.html">I'm a link</a>
<!--
You can declare variables and use them in the HTML by
escaping the name
-->
let name = "cozis"
<p>Hello from \name</p>
<!--
HTML attributes are evaluated as WL expressions.
The following evaluates to
<p A=Some value B=7></p>
which, to be fair, isn't right. There shoud be quotes
around "Some value"
-->
let valueA = "Some value"
<p A=valueA B=1+2*3></p>
<!--
HTML attributes are just expressions, and therefore
can be assigned to variables
-->
let link = <a href="page.html">Click me</a>
<!--
And then can be printed by simply stating the name
of the variable or by embedding it in a lager element
-->
link
<p>You should click this link: \link</p>
-58
View File
@@ -1,58 +0,0 @@
<!--
You can embed elements based on conditions
using plain if-else statements
-->
let a = 2
if a == 1: {
<a>The condition is true</a>
} else {
<a>It is false</a>
}
<!--
If the branch occurs inside an HTML element,
you must escape the if keyword with a backslash
-->
<p>
\if a == 1: {
<a>The condition is true</a>
} else {
<a>It is false</a>
}
</p>
<!--
Similarly, you can execute code multiple times
based on a condition using a while statement.
The following loop generates the output
<a>I'm link number 1</a>
<a>I'm link number 2</a>
<a>I'm link number 3</a>
Note how the i variable is printed after adding
1 to it.
-->
let i = 0
while i < 3: {
<a>I'm link number \i + 1</a>
i = i + 1
}
<!--
To insert the loop in an HTML element, you need
to escape it
-->
i = 0
<ul>
\while i < 3: {
<li>I'm link number \i + 1</li>
i = i + 1
}
</ul>
-83
View File
@@ -1,83 +0,0 @@
<!--
WL supports integers, floats, strings, array and map
values. Arrays are what you expect. They allow one to
store sequences of elements.
The following snippet prints
123
which is the string obtained by concatenating all
the elements
-->
let my_var = [1, 2, 3]
my_var
<!--
Maps are similar to Python dicts and Javascript
objects. They store associations between values
The following prints
Second
-->
let my_map = { 1: "First", 2: "Second", 3: "Third" }
my_map[2]
<!--
You can have any type as a map key, and if the
key is a string that is also a valid variable
name, you can drop the double quotes
-->
let person = { "name": "Cozis" }
let person_no_quotes = { name: "Cozis" }
<!--
You can iterate over the keys of a map using the
for loop. The following prints the string
ABC
-->
for key in { A: 1, B: 2, C: 3 }: {
key
}
for key, i in { A: 1, B: 2, C: 3 }: {
<!--
You can keep track of the current index by adding
a second interation variable
-->
}
<!--
When using a for loop over a map, the first
iteration variable holds its keys. When the
iterated value is an array, the first variable
returns its values
-->
for val in [5, 3, 7]:
val
for val, i in [5, 3, 7]:
i
<!--
As usual, you can have for statements in HTML
by escaping them
-->
let links = ["http://github.com", "http://reddit.com"]
<ul>
\for link, i in links:
<li><a href=link>I'm link number \i</a></li>
</ul>
-26
View File
@@ -1,26 +0,0 @@
<!--
You can declare functions too.
Unlike the global scope, expressions
are not printed by default, so you need
to use the print statement to do so.
-->
fun say_hello(name) {
print "Hello to "
print name
}
say_hello("cozis")
<!--
If a function is implemented with a single
expression, you can omit the curly braces
to return it
-->
fun say_hello_2(name)
<a>Hello, \name!</a>
say_hello_2("cozis")
+6
View File
@@ -0,0 +1,6 @@
let a = []
a << 1 << 2
a
+157 -89
View File
@@ -1,121 +1,189 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include "wl.h"
#include "WL.h"
typedef struct FileData FileData;
struct FileData {
FileData *next;
int size;
char data[];
};
FileData *load_file(WL_String path)
{
char buf[1<<10];
if (path.len >= (int) sizeof(buf))
return NULL;
memcpy(buf, path.ptr, path.len);
buf[path.len] = '\0';
FILE *f = fopen(buf, "rb");
if (f == NULL)
return NULL;
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
FileData *data = malloc(sizeof(FileData) + size);
if (data == NULL) {
fclose(f);
return NULL;
}
data->size = size;
data->next = NULL;
fread(data->data, 1, size, f);
fclose(f);
return data;
}
int main(int argc, char **argv)
{
if (argc < 2) {
printf("Missing file path\n");
char *entry_file = NULL;
bool bc = false;
bool ast = false;
bool run = true;
for (int i = 1; i < argc; i++) {
if (!strcmp(argv[i], "--bc"))
bc = true;
else if (!strcmp(argv[i], "--ast"))
ast = true;
else if (!strcmp(argv[i], "--no-run"))
run = false;
else
entry_file = argv[i];
}
if (entry_file == NULL) {
fprintf(stderr, "Usage: %s file.wl\n", argv[0]);
return -1;
}
char *file = argv[1];
char err[1<<9];
char *mem = malloc(1<<20);
WL_Arena a = { mem, 1<<20, 0 };
WL_Compiler *compiler = WL_Compiler_init(&a);
if (compiler == NULL) {
assert(0); // TODO
int cap = 1<<20;
char *mem = malloc(cap);
if (mem == NULL) {
fprintf(stderr, "Error: Allocation failure\n");
return -1;
}
WL_Arena arena = { mem, cap, 0 };
int num_loaded_files = 0;
char *loaded_files[128];
WL_CompileResult result;
WL_String path = { file, strlen(file) };
for (int i = 0;; i++) {
char buf[1<<10];
if (path.len >= (int) sizeof(buf)) {
assert(0); // TODO
}
memcpy(buf, path.ptr, path.len);
buf[path.len] = '\0';
FILE *f = fopen(buf, "rb");
if (f == NULL) {
printf("File not found '%.*s'\n", path.len, path.ptr);
WL_Program program;
{
WL_Compiler *c = wl_compiler_init(&arena);
if (c == NULL) {
fprintf(stderr, "Error: Out of memory");
return -1;
}
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
fseek(f, 0, SEEK_SET);
FileData *file_head;
FileData **file_tail = &file_head;
char *file_data = malloc(file_size);
WL_String path = { entry_file, strlen(entry_file) };
for (;;) {
fread(file_data, 1, file_size, f);
fclose(f);
FileData *file = load_file(path);
if (file == NULL) {
printf("Couldn't open '%.*s'\n", path.len, path.ptr);
return -1;
}
*file_tail = file;
file_tail = &file->next;
result = WL_compile(compiler, path, (WL_String) { file_data, file_size });
loaded_files[num_loaded_files++] = file_data;
if (result.type == WL_COMPILE_RESULT_ERROR) {
printf("Compilation of '%.*s' failed\n", path.len, path.ptr);
WL_AddResult res = wl_compiler_add(c, (WL_String) { file->data, file->size });
if (res.type == WL_ADD_ERROR) {
fprintf(stderr, "Error: %s\n", wl_compiler_error(c).ptr);
return -1;
}
if (res.type == WL_ADD_AGAIN) {
path = res.path;
continue;
}
assert(res.type == WL_ADD_LINK);
break;
}
if (result.type == WL_COMPILE_RESULT_DONE)
break;
*file_tail = NULL;
assert(result.type == WL_COMPILE_RESULT_FILE);
path = result.path;
}
if (ast) {
char buf[1<<10];
int len = wl_dump_ast(c, buf, sizeof(buf));
if (len > sizeof(buf)-1)
len = sizeof(buf)-1;
buf[len] = '\0';
for (int i = 0; i < num_loaded_files; i++)
free(loaded_files[i]);
printf("%s\n", buf);
}
WL_Compiler_free(compiler);
int ret = wl_compiler_link(c, &program);
if (ret < 0) {
WL_String err = wl_compiler_error(c);
fprintf(stderr, "Error: %s\n", err.ptr);
return -1;
}
if (result.type == WL_COMPILE_RESULT_ERROR) {
printf("Compilation error\n");
return -1;
}
WL_Program program = result.program;
if (bc)
wl_dump_program(program);
WL_State *state = WL_State_init(&a, program, err, (int) sizeof(err));
WL_State_trace(state, 0);
for (bool done = false; !done; ) {
WL_Result result = WL_eval(state);
switch (result.type) {
case WL_DONE:
done = true;
break;
case WL_ERROR:
done = true;
printf("%s\n", err);
break;
case WL_VAR:
if (0) {}
else if (WL_streq(result.str, "varA", -1)) WL_pushint(state, 1);
else if (WL_streq(result.str, "varB", -1)) WL_pushint(state, 1);
else if (WL_streq(result.str, "varC", -1)) WL_pushint(state, 1);
break;
case WL_CALL:
// TODO
printf("(called [%.*s])\n", result.str.len, result.str.ptr);
break;
case WL_OUTPUT:
fwrite(result.str.ptr, 1, result.str.len, stdout);
break;
FileData *file = file_head;
while (file) {
FileData *next = file->next;
free(file);
file = next;
}
}
if (run) {
WL_Runtime *rt = wl_runtime_init(&arena, program);
if (rt == NULL) {
fprintf(stderr, "Error: Invalid program or out of memory\n");
return -1;
}
FILE *output = stdout;
for (bool done = false; !done; ) {
WL_EvalResult res = wl_runtime_eval(rt);
wl_runtime_dump(rt);
switch (res.type) {
case WL_EVAL_NONE:
break;
case WL_EVAL_DONE:
done = true;
break;
case WL_EVAL_ERROR:
printf("Error: %s\n", wl_runtime_error(rt).ptr);
return -1;
case WL_EVAL_OUTPUT:
fwrite(res.str.ptr, 1, res.str.len, output);
break;
case WL_EVAL_SYSVAR:
if (wl_streq(res.str, "varA", -1)) wl_push_s64(rt, 1);
if (wl_streq(res.str, "varB", -1)) wl_push_s64(rt, 7);
if (wl_streq(res.str, "varC", -1)) wl_push_s64(rt, 13);
break;
case WL_EVAL_SYSCALL:
if (wl_streq(res.str, "testfn", -1)) {
for (int i = 0; i < wl_arg_count(rt); i++)
wl_push_arg(rt, i);
}
break;
}
}
}
WL_State_free(state);
free(mem);
return 0;
}
-57
View File
@@ -1,57 +0,0 @@
include "example.wl"
ugu
let posts = [
{
author: "UserA",
title: "I'm the first post",
content: "Sup everyone!",
date: "1 Jan 2025",
comments: [
{
author: "UserB",
content: "Hello! This is a comment!",
comments: [
{
author: "UserA",
content: "Hello to you!",
comments: []
}
]
}
]
}
]
fun comment(c)
<div>
<a>\c.author</a>
<p>\c.content</p>
\for subc in c.comments:
comment(subc)
</div>
<html>
<head>
<title>List of posts</title>
</head>
<body>
\for post in posts:
<div class="post">
<h2>
\post.title
</h2>
<span>
Posted on \post.date by \post.author
</span>
<p>
\post.content
</p>
<div class="comments">
\for c in post.comments:
comment(c)
</div>
</div>
</body>
</html>
+198
View File
@@ -0,0 +1,198 @@
# Expressions
A WL file is a sequence of statements. One type of statement is expressions:
```
1 + 2
```
All expressions statements are evaluated and written to output.
## Supported Types
WL supports these type of values:
1. None: A value which is only equal to itself
2. Booleans
3. Integers: Equivalent to `int64_t` in C
4. Floats: Equivalent to `double` in C
5. Strings: Sequence of bytes
6. Arrays: Etherogeneous sequences of values
7. Maps: Associations between arbitrary key-value paris
This is how the literals are used:
```
none
true
false
"I'm a string"
'I'm a string too!'
[1, 2, 3]
+{ "I'm the first key": 1, "I'm the second key": 2 }
```
## Unary Operators
The supported unary operators are
1. `+`: Allowed on any type and returns the operand unchanged
2. `-`: Negates an integer or float value
3. `len`: Returns the number of items stored into an array or the number of key-value pairs in a map
## Binary Operators
The supported binary operators are
1. `+`: Sums two numeric values. If a float value is involved, the result is a float too.
1. `-`: Subtracts two numeric values. If a float value is involved, the result is a float too.
1. `*`: Multiplies two numeric values. If a float value is involved, the result is a float too.
1. `/`: Divides two numeric values. If a float value is involved, the result is a float too.
1. `%`: Returns the division's remainder. The operands must be integers.
1. `==`: Returns `true` if the operands are the same, else returns `false`.
1. `!=`: Returns `true` if the operands are different, else returns `false`
1. `<`: Returns `true` if the first operand is lower than the second one, else returns `false`. The operands must be numeric.
1. `>`: Returns `true` if the first operand is greater than the second one, else returns `false`. The operands must be numeric.
Note that there are no implicit conversions, so for instance the integer `1` is different from the floating point `1.0`.
## Escaping Characters In String Literals
String literals can only contain printable ASCII characters (codepoints 32 to 127). Any other byte value must be escaped.
You can use `\n`, `\t`, `\r` to represent the line feed, horizontal tab, and carriage return characters.
Since single `'` or double `"` quotes are used as string delimiters, you must escape any quote that's part of the value using a backslash: `\'`, `\"`.
If a string contains a backslash, the backslash itself must be escaped `\\`.
Any byte value can be encoded using the `\x` notation
```
"This byte \xFF is not valid ASCII"
```
It allows to encode any byte value with its uppercase or lowercase hexadecimal representation. There must always be two hex digits, even if the high bits are zero.
## Variables & Scopes
You can bind expression results to variables
```
let a = 1+2
```
This will bind the result of the expression to the name "a". Variable names can contain digits, letters, and underscores, but the first character can't be a digit. When an expression is bound to a variable, it is not written to output.
You can later reuse the bound value by its variable name
```
a + 3
```
This will output `6`.
You can't declare two variables with the same time. The following is invalid:
```
let a = 1
let a = 2
```
You can reuse the same variable name by declaring a new scope:
```
let a = 1
{
let a = 2
a
}
```
Grouping statements into scopes this way allows one to reuse variable names. Whenever a variable is referenced, the one in the nearest scope is used. So the previous example will output `2`.
# If-else statements
You can optionally run some code based on an expression result using if-else statements:
```
if 1 > 2: {
"First branch taken"
} else {
"Second branch taken"
}
```
As usual, you can omit the else branch
```
if 1 > 2: {
"Branch taken"
}
```
If the branch only contains one statement, you can omit the curly braces
```
if 1 > 2:
"First branch taken"
else
"Second branch taken"
```
A consequence of this is that you can chain if-else statements
```
let a = 4
if a == 1: {
"a is 1"
} else if a == 2: {
"a is 2"
} else if a == 3: {
"a is 3"
} else {
"a is something else"
}
```
# While statements
You can loop while a certain condition is true using a while statement
```
let i = 0
while i < 3: {
"i="
i
"\n"
i = i+1
}
```
Which will print:
```
i=0
i=1
i=2
```
# For statements
You can iterate over the items of an array using the for statement
```
for item in ["A", "B", "C"]: {
item
}
```
This will print
```
ABC
```
By adding a second iteration variable, you will be able to read the current index
-28
View File
@@ -1,28 +0,0 @@
---3;
hello;
world;
if 1 + 2: {
a;
b + 3 * 4;
c;
}
while 1 + 2:
"This is a string";
<html>
<head name=hello xxx>
aaa
<title>Some Title</title>
bbb
</head>
<body>
<ul>
$ while 1:
<li>$post.name; $post.date;</li>;
</ul>
</body>
</html>;
View File
-1637
View File
File diff suppressed because it is too large Load Diff
-65
View File
@@ -1,65 +0,0 @@
#ifndef WL_ASSEMBLE_INCLUDED
#define WL_ASSEMBLE_INCLUDED
#ifndef WL_AMALGAMATION
#include "public.h"
#include "parse.h"
#endif
enum {
OPCODE_NOPE = 0x00,
OPCODE_EXIT = 0x23,
OPCODE_GROUP = 0x25,
OPCODE_GPOP = 0x26,
OPCODE_GPRINT = 0x27,
OPCODE_GTRUNC = 0x28,
OPCODE_GCOALESCE = 0x29,
OPCODE_GOVERWRITE = 0x2A,
OPCODE_GPACK = 0x2B,
OPCODE_PUSHI = 0x01,
OPCODE_PUSHF = 0x02,
OPCODE_PUSHS = 0x03,
OPCODE_PUSHV = 0x04,
OPCODE_PUSHA = 0x05,
OPCODE_PUSHM = 0x06,
OPCODE_PUSHN = 0x21,
OPCODE_POP = 0x07,
OPCODE_NEG = 0x08,
OPCODE_EQL = 0x09,
OPCODE_NQL = 0x0A,
OPCODE_LSS = 0x0B,
OPCODE_GRT = 0x0C,
OPCODE_ADD = 0x0D,
OPCODE_SUB = 0x0E,
OPCODE_MUL = 0x0F,
OPCODE_DIV = 0x10,
OPCODE_MOD = 0x11,
OPCODE_SETV = 0x12,
OPCODE_JUMP = 0x13,
OPCODE_JIFP = 0x14,
OPCODE_CALL = 0x15,
OPCODE_RET = 0x16,
OPCODE_APPEND = 0x17,
OPCODE_INSERT1 = 0x18,
OPCODE_INSERT2 = 0x19,
OPCODE_SELECT = 0x20,
OPCODE_PRINT = 0x24,
OPCODE_SYSVAR = 0x2C,
OPCODE_SYSCALL = 0x2D,
OPCODE_FOR = 0x2E,
OPCODE_PUSHT = 0x2F,
OPCODE_PUSHFL = 0x30,
OPCODE_LEN = 0x31,
};
typedef struct {
WL_Program program;
int errlen;
} AssembleResult;
int parse_program_header(WL_Program p, String *code, String *data, char *errbuf, int errmax);
void print_program(WL_Program program);
char *print_instruction(char *p, char *data);
AssembleResult assemble(Node *root, WL_Arena *arena, char *errbuf, int errmax);
#endif // WL_ASSEMBLE_INCLUDED
-98
View File
@@ -1,98 +0,0 @@
#ifndef WL_AMALGAMATION
#include "includes.h"
#include "basic.h"
#include "public.h"
#endif
bool is_space(char c)
{
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
bool is_digit(char c)
{
return c >= '0' && c <= '9';
}
bool is_alpha(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
bool is_printable(char c)
{
return c >= ' ' && c <= '~';
}
bool is_hex_digit(char c)
{
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
char to_lower(char c)
{
if (c >= 'A' && c <= 'Z')
return c - 'A' + 10;
return c;
}
int hex_digit_to_int(char c)
{
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return c - '0';
}
bool streq(String a, String b)
{
if (a.len != b.len)
return false;
for (int i = 0; i < a.len; i++)
if (a.ptr[i] != b.ptr[i])
return false;
return true;
}
bool streqcase(String a, String b)
{
if (a.len != b.len)
return false;
for (int i = 0; i < a.len; i++)
if (to_lower(a.ptr[i]) != to_lower(b.ptr[i]))
return false;
return true;
}
void *alloc(WL_Arena *a, int len, int align)
{
int pad = -(intptr_t) (a->ptr + a->cur) & (align-1);
if (a->len - a->cur < len + pad)
return NULL;
void *ret = a->ptr + a->cur + pad;
a->cur += pad + len;
return ret;
}
bool grow_alloc(WL_Arena *a, char *p, int new_len)
{
int new_cur = (p - a->ptr) + new_len;
if (new_cur > a->len)
return false;
a->cur = new_cur;
return true;
}
String copystr(String s, WL_Arena *a)
{
char *p = alloc(a, s.len, 1);
if (p == NULL)
return (String) { NULL, 0 };
memcpy(p, s.ptr, s.len);
return (String) { p, s.len };
}
-43
View File
@@ -1,43 +0,0 @@
#ifndef WL_BASIC_INCLUDED
#define WL_BASIC_INCLUDED
#ifndef WL_AMALGAMATION
#include "public.h"
#endif
typedef struct {
char *ptr;
int len;
} String;
#ifdef _WIN32
#define LLU "llu"
#define LLD "lld"
#else
#define LLU "lu"
#define LLD "ld"
#endif
#define S(X) (String) { (X), (int) sizeof(X)-1 }
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))
#define COUNT(X) (int) (sizeof(X) / sizeof((X)[0]))
bool is_space(char c);
bool is_digit(char c);
bool is_alpha(char c);
bool is_printable(char c);
char to_lower(char c);
bool is_hex_digit(char c);
int hex_digit_to_int(char c);
bool streq(String a, String b);
bool streqcase(String a, String b);
String copystr(String s, WL_Arena *a);
void *alloc(WL_Arena *a, int len, int align);
bool grow_alloc(WL_Arena *a, char *p, int new_len);
#endif // WL_BASIC_INCLUDED
-123
View File
@@ -1,123 +0,0 @@
#ifndef WL_AMALGAMATION
#include "eval.h"
#include "parse.h"
#include "assemble.h"
#include "compile.h"
#endif
#define FILE_LIMIT 32
typedef struct {
String file;
Node* root;
Node* includes;
} CompiledFile;
struct WL_Compiler {
WL_Arena* arena;
CompiledFile files[FILE_LIMIT];
int num_files;
String waiting_file;
};
int WL_streq(WL_String a, char *b, int blen)
{
if (b == NULL) b = "";
if (blen < 0) blen = strlen(b);
if (a.len != blen)
return 0;
for (int i = 0; i < a.len; i++)
if (a.ptr[i] != b[i])
return 0;
return 1;
}
WL_Compiler *WL_Compiler_init(WL_Arena *arena)
{
WL_Compiler *compiler = alloc(arena, (int) sizeof(WL_Compiler), _Alignof(WL_Compiler));
if (compiler == NULL)
return NULL;
compiler->arena = arena;
compiler->num_files = 0;
compiler->waiting_file = (String) { NULL, 0 };
return compiler;
}
void WL_Compiler_free(WL_Compiler *compiler)
{
(void) compiler;
// TODO
}
WL_CompileResult WL_compile(WL_Compiler *compiler, WL_String file, WL_String content)
{
if (compiler->waiting_file.len > 0)
file = (WL_String) { compiler->waiting_file.ptr, compiler->waiting_file.len };
else {
// TODO: copy file path
// file = strdup(file, compiler->arena)
}
char err[1<<9];
ParseResult pres = parse((String) { content.ptr, content.len }, compiler->arena, err, (int) sizeof(err));
if (pres.node == NULL) {
printf("%s\n", err); // TODO
return (WL_CompileResult) { .type=WL_COMPILE_RESULT_ERROR };
}
CompiledFile compiled_file = {
.file = { file.ptr, file.len },
.root = pres.node,
.includes = pres.includes,
};
compiler->files[compiler->num_files++] = compiled_file;
for (int i = 0; i < compiler->num_files; i++) {
Node *include = compiler->files[i].includes;
while (include) {
assert(include->type == NODE_INCLUDE);
if (include->include_root == NULL) {
for (int j = 0; j < compiler->num_files; j++) {
if (streq(include->include_path, compiler->files[j].file)) {
include->include_root = compiler->files[j].root;
break;
}
}
}
if (include->include_root == NULL) {
if (compiler->num_files == FILE_LIMIT) {
assert(0); // TODO
}
// TODO: Make the path relative to the compiled file
compiler->waiting_file = include->include_path;
return (WL_CompileResult) { .type=WL_COMPILE_RESULT_FILE, .path={ include->include_path.ptr, include->include_path.len } };
}
include = include->include_next;
}
}
AssembleResult ares = assemble(compiler->files[0].root, compiler->arena, err, (int) sizeof(err));
if (ares.errlen) {
printf("%s\n", err); // TODO
return (WL_CompileResult) { .type=WL_COMPILE_RESULT_ERROR };
}
return (WL_CompileResult) { .type=WL_COMPILE_RESULT_DONE, .program=ares.program };
}
void WL_dump_program(WL_Program program)
{
print_program(program);
}
-83
View File
@@ -1,83 +0,0 @@
#ifndef WL_PUBLIC_INCLUDED
#define WL_PUBLIC_INCLUDED
#ifndef WL_AMALGAMATION
#include "includes.h"
#endif
typedef struct WL_State WL_State;
typedef struct {
char *ptr;
int len;
} WL_String;
typedef struct {
char *ptr;
int len;
} WL_Program;
typedef enum {
WL_DONE,
WL_ERROR,
WL_VAR,
WL_CALL,
WL_OUTPUT,
} WL_ResultType;
typedef struct {
WL_ResultType type;
WL_String str;
} WL_Result;
typedef struct {
char *ptr;
int len;
int cur;
} WL_Arena;
typedef struct WL_Compiler WL_Compiler;
typedef enum {
WL_COMPILE_RESULT_DONE,
WL_COMPILE_RESULT_FILE,
WL_COMPILE_RESULT_ERROR,
} WL_CompileResultType;
typedef struct {
WL_CompileResultType type;
WL_Program program;
WL_String path;
} WL_CompileResult;
WL_Compiler* WL_Compiler_init (WL_Arena *arena);
void WL_Compiler_free (WL_Compiler *compiler);
WL_CompileResult WL_compile (WL_Compiler *compiler, WL_String file, WL_String content);
WL_State* WL_State_init (WL_Arena *a, WL_Program p, char *err, int errmax);
void WL_State_free (WL_State *state);
void WL_State_trace (WL_State *state, int trace);
WL_Result WL_eval (WL_State *state);
void WL_dump_program(WL_Program program);
int WL_streq (WL_String a, char *b, int blen);
int WL_peeknone (WL_State *state, int off);
int WL_peekint (WL_State *state, int off, long long *x);
int WL_peekfloat (WL_State *state, int off, float *x);
int WL_peekstr (WL_State *state, int off, WL_String *str);
int WL_popnone (WL_State *state);
int WL_popint (WL_State *state, long long *x);
int WL_popfloat (WL_State *state, float *x);
int WL_popstr (WL_State *state, WL_String *str);
int WL_popany (WL_State *state);
void WL_select (WL_State *state);
void WL_pushnone (WL_State *state);
void WL_pushint (WL_State *state, long long x);
void WL_pushfloat (WL_State *state, float x);
void WL_pushstr (WL_State *state, WL_String str);
void WL_pusharray (WL_State *state, int cap);
void WL_pushmap (WL_State *state, int cap);
void WL_insert (WL_State *state);
void WL_append (WL_State *state);
#endif // WL_PUBLIC_INCLUDED
-1389
View File
File diff suppressed because it is too large Load Diff
-11
View File
@@ -1,11 +0,0 @@
#ifndef WL_EVAL_INCLUDED
#define WL_EVAL_INCLUDED
#ifndef WL_AMALGAMATION
#include "assemble.h"
#endif
// TODO: pretty sure this is unused
int eval(WL_Program p, WL_Arena *a, char *errbuf, int errmax);
#endif // WL_EVAL_INCLUDED
-114
View File
@@ -1,114 +0,0 @@
#ifndef WL_AMALGAMATION
#include "includes.h"
#include "file.h"
#endif
int file_open(String path, File *handle, int *size)
{
char zt[1<<10];
if (path.len >= COUNT(zt))
return -1;
memcpy(zt, path.ptr, path.len);
zt[path.len] = '\0';
#ifdef _WIN32
*handle = CreateFileA(
zt,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (*handle == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND ||
error == ERROR_ACCESS_DENIED)
return 1;
return -1;
}
LARGE_INTEGER fileSize;
if (!GetFileSizeEx(*handle, &fileSize)) {
CloseHandle(*handle);
return -1;
}
if (fileSize.QuadPart > INT_MAX) {
CloseHandle(*handle);
return -1;
}
*size = (int) fileSize.QuadPart;
#else
*handle = open(zt, O_RDONLY);
if (*handle < 0) {
if (errno == ENOENT)
return 1;
return -1;
}
struct stat info;
if (fstat(*handle, &info) < 0) {
close(*handle);
return -1;
}
if (S_ISDIR(info.st_mode)) {
close(*handle);
return 1;
}
if (info.st_size > INT_MAX) {
close(*handle);
return -1;
}
*size = (int) info.st_size;
#endif
return 0;
}
void file_close(File file)
{
#ifdef _WIN32
CloseHandle(file);
#else
close(file);
#endif
}
int file_read(File file, char *dst, int max)
{
#ifdef _WIN32
DWORD num;
BOOL ok = ReadFile(file, dst, max, &num, NULL);
if (!ok)
return -1;
return (int) num;
#else
return read(file, dst, max);
#endif
}
int file_read_all(String path, String *dst)
{
int len;
File handle;
if (file_open(path, &handle, &len) < 0)
return -1;
char *ptr = malloc(len+1);
if (ptr == NULL) {
file_close(handle);
return -1;
}
for (int copied = 0; copied < len; ) {
int ret = file_read(handle, ptr + copied, len - copied);
if (ret <= 0) {
free(ptr);
file_close(handle);
return -1;
}
copied += ret;
}
*dst = (String) { ptr, len };
file_close(handle);
return 0;
}
-20
View File
@@ -1,20 +0,0 @@
#ifndef WL_FILE_INCLUDED
#define WL_FILE_INCLUDED
#ifndef WL_AMALGAMATION
#include "includes.h"
#include "basic.h"
#endif
#ifdef _WIN32
typedef HANDLE File;
#else
typedef int File;
#endif
int file_open(String path, File *handle, int *size);
void file_close(File file);
int file_read(File file, char *dst, int max);
int file_read_all(String path, String *dst);
#endif // WL_FILE_INCLUDED
-25
View File
@@ -1,25 +0,0 @@
#ifndef WL_INCLUDES_INCLUDED
#define WL_INCLUDES_INCLUDED
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
#ifndef _WIN32
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <limits.h>
#include <sys/stat.h>
#endif
#endif // WL_INCLUDES_INCLUDED
-1958
View File
File diff suppressed because it is too large Load Diff
-99
View File
@@ -1,99 +0,0 @@
#ifndef WL_PARSE_INCLUDED
#define WL_PARSE_INCLUDED
#ifndef WL_AMALGAMATION
#include "includes.h"
#include "basic.h"
#endif
typedef enum {
NODE_FUNC_DECL,
NODE_FUNC_ARG,
NODE_FUNC_CALL,
NODE_VAR_DECL,
NODE_PRINT,
NODE_BLOCK,
NODE_GLOBAL_BLOCK,
NODE_IFELSE,
NODE_FOR,
NODE_WHILE,
NODE_INCLUDE,
NODE_SELECT,
NODE_NESTED,
NODE_OPER_LEN,
NODE_OPER_POS,
NODE_OPER_NEG,
NODE_OPER_ASS,
NODE_OPER_EQL,
NODE_OPER_NQL,
NODE_OPER_LSS,
NODE_OPER_GRT,
NODE_OPER_ADD,
NODE_OPER_SUB,
NODE_OPER_MUL,
NODE_OPER_DIV,
NODE_OPER_MOD,
NODE_VALUE_INT,
NODE_VALUE_FLOAT,
NODE_VALUE_STR,
NODE_VALUE_NONE,
NODE_VALUE_TRUE,
NODE_VALUE_FALSE,
NODE_VALUE_VAR,
NODE_VALUE_SYSVAR,
NODE_VALUE_HTML,
NODE_VALUE_ARRAY,
NODE_VALUE_MAP,
NODE_HTML_PARAM,
} NodeType;
typedef struct Node Node;
struct Node {
NodeType type;
Node *next;
Node *key;
Node *left;
Node *right;
uint64_t ival;
double dval;
String sval;
Node *params;
Node *child;
bool no_body;
Node *cond;
String tagname;
String attr_name;
Node *attr_value;
String for_var1;
String for_var2;
Node *for_set;
String func_name;
Node *func_args;
Node *func_body;
String var_name;
Node *var_value;
String include_path;
Node* include_next;
Node* include_root;
};
typedef struct {
Node *node;
Node *includes;
int errlen;
} ParseResult;
void print_node(Node *node);
ParseResult parse(String src, WL_Arena *a, char *errbuf, int errmax);
#endif // WL_PARSE_INCLUDED
-501
View File
@@ -1,501 +0,0 @@
#ifndef WL_AMALGAMATION
#include "value.h"
#endif
#define ITEMS_PER_MAP_BATCH 8
#define ITEMS_PER_ARRAY_BATCH 16
typedef struct MapItems MapItems;
struct MapItems {
MapItems *next;
Value keys [ITEMS_PER_MAP_BATCH];
Value items[ITEMS_PER_MAP_BATCH];
};
typedef struct {
Type type;
int count;
int tail_count;
MapItems head;
MapItems *tail;
} MapValue;
typedef struct ArrayItems ArrayItems;
struct ArrayItems {
ArrayItems *next;
Value items[ITEMS_PER_ARRAY_BATCH];
};
typedef struct {
Type type;
int count;
int tail_count;
ArrayItems head;
ArrayItems *tail;
} ArrayValue;
typedef struct {
Type type;
double raw;
} FloatValue;
typedef struct {
Type type;
int64_t raw;
} IntValue;
typedef struct {
Type type;
int len;
char data[];
} StringValue;
Type type_of(Value v)
{
// 000 none
// 001 true
// 010 false
// 011 int
// 100
// 101
// 110 error
// 111 pointer
switch (v & 7) {
case 0: return TYPE_NONE;
case 1: return TYPE_BOOL;
case 2: return TYPE_BOOL;
case 3: return TYPE_INT;
case 4: break;
case 5: break;
case 6: return TYPE_ERROR;
case 7: return *(Type*) ((uintptr_t) v & ~(uintptr_t) 7);
}
return TYPE_ERROR;
}
int64_t get_int(Value v)
{
if ((v & 7) == 3)
return (int64_t) (v >> 3);
IntValue *p = (IntValue*) v;
return p->raw;
}
float get_float(Value v)
{
FloatValue *p = (FloatValue*) v;
return p->raw;
}
String get_str(Value v)
{
StringValue *p = (StringValue*) (v & ~(uintptr_t) 7);
return (String) { p->data, p->len };
}
static MapValue *get_map(Value v)
{
return (MapValue*) (v & ~(uintptr_t) 7);
}
static ArrayValue *get_array(Value v)
{
return (ArrayValue*) (v & ~(uintptr_t) 7);
}
Value make_int(WL_Arena *a, int64_t x)
{
if (x <= (int64_t) (1ULL << 60)-1 && x >= (int64_t) -(1ULL << 60))
return ((Value) x << 3) | 3;
IntValue *v = alloc(a, (int) sizeof(IntValue), _Alignof(IntValue));
if (v == NULL)
return VALUE_ERROR;
v->type = TYPE_INT;
v->raw = x;
assert(((uintptr_t) v & 7) == 0);
return ((Value) v) | 7;
}
Value make_float(WL_Arena *a, float x)
{
FloatValue *v = alloc(a, (int) sizeof(FloatValue), _Alignof(FloatValue));
if (v == NULL)
return VALUE_ERROR;
v->type = TYPE_FLOAT;
v->raw = x;
assert(((uintptr_t) v & 7) == 0);
return ((Value) v) | 7;
}
Value make_str(WL_Arena *a, String x) // TODO: This should reuse the string contents when possible
{
StringValue *v = alloc(a, (int) sizeof(StringValue) + x.len, 8);
if (v == NULL)
return VALUE_ERROR;
v->type = TYPE_STRING;
v->len = x.len;
memcpy(v->data, x.ptr, x.len);
assert(((uintptr_t) v & 7) == 0);
return ((Value) v) | 7;
}
Value make_map(WL_Arena *a)
{
MapValue *m = alloc(a, (int) sizeof(MapValue), _Alignof(MapValue));
if (m == NULL)
return VALUE_ERROR;
m->type = TYPE_MAP;
m->count = 0;
m->tail_count = 0;
m->tail = &m->head;
m->head.next = NULL;
return (Value) m | 7;
}
Value make_array(WL_Arena *a)
{
ArrayValue *v = alloc(a, (int) sizeof(ArrayValue), _Alignof(ArrayValue));
if (v == NULL)
return VALUE_ERROR;
v->type = TYPE_ARRAY;
v->count = 0;
v->tail_count = 0;
v->tail = &v->head;
v->head.next = NULL;
return (Value) v | 7;
}
int map_select(Value map, Value key, Value *val)
{
MapValue *p = get_map(map);
MapItems *batch = &p->head;
while (batch) {
int num = ITEMS_PER_MAP_BATCH;
if (batch->next == NULL)
num = p->tail_count;
for (int i = 0; i < num; i++)
if (valeq(batch->keys[i], key)) {
*val = batch->items[i];
return 0;
}
batch = batch->next;
}
return -1;
}
Value *map_select_by_index(Value map, int key)
{
MapValue *p = get_map(map);
MapItems *batch = &p->head;
int cursor = 0;
while (batch) {
int num = ITEMS_PER_MAP_BATCH;
if (batch->next == NULL)
num = p->tail_count;
if (cursor <= key && key < cursor + num)
return &batch->keys[key - cursor];
batch = batch->next;
cursor += num;
}
return NULL;
}
int map_insert(WL_Arena *a, Value map, Value key, Value val)
{
MapValue *p = get_map(map);
if (p->tail_count == ITEMS_PER_MAP_BATCH) {
MapItems *batch = alloc(a, (int) sizeof(MapItems), _Alignof(MapItems));
if (batch == NULL)
return -1;
batch->next = NULL;
if (p->tail)
p->tail->next = batch;
p->tail = batch;
p->tail_count = 0;
}
p->tail->keys[p->tail_count] = key;
p->tail->items[p->tail_count] = val;
p->tail_count++;
p->count++;
return 0;
}
Value *array_select(Value array, int key)
{
ArrayValue *p = get_array(array);
ArrayItems *batch = &p->head;
int cursor = 0;
while (batch) {
int num = ITEMS_PER_ARRAY_BATCH;
if (batch->next == NULL)
num = p->tail_count;
if (cursor <= key && key < cursor + num)
return &batch->items[key - cursor];
batch = batch->next;
cursor += num;
}
return NULL;
}
int array_append(WL_Arena *a, Value array, Value val)
{
ArrayValue *p = get_array(array);
if (p->tail_count == ITEMS_PER_ARRAY_BATCH) {
ArrayItems *batch = alloc(a, (int) sizeof(ArrayItems), _Alignof(ArrayItems));
if (batch == NULL)
return -1;
batch->next = NULL;
if (p->tail)
p->tail->next = batch;
p->tail = batch;
p->tail_count = 0;
}
p->tail->items[p->tail_count] = val;
p->tail_count++;
p->count++;
return 0;
}
bool valeq(Value a, Value b)
{
Type t1 = type_of(a);
Type t2 = type_of(b);
if (t1 != t2)
return false;
switch (t1) {
case TYPE_NONE:
return VALUE_TRUE;
case TYPE_BOOL:
return a == b;
case TYPE_INT:
return get_int(a) == get_int(b);
case TYPE_FLOAT:
return get_float(a) == get_float(b);
case TYPE_MAP:
return false; // TODO
case TYPE_ARRAY:
return false; // TODO
case TYPE_STRING:
return streq(get_str(a), get_str(b));
case TYPE_ERROR:
return true;
}
return false;
}
bool valgrt(Value a, Value b)
{
Type t1 = type_of(a);
Type t2 = type_of(b);
if (t1 != t2)
return false;
switch (t1) {
case TYPE_NONE:
return VALUE_FALSE;
case TYPE_BOOL:
return VALUE_FALSE;
case TYPE_INT:
return get_int(a) > get_int(b);
case TYPE_FLOAT:
return get_float(a) > get_float(b);
case TYPE_MAP:
return false;
case TYPE_ARRAY:
return false;
case TYPE_STRING:
return false;
case TYPE_ERROR:
return false;
}
return false;
}
int value_length(Value v)
{
Type type = type_of(v);
if (type == TYPE_ARRAY)
return get_array(v)->count;
if (type == TYPE_MAP)
return get_map(v)->count;
return -1;
}
typedef struct {
char *dst;
int max;
int len;
} ToStringContext;
static void tostr_appends(ToStringContext *tostr, String x)
{
if (tostr->max > tostr->len) {
int cpy = tostr->max - tostr->len;
if (cpy > x.len)
cpy = x.len;
memcpy(tostr->dst + tostr->len, x.ptr, cpy);
}
tostr->len += x.len;
}
static void tostr_appendi(ToStringContext *tostr, int64_t x)
{
int len;
if (tostr->max >= tostr->len)
len = snprintf(tostr->dst + tostr->len, tostr->max - tostr->len, "%" LLD, x);
else
len = snprintf(NULL, 0, "%" LLD, x);
tostr->len += len;
}
static void tostr_appendf(ToStringContext *tostr, double x)
{
int len;
if (tostr->max >= tostr->len)
len = snprintf(tostr->dst + tostr->len, tostr->max - tostr->len, "%f", x);
else
len = snprintf(NULL, 0, "%f", x);
tostr->len += len;
}
static void value_to_string_inner(Value v, ToStringContext *tostr)
{
switch (type_of(v)) {
case TYPE_NONE:
//tostr_appends(tostr, S("none"));
break;
case TYPE_BOOL:
// TODO
//tostr_appends(tostr, get_bool(v) ? S("true") : S("false"));
break;
case TYPE_INT:
tostr_appendi(tostr, get_int(v));
break;
case TYPE_FLOAT:
tostr_appendf(tostr, get_float(v));
break;
case TYPE_MAP:
{
tostr_appends(tostr, S("{ "));
MapValue *m = get_map(v);
MapItems *batch = &m->head;
while (batch) {
int num = ITEMS_PER_MAP_BATCH;
if (batch->next == NULL)
num = m->tail_count;
for (int i = 0; i < num; i++) {
value_to_string_inner(batch->keys[i], tostr);
tostr_appends(tostr, S(": "));
value_to_string_inner(batch->items[i], tostr);
if (batch->next != NULL || i+1 < num)
tostr_appends(tostr, S(", "));
}
batch = batch->next;
}
tostr_appends(tostr, S(" }"));
}
break;
case TYPE_ARRAY:
{
ArrayValue *a = get_array(v);
ArrayItems *batch = &a->head;
int cursor = 0;
while (batch) {
int num = ITEMS_PER_ARRAY_BATCH;
if (batch->next == NULL)
num = a->tail_count;
for (int i = 0; i < num; i++)
value_to_string_inner(batch->items[i], tostr);
batch = batch->next;
cursor += num;
}
}
break;
case TYPE_STRING:
tostr_appends(tostr, get_str(v));
break;
case TYPE_ERROR:
tostr_appends(tostr, S("error"));
break;
}
}
int value_to_string(Value v, char *dst, int max)
{
ToStringContext tostr = { dst, max, 0 };
value_to_string_inner(v, &tostr);
return tostr.len;
}
-46
View File
@@ -1,46 +0,0 @@
#ifndef WL_VALUE_INCLUDED
#define WL_VALUE_INCLUDED
#ifndef WL_AMALGAMATION
#include "basic.h"
#include "includes.h"
#endif
#define VALUE_NONE ((Value) 0)
#define VALUE_TRUE ((Value) 1)
#define VALUE_FALSE ((Value) 2)
#define VALUE_ERROR ((Value) 6)
typedef enum {
TYPE_NONE,
TYPE_BOOL,
TYPE_INT,
TYPE_FLOAT,
TYPE_MAP,
TYPE_ARRAY,
TYPE_STRING,
TYPE_ERROR,
} Type;
typedef uint64_t Value;
Type type_of (Value v);
int64_t get_int (Value v);
float get_float (Value v);
String get_str (Value v);
Value make_int (WL_Arena *a, int64_t x);
Value make_float (WL_Arena *a, float x);
Value make_str (WL_Arena *a, String x);
Value make_map (WL_Arena *a);
Value make_array (WL_Arena *a);
int map_select (Value map, Value key, Value *val);
Value* map_select_by_index(Value map, int key);
int map_insert (WL_Arena *a, Value map, Value key, Value val);
Value* array_select (Value array, int key);
int array_append (WL_Arena *a, Value array, Value val);
bool valeq (Value a, Value b);
bool valgrt (Value a, Value b);
int value_length (Value v);
int value_to_string(Value v, char *dst, int max);
#endif // WL_VALUE_INCLUDED
+237
View File
@@ -0,0 +1,237 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <stdbool.h>
#include "wl.h"
#define COUNT(X) (int) (sizeof(X) / sizeof((X)[0]))
struct {
int line;
char *in;
char *out;
} tests[] = {
{__LINE__, "", ""},
{__LINE__, "1", "1"},
{__LINE__, "none", ""},
{__LINE__, "true", "true"},
{__LINE__, "false", "false"},
{__LINE__, "\"Hello, world!\"", "Hello, world!"},
{__LINE__, "'Hello, world!'", "Hello, world!"},
{__LINE__, "\"\\\\\"", "\\"},
{__LINE__, "\"\\n\"", "\n"},
{__LINE__, "\"\\t\"", "\t"},
{__LINE__, "\"\\r\"", "\r"},
{__LINE__, "\"\\\"\"", "\""},
{__LINE__, "\"\\'\"", "'"},
{__LINE__, "\"\\xFF\"", "\xFF"},
{__LINE__, "\"\\x0F\"", "\x0F"},
{__LINE__, "\"\\xF0\"", "\xF0"},
{__LINE__, "[1, 2, 3]", "123"},
{__LINE__, "+{}", "{}"},
{__LINE__, "+{a:1}", "{a: 1}"},
{__LINE__, "+{a:1,b:2,c:3}", "{a: 1, b: 2, c: 3}"},
{__LINE__, "10-1-2", "7"},
{__LINE__, "1+2*3", "7"},
{__LINE__, "(1+2)*3", "9"},
{__LINE__, "1+(2*3)", "7"},
{__LINE__, "1+2", "3"},
{__LINE__, "2-1", "1"},
{__LINE__, "1-2", "-1"},
{__LINE__, "2*3", "6"},
{__LINE__, "2/3", "0"},
{__LINE__, "4/2", "2"},
{__LINE__, "0%2", "0"},
{__LINE__, "3%2", "1"},
{__LINE__, "1.0+2.0", "3.00"},
{__LINE__, "2.0-1.0", "1.00"},
{__LINE__, "1.0-2.0", "-1.00"},
{__LINE__, "2.0*3.0", "6.00"},
{__LINE__, "2.0/3.0", "0.67"},
{__LINE__, "4.0/2.0", "2.00"},
{__LINE__, "1+2.0", "3.00"},
{__LINE__, "2-1.0", "1.00"},
{__LINE__, "1-2.0", "-1.00"},
{__LINE__, "2*3.0", "6.00"},
{__LINE__, "2/3.0", "0.67"},
{__LINE__, "4/2.0", "2.00"},
{__LINE__, "1.0+2", "3.00"},
{__LINE__, "2.0-1", "1.00"},
{__LINE__, "1.0-2", "-1.00"},
{__LINE__, "2.0*3", "6.00"},
{__LINE__, "2.0/3", "0.67"},
{__LINE__, "4.0/2", "2.00"},
{__LINE__, "len []", "0"},
{__LINE__, "len [1, 2, 3]", "3"},
{__LINE__, "len {}", "0"},
{__LINE__, "len {a:1}", "1"},
{__LINE__, "len {a:1,b:2,c:3}", "3"},
{__LINE__, "-12", "-12"},
{__LINE__, "-12.34", "-12.34"},
{__LINE__, "1<2", "true"},
{__LINE__, "2<1", "false"},
{__LINE__, "1>2", "false"},
{__LINE__, "2>1", "true"},
{__LINE__, "1==1", "true"},
{__LINE__, "1==2", "false"},
{__LINE__, "1!=1", "false"},
{__LINE__, "1!=2", "true"},
{__LINE__, "let a = 1\na", "1"},
{__LINE__, "let a = 1\na = 2\na", "2"},
{__LINE__, "[5, 6, 7][0]", "5"},
{__LINE__, "[5, 6, 7][1]", "6"},
{__LINE__, "[5, 6, 7][2]", "7"},
{__LINE__, "+{a:5,b:6,c:7}.a", "5"},
{__LINE__, "+{a:5,b:6,c:7}.b", "6"},
{__LINE__, "+{a:5,b:6,c:7}.c", "7"},
{__LINE__, "+{a:5,b:6,c:7}['a']", "5"},
{__LINE__, "+{a:5,b:6,c:7}['b']", "6"},
{__LINE__, "+{a:5,b:6,c:7}['c']", "7"},
{__LINE__, "let x = 1\n{\nlet x = 2\nx\n}", "2"},
{__LINE__, "let x = {a:5,b:6,c:7}\nx.a = true\nx", "{a: true, b: 6, c: 7}"},
{__LINE__, "let x = {a:5,b:6,c:7}\nx.b = true\nx", "{a: 5, b: true, c: 7}"},
{__LINE__, "let x = {a:5,b:6,c:7}\nx.c = true\nx", "{a: 5, b: 6, c: true}"},
{__LINE__, "let x = {a:5,b:6,c:7}\nx['a'] = true\nx", "{a: true, b: 6, c: 7}"},
{__LINE__, "let x = {a:5,b:6,c:7}\nx['b'] = true\nx", "{a: 5, b: true, c: 7}"},
{__LINE__, "let x = {a:5,b:6,c:7}\nx['c'] = true\nx", "{a: 5, b: 6, c: true}"},
{__LINE__, "let a\nlet b\nlet c\na = b = c = 5\n", ""},
{__LINE__, "let a\nlet b\nlet c\na = b = c = 5\na\nb\nc", "555"},
{__LINE__, "if 1 < 2: 1\n2", "12"},
{__LINE__, "if 1 > 2: 1\n2", "2"},
{__LINE__, "if 1 < 2: 1 else 2\n3", "13"},
{__LINE__, "if 1 > 2: 1 else 2\n3", "23"},
{__LINE__, "let i = 0\nwhile i < 3: {\ntrue\ni = i + 1\n}\n", "truetruetrue"},
{__LINE__, "for a in ['A', 'B', 'C']: a", "ABC"},
{__LINE__, "for a, b in ['A', 'B', 'C']: { a\n b }", "A0B1C2"},
{__LINE__, "for a in {x:1,y:2,z:3}: a", "xyz"},
{__LINE__, "for a, b in {x:1,y:2,z:3}: { a\n b }", "x0y1z2"},
{__LINE__, "procedure P() 0", ""},
{__LINE__, "procedure P(a) a", ""},
{__LINE__, "procedure P(a, b, c) { a\nb\nc }", ""},
{__LINE__, "procedure P() 0\nP()", "0"},
{__LINE__, "procedure P(a, b, c) { a\nb\nc }\nP()", ""},
{__LINE__, "procedure P(a, b, c) { a\nb\nc }\nP(1)", "1"},
{__LINE__, "procedure P(a, b, c) { a\nb\nc }\nP(1, 2)", "12"},
{__LINE__, "procedure P(a, b, c) { a\nb\nc }\nP(1, 2, 3)", "123"},
{__LINE__, "procedure N(n) n\nN(1) + N(2)", "3"},
{__LINE__, "P()\nprocedure P() 1", "1"},
{__LINE__, "P()\nprocedure P() 1\n{\nprocedure P() 2\n}", "1"},
{__LINE__, "procedure P() 1\nP()\n{\nprocedure P() 2\n}", "1"},
{__LINE__, "procedure P() 1\n{\nP()\nprocedure P() 2\n}", "2"},
{__LINE__, "procedure P() 1\n{\nprocedure P() 2\nP()\n}", "2"},
{__LINE__, "procedure P() 1\n{\nprocedure P() 2\n}\nP()", "1"},
{__LINE__, "<a>Hello, world!</a>", "<a>Hello, world!</a>"},
{__LINE__, "<ul><li>A</li><li>B</li><li>C</li></ul>", "<ul><li>A</li><li>B</li><li>C</li></ul>"},
{__LINE__, "let a = <ul><li>A</li><li>B</li><li>C</li></ul>", ""},
{__LINE__, "let a = <ul><li>A</li><li>B</li><li>C</li></ul>\na", "<ul><li>A</li><li>B</li><li>C</li></ul>"},
};
int run_test(char *in, char *out, void *mem, int cap, int test_line)
{
WL_Arena arena = { mem, cap, 0 };
WL_Program program;
WL_Compiler *c = wl_compiler_init(&arena);
if (c == NULL) {
fprintf(stderr, "Error: Out of memory");
return -1;
}
WL_AddResult res = wl_compiler_add(c, (WL_String) { in, strlen(in) });
if (res.type == WL_ADD_ERROR) {
fprintf(stderr, "Error: %s\n", wl_compiler_error(c).ptr);
return -1;
}
if (res.type != WL_ADD_LINK) {
fprintf(stderr, "Error: Unexpected compiler state\n");
return -1;
}
int ret = wl_compiler_link(c, &program);
if (ret < 0) {
WL_String err = wl_compiler_error(c);
fprintf(stderr, "Error: %s\n", err.ptr);
return -1;
}
WL_Runtime *rt = wl_runtime_init(&arena, program);
if (rt == NULL) {
fprintf(stderr, "Error: Invalid program or out of memory\n");
return -1;
}
char output[1<<10];
int outlen = 0;
for (bool done = false; !done; ) {
WL_EvalResult res = wl_runtime_eval(rt);
switch (res.type) {
case WL_EVAL_NONE:
break;
case WL_EVAL_DONE:
done = true;
break;
case WL_EVAL_ERROR:
printf("Error: %s (test at line %d)\n", wl_runtime_error(rt).ptr, test_line);
return 0;
case WL_EVAL_OUTPUT:
if ((int) sizeof(output) - outlen < res.str.len) {
printf("Error: Output is too long\n");
return -1;
}
memcpy(output + outlen, res.str.ptr, res.str.len);
outlen += res.str.len;
break;
case WL_EVAL_SYSVAR:
break;
case WL_EVAL_SYSCALL:
break;
}
}
//printf("[%s] -> [%.*s]\n", in, outlen, output);
if (outlen != strlen(out) || memcmp(out, output, outlen)) {
fprintf(stderr, "Error: Output mistmatch\n");
fprintf(stderr, " Source : [%s]\n", in);
fprintf(stderr, " Output : [%.*s]\n", outlen, output);
fprintf(stderr, " Expected: [%s]\n", out);
#if 1
fprintf(stderr, " Program:\n");
char buf[1<<10];
int len = wl_dump_program(program, buf, sizeof(buf));
if (len < 0)
fprintf(stderr, " (Invalid program)\n");
else if (len >= sizeof(buf)) {
fprintf(stderr, " (Program disassembly is too big)\n");
} else {
buf[len] = '\0';
fprintf(stderr, "%s", buf);
}
#endif
return 0;
}
return 1;
}
int main(void)
{
int cap = 1<<20;
char *mem = malloc(cap);
if (mem == NULL)
return -1;
for (int i = 0; i < COUNT(tests); i++)
run_test(tests[i].in, tests[i].out, mem, cap, tests[i].line);
free(mem);
return 0;
}