Skip to content

Commit 3f5223a

Browse files
authored
Add minimal example of a web platform (#134)
This demonstrates one example of a platform that does not provide an APIs for Scrapscript to call but instead calls the function that a scrap returns.
1 parent 6ee44fe commit 3f5223a

File tree

7 files changed

+2192
-51
lines changed

7 files changed

+2192
-51
lines changed

cli.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
int main() {
2+
heap = make_heap(MEMORY_SIZE);
3+
HANDLES();
4+
GC_HANDLE(struct object*, result, scrap_main());
5+
println(result);
6+
destroy_heap(heap);
7+
return 0;
8+
}

compiler.py

Lines changed: 49 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import os
77
import typing
88

9-
from typing import Dict, Optional
9+
from typing import Dict, Optional, Tuple
1010

1111
from scrapscript import (
1212
Access,
@@ -63,16 +63,35 @@ def __init__(self, main_fn: CompiledFunction) -> None:
6363
self.functions: typing.List[CompiledFunction] = [main_fn]
6464
self.function: CompiledFunction = main_fn
6565
self.record_keys: Dict[str, int] = {}
66+
self.record_builders: Dict[Tuple[str, ...], CompiledFunction] = {}
6667
self.variant_tags: Dict[str, int] = {}
6768
self.debug: bool = False
68-
self.used_runtime_functions: set[str] = set()
6969

70-
def record_key(self, key: str) -> int:
71-
result = self.record_keys.get(key)
72-
if result is not None:
73-
return result
74-
result = self.record_keys[key] = len(self.record_keys)
75-
return result
70+
def record_key(self, key: str) -> str:
71+
if key not in self.record_keys:
72+
self.record_keys[key] = len(self.record_keys)
73+
return f"Record_{key}"
74+
75+
def record_builder(self, keys: Tuple[str, ...]) -> CompiledFunction:
76+
builder = self.record_builders.get(keys)
77+
if builder is not None:
78+
return builder
79+
80+
builder = CompiledFunction(f"Record_builder_{'_'.join(keys)}", list(keys))
81+
self.functions.append(builder)
82+
cur = self.function
83+
self.function = builder
84+
85+
result = self._mktemp(f"mkrecord(heap, {len(keys)})")
86+
for i, key in enumerate(keys):
87+
key_idx = self.record_key(key)
88+
self._emit(f"record_set({result}, /*index=*/{i}, (struct record_field){{.key={key_idx}, .value={key}}});")
89+
self._debug("collect(heap);")
90+
self._emit(f"return {result};")
91+
92+
self.function = cur
93+
self.record_builders[keys] = builder
94+
return builder
7695

7796
def variant_tag(self, key: str) -> int:
7897
result = self.variant_tags.get(key)
@@ -295,11 +314,6 @@ def compile(self, env: Env, exp: Object) -> str:
295314
raise NameError(f"name '{exp.name}' is not defined")
296315
return var_value
297316
if isinstance(exp, Apply):
298-
if isinstance(exp.func, Var):
299-
if exp.func.name == "runtime":
300-
assert isinstance(exp.arg, String)
301-
self.used_runtime_functions.add(exp.arg.value)
302-
return f"builtin_{exp.arg.value}"
303317
callee = self.compile(env, exp.func)
304318
arg = self.compile(env, exp.arg)
305319
return self._mktemp(f"closure_call({callee}, {arg})")
@@ -314,14 +328,9 @@ def compile(self, env: Env, exp: Object) -> str:
314328
values: Dict[str, str] = {}
315329
for key, value_exp in exp.data.items():
316330
values[key] = self.compile(env, value_exp)
317-
result = self._mktemp(f"mkrecord(heap, {len(values)})")
318-
for i, (key, value) in enumerate(values.items()):
319-
key_idx = self.record_key(key)
320-
self._emit(
321-
f"record_set({result}, /*index=*/{i}, (struct record_field){{.key={key_idx}, .value={value}}});"
322-
)
323-
self._debug("collect(heap);")
324-
return result
331+
keys = tuple(sorted(exp.data.keys()))
332+
builder = self.record_builder(keys)
333+
return self._mktemp(f"{builder.name}({', '.join(values[key] for key in keys)})")
325334
if isinstance(exp, Access):
326335
assert isinstance(exp.at, Var), f"only Var access is supported, got {type(exp.at)}"
327336
record = self.compile(env, exp.obj)
@@ -345,11 +354,6 @@ def compile(self, env: Env, exp: Object) -> str:
345354
# The const heap will never be scanned
346355
# The const heap can be serialized to disk and mmap'd
347356

348-
BUILTINS = [
349-
"print",
350-
"println",
351-
]
352-
353357

354358
def env_get_split(key: str, default: Optional[typing.List[str]] = None) -> typing.List[str]:
355359
import shlex
@@ -362,7 +366,7 @@ def env_get_split(key: str, default: Optional[typing.List[str]] = None) -> typin
362366
return []
363367

364368

365-
def compile_to_string(source: str, memory: int, debug: bool) -> str:
369+
def compile_to_string(source: str, debug: bool) -> str:
366370
program = parse(tokenize(source))
367371

368372
main_fn = CompiledFunction("scrap_main", params=[])
@@ -371,20 +375,20 @@ def compile_to_string(source: str, memory: int, debug: bool) -> str:
371375
result = compiler.compile({}, program)
372376
main_fn.code.append(f"return {result};")
373377

374-
builtins = [builtin for builtin in BUILTINS if builtin in compiler.used_runtime_functions]
375-
for builtin in builtins:
376-
fn = CompiledFunction(f"builtin_{builtin}_wrapper", params=["this", "arg"])
377-
fn.code.append(f"return {builtin}(arg);")
378-
compiler.functions.append(fn)
379-
380378
f = io.StringIO()
381-
print('#include "runtime.c"', file=f)
379+
with open("runtime.c", "r") as runtime:
380+
print(runtime.read(), file=f)
382381
print("#define OBJECT_HANDLE(name, exp) GC_HANDLE(struct object*, name, exp)", file=f)
383382
# Declare all functions
384383
print("const char* record_keys[] = {", file=f)
385384
for key in compiler.record_keys:
386385
print(f'"{key}",', file=f)
387386
print("};", file=f)
387+
if compiler.record_keys:
388+
print("enum {", file=f)
389+
for key, idx in compiler.record_keys.items():
390+
print(f"Record_{key} = {idx},", file=f)
391+
print("};", file=f)
388392
if compiler.variant_tags:
389393
print("const char* variant_names[] = {", file=f)
390394
for key in compiler.variant_tags:
@@ -399,23 +403,11 @@ def compile_to_string(source: str, memory: int, debug: bool) -> str:
399403
print("const char* variant_names[] = { NULL };", file=f)
400404
for function in compiler.functions:
401405
print(function.decl() + ";", file=f)
402-
for builtin in builtins:
403-
print(f"struct object* builtin_{builtin} = NULL;", file=f)
404406
for function in compiler.functions:
405407
print(f"{function.decl()} {{", file=f)
406408
for line in function.code:
407409
print(line, file=f)
408410
print("}", file=f)
409-
print("int main() {", file=f)
410-
print(f"heap = make_heap({memory});", file=f)
411-
print("HANDLES();", file=f)
412-
for builtin in builtins:
413-
print(f"builtin_{builtin} = mkclosure(heap, builtin_{builtin}_wrapper, 0);", file=f)
414-
print(f"GC_PROTECT(builtin_{builtin});", file=f)
415-
print(f"struct object* result = {main_fn.name}();", file=f)
416-
print("println(result);", file=f)
417-
print("destroy_heap(heap);", file=f)
418-
print("}", file=f)
419411
return f.getvalue()
420412

421413

@@ -433,18 +425,18 @@ def discover_cflags(cc: typing.List[str], debug: bool = True) -> typing.List[str
433425

434426
def compile_to_binary(source: str, memory: int, debug: bool) -> str:
435427
import shlex
436-
import shutil
437428
import subprocess
438429
import sysconfig
439430
import tempfile
440431

441432
cc = env_get_split("CC", shlex.split(sysconfig.get_config_var("CC")))
442433
cflags = discover_cflags(cc, debug)
443-
c_code = compile_to_string(source, memory, debug)
434+
cflags += [f"-DMEMORY_SIZE={memory}"]
435+
c_code = compile_to_string(source, debug)
444436
with tempfile.NamedTemporaryFile(mode="w", suffix=".c", delete=False) as c_file:
445-
outdir = os.path.dirname(c_file.name)
446-
shutil.copy("runtime.c", outdir)
447437
c_file.write(c_code)
438+
with open("cli.c", "r") as f:
439+
c_file.write(f.read())
448440
with tempfile.NamedTemporaryFile(mode="w", suffix=".out", delete=False) as out_file:
449441
subprocess.run([*cc, *cflags, "-o", out_file.name, c_file.name], check=True)
450442
return out_file.name
@@ -461,15 +453,20 @@ def main() -> None:
461453
parser.add_argument("--memory", type=int, default=1024)
462454
parser.add_argument("--run", action="store_true")
463455
parser.add_argument("--debug", action="store_true", default=False)
456+
parser.add_argument("--platform", default="cli.c")
464457
args = parser.parse_args()
465458

466459
with open(args.file, "r") as f:
467460
source = f.read()
468461

469-
c_program = compile_to_string(source, args.memory, args.debug)
462+
c_program = compile_to_string(source, args.debug)
463+
464+
with open(args.platform, "r") as f:
465+
platform = f.read()
470466

471467
with open(args.output, "w") as f:
472468
f.write(c_program)
469+
f.write(platform)
473470

474471
if args.format:
475472
import subprocess
@@ -481,6 +478,7 @@ def main() -> None:
481478

482479
cc = env_get_split("CC", ["clang"])
483480
cflags = discover_cflags(cc, args.debug)
481+
cflags += [f"-DMEMORY_SIZE={args.memory}"]
484482
ldflags = env_get_split("LDFLAGS")
485483
subprocess.run([*cc, "-o", "a.out", *cflags, args.output, *ldflags], check=True)
486484

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
| "/" -> (status 200 <| page "you're on the index")
2+
| "/about" -> (status 200 <| page "you're on the about page")
3+
| _ -> notfound
4+
5+
. notfound = (status 404 <| page "not found")
6+
. status = code -> body -> { code = code, body = body }
7+
. page = body -> "<!doctype html><html><body>" ++ body ++ "</body></html>"
8+

examples/11_platforms/web/web.c

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include <string.h>
2+
#include <stddef.h>
3+
#include <stdlib.h>
4+
#define WBY_IMPLEMENTATION
5+
#include "web.h"
6+
7+
// $ CFLAGS="-I examples/11_platforms/web/" ./compiler.py --compile --platform examples/11_platforms/web/web.c examples/11_platforms/web/handler.scrap
8+
// or
9+
// $ ./compiler.py --platform examples/11_platforms/web/web.c examples/11_platforms/web/handler.scrap
10+
// $ cc -I examples/11_platforms/web/ output.c
11+
12+
static int
13+
dispatch(struct wby_con *connection, void *userdata)
14+
{
15+
HANDLES();
16+
GC_HANDLE(struct object*, handler, *(struct object**)userdata);
17+
GC_HANDLE(struct object*, url, mkstring(heap, connection->request.uri, strlen(connection->request.uri)));
18+
GC_HANDLE(struct object*, response, closure_call(handler, url));
19+
assert(is_record(response));
20+
GC_HANDLE(struct object*, code, record_get(response, Record_code));
21+
assert(is_num(code));
22+
GC_HANDLE(struct object*, body, record_get(response, Record_body));
23+
assert(is_string(body));
24+
25+
wby_response_begin(connection, num_value(code), string_length(body), NULL, 0);
26+
// TODO(max): Copy into buffer or strdup
27+
wby_write(connection, as_heap_string(body)->data, string_length(body));
28+
wby_response_end(connection);
29+
fprintf(stderr, "%ld %s\n", num_value(code), connection->request.uri);
30+
return num_value(code) == 200;
31+
}
32+
33+
int main(int argc, const char * argv[])
34+
{
35+
/* boot scrapscript */
36+
heap = make_heap(MEMORY_SIZE);
37+
HANDLES();
38+
GC_HANDLE(struct object*, handler, scrap_main());
39+
assert(is_closure(handler));
40+
41+
/* setup config */
42+
struct wby_config config;
43+
memset(&config, 0, sizeof(config));
44+
config.address = "0.0.0.0";
45+
config.port = 8000;
46+
config.connection_max = 8;
47+
config.request_buffer_size = 2048;
48+
config.io_buffer_size = 8192;
49+
config.dispatch = dispatch;
50+
config.userdata = &handler;
51+
52+
/* compute and allocate needed memory and start server */
53+
struct wby_server server;
54+
size_t needed_memory;
55+
wby_init(&server, &config, &needed_memory);
56+
void *memory = calloc(needed_memory, 1);
57+
printf("serving at http://%s:%d\n", config.address, config.port);
58+
wby_start(&server, memory);
59+
while (1) {
60+
wby_update(&server);
61+
}
62+
wby_stop(&server);
63+
free(memory);
64+
}
65+

0 commit comments

Comments
 (0)