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

postfix-haskell

v0.1.5

Published

A very low-level functional programming language designed to compile directly to WebAssembly

Downloads

32

Readme

postfix-haskell

A very low-level functional programming language designed to compile to WebAssembly in the browser. The language actually bears very little resemblance to Haskell despite the name.

How to use

The all examples can be run in an interactive shell like below. Alternatively you can install the more stable npm package). For better examples check out the recently edited files in the planning/* folder. Also note the standard library in /std/*.

$ git clone https://github.com/dvtate/postfix-haskell
$ cd postfix-haskell
$ npm run build
$ npm install --global
$ phc shell
> "lang" require use
> 1 2 + :data
:data - 3n

Utilities

The compiler now has a main program that lets you use it through the command line. This can be done by first doing npm run build and then either run it locally as node dist/index.js or install it to your machine via npm install --global so that you can use it via phc.

[postfix-haskell]$ npm run build
[postfix-haskell]$ sudo npm install --global
[postfix-haskell]$ phc --help
phc <command> [args]

Commands:
  phc shell [options]        run interactive shell                     [default]
  phc file <name> [options]  compile a file to WAT

Options:
      --version  Show version number                                   [boolean]
  -v, --verbose  include verbose output               [boolean] [default: false]
      --help     Show help                                             [boolean]
  -l, --lex      debug lexer tokens                   [boolean] [default: false]

Shell

The shell is probably the best way to learn the language, allowing you to run short bits of code and test expected compiler behavior. In addition to normal code there exist some compiler macros that make debugging easier you can find these in lib/debug_macros.ts, for example:

[postfix-haskell]$ npm start
> "lang" require use
> 1 2 + :data
:data - 3n

> 1 2 + :type
:type - {
  syntaxType: 'Data',
  datatype: PrimitiveType { token: undefined, name: 'i32' }
}

> ( I32 ) (: 1 + ) "incr" export :compile
:compile - (module
  (func (;0;) (param i32) (result i32)
    local.get 0
    i32.const 1
    i32.add)
  (export "incr" (func 0))
  (memory (;0;) 1)
  (export "memory" (memory 0))
  (data (;0;) (i32.const 0) "")
  (type (;0;) (func (param i32) (result i32))))

File

For compiling a file to WASM Text format with expectation of errors. Once you know it compiles you can use tools/optimized.sh to get an optimized binary

phc file <name> [options]

compile a file to WAT

Positionals:
  name  name of the file to open                             [string] [required]

Options:
      --version     Show version number                                [boolean]
  -v, --verbose     include verbose output            [boolean] [default: false]
      --help        Show help                                          [boolean]
  -t, --track-time  track time spent compiling         [boolean] [default: true]
      --fast        skip validation and pretty-print steps
                                                      [boolean] [default: false]
      --folding     use folding/s-expr WAT syntax     [boolean] [default: false]
  -O, --optimize    pass compiled output through binaryen optimizer
                                                                [default: false]

Optimized.sh

Compile given file using tools/file.ts and then pass it's output through wasm-opt from binaryen. Flags passed at the end are passed to wasm-opt, defaulting to -O

[postfix-haskell]$ ./tools/optimized.sh ./planning/sqrt.phs -Oz
(module ... )

Inline

You can embed the language in JavaScript or TypeScript. See a demo in planning/inline.ts.

Quick intro to language

  • functional: immutable variables defined via = operator
  • postfix: operators follow the operands they act on (ie - 1 2 +)
    • the stack (place where expressions are put) is a compile-time abstraction, and values stored on it often aren't included in the compiled code
  • All the below examples assume you've imported the standard library which contains many basic operators like + and &&.
    • to import it use "/[path to this repo]/postfix-haskell/std/lang.phs" require use

Identifiers

  • Escaped identifiers (ie - $name) can be used to store any type of value
  • Unescaped identifiers will invoke stored value, either running it in place or pushing it's value onto the stack
  • There are two operators which act on identifiers:
    • @ : this is the same as unecaping the identifier, it will invoke values
    • ~ : this will unescape the the value but will not invoke it
# equiv to `let a = 4 * (1 + 2)`
1 2 + 4 * $a =

# Prints `:data - 12` at compile time
a :data

Macros

  • Macros are equivalent to functions in other languages
  • Conceptually similar to executable arrays in postscript
  • When invoked, compiler will do as needed to make them run in place
# Macro that returns the next number
# Notice we specified optional type annotations
((I32)(I32): 1 + ) $incr =

# :data - 6
5 incr :data
5 1 +  :data
  • macros aren't functions however and aren't limited to a single return value
# (a,b)->(b,a)
(: ( $a $b ) = b a ) $swap =

# (a)->()
(: $_ = ) $pop =

# ()->(a,b)
# Notice the optional type annotations
(()(F64 F64): 1.0 2.0 ) $nums =

nums / :data # 0.5
nums swap / :data # 2
nums pop :data # 1

Notice that if we want to get the macro stored in a variable we cannot simply unescape it, and must use the ~ operator.

(: $op = 1 2 op ) $apply_operator =
(: + 2 *) $add_and_double =

# This is valid
$add_and_double ~ apply_operator :data # 6

# This is wrong and will not compile
add_and_double apply_operator :data

Branching

  • Functions are like macros but overloadable with conditions
  • Functions are the only way to do branching in the language
  • When invoked, the conditions are checked until reaching one which is truthy
  • The truthy branch is taken
  • If there are no possible branches the compiler will error
  • they syntax for functions is { condition } { action } $identifier fun
# Use bigger of two values

# Here we're checking a runtime condition
(: true ) (: pop ) $max fun
(: < ) (: ( $a $b ) = b ) $max fun

# Here we're checking a compile-time condition
((F32): 1 ) (: "f32.max" asm ) $max fun

# This does basically the same as before but for F64
(: type F64 == ) (: "f64.max" asm ) $max fun

# This takes the F64 branch
1.0 30.1 max :data # 30.1

# This takes the (: < ) branch
# Condition: 30 gets promoted to 30.0 causing it to take the branch
# Action: the other 30 remains on the stack as the result
1.2 30 max :data # 30

Namespaces and Modules

  • Namespaces are used to organize identifiers
  • Namespaces are made using the namespace keyword
  • To use an identifier stored in a namespace simply add a . between the namespace identifier and the identifier to access.
  • The global namespace is available at all scopes
  • The use keyword applies namespaces identifiers to current scope
  • Modules are imported using the require keyword which gives a namespace
  • the export keyword
# Import basic language features such as `+` and `-` among other things
# And apply it to the current scope
"./std/lang.phs" require use

# Create a namespace 'ns'
(:
    5 $five =
    (: + ) $add =
) namespace $ns =

10 $five =

( global.I32 ) (:
    # Get 'five' from the namespace
    ns.five

    # Invoke 'add' from the namespace
    ns.add

    five -
) "demo" export

Syntactic Types

TODO

Should to add to guide

  • Complete list of operators
  • List of data and syntactic types
  • namespaces elaborate on modules
  • recursion