Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

my-lang

A small tree-walking interpreter, built from scratch in TypeScript. Lexer → parser → interpreter, with variables, control flow, functions, and closures.

Quick start

npm install

npm run run examples/demo.lang     # run a script file
npm run run 'print 1 + 2;'         # run inline source
npm run run                        # start the REPL
npm test                           # run the checkpoint test suite

Example

// FizzBuzz, closures, and recursion in one file.

fun fizzbuzz(n) {
  var i = 1;
  while (i <= n) {
    if (i % 3 == 0 and i % 5 == 0) {
      print "FizzBuzz";
    } else if (i % 3 == 0) {
      print "Fizz";
    } else if (i % 5 == 0) {
      print "Buzz";
    } else {
      print i;
    }
    i = i + 1;
  }
}

fun makeCounter() {
  var count = 0;
  fun increment() {
    count = count + 1;
    return count;
  }
  return increment;
}

fizzbuzz(15);

var counter = makeCounter();
print counter();  // 1
print counter();  // 2

Language reference

Variables & types — numbers, strings, booleans, nil. nil and false are falsy; everything else (including 0 and "") is truthy.

var x = 5;
var name = "hello";
var flag = true;
var nothing;        // implicitly nil

Operators

+ - * / %          // arithmetic (+ also concatenates strings)
== != < <= > >=     // comparison
and or !            // logical (and/or short-circuit)

Print & comments

print "hi " + name;
// line comment

Control flow{ } blocks create their own scope; shadowing a variable inside a block doesn't touch the outer one.

if (x > 0) { print "pos"; } else { print "non-pos"; }

var i = 0;
while (i < 5) { print i; i = i + 1; }

Functions & closures

fun add(a, b) { return a + b; }

fun makeCounter() {
  var count = 0;
  fun increment() { count = count + 1; return count; }
  return increment;
}

Recursion works normally. Each call gets its own environment, chained to the closure it was defined in — not the caller's — so closures capture correctly.

Project layout

src/
  token.ts         # TokenType enum + Token shape
  lexer.ts         # source string -> tokens
  ast.ts            # Expr / Stmt discriminated unions
  parser.ts         # tokens -> AST (recursive descent, panic-mode error recovery)
  environment.ts     # linked-scope variable storage
  interpreter.ts      # AST -> executed program
  lang-function.ts    # user-defined functions + closures
  values.ts            # runtime value types
  errors.ts             # ReturnSignal, ParseErrors, RuntimeError
  run.ts                 # CLI entry point (file / inline / REPL)
test/
  checkpoints.ts           # asserts stdout for every language feature
examples/
  demo.lang                 # FizzBuzz + closures + recursion

Not implemented (by design)

Arrays/lists, classes/objects, a standard library beyond print, string methods. Natural next steps from here: a bytecode VM (for speed) or a static type checker (for safety).

Requirements

Node.js, and typescript pinned to the 6.x line — ts-node isn't yet compatible with TypeScript 7's rewritten compiler internals.

About

A tree-walking interpreter built from scratch in TypeScript, lexer, recursive-descent parser, and evaluator supporting variables, control flow, functions, and closures.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages