write a reader
A reader is a frontend: source text in, shared AST out. Wrap it around lang’s kernel and you get a native compiler for your syntax.
$ git clone \
https://github.com/ehrlich-b/lang
$ cd lang && make init
the 20-line reader
// tiny.lang: `answer 42` becomes a native program.
include "std/tok.lang"
include "std/ast.lang"
reader tiny(text *u8) *u8 {
var tokens *Tokenizer = tok_new(text);
if tok_kind(tokens) != TOK_IDENT || !streq(tok_text(tokens), "answer") {
eprintln("tiny: expected `answer NUMBER`");
return nil;
}
tok_next(tokens);
if tok_kind(tokens) != TOK_NUMBER {
eprintln("tiny: expected a number");
return nil;
}
var body *u8 = ast_block1(ast_return(ast_number(tok_text(tokens))));
return ast_program1(ast_func("main", ast_vec(), ast_type_i64(), body));
}
Compile and run the checked-in example:
$ ./out/lang run example/tiny/tiny.lang example/tiny/answer.tiny
$ echo $?
42
mint the compiler
$ ./out/lang compiler tiny example/tiny/tiny.lang -o tinyc
$ ./tinyc example/tiny/answer.tiny -o answer.ll
tinyc is the compiler. Your reader supplies syntax and lowering;
the kernel supplies the type system, LLVM backend, native and wasm targets,
and the ABI shared with every other reader.
grow it
Next, read the
small precedence parser
and the
AST builder quick reference.
Use #parser{} for compact transactional grammars; use the
calculator’s manual pattern for precedence and recovery. The
parser guide
spells out that boundary. The
shipped C, Lisp, Forth, flow, and minipy readers are examples, not a closed list.