npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@dhconnelly/parents

v2.0.1

Published

a bytecode vm and compiler for a minimal lisp

Downloads

4

Readme

parents

a bytecode vm and compiler for a minimal lisp

Why

to practice typescript: "paren" + "ts" === "parents"

Features

The minimal set needed for implementing cons lists (see examples/list.lisp) and the Church encoding (see examples/church.lisp) in user space using lambda caclulus. Plus console output and assertions.

  • simple types: integers, booleans (#t and #f), and nil
  • first-class functions with lexical scope and closures
  • special forms: if, lambda, define, seq, let
  • built-ins: +, -, *, <, =, nil, isnil, display, assert

For example, here's fibonacci:

(define (fib n)
    (if (< n 2)
        n
        (+ (rec (- n 1)) (rec (- n 2)))))

See the examples/ directory for more.

Status

Essentially complete. All the example programs compile and run correctly. There are closures and recursion and cons lists in user space and garbage collection.

Possible future work:

  • more unit tests
  • string support
  • add built-ins for list operations
  • better command-line interface
  • additional refactoring as I learn more TypeScript

Usage

To install globally:

npm install -g @dhconnelly/parents

For development, you can check out the code, build, run the tests, and link your local build:

git clone [email protected]:dhconnelly/parents.git
cd parents
npm install
npm test
npm link

Or, to install nothing, you can use npx @dhconnelly/parents in place of the parents command.

To compile some programs:

parents compile [file ...]

This will write file.bytecode in the same directory as each file passed as an argument. To run the generated bytecode:

parents vm [bytecode_file ...]

For example, to build and run all the examples from the examples/ directory:

parents compile examples/*.lisp
parents vm examples/*.lisp.bytecode

To disassemble the bytecode and see the VM instructions:

parents disasm [bytecode_file ...]

There's also a tree-walking interpreter, in case you don't want to compile:

parents run [file ...]

Implementation Details

There are two implementations:

  • a tree-walking interpreter that relies on the JavaScript VM to handle garbage collection (in src/interpreter)

  • a compiler (in src/compiler) that targets a stack-based virtual machine (see src/vm) which uses a naive mark-and-sweep garbage collection scheme (in src/vm/heap.ts).

Both implementations use a hand-written recursive-descent parser (see src/lexer.ts and src/parser.ts).

Both seq and let are implemented by desugaring to immediately-invoked lambda expressions. For simplicity, cons lists are not provided and can be implemented in user space using lambdas; see examples/list.lisp for an example.

Virtual Machine

Contains a stack, heap, and globals table. For simplicity, garbage collection (mark and sweep, with the stack and globals forming the root set) is potentially triggered only at instruction step time.

The supported instruction set is as follows:

  • Push <type> <value>: Push a literal value onto the stack

  • Pop: Pop the topmost value off the stack and discard it

  • Get <index>: Get a value from the stack and push it on top

  • DefGlobal: Pop the topmost stack element and add it as a global

  • GetGlobal <index>: Get the specified global and push it on the stack

  • Jmp <pc>: Jump to the given byte offset in the program and continue

  • JmpIf <pc>: Pop the topmost stack value and jump if it is true

  • MakeLambda <pc> <arity> <captures>: Allocate a lambda object representing a function that begins at byte offset <pc> with <arity> parameters onto the heap, with <captures> values popped off the stack and copied into the lambda. Pushes a pointer to the lambda on top of the stack.

  • Call <arity>: Pops a lambda pointer (as pushed by MakeLambda) from the stack and invokes it with <arity> values. Assumes the argument values are on the stack just below the lambda pointer. Pushes a pointer to the invoked lambda (to support recursive calls) and all the captures, then jumps to the byte offset <pc> in the lambda. The stack will therefore contain, once execution continues, [...args, fn_ptr, ...captures] and the top of the stack will point just after the captures. Saves the return address (one instr past the offset at which Call was executed).

  • Return: Pops and saves the top of the stack (the return value), pops all of the captures and recursive lambda pointer and arguments from the stack, pushes the return value back on top, updates the <pc> to the return address saved by Call, and continues execution.

For more details, see src/instr.ts (for the instruction definitions and serialization/deserialization to/from bytecode) and src/vm.ts for more implementation details. Runtime types and values support is found in src/values.ts (for types common to both the vm and the interpreter) and src/vm/values.ts.

Questions

Is this a good idea?

No. In particular, since the VM and garbage collector are implemented in TypeScript and run on Node.js, the V8 garbage collector can interrupt our own garbage collector to collect garbage. Also, since this is essentially JavaScript, a stray reference to a heap object can prevent cleanup.

Why is the code style all over the place?

This is my first TypeScript project. I was playing with the features and patterns. Exceptions and monadic error handling are intermingled, and in some places types vs. interfaces vs. classes are not used consistently or coherently. Sometimes there's more imperative looping, sometimes more forEach/map/etc. Not quite sure how to consistently structure discriminated unions. And so on.

Who

Daniel Connelly [email protected] (https://dhconnelly.com)

License

MIT License. Copyright (c) 2021 Daniel Connelly