← lang

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.