Full Code of Storyyeller/cubiml-demo for AI

master d1cc73b05bbd cached
20 files
118.5 KB
30.0k tokens
162 symbols
1 requests
Download .txt
Repository: Storyyeller/cubiml-demo
Branch: master
Commit: d1cc73b05bbd
Files: 20
Total size: 118.5 KB

Directory structure:
gitextract_6axljtt2/

├── .gitignore
├── Cargo.toml
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
├── demo.html
├── demo.js
├── rustfmt.toml
├── src/
│   ├── ast.rs
│   ├── codegen.rs
│   ├── core.rs
│   ├── grammar.lalr
│   ├── js.rs
│   ├── lib.rs
│   ├── main.rs
│   ├── reachability.rs
│   ├── spans.rs
│   ├── typeck.rs
│   └── utils.rs
└── tests/
    └── web.rs

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
/target
**/*.rs.bk
Cargo.lock
bin/
pkg/
wasm-pack.log

local.html
src/grammar.rs

test*.ml
perf.*
*.svg


================================================
FILE: Cargo.toml
================================================
[package]
name = "cubiml-demo"
version = "0.0.0"
authors = ["Robert Grosse <n210241048576@gmail.com>"]
license = "Apache-2.0/MIT"
edition = "2018"

[lib]
crate-type = ["cdylib", "rlib"]

[features]
default = ["console_error_panic_hook"]

[dependencies]
lalrpop-util = { version = "0.20.2", features = ["lexer"] }
wasm-bindgen = "0.2.63"

# The `console_error_panic_hook` crate provides better debugging of panics by
# logging them with `console.error`. This is great for development, but requires
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
# code size when deploying.
console_error_panic_hook = { version = "0.1.6", optional = true }

[dev-dependencies]
wasm-bindgen-test = "0.3.13"

[profile.release]
# Tell `rustc` to optimize for small code size.
opt-level = "s"


================================================
FILE: LICENSE_APACHE
================================================
                              Apache License
                        Version 2.0, January 2004
                     http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

   "License" shall mean the terms and conditions for use, reproduction,
   and distribution as defined by Sections 1 through 9 of this document.

   "Licensor" shall mean the copyright owner or entity authorized by
   the copyright owner that is granting the License.

   "Legal Entity" shall mean the union of the acting entity and all
   other entities that control, are controlled by, or are under common
   control with that entity. For the purposes of this definition,
   "control" means (i) the power, direct or indirect, to cause the
   direction or management of such entity, whether by contract or
   otherwise, or (ii) ownership of fifty percent (50%) or more of the
   outstanding shares, or (iii) beneficial ownership of such entity.

   "You" (or "Your") shall mean an individual or Legal Entity
   exercising permissions granted by this License.

   "Source" form shall mean the preferred form for making modifications,
   including but not limited to software source code, documentation
   source, and configuration files.

   "Object" form shall mean any form resulting from mechanical
   transformation or translation of a Source form, including but
   not limited to compiled object code, generated documentation,
   and conversions to other media types.

   "Work" shall mean the work of authorship, whether in Source or
   Object form, made available under the License, as indicated by a
   copyright notice that is included in or attached to the work
   (an example is provided in the Appendix below).

   "Derivative Works" shall mean any work, whether in Source or Object
   form, that is based on (or derived from) the Work and for which the
   editorial revisions, annotations, elaborations, or other modifications
   represent, as a whole, an original work of authorship. For the purposes
   of this License, Derivative Works shall not include works that remain
   separable from, or merely link (or bind by name) to the interfaces of,
   the Work and Derivative Works thereof.

   "Contribution" shall mean any work of authorship, including
   the original version of the Work and any modifications or additions
   to that Work or Derivative Works thereof, that is intentionally
   submitted to Licensor for inclusion in the Work by the copyright owner
   or by an individual or Legal Entity authorized to submit on behalf of
   the copyright owner. For the purposes of this definition, "submitted"
   means any form of electronic, verbal, or written communication sent
   to the Licensor or its representatives, including but not limited to
   communication on electronic mailing lists, source code control systems,
   and issue tracking systems that are managed by, or on behalf of, the
   Licensor for the purpose of discussing and improving the Work, but
   excluding communication that is conspicuously marked or otherwise
   designated in writing by the copyright owner as "Not a Contribution."

   "Contributor" shall mean Licensor and any individual or Legal Entity
   on behalf of whom a Contribution has been received by Licensor and
   subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of
   this License, each Contributor hereby grants to You a perpetual,
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
   copyright license to reproduce, prepare Derivative Works of,
   publicly display, publicly perform, sublicense, and distribute the
   Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of
   this License, each Contributor hereby grants to You a perpetual,
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
   (except as stated in this section) patent license to make, have made,
   use, offer to sell, sell, import, and otherwise transfer the Work,
   where such license applies only to those patent claims licensable
   by such Contributor that are necessarily infringed by their
   Contribution(s) alone or by combination of their Contribution(s)
   with the Work to which such Contribution(s) was submitted. If You
   institute patent litigation against any entity (including a
   cross-claim or counterclaim in a lawsuit) alleging that the Work
   or a Contribution incorporated within the Work constitutes direct
   or contributory patent infringement, then any patent licenses
   granted to You under this License for that Work shall terminate
   as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the
   Work or Derivative Works thereof in any medium, with or without
   modifications, and in Source or Object form, provided that You
   meet the following conditions:

   (a) You must give any other recipients of the Work or
       Derivative Works a copy of this License; and

   (b) You must cause any modified files to carry prominent notices
       stating that You changed the files; and

   (c) You must retain, in the Source form of any Derivative Works
       that You distribute, all copyright, patent, trademark, and
       attribution notices from the Source form of the Work,
       excluding those notices that do not pertain to any part of
       the Derivative Works; and

   (d) If the Work includes a "NOTICE" text file as part of its
       distribution, then any Derivative Works that You distribute must
       include a readable copy of the attribution notices contained
       within such NOTICE file, excluding those notices that do not
       pertain to any part of the Derivative Works, in at least one
       of the following places: within a NOTICE text file distributed
       as part of the Derivative Works; within the Source form or
       documentation, if provided along with the Derivative Works; or,
       within a display generated by the Derivative Works, if and
       wherever such third-party notices normally appear. The contents
       of the NOTICE file are for informational purposes only and
       do not modify the License. You may add Your own attribution
       notices within Derivative Works that You distribute, alongside
       or as an addendum to the NOTICE text from the Work, provided
       that such additional attribution notices cannot be construed
       as modifying the License.

   You may add Your own copyright statement to Your modifications and
   may provide additional or different license terms and conditions
   for use, reproduction, or distribution of Your modifications, or
   for any such Derivative Works as a whole, provided Your use,
   reproduction, and distribution of the Work otherwise complies with
   the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise,
   any Contribution intentionally submitted for inclusion in the Work
   by You to the Licensor shall be under the terms and conditions of
   this License, without any additional terms or conditions.
   Notwithstanding the above, nothing herein shall supersede or modify
   the terms of any separate license agreement you may have executed
   with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade
   names, trademarks, service marks, or product names of the Licensor,
   except as required for reasonable and customary use in describing the
   origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or
   agreed to in writing, Licensor provides the Work (and each
   Contributor provides its Contributions) on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
   implied, including, without limitation, any warranties or conditions
   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
   PARTICULAR PURPOSE. You are solely responsible for determining the
   appropriateness of using or redistributing the Work and assume any
   risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory,
   whether in tort (including negligence), contract, or otherwise,
   unless required by applicable law (such as deliberate and grossly
   negligent acts) or agreed to in writing, shall any Contributor be
   liable to You for damages, including any direct, indirect, special,
   incidental, or consequential damages of any character arising as a
   result of this License or out of the use or inability to use the
   Work (including but not limited to damages for loss of goodwill,
   work stoppage, computer failure or malfunction, or any and all
   other commercial damages or losses), even if such Contributor
   has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing
   the Work or Derivative Works thereof, You may choose to offer,
   and charge a fee for, acceptance of support, warranty, indemnity,
   or other liability obligations and/or rights consistent with this
   License. However, in accepting such obligations, You may act only
   on Your own behalf and on Your sole responsibility, not on behalf
   of any other Contributor, and only if You agree to indemnify,
   defend, and hold each Contributor harmless for any liability
   incurred by, or claims asserted against, such Contributor by reason
   of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS


================================================
FILE: LICENSE_MIT
================================================
Copyright (c) 2020 Robert Grosse <n210241048576@gmail.com>

Permission is hereby granted, free of charge, to any
person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the
Software without restriction, including without
limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software
is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice
shall be included in all copies or substantial portions
of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================

Cubiml is a simple ML-like programming language with subtyping and full type inference. You can try it out online in your browser [here](https://storyyeller.github.io/cubiml-demo/demo.html). Cubiml uses _cubic biunification_, a faster and simpler type inference algorithm based on [Algebraic Subtyping](https://www.cs.tufts.edu/~nr/cs257/archive/stephen-dolan/thesis.pdf). Cubiml is not intended to be used in its own right, but rather to serve as a tutorial for implementing cubic biunification, and therefore has a deliberately minimal feature set. 

## Usage

You can try out cubiml online in your browser at https://storyyeller.github.io/cubiml-demo/demo.html. 

## A quick tour of cubiml

Cubiml syntax is mostly a subset of Ocaml syntax.

#### Conditionals

In cubiml `if` is an expression, not a statement. The general form is `if <expr> then <expr> else <expr>` where the `<expr>`s are sub-expressions. The first expression is evaluated, and depending on whether it is true or false, one of the other two subexpressions is evaluated, and the result of the `if` expression is that expression's value. For example, evaluating `if false then "Hello" else "World"` would result in `"World"`. You can think of this as similar to the ternary operator (`a ? b : c`) present in some programming languages.

#### Records and fields

Records are a grouping of zero or more named values similar to "objects" or "structs" in other programming languages and are defined via `{name1=val1; name2=val2; ...}`. You can access the value of a field using the usual `.name` syntax. For example `{a=true; b="Hello"; c={}}.b` would evaluate to `"Hello"`.

There is a special shorthand syntax for fields with the same name as their value - `{a; b; c=4}` is equivalent to `{a=a; b=b; c=4}`.

Unlike in Ocaml, records are structurally typed. In fact, Cubiml has *only* structural types, with no named types.

#### Functions

In cubiml, all functions are required to take exactly one argument for simplicity. They are defined by `fun <arg> -> <expr>`. For example, a function which returns its argument unchanged could be written as `fun x -> x`. Functions are called by simply suffixing an argument, i.e. writing `a b` where `a` is the function to be called and `b` is the argument. For example 

    (fun b -> if b then "Hello" else "World") true

would evaluate to `"Hello"`, while 

    (fun x -> x.foo) {bar=false; foo="Bob"}

would evaluate to `"Bob"`. 


You can work around the one-argument limitation and simulate multiple function arguments by passing in a record. For example, instead of 

```js
function sum(a, b) {
    return a + b;
}

sum(7, 8)
```

in cubiml, you can do

```ocaml
let sum = fun args -> args.a + args.b;

sum {a=7; b=8}
```

In fact, you can simplify this further using destructuring patterns:

```ocaml
let sum = fun {a; b} -> a + b;

sum {a=7; b=8}
```

Unlike in Ocaml, `a b c` is parsed as `a (b c)` rather than `(a b) c`. Since all functions and variants have exactly one argument in Cubiml, this behavior is much more convenient than the Ocaml behavior.

#### Let bindings

No programming language would be complete without the ability to bind values to a variable name for later reference. In cubiml, this is done slightly differently than you may be used to. The general format is `let <name> = <expr1> in <expr2>`, where the variable `<name>` is visible in the body of `<expr2>`. The entire thing is an expression which evaluates to whatever `<expr2>` evaluates to.

For example,
```ocaml
let x = 7 * 8 in {foo=x; bar=x}     
```
would evaluate to `{foo=56; bar=56}`.

Let bindings can of course be nested like any other expression. For example, 
```ocaml
let x = 3 + 4 in
    let y = x * 2 in
        {x=x; y=y}
```
would evaluate to `{x=7; y=14}`.


This provides an equivalent to traditional imperative style code like the following that you might see in other languages

```js
let x = 3 + 4;
let y = x * 2;
return {x=x, y=y};
```

Unlike in OCaml, you can *also* use semicolons rather than "in". `let x = 4; x + x` and `let x = 4 in x + x` are exactly equivalent except that "in" has higher precendence than ";". In most cases, you will need to wrap your code in parenthesis or `begin`/`end` when using semicolons.

Therefore, the previous example could also be written using semicolons like this:

```ocaml
begin
    let x = 3 + 4;
    let y = x * 2;
    {x=x; y=y}
end
```


Note that the above format produces an expression which can be used in any context where an expression is expected. Cubiml follows the ML philosophy that (nearly) everything is an expression, even conditionals, function definitions, variable bindings, and other things that are statements in some languages.

However, this style is inconvenient when  interactively entering code into a REPL ([Read Evaluate Print Loop](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop)), because it requires you to input the entire program at once. To handle this, cubiml allows an alternate non-expression format at the top level of your code. At the top level, you can omit the `in <expr>` part, in which case the let statement produces a global binding which is visible to all subsequent code. For example, here is a possible session of code entered into the cubiml REPL. 

```
>> let x = {}

{}

>> let y = {x=x; other=false}

{x={}; other=false}

>> let z = {other=y.other; foo={y=y; x=x}}

{other=false; foo={y={x={}; other=false}; x=...}}
```

You can also separate multiple top level definitions with semicolons if you are entering multiple items at once.

```
>> let a = z.foo.y; let b = true

true

>> if b then y else x

{x={}; other=false}
```

You can also use destructuring patterns in let assignments. For example, `let {a; b=c} = {a=3; b=4};` results in two variables, `a` set to 3 and `c` set to 4.


#### Recursive let bindings

Sometimes, one wishes to have functions that call themselves recursively. Unfortunately, this is impossible with the above constructs since plain let-expressions can only refer to variables that were already defined. 

In order to support recursion, cubiml offers _recursive let expressions_ which are defined via `let rec` and allow the definition of the variable to refer to itself. For example, you could define a recursive fibonacci function as follows:

```ocaml
let rec fib = fun x ->
    if x <= 1 then 
        1
    else
        fib(x - 1) + fib(x - 2)
```

In order to avoid code referring to variables that don't exist yet, the right hand side of `let rec` variable definitions is restricted to be a function definition.


#### Mutual recursion

The above syntax works for a single function that refers to itself, but in some cases, you may want to have multiple functions that each refer to each other. Unlike in the case with `let`, simply nesting `let rec`s won't work. Therefore, `let rec` allows _multiple_ variable bindings, separated by `and`. For example, you can define mutually recursive `even` and `odd` functions as follows:

```ocaml
let rec even = fun x -> if x == 0 then true else odd(x - 1)
    and odd = fun x -> if x == 0 then false else even(x - 1)
```

#### Case types and matching

Sometimes you need to make different decisions based on runtime data in a type safe manner. Cubiml supports this via _case types_, also known as _sum types_ or _enums_. Basically, the way they work is that you can wrap a value with a tag, and then later match against it. The match expression has branches that execute different code depending on the runtime value of the tag. Crucially, each match branch has access to the static type of the original wrapped value for that specific tag. You can think of it like a simpler, statically checked version of Java's visitor pattern or a giant switch statement on an union in C.

To wrap a value, prefix it with a grave (\`) character and an uppercase Tag. E.g. `` `Foo {hello="Hello"}``

You can later match on it like follows

```ocaml
let calculate_area = fun shape ->
    match shape with
        | `Circle v -> v.rad *. v.rad *. 3.1415926
        | `Rectangle v -> v.length *. v.height;

calculate_area `Circle {rad=6.7}
calculate_area `Rectangle {height=1.1; length=2.2}
```

Notice that within the Circle branch, the code can access the rad field, and within the Rectangle branch, it can access the length and height field. Case types and matches let you essentially "unmix" distinct data types after they are mixed together in the program flow. Without case types, this would be impossible to do in a type safe manner.

#### Wildcard matches

Match expressions can optionally end with a wildcard match, which is the same as a regular case match except that it doesn't include a tag. The wildcard branch will be taken if the runtime tag of the matched value does not match any of the explicitly listed tags in the match expression.

```ocaml
let calculate_area = fun shape ->
    match shape with
        | `Circle v -> v.rad *. v.rad *. 3.1415926
        | `Rectangle v -> v.length *. v.height
        |  v -> "got something unexpected!"
```

Within a wildcard match, the bound variable has the same type as the input expression, except with the explicitly matched cases statically excluded. For example, in the `calculate_area` example above, `v` would have the type "same as `shape` except not a `Circle` or `Rectangle`".

This makes it possible to further match on the wildcard value elsewhere. For example, in the below code, the new `calculate_area2` function explicitly handles the `Square` case and otherwise defers to the previously defined function to handle the `Circle` and `Rectangle` cases. This works because the compiler knows that the `v` in the wildcard branch is not a `Square`, so it will not complain that the original `calculate_area` function fails to handle squares.

```ocaml
let calculate_area = fun shape ->
    match shape with
        | `Circle v -> v.rad *. v.rad *. 3.1415926
        | `Rectangle v -> v.length *. v.height;

let calculate_area2 = fun shape ->
    match shape with
        | `Square v -> v.len *. v.len
        |  v -> calculate_area v;

calculate_area2 `Circle {rad=6.7}
calculate_area2 `Square {len=9.17}
```

#### Literals 

Cubiml has boolean, int, float, string, and null literals. Integers are arbitrary precision and can't have leading zeros. Floating point literals must contain a decimal point, but the fraction part is optional. Strings are double quoted and use backslash escapes.

```ocaml
true;
false;
null;
0;
-213132;
999999999999999999999999999999999999999999999999;
8.213;
-1.;
-0.01e33;
7.e-77;
"";
"Hello world!";
"Quote -> \" backslash -> \\ Single quote -> \'"
```

#### Operators

Use `+, -, *, /, %` for integer math. For floating point math, add a `.` to the end of the operator, e.g. `7.5 /. 12.0`. String concatenation is `^`. 

`<, <=, >, >=` can compare both ints and floats. `==` and `!=` compare values of any type (values of different types compare unequal).

```ocaml
5 * 2 + 1;
-1 <= 0;
7.5 /. 12.0;
0 > 0.1;
9 == "what?";
"Hello," ^ " World!";
"" != {}
```

#### References

Cubiml supports mutability via ML-style references. You can simulate traditional mutable fields by storing a reference in a record field, and a mutable variable by storing a reference in a variable and so on.

ML references are not quite the same as what you may be used to. They are pointers to a mutable, garbage collected storage location on the heap and support three operations:

* `ref x` creates a new reference that initially holds the value `x`. Note that this _copies_ the value of `x` to a new location and returns a pointer to that location. `ref foo.bar` returns a pointer to a location that is initialized to the value of `foo.bar`, rather than a pointer to the field of `foo` itself.
* `!r` _dereferences_ the reference `r` and returns whatever value is currently stored inside. Note that this differs from the `!` operator in C style syntax.
* `r := x` _stores_ `x` inside the reference `r`, overwriting whatever value was previously stored there. Traditionally, this operation returns a unit value (i.e. empty record), but cubiml instead follows the approach of C-style assignments as Javascript does, where assignment returns the new value.

Note that references may be aliased. For example, we can create a reference `a` and copy it to the variable `b`. Then any changes made via `b` are visible via `a` and vice versa, as shown in the following REPL session.

```
>> let a = ref 42

ref 42

>> let b = a

ref 42

>> b := 77

77

>> !a

77
```

#### Record extension

When creating a record, you can optionally copy over all the fields from another record by writing `foo with` at the start of the record, where `foo` is the record you want to copy from.

```ocaml
let foo = {a=1; b=""; c=false};
let bar = {foo with a=true; d=-23}
```

The value you are copying fields from does not have to be a statically known value. It can be any arbitrary expression (as long as you surround it in parenthesis).

```
>> let baz = {(if foo.c then foo else bar) with c=true}

{a=true; b=""; c=true; d=-23}
```

#### Comments

Comments use `(* *)` and cannot be nested. 

```ocaml
(* define x = 4 *)
let x = 4;

let y = x + (
    (* 2 is an int *) 
    2
);

let z = (* let's define a new record! *) {
    (* a is a record field *)
    a = x;

    b = 
        (* comments can also go before expressions *) 
        "Hello world!";

    c = match `Some y with
        | `Some y -> y
        (* this match arm isn't actually reachable *)
        | `None _ -> _
}
```

### Type annotations

Expressions can manually be annotated with a type via `(expr : type)`, e.g. `(24 : int)` or `(fun x -> x : str -> str)`. Type annotations can be one of the following:

#### Base types

The primitive types are `bool`, `float`, `int`, `str`, `null`, `number`, `top`, `bot`. 

`number` represents a value that can be an `int` _or_ a `float`.

`top` is the supertype of all types. `bot` is the subtype of all types. It's impossible to do anything useful with a value of type `top`, while it is impossible to _create_ a value of type `bot`. 

#### Nullable types

`type?`

#### Functions types

`type -> type`

Function type arrows have the lowest precedence. For example `int -> int?` is parsed as `int -> (int?)`, i.e. a function that takes an int and returns an int or null. To represent a _function_ or null, you need to instead write `(int -> int)?`.

#### Reference types

`type ref`, `type readonly ref`, or `type writeonly ref`

#### Record types

Explicit list of fields:

`{field1: type1; field2: type2}`

Explicit list of fields plus any number of fields not mentioned:

`{_ with field1: type1; field2: type2}`

Note: The list of fields cannot be empty. `{}` is not a valid type annotation. Use `top` instead.

#### Case types 

Explicit list of cases:

`[tag1 of type1 | tag2 of type2]`

Explicit list of cases plus any number of cases not mentioned:

`[_ | tag1 of type1 | tag2 of type2]`

Note: The list of cases cannot be empty. `[]` is not a valid type annotation. Use `bot` instead.

#### Holes

`_` creates a fresh type variable. Effectively, this leaves a hole which gets filled in with the corresponding part of the inferred type. It is useful if you only want to constrain part of the type with a type annotation. 

For example if you have a record `foo`, with fields `a` and `b` you could write `(foo : {a: int; b: _})` to ensure `foo.a` is an int while placing no constraints on `foo.b`. 

`_` can also be used to extend record and case type annotations with any number of fields or tags not specified. For example the above example could also be written `(foo : {_ with a: int})`. This says that `foo` has a field named `a` that is an `int` and can have any number of other fields with any types. Likewise you can write types like ``[_ | `A of int | `B of str]`` to represent a case type where the `A` tag has type `int`, the `B` tag has type `str`, and there can be any number of other tags not specified with any types.

#### Recursive types

You can give an explicit name to a type via `type as 'name` and then reference it later via `'name`. This lets you express recursive types. For example, the following code demonstrates a type annotation with a simple recursive list type:

```ocaml
let rec build_list = fun n ->
    if n < 0 then
        null
    else
        {val=n; next=build_list (n - 1)};

let list = (build_list 4 : {val: int; next: 'list}? as 'list)
```

Type aliases can appear anywhere within a type annotation, not just at the top level. This means you can define multiple type variables within a type annotation, allowing you to express mutually recursive types.


## Building cubiml from source

You will need to have lalrpop and wasm-pack installed. First, generate the parser code

    lalrpop src/grammar.lalr

Then build the wasm module and js wrapper with wasm-pack

    wasm-pack build --target web


================================================
FILE: demo.html
================================================
<html>
    <meta charset="utf-8">
    <head>
        <title>CubiML demo</title>
    </head>
    <body>
        <noscript><em><strong>Error: This demo requires Javascript to run.</strong></em></noscript>
        <cubiml-demo></cubiml-demo>
    </body>
</html>

<script src='demo.js'></script>



================================================
FILE: demo.js
================================================
'use strict';


let mod = null;
const mod_promise = import('./pkg/cubiml_demo.js').then(
    m => (mod = m, mod.default()));



const HTML = `
<style>
    #container {
        height: 32em;
        position: relative;
        font: medium monospace;
    }
    #container.loading {
        opacity: 85%;
    }

    #container form {
        margin: 0;
    }
    #container, #prompt, #editor {
        background: darkslategrey;
        color: white;
    }

    #loading {
        position: absolute;
        top: 15%;
        left: 12%;
    }


    #pane1, #pane2 {
        float: left;
        width: 50%;
        height: 100%;

        display: flex;
        flex-direction: column;
    }
    #editor {
        height: 100%;
        resize: none;
        margin: 0;
    }


    #container .error {
        background: darkred;
    }

    #container pre {
        white-space: pre-wrap;
        overflow-wrap: anywhere;
        margin: 0;
    }

    #output {
        overflow-y: scroll;
    }
    #input-line {
        display: flex;
    }
    #prompt {
        flex-grow: 1;
        border: 0;
    }
    #space-below-prompt {
        flex-grow: 1;
    }
</style>


<div id=container class=loading>
    <div id=loading>Loading, please wait...</div>

    <div id=pane1>
        <textarea id=editor>
(* calculate fibonacci numbers recursively *)
let fib =
    let rec fib_sub = fun {n; a; b} ->
        if n <= 1 then
            a
        else
            fib_sub {n=n - 1; a=a + b; b=a}
    in
    fun n -> fib_sub {n; a=1; b=1};

(* matching on case types *)
let area = fun x ->
    match x with
        | \`Square x -> x.len *. x.len
        | \`Rect x -> x.height *. x.width;

print "area \`Square {len=4.}; =", area \`Square {len=4.};
print "area \`Rect {height=4.; width=2.5}; =", area \`Rect {height=4.; width=2.5};

(* wildcard match delegates to first area function
    for the non-Circle cases in a type safe manner *)
let area = fun x ->
    match x with
        | \`Circle x -> x.radius *. x.radius *. 3.1415926
        | x -> area x;

print "area \`Square {len=4.}; =", area \`Square {len=4.};
print "area \`Rect {height=4.; width=2.5}; =", area \`Rect {height=4.; width=2.5};
print "area \`Circle {radius=1.2}; =", area \`Circle {radius=1.2};


(* ints are arbitrary precision *)
(* 999th fibonacci number = 43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875 *)
print "fib 999 =", fib 999;


        </textarea>
        <button id=compile-and-run type=button>Compile and run</button>
    </div>

    <div id=pane2>
        <div id=output>
        </div>

        <form id=rhs-form>
        <pre id=input-line>&gt;&gt; <input id=prompt type="text" autocomplete="off" placeholder="Enter code here or to the left" disabled></pre>
        </form>

        <div id=space-below-prompt></div>
    </div>
</div>
`;

class CubimlDemo extends HTMLElement {
  constructor() {
    // Always call super first in constructor
    super();

    // Create a shadow root
    const shadow = this.attachShadow({mode: 'open'});
    shadow.innerHTML = HTML;

    mod_promise.then(
        wasm => initializeRepl(shadow, mod.State.new(), Printer)).catch(
        e => {shadow.getElementById('loading').textContent = 'Failed to load demo: ' + e});

  }
}
customElements.define('cubiml-demo', CubimlDemo);


function initializeRepl(root, compiler, Printer) {
    console.log('Initializing REPL');
    const container = root.getElementById('container');
    const output = root.getElementById('output');
    const prompt = root.getElementById('prompt');
    const editor = root.getElementById('editor');

    function addOutput(line, cls) {
        const l = document.createElement('pre');
        l.textContent = line;
        if (cls) {
            l.classList.add(cls);
        }
        output.appendChild(l);
        return l;
    }

    const $ = Object.create(null);
    const history = [];
    let history_offset = -1;

    function execCode(script) {
        let compiled;
        try {
            if (!compiler.process(script)) {return [false, compiler.get_err()];}
            compiled = '(' + compiler.get_output() + ')';
        } catch (e) {
            return [false, 'Internal compiler error: ' + e.toString()];
        }

        try {
            const p = new Printer;
            const val = eval(compiled);
            p.visit(val);
            return [true, p.parts.join('')];
        } catch (e) {
            return [false, 'An error occurred during evaluation in the repl: ' + e.toString()];
        }
    }

    function processCode(script) {
        const [success, res] = execCode(script);
        addOutput(res, success ? 'success' : 'error');
        // scroll output window to the bottom
        output.scrollTop = output.scrollHeight;
        return success;
    }


    function processReplInput(line) {
        line = line.trim();
        if (!line) {return;}

        history_offset = -1;
        if (history[history.length-1] !== line) {history.push(line);}
        // \u00a0 = non breaking space
        addOutput('>>\u00a0' + line, 'input');
        processCode(line);
    }

    root.getElementById('compile-and-run').addEventListener('click', e => {
        const s = editor.value.trim();
        if (!s) {return;}

        // Clear repl output
        output.textContent = '';
        compiler.reset();
        if (processCode(s)) {prompt.focus({preventScroll: true})}
    });

    // Implement repl command history
    prompt.addEventListener('keydown', e => {
        switch (e.key) {
            case 'ArrowDown': history_offset -= 1; break;
            case 'ArrowUp': history_offset += 1; break;
            default: return;
        }
        e.preventDefault();

        if (history_offset >= history.length) {history_offset = history.length - 1;}
        if (history_offset < 0) {history_offset = 0;}
        prompt.value = history[history.length - history_offset - 1];
    });

    // If they click in the space below the prompt, focus on the prompt to make it easier to select
    root.getElementById('space-below-prompt').addEventListener('click', e => {
        e.preventDefault();
        prompt.focus({preventScroll: true});
    });

    root.getElementById('rhs-form').addEventListener('submit', e => {
        e.preventDefault();
        const s = prompt.value.trim();
        prompt.value = '';

        if (!s) {return;}
        processReplInput(s);
    });

    container.classList.remove('loading');
    prompt.disabled = false;
    container.removeChild(root.getElementById('loading'));
    console.log('Initialized REPL');

    // Run the example code
    processCode(editor.value.trim())
}

class Printer {
    constructor() {
        this.parts = [];
        this.seen = new WeakSet;
    }

    visit(e) {
        const type = typeof e;
        if (type === 'boolean' || type === 'bigint') {this.parts.push(e.toString()); return;}
        if (type === 'string') {this.parts.push(JSON.stringify(e)); return;}
        if (type === 'number') {
            let s = e.toString();
            if (/^-?\d+$/.test(s)) {s += '.0'}
            this.parts.push(s);
            return;
        }
        if (type === 'function') {this.parts.push('<fun>'); return;}
        if (type === 'symbol') {this.parts.push('<sym>'); return;}
        if (e === null) {this.parts.push('null'); return;}
        if (e === undefined) {this.parts.push('<undefined>'); return;}

        if (this.seen.has(e)) {this.parts.push('...'); return;}
        this.seen.add(e);

        if (e.$tag) {
            this.parts.push(e.$tag);
            if (!e.$val || typeof e.$val !== 'object') {
                this.parts.push(' ');
            }
            this.visit(e.$val);
        } else if ('$p' in e) {
            this.parts.push('ref ');
            this.visit(e.$p);
        } else {
            this.parts.push('{');
            let first = true;
            for (const [k, v] of Object.entries(e)) {
                if (!first) {this.parts.push('; ')}
                first = false;

                this.parts.push(k + '=');
                this.visit(v);
            }
            this.parts.push('}');
        }
    }

    println(...args) {
        for (let arg of args) {
            if (typeof arg === 'string') {
                this.parts.push(arg);
            } else {
                this.visit(arg);
            }

            this.parts.push(' ');
        }
        this.parts.pop();
        this.parts.push('\n');
    }

    // print(e) {this.visit(e); return this.parts.join('');}
}


================================================
FILE: rustfmt.toml
================================================
max_width = 125


================================================
FILE: src/ast.rs
================================================
use crate::spans::{Span, Spanned};

#[derive(Debug, Clone)]
pub enum Literal {
    Bool,
    Float,
    Int,
    Null,
    Str,
}

#[derive(Debug, Clone)]
pub enum Op {
    Add,
    Sub,
    Mult,
    Div,
    Rem,

    Lt,
    Lte,
    Gt,
    Gte,

    Eq,
    Neq,
}

#[derive(Debug, Clone)]
pub enum OpType {
    IntOp,
    FloatOp,
    StrOp,

    IntOrFloatCmp,
    AnyCmp,
}

type LetDefinition = (LetPattern, Box<Expr>);
type VarDefinition = (String, Box<Expr>);

#[derive(Debug, Clone)]
pub enum LetPattern {
    Var(String),
    Record(Vec<(Spanned<String>, Box<LetPattern>)>),
}

#[derive(Debug, Clone)]
pub enum MatchPattern {
    Case(String, String),
    Wildcard(String),
}

#[derive(Debug, Clone)]
pub enum Expr {
    BinOp(Spanned<Box<Expr>>, Spanned<Box<Expr>>, OpType, Op, Span),
    Block(Vec<Statement>, Box<Expr>),
    Call(Box<Expr>, Box<Expr>, Span),
    Case(Spanned<String>, Box<Expr>),
    FieldAccess(Box<Expr>, String, Span),
    FuncDef(Spanned<(LetPattern, Box<Expr>)>),
    If(Spanned<Box<Expr>>, Box<Expr>, Box<Expr>),
    Literal(Literal, Spanned<String>),
    Match(Box<Expr>, Vec<(Spanned<MatchPattern>, Box<Expr>)>, Span),
    NewRef(Box<Expr>, Span),
    Record(Option<Box<Expr>>, Vec<(Spanned<String>, Box<Expr>)>, Span),
    RefGet(Spanned<Box<Expr>>),
    RefSet(Spanned<Box<Expr>>, Box<Expr>),
    Typed(Box<Expr>, TypeExpr),
    Variable(Spanned<String>),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Readability {
    ReadWrite,
    ReadOnly,
    WriteOnly,
}

#[derive(Debug, Clone)]
pub enum TypeExpr {
    Alias(Box<TypeExpr>, Spanned<String>),
    Case(Option<Box<TypeExpr>>, Vec<(Spanned<String>, Box<TypeExpr>)>, Span),
    Func(Spanned<(Box<TypeExpr>, Box<TypeExpr>)>),
    Ident(Spanned<String>),
    Nullable(Box<TypeExpr>, Span),
    Record(Option<Box<TypeExpr>>, Vec<(Spanned<String>, Box<TypeExpr>)>, Span),
    Ref(Box<TypeExpr>, Spanned<Readability>),
    TypeVar(Spanned<String>),
}

#[derive(Debug, Clone)]
pub enum Statement {
    Empty,
    Expr(Expr),
    LetDef(LetDefinition),
    LetRecDef(Vec<VarDefinition>),
    Println(Vec<Expr>),
}


================================================
FILE: src/codegen.rs
================================================
use std::collections::HashMap;
use std::mem::swap;

use crate::ast;
use crate::js;

pub struct ModuleBuilder {
    scope_expr: js::Expr,
    scope_counter: u64,
    param_counter: u64,
    var_counter: u64,                    // For choosing new var names
    bindings: HashMap<String, js::Expr>, // ML name -> JS exor for current scope
    changes: Vec<(String, Option<js::Expr>)>,
}
impl ModuleBuilder {
    pub fn new() -> Self {
        Self {
            scope_expr: js::var("$".to_string()),
            scope_counter: 0,
            param_counter: 0,
            var_counter: 0,
            bindings: HashMap::new(),
            changes: Vec::new(),
        }
    }

    pub fn compile_script(&mut self, def: &[ast::Statement]) -> js::Expr {
        compile_script(self, def)
    }

    fn ml_scope<T>(&mut self, cb: impl FnOnce(&mut Self) -> T) -> T {
        let n = self.changes.len();
        let res = cb(self);
        while self.changes.len() > n {
            let (k, old) = self.changes.pop().unwrap();
            match old {
                Some(v) => self.bindings.insert(k, v),
                None => self.bindings.remove(&k),
            };
        }
        res
    }

    fn fn_scope<T>(&mut self, cb: impl FnOnce(&mut Self) -> T) -> T {
        let old_var_counter = self.var_counter;
        let old_param_counter = self.param_counter;
        let old_scope_counter = self.scope_counter;
        self.var_counter = 0;

        let res = self.ml_scope(cb);

        self.var_counter = old_var_counter;
        self.param_counter = old_param_counter;
        self.scope_counter = old_scope_counter;
        res
    }

    fn set_binding(&mut self, k: String, v: js::Expr) {
        let old = self.bindings.insert(k.clone(), v);
        self.changes.push((k, old));
    }

    fn new_var_name(&mut self) -> String {
        let js_name = format!("v{}", self.var_counter);
        self.var_counter += 1;
        js_name
    }

    fn new_temp_var(&mut self) -> js::Expr {
        let js_name = format!("t{}", self.var_counter);
        self.var_counter += 1;

        js::field(self.scope_expr.clone(), js_name)
    }

    fn new_var(&mut self, ml_name: &str) -> js::Expr {
        let js_name = self.new_var_name();
        let expr = js::field(self.scope_expr.clone(), js_name);
        self.set_binding(ml_name.to_string(), expr.clone());
        expr
    }

    fn new_scope_name(&mut self) -> String {
        let js_name = format!("s{}", self.scope_counter);
        self.scope_counter += 1;
        js_name
    }

    fn new_param_name(&mut self) -> String {
        let js_name = format!("p{}", self.param_counter);
        self.param_counter += 1;
        js_name
    }
}

fn compile(ctx: &mut ModuleBuilder, expr: &ast::Expr) -> js::Expr {
    match expr {
        ast::Expr::BinOp((lhs_expr, lhs_span), (rhs_expr, rhs_span), op_type, op, full_span) => {
            let lhs = compile(ctx, lhs_expr);
            let rhs = compile(ctx, rhs_expr);
            let jsop = match op {
                ast::Op::Add => js::Op::Add,
                ast::Op::Sub => js::Op::Sub,
                ast::Op::Mult => js::Op::Mult,
                ast::Op::Div => js::Op::Div,
                ast::Op::Rem => js::Op::Rem,

                ast::Op::Lt => js::Op::Lt,
                ast::Op::Lte => js::Op::Lte,
                ast::Op::Gt => js::Op::Gt,
                ast::Op::Gte => js::Op::Gte,

                ast::Op::Eq => js::Op::Eq,
                ast::Op::Neq => js::Op::Neq,
            };
            js::binop(lhs, rhs, jsop)
        }
        ast::Expr::Block(statements, rest_expr) => {
            ctx.ml_scope(|ctx| {
                let mut exprs = Vec::new(); // a list of assignments, followed by rest

                for stmt in statements {
                    compile_statement(ctx, &mut exprs, stmt);
                }

                exprs.push(compile(ctx, rest_expr));
                js::comma_list(exprs)
            })
        }
        ast::Expr::Call(func, arg, _) => {
            let lhs = compile(ctx, func);
            let rhs = compile(ctx, arg);
            js::call(lhs, rhs)
        }
        ast::Expr::Case((tag, _), expr) => {
            let tag = js::lit(format!("\"{}\"", tag));
            let expr = compile(ctx, expr);
            js::obj(None, vec![("$tag".to_string(), tag), ("$val".to_string(), expr)])
        }
        ast::Expr::FieldAccess(lhs_expr, name, _) => {
            let lhs = compile(ctx, lhs_expr);
            js::field(lhs, name.clone())
        }
        ast::Expr::FuncDef(((arg_pattern, body_expr), _)) => {
            ctx.fn_scope(|ctx| {
                let new_scope_name = ctx.new_scope_name();
                let mut scope_expr = js::var(new_scope_name.clone());
                swap(&mut scope_expr, &mut ctx.scope_expr);

                //////////////////////////////////////////////////////
                let js_pattern = compile_let_pattern(ctx, arg_pattern);
                let body = compile(ctx, body_expr);
                //////////////////////////////////////////////////////

                swap(&mut scope_expr, &mut ctx.scope_expr);
                js::func(js_pattern, new_scope_name, body)
            })
        }
        ast::Expr::If((cond_expr, _), then_expr, else_expr) => {
            let cond_expr = compile(ctx, cond_expr);
            let then_expr = compile(ctx, then_expr);
            let else_expr = compile(ctx, else_expr);
            js::ternary(cond_expr, then_expr, else_expr)
        }
        ast::Expr::Literal(type_, (code, _)) => {
            let mut code = code.clone();
            if let ast::Literal::Int = type_ {
                code.push_str("n");
            }
            if code.starts_with("-") {
                js::unary_minus(js::lit(code[1..].to_string()))
            } else {
                js::lit(code)
            }
        }
        ast::Expr::Match(match_expr, cases, _) => {
            let temp_var = js::field(ctx.scope_expr.clone(), ctx.new_var_name());
            let part1 = js::assign(temp_var.clone(), compile(ctx, match_expr));

            let tag_expr = js::field(temp_var.clone(), "$tag".to_string());
            let val_expr = js::field(temp_var.clone(), "$val".to_string());

            let mut branches = Vec::new();
            for ((pattern, _), rhs_expr) in cases {
                use ast::MatchPattern::*;
                match pattern {
                    Case(tag, name) => {
                        ctx.ml_scope(|ctx| {
                            ctx.set_binding(name.to_string(), val_expr.clone());
                            branches.push((tag.as_str(), compile(ctx, rhs_expr)));
                        });
                    }
                    Wildcard(name) => {
                        ctx.ml_scope(|ctx| {
                            ctx.set_binding(name.to_string(), temp_var.clone());
                            branches.push(("", compile(ctx, rhs_expr)));
                        });
                    }
                }
            }

            let mut res = branches.pop().unwrap().1;
            while let Some((tag, rhs_expr)) = branches.pop() {
                assert!(tag.len() > 0);
                let cond = js::eqop(tag_expr.clone(), js::lit(format!("\"{}\"", tag)));
                res = js::ternary(cond, rhs_expr, res);
            }
            js::comma_pair(part1, res)
        }
        ast::Expr::NewRef(expr, span) => {
            let expr = compile(ctx, expr);
            js::obj(None, vec![("$p".to_string(), expr)])
        }
        ast::Expr::Record(proto, fields, span) => js::obj(
            proto.as_ref().map(|expr| compile(ctx, expr)),
            fields
                .iter()
                .map(|((name, _), expr)| (name.clone(), compile(ctx, expr)))
                .collect(),
        ),
        ast::Expr::RefGet((expr, span)) => {
            let expr = compile(ctx, expr);
            js::field(expr, "$p".to_string())
        }
        ast::Expr::RefSet((lhs_expr, lhs_span), rhs_expr) => {
            let lhs = compile(ctx, lhs_expr);
            let rhs = compile(ctx, rhs_expr);
            js::assign(js::field(lhs, "$p".to_string()), rhs)
        }
        ast::Expr::Typed(expr, _) => compile(ctx, expr),
        ast::Expr::Variable((name, _)) => ctx.bindings.get(name).unwrap().clone(),
    }
}

fn compile_let_pattern_flat(ctx: &mut ModuleBuilder, out: &mut Vec<js::Expr>, pat: &ast::LetPattern, rhs: js::Expr) {
    use ast::LetPattern::*;
    match pat {
        Var(ml_name) => {
            let lhs = ctx.new_var(ml_name);
            out.push(js::assign(lhs, rhs));
        }
        Record(pairs) => {
            // Assign the rhs to a temporary value, and then do a = temp.foo for each field
            let lhs = ctx.new_temp_var();
            out.push(js::assign(lhs.clone(), rhs));

            for ((name, _), pat) in pairs.iter() {
                compile_let_pattern_flat(ctx, out, pat, js::field(lhs.clone(), name.clone()));
            }
        }
    }
}

fn compile_let_pattern(ctx: &mut ModuleBuilder, pat: &ast::LetPattern) -> js::Expr {
    use ast::LetPattern::*;
    match pat {
        Var(ml_name) => {
            let js_arg = js::var(ctx.new_param_name());
            ctx.set_binding(ml_name.to_string(), js_arg.clone());
            js_arg
        }
        Record(pairs) => js::obj(
            None,
            pairs
                .iter()
                .map(|((name, _), pat)| (name.clone(), compile_let_pattern(ctx, &*pat)))
                .collect(),
        ),
    }
}

fn compile_statement(ctx: &mut ModuleBuilder, exprs: &mut Vec<js::Expr>, stmt: &ast::Statement) {
    use ast::Statement::*;
    match stmt {
        Empty => {}
        Expr(expr) => exprs.push(compile(ctx, expr)),
        LetDef((pat, var_expr)) => {
            let rhs = compile(ctx, var_expr);
            compile_let_pattern_flat(ctx, exprs, pat, rhs);
        }
        LetRecDef(defs) => {
            let mut vars = Vec::new();
            let mut rhs_exprs = Vec::new();
            for (name, _) in defs {
                vars.push(ctx.new_var(name))
            }
            for (_, expr) in defs {
                rhs_exprs.push(compile(ctx, expr))
            }

            for (lhs, rhs) in vars.into_iter().zip(rhs_exprs) {
                exprs.push(js::assign(lhs, rhs));
            }
        }
        Println(args) => {
            let args = args.iter().map(|expr| compile(ctx, expr)).collect();
            exprs.push(js::println(args));
        }
    }
}

fn compile_script(ctx: &mut ModuleBuilder, parsed: &[ast::Statement]) -> js::Expr {
    let mut exprs = Vec::new();

    for item in parsed {
        compile_statement(ctx, &mut exprs, item);
    }

    if exprs.is_empty() {
        js::lit("0".to_string())
    } else {
        js::comma_list(exprs)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        use ast::*;

        fn intlit(s: &str) -> Box<ast::Expr> {
            Box::new(Expr::Literal(Literal::Int, s.to_string()))
        }

        let mut mb = ModuleBuilder::new();
        assert_eq!(compile(&mut mb, &intlit("-1")).to_source(), "-1n");
        assert_eq!(
            compile(&mut mb, &Expr::FieldAccess(intlit("-1"), "toString".to_string())).to_source(),
            "(-1n).toString"
        );

        assert_eq!(
            compile(
                &mut mb,
                &Expr::BinOp(intlit("42"), intlit("-1"), ast::OpType::IntOp, ast::Op::Sub)
            )
            .to_source(),
            "42n- -1n"
        );
    }
}


================================================
FILE: src/core.rs
================================================
use std::collections::HashMap;
use std::error;
use std::fmt;

use crate::reachability;
use crate::spans::{Span, SpannedError as TypeError};

type ID = usize;

#[derive(Copy, Clone, Debug)]
pub struct Value(ID);
#[derive(Copy, Clone, Debug)]
pub struct Use(ID);

pub type LazyFlow = (Value, Use);

#[derive(Debug, Clone)]
enum VTypeHead {
    VBool,
    VFloat,
    VInt,
    VNull,
    VStr,
    VFunc {
        arg: Use,
        ret: Value,
    },
    VObj {
        fields: HashMap<String, Value>,
        proto: Option<Value>,
    },
    VCase {
        case: (String, Value),
    },
    VRef {
        write: Option<Use>,
        read: Option<Value>,
    },
}
#[derive(Debug, Clone)]
enum UTypeHead {
    UBool,
    UFloat,
    UInt,
    UNull,
    UStr,
    UIntOrFloat,
    UFunc {
        arg: Value,
        ret: Use,
    },
    UObj {
        field: (String, Use),
    },
    UCase {
        cases: HashMap<String, (Use, LazyFlow)>,
        wildcard: Option<(Use, LazyFlow)>,
    },
    URef {
        write: Option<Value>,
        read: Option<Use>,
    },
    UNullCase {
        nonnull: Use,
    },
}

fn check_heads(
    lhs_ind: ID,
    lhs: &(VTypeHead, Span),
    rhs_ind: ID,
    rhs: &(UTypeHead, Span),
    out: &mut Vec<(Value, Use)>,
) -> Result<(), TypeError> {
    use UTypeHead::*;
    use VTypeHead::*;

    match (&lhs.0, &rhs.0) {
        (&VBool, &UBool) => Ok(()),
        (&VFloat, &UFloat) => Ok(()),
        (&VInt, &UInt) => Ok(()),
        (&VNull, &UNull) => Ok(()),
        (&VStr, &UStr) => Ok(()),
        (&VInt, &UIntOrFloat) => Ok(()),
        (&VFloat, &UIntOrFloat) => Ok(()),

        (&VFunc { arg: arg1, ret: ret1 }, &UFunc { arg: arg2, ret: ret2 }) => {
            out.push((ret1, ret2));
            // flip the order since arguments are contravariant
            out.push((arg2, arg1));
            Ok(())
        }
        (
            &VObj {
                fields: ref fields1,
                proto,
            },
            &UObj { field: (ref name, rhs2) },
        ) => {
            // Check if the accessed field is defined
            if let Some(&lhs2) = fields1.get(name) {
                out.push((lhs2, rhs2));
                Ok(())
            } else if let Some(lhs2) = proto {
                out.push((lhs2, Use(rhs_ind)));
                Ok(())
            } else {
                Err(TypeError::new2(
                    format!("TypeError: Missing field {}\nNote: Field is accessed here", name),
                    rhs.1,
                    "But the record is defined without that field here.",
                    lhs.1,
                ))
            }
        }
        (
            &VCase { case: (ref name, lhs2) },
            &UCase {
                cases: ref cases2,
                wildcard,
            },
        ) => {
            // Check if the right case is handled
            if let Some((rhs2, lazy_flow)) = cases2.get(name).copied() {
                out.push((lhs2, rhs2));
                out.push(lazy_flow);
                Ok(())
            } else if let Some((rhs2, lazy_flow)) = wildcard {
                out.push((Value(lhs_ind), rhs2));
                out.push(lazy_flow);
                Ok(())
            } else {
                Err(TypeError::new2(
                    format!("TypeError: Unhandled case {}\nNote: Case originates here", name),
                    lhs.1,
                    "But it is not handled here.",
                    rhs.1,
                ))
            }
        }

        (&VRef { read: r1, write: w1 }, &URef { read: r2, write: w2 }) => {
            if let Some(r2) = r2 {
                if let Some(r1) = r1 {
                    out.push((r1, r2));
                } else {
                    return Err(TypeError::new2(
                        "TypeError: Reference is not readable.\nNote: Ref is made write-only here",
                        lhs.1,
                        "But is read here.",
                        rhs.1,
                    ));
                }
            }
            if let Some(w2) = w2 {
                if let Some(w1) = w1 {
                    // flip the order since writes are contravariant
                    out.push((w2, w1));
                } else {
                    return Err(TypeError::new2(
                        "TypeError: Reference is not writable.\nNote: Ref is made read-only here",
                        lhs.1,
                        "But is written here.",
                        rhs.1,
                    ));
                }
            }
            Ok(())
        }

        (&VNull, &UNullCase { .. }) => Ok(()),
        (_, &UNullCase { nonnull }) => {
            out.push((Value(lhs_ind), nonnull));
            Ok(())
        }

        _ => {
            let found = match lhs.0 {
                VBool => "boolean",
                VFloat => "float",
                VInt => "integer",
                VNull => "null",
                VStr => "string",
                VFunc { .. } => "function",
                VObj { .. } => "record",
                VCase { .. } => "case",
                VRef { .. } => "ref",
            };
            let expected = match rhs.0 {
                UBool => "boolean",
                UFloat => "float",
                UInt => "integer",
                UNull => "null",
                UStr => "string",
                UIntOrFloat => "float or integer",
                UFunc { .. } => "function",
                UObj { .. } => "record",
                UCase { .. } => "case",
                URef { .. } => "ref",
                UNullCase { .. } => unreachable!(),
            };

            Err(TypeError::new2(
                format!("TypeError: Value is required to be a {} here,", expected),
                rhs.1,
                format!("But that value may be a {} originating here.", found),
                lhs.1,
            ))
        }
    }
}

#[derive(Debug, Clone)]
enum TypeNode {
    Var,
    Value((VTypeHead, Span)),
    Use((UTypeHead, Span)),
}
#[derive(Debug, Clone)]
pub struct TypeCheckerCore {
    r: reachability::Reachability,
    types: Vec<TypeNode>,
}
impl TypeCheckerCore {
    pub fn new() -> Self {
        Self {
            r: Default::default(),
            types: Vec::new(),
        }
    }

    pub fn flow(&mut self, lhs: Value, rhs: Use) -> Result<(), TypeError> {
        let mut pending_edges = vec![(lhs, rhs)];
        let mut type_pairs_to_check = Vec::new();
        while let Some((lhs, rhs)) = pending_edges.pop() {
            self.r.add_edge(lhs.0, rhs.0, &mut type_pairs_to_check);

            // Check if adding that edge resulted in any new type pairs needing to be checked
            while let Some((lhs, rhs)) = type_pairs_to_check.pop() {
                if let TypeNode::Value(lhs_head) = &self.types[lhs] {
                    if let TypeNode::Use(rhs_head) = &self.types[rhs] {
                        check_heads(lhs, lhs_head, rhs, rhs_head, &mut pending_edges)?;
                    }
                }
            }
        }
        assert!(pending_edges.is_empty() && type_pairs_to_check.is_empty());
        Ok(())
    }

    fn new_val(&mut self, val_type: VTypeHead, span: Span) -> Value {
        let i = self.r.add_node();
        assert!(i == self.types.len());
        self.types.push(TypeNode::Value((val_type, span)));
        Value(i)
    }

    fn new_use(&mut self, constraint: UTypeHead, span: Span) -> Use {
        let i = self.r.add_node();
        assert!(i == self.types.len());
        self.types.push(TypeNode::Use((constraint, span)));
        Use(i)
    }

    pub fn var(&mut self) -> (Value, Use) {
        let i = self.r.add_node();
        assert!(i == self.types.len());
        self.types.push(TypeNode::Var);
        (Value(i), Use(i))
    }

    pub fn bool(&mut self, span: Span) -> Value {
        self.new_val(VTypeHead::VBool, span)
    }
    pub fn float(&mut self, span: Span) -> Value {
        self.new_val(VTypeHead::VFloat, span)
    }
    pub fn int(&mut self, span: Span) -> Value {
        self.new_val(VTypeHead::VInt, span)
    }
    pub fn null(&mut self, span: Span) -> Value {
        self.new_val(VTypeHead::VNull, span)
    }
    pub fn str(&mut self, span: Span) -> Value {
        self.new_val(VTypeHead::VStr, span)
    }

    pub fn bool_use(&mut self, span: Span) -> Use {
        self.new_use(UTypeHead::UBool, span)
    }
    pub fn float_use(&mut self, span: Span) -> Use {
        self.new_use(UTypeHead::UFloat, span)
    }
    pub fn int_use(&mut self, span: Span) -> Use {
        self.new_use(UTypeHead::UInt, span)
    }
    pub fn null_use(&mut self, span: Span) -> Use {
        self.new_use(UTypeHead::UNull, span)
    }
    pub fn str_use(&mut self, span: Span) -> Use {
        self.new_use(UTypeHead::UStr, span)
    }
    pub fn int_or_float_use(&mut self, span: Span) -> Use {
        self.new_use(UTypeHead::UIntOrFloat, span)
    }

    pub fn func(&mut self, arg: Use, ret: Value, span: Span) -> Value {
        self.new_val(VTypeHead::VFunc { arg, ret }, span)
    }
    pub fn func_use(&mut self, arg: Value, ret: Use, span: Span) -> Use {
        self.new_use(UTypeHead::UFunc { arg, ret }, span)
    }

    pub fn obj(&mut self, fields: Vec<(String, Value)>, proto: Option<Value>, span: Span) -> Value {
        let fields = fields.into_iter().collect();
        self.new_val(VTypeHead::VObj { fields, proto }, span)
    }
    pub fn obj_use(&mut self, field: (String, Use), span: Span) -> Use {
        self.new_use(UTypeHead::UObj { field }, span)
    }

    pub fn case(&mut self, case: (String, Value), span: Span) -> Value {
        self.new_val(VTypeHead::VCase { case }, span)
    }
    pub fn case_use(&mut self, cases: Vec<(String, (Use, LazyFlow))>, wildcard: Option<(Use, LazyFlow)>, span: Span) -> Use {
        let cases = cases.into_iter().collect();
        self.new_use(UTypeHead::UCase { cases, wildcard }, span)
    }

    pub fn reference(&mut self, write: Option<Use>, read: Option<Value>, span: Span) -> Value {
        self.new_val(VTypeHead::VRef { write, read }, span)
    }
    pub fn reference_use(&mut self, write: Option<Value>, read: Option<Use>, span: Span) -> Use {
        self.new_use(UTypeHead::URef { write, read }, span)
    }

    pub fn null_check_use(&mut self, nonnull: Use, span: Span) -> Use {
        self.new_use(UTypeHead::UNullCase { nonnull }, span)
    }

    pub fn save(&self) -> SavePoint {
        (self.types.len(), self.r.clone())
    }
    pub fn restore(&mut self, mut save: SavePoint) {
        self.types.truncate(save.0);
        std::mem::swap(&mut self.r, &mut save.1);
    }
}

type SavePoint = (usize, reachability::Reachability);


================================================
FILE: src/grammar.lalr
================================================
use lalrpop_util::ParseError;

use super::ast; // super instead of self because lalrpop wraps this in an internal module
use super::spans;

#[LALR]
grammar(ctx: &mut spans::SpanMaker<'input>);

extern {
    type Error = (&'static str, spans::Span);
}

// Tokens ////////////////////////////////////////////////////////////
match {
    r"\s*" => { }, // The default whitespace skipping is disabled if an `ignore pattern` is specified
    r"//[^\n\r]*[\n\r]*" => { }, // Skip `// comments`
    r#"\(\*[^*]*\*+(?:[^\)*][^*]*\*+)*\)"# => { },  // Skip `(* comments *)`
} else {
    _
}


Ident: String = <r"[a-z_]\w*"> => String::from(<>);
Tag: String = <r"`[A-Z0-9]\w*"> => String::from(<>);

IntLiteral: String = {
    <l: @L> <s: r"-?(?:[0-9]+)"> <r: @R> =>? {
        let s2 = s.trim_start_matches('-');
        if s2 != "0" && s2.starts_with("0") {
            Err(ParseError::User {
                error: ("SyntaxError: Numbers can't contain leading 0s", ctx.span(l, r))
            })
        } else {
            Ok(String::from(s))
        }
    },
};
FloatLiteral: String =
    <r"-?(?:0|[1-9][0-9]*)\.[0-9]*(?:[eE]-?[0-9]+)?"> => String::from(<>);
StringLiteral: String =
    <r#""[^\\"\n\r]*(?:\\[tn'"\\][^\\"\n\r]*)*""#> => String::from(<>);


// make sure __proto__ is not considered a valid identifier
Illegal = "__proto__";


// Macros ////////////////////////////////////////////////////////////
Box<T>: Box<T> = {
    <T> => Box::new(<>),
}
SepList<T, Sep>: Vec<T> = {
    <v:(<T> Sep)*> <e:T> => {
        let mut v = v;
        v.push(e);
        v
    }
};
SepListOpt<T, Sep>: Vec<T> = {
    SepList<T, Sep>,
    => Vec::new(),
};






Spanned<T>: spans::Spanned<T> = {
    <l: @L> <val: T> <r: @R> => (val, ctx.span(l, r))
};

// Types /////////////////////////////////////////////////////////////
RecordTypeExtension = <Box<Type>> "with";
KeyPairType = {
    <Spanned<Ident>> ":" <Box<Type>>,
}
RecordTypeSub = "{" <RecordTypeExtension?> <SepList<KeyPairType, ";">> "}";
RecordType: ast::TypeExpr = {
    Spanned<RecordTypeSub> => {
        let ((ext, fields), span) = <>;
        ast::TypeExpr::Record(ext, fields, span)
    }
}

CaseTypeExtension = <Box<Type>> "|";
VariantType = <Spanned<Tag>> "of" <Box<NoFunType>>;
CaseTypeSub = "[" <CaseTypeExtension?> <SepList<VariantType, "|">> "]";
CaseType: ast::TypeExpr = {
    Spanned<CaseTypeSub> => {
        let ((ext, cases), span) = <>;
        ast::TypeExpr::Case(ext, cases, span)
    }
}

RefReadability: ast::Readability = {
    "readonly" "ref" => ast::Readability::ReadOnly,
    "writeonly" "ref" => ast::Readability::WriteOnly,
    "ref" => ast::Readability::ReadWrite,
}
RefType: ast::TypeExpr = {
    Box<NoFunType> Spanned<RefReadability> => ast::TypeExpr::Ref(<>),
}

QMark: spans::Span = Spanned<"?"> => <>.1;
NullableType: ast::TypeExpr = {
    Box<NoFunType> QMark => ast::TypeExpr::Nullable(<>),
}

TypeVar = "'" <Ident>;

NoFunType: ast::TypeExpr = {
    Spanned<Ident> => ast::TypeExpr::Ident(<>),
    <Box<NoFunType>> "as" <Spanned<TypeVar>> => ast::TypeExpr::Alias(<>),
    Spanned<TypeVar> => ast::TypeExpr::TypeVar(<>),

    RecordType,
    CaseType,
    RefType,
    NullableType,
    "(" <Type> ")",
}
FuncTypeSub = <Box<NoFunType>> "->" <Box<Type>>;
FuncType: ast::TypeExpr = {
    Spanned<FuncTypeSub> => ast::TypeExpr::Func(<>),
}

Type = {
    NoFunType,
    FuncType,
}




//////////////////////////////////////////////////////////////////////
// Expressions ///////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////



// SimpleExpr ////////////////////////////////////////////////////////
FieldAccess: ast::Expr = {
    <lhs: Box<SimpleExpr>> <rhs: Spanned<("." Ident)>> => ast::Expr::FieldAccess(lhs, (rhs.0).1, rhs.1),
}

RecordExtension = {
    <Box<CallExpr>> "with"
}
KeyPairExpr: (spans::Spanned<String>, Box<ast::Expr>) = {
    <Spanned<Ident>> "=" <Box<NoSemiExpr>>,
    <name: Spanned<Ident>> => (name.clone(), Box::new(ast::Expr::Variable(name))),
}
RecordSub = "{" <RecordExtension?> <SepListOpt<KeyPairExpr, ";">> "}";
Record: ast::Expr = {
    Spanned<RecordSub> => {
        let ((proto, fields), span) = <>;
        ast::Expr::Record(proto, fields, span)
    }
}

VarOrLiteral: ast::Expr = {
    Spanned<Ident> =>
        match <>.0.as_str() {
            "false" | "true" => ast::Expr::Literal(ast::Literal::Bool, <>),
            "null" => ast::Expr::Literal(ast::Literal::Null, <>),
            _ => ast::Expr::Variable(<>)
        }
    ,

    Spanned<FloatLiteral> => ast::Expr::Literal(ast::Literal::Float, <>),
    Spanned<IntLiteral> => ast::Expr::Literal(ast::Literal::Int, <>),
    Spanned<StringLiteral> => ast::Expr::Literal(ast::Literal::Str, <>),
}

SimpleExpr = {
    FieldAccess,
    Record,
    VarOrLiteral,
    "(" <Expr> ")",
    "(" <Box<Expr>> ":" <Type> ")" => ast::Expr::Typed(<>),
    "begin" <Expr> "end",
}
//////////////////////////////////////////////////////////////////////


// RefExpr ///////////////////////////////////////////////////////////
RefGet: ast::Expr = {
    "!" <Spanned<Box<RefExpr>>> => ast::Expr::RefGet(<>)
}
RefExpr = {
    SimpleExpr,
    RefGet,
}
//////////////////////////////////////////////////////////////////////

// CallExpr //////////////////////////////////////////////////////////
Call: ast::Expr = {
    Spanned<Box<RefExpr>> Box<CallExpr> => {
        let ((lhs, span), rhs) = (<>);
        ast::Expr::Call(lhs, rhs, span)
    }
}
Case: ast::Expr = {
    <Spanned<Tag>> <Box<CallExpr>> => ast::Expr::Case(<>),
}
NewRef: ast::Expr = {
    Spanned<("ref" Box<CallExpr>)> => {
        let ((_, expr), span) = <>;
        ast::Expr::NewRef(expr, span)
    }
}

CallExpr = {
    RefExpr,
    Call,
    Case,
    NewRef,
}
//////////////////////////////////////////////////////////////////////

// Binary expressions/////////////////////////////////////////////////
MultOpSub: (ast::OpType, ast::Op) = {
    "*" => (ast::OpType::IntOp, ast::Op::Mult),
    "/" => (ast::OpType::IntOp, ast::Op::Div),
    "%" => (ast::OpType::IntOp, ast::Op::Rem),
    "*." => (ast::OpType::FloatOp, ast::Op::Mult),
    "/." => (ast::OpType::FloatOp, ast::Op::Div),
    "%." => (ast::OpType::FloatOp, ast::Op::Rem),
}
MultOp: ast::Expr = {
    Spanned<(Spanned<Box<MultExpr>> MultOpSub Spanned<Box<CallExpr>>)> => {
        let ((lhs, op, rhs), span) = <>;
        ast::Expr::BinOp(lhs, rhs, op.0, op.1, span)
    },
}
AddOpSub: (ast::OpType, ast::Op) = {
    "+" => (ast::OpType::IntOp, ast::Op::Add),
    "-" => (ast::OpType::IntOp, ast::Op::Sub),
    "+." => (ast::OpType::FloatOp, ast::Op::Add),
    "-." => (ast::OpType::FloatOp, ast::Op::Sub),
    "^" => (ast::OpType::StrOp, ast::Op::Add),
}
AddOp: ast::Expr = {
    Spanned<(Spanned<Box<AddExpr>> AddOpSub Spanned<Box<MultExpr>>)> => {
        let ((lhs, op, rhs), span) = <>;
        ast::Expr::BinOp(lhs, rhs, op.0, op.1, span)
    },
}
CmpOpSub: (ast::OpType, ast::Op) = {
    "<" => (ast::OpType::IntOrFloatCmp, ast::Op::Lt),
    "<=" => (ast::OpType::IntOrFloatCmp, ast::Op::Lte),
    ">" => (ast::OpType::IntOrFloatCmp, ast::Op::Gt),
    ">=" => (ast::OpType::IntOrFloatCmp, ast::Op::Gte),

    "==" => (ast::OpType::AnyCmp, ast::Op::Eq),
    "!=" => (ast::OpType::AnyCmp, ast::Op::Neq),
}
CmpOp: ast::Expr = {
    Spanned<(Spanned<Box<AddExpr>> CmpOpSub Spanned<Box<AddExpr>>)> => {
        let ((lhs, op, rhs), span) = <>;
        ast::Expr::BinOp(lhs, rhs, op.0, op.1, span)
    },
}


MultExpr = {
    CallExpr,
    MultOp,
}
AddExpr = {
    MultExpr,
    AddOp,
}
CompareExpr = {
    AddExpr,
    CmpOp,
}
//////////////////////////////////////////////////////////////////////

// Top level expressions /////////////////////////////////////////////
KeyPairPattern: (spans::Spanned<String>, Box<ast::LetPattern>) = {
    <name: Spanned<Ident>> "=" <pat: LetPattern> => (name, Box::new(pat)),
    <name: Spanned<Ident>> => (name.clone(), Box::new(ast::LetPattern::Var(name.0))),
}
LetPattern: ast::LetPattern = {
    <Ident> => ast::LetPattern::Var(<>),
    "{" <SepList<KeyPairPattern, ";">> "}" => ast::LetPattern::Record(<>),
}
FuncSub = "fun" <LetPattern> "->" <Box<NoSemiExpr>>;
FuncDef: ast::Expr = {
    Spanned<FuncSub> => ast::Expr::FuncDef(<>),
}


If: ast::Expr = {
    "if" <Spanned<Box<Expr>>> "then" <Box<Expr>> "else" <Box<NoSemiExpr>> => ast::Expr::If(<>),
}


LetLHS = {
    "let" <LetPattern> "=" <Box<NoSemiExpr>>,
}
LetRHS = {
    "in" <Box<NoSemiExpr>>,
}
Let: ast::Expr = {
    <lhs: LetLHS> <rhs: LetRHS> => ast::Expr::Block(vec![ast::Statement::LetDef(lhs)], rhs),
}


LetRecDef = {
    <Ident> "=" <Box<FuncDef>>,
}
LetRecLHS = {
    "let" "rec" <SepList<LetRecDef, "and">>,
}
LetRec: ast::Expr = {
     <lhs: LetRecLHS> <rhs: LetRHS> => ast::Expr::Block(vec![ast::Statement::LetRecDef(lhs)], rhs),
}


MatchPattern: ast::MatchPattern = {
    Tag Ident => ast::MatchPattern::Case(<>),
    Ident => ast::MatchPattern::Wildcard(<>),
}
MatchArm = {
    "|" <Spanned<MatchPattern>> "->" <Box<CompareExpr>>,
}
MatchSub = "match" <Spanned<Box<Expr>>> "with" <MatchArm+>;
Match: ast::Expr = {
    MatchSub => {
        let ((param, span), arms) = <>;
        ast::Expr::Match(param, arms, span)
    }
}


RefSet: ast::Expr = {
    <Spanned<Box<CallExpr>>> ":=" <Box<NoSemiExpr>> => ast::Expr::RefSet(<>)
}



NoSemiExpr = {
    CompareExpr,
    FuncDef,
    If,
    Let,
    LetRec,
    Match,
    RefSet,
}
Expr: ast::Expr = {
    <stmts: (<Statement> ";")*> <rest: NoSemiExpr> => {
        if stmts.is_empty() {
            rest
        } else {
            ast::Expr::Block(stmts, Box::new(rest))
        }
    }
}
//////////////////////////////////////////////////////////////////////



Statement: ast::Statement = {
    <LetLHS> => ast::Statement::LetDef(<>),
    <LetRecLHS> => ast::Statement::LetRecDef(<>),
    <NoSemiExpr> => ast::Statement::Expr(<>),
    "print" <SepListOpt<NoSemiExpr, ",">> => ast::Statement::Println(<>),
    => ast::Statement::Empty,
}

pub Script = {
   <SepList<Statement, ";">>
}


================================================
FILE: src/js.rs
================================================
#![allow(dead_code)]

#[derive(Debug, Clone, Copy)]
pub enum Op {
    Add,
    Sub,
    Mult,
    Div,
    Rem,

    Lt,
    Lte,
    Gt,
    Gte,

    Eq,
    Neq,
}

/////////////////////////////////////////////////////////////////////////////////////////////
pub fn assign(lhs: Expr, rhs: Expr) -> Expr {
    Expr(Expr2::Assignment(lhs.0.into(), rhs.0.into()))
}
pub fn binop(lhs: Expr, rhs: Expr, op: Op) -> Expr {
    Expr(Expr2::BinOp(lhs.0.into(), rhs.0.into(), op))
}
pub fn call(lhs: Expr, rhs: Expr) -> Expr {
    Expr(Expr2::Call(lhs.0.into(), rhs.0.into()))
}
pub fn comma_pair(lhs: Expr, rhs: Expr) -> Expr {
    Expr(Expr2::Comma(lhs.0.into(), rhs.0.into()))
}
pub fn unary_minus(rhs: Expr) -> Expr {
    Expr(Expr2::Minus(rhs.0.into()))
}
pub fn eqop(lhs: Expr, rhs: Expr) -> Expr {
    Expr(Expr2::BinOp(lhs.0.into(), rhs.0.into(), Op::Eq))
}
pub fn field(lhs: Expr, rhs: String) -> Expr {
    Expr(Expr2::Field(lhs.0.into(), rhs))
}
pub fn lit(code: String) -> Expr {
    Expr(Expr2::Literal(code))
}
pub fn ternary(cond: Expr, e1: Expr, e2: Expr) -> Expr {
    Expr(Expr2::Ternary(cond.0.into(), e1.0.into(), e2.0.into()))
}
pub fn var(s: String) -> Expr {
    Expr(Expr2::Var(s))
}

pub fn comma_list(mut exprs: Vec<Expr>) -> Expr {
    // Reverse the list so we can easily create a left-recursive structure instead of right recursive
    exprs.reverse();
    let mut res = exprs.pop().unwrap().0;
    while let Some(expr) = exprs.pop() {
        res = Expr2::Comma(Box::new(res), expr.0.into());
    }
    Expr(res)
}

pub fn println(exprs: Vec<Expr>) -> Expr {
    Expr(Expr2::Print(exprs.into_iter().map(|e| e.0).collect()))
}

pub fn func(arg: Expr, scope: String, body: Expr) -> Expr {
    Expr(Expr2::ArrowFunc(Box::new(arg.0), scope, Box::new(body.0)))
}

pub fn obj(spread: Option<Expr>, fields: Vec<(String, Expr)>) -> Expr {
    let mut prop_defs = Vec::new();
    if let Some(v) = spread {
        prop_defs.push(PropertyDefinition::Spread(v.0.into()));
    }
    for (name, v) in fields {
        prop_defs.push(PropertyDefinition::Named(name, v.0.into()));
    }

    Expr(Expr2::Obj(prop_defs))
}

#[derive(Clone, Debug)]
pub struct Expr(Expr2);
impl Expr {
    // pub fn add_parens(&mut self) {
    //     self.0.add_parens();
    // }

    pub fn to_source(mut self) -> String {
        self.0.add_parens();

        let mut s = "".to_string();
        self.0.write(&mut s);
        s
    }
}

#[derive(Clone, Copy, PartialEq, Eq)]
enum Token {
    OTHER,
    BRACE,
    PAREN,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Precedence {
    PRIMARY = 0,
    MEMBER,
    CALL,
    LHS,
    UNARY,
    EXPONENT,
    MULTIPLICATIVE,
    ADDITIVE,
    SHIFT,
    RELATIONAL,
    EQUALITY,
    LOR,
    CONDITIONAL,
    ASSIGN,
    EXPR,
}

#[derive(Clone, Debug)]
enum PropertyDefinition {
    Named(String, Box<Expr2>),
    Spread(Box<Expr2>),
}

#[derive(Clone, Debug)]
enum Expr2 {
    Paren(Box<Expr2>),
    Literal(String),
    Obj(Vec<PropertyDefinition>),
    Var(String),

    Field(Box<Expr2>, String),

    Call(Box<Expr2>, Box<Expr2>),

    Minus(Box<Expr2>),

    BinOp(Box<Expr2>, Box<Expr2>, Op),

    Ternary(Box<Expr2>, Box<Expr2>, Box<Expr2>),

    Assignment(Box<Expr2>, Box<Expr2>),
    ArrowFunc(Box<Expr2>, String, Box<Expr2>),

    Comma(Box<Expr2>, Box<Expr2>),

    // Temp hack
    Print(Vec<Expr2>),
}
impl Expr2 {
    fn precedence(&self) -> Precedence {
        use Expr2::*;
        use Op::*;
        use Precedence::*;
        match self {
            Paren(..) => PRIMARY,
            Literal(..) => PRIMARY,
            Obj(..) => PRIMARY,
            Var(..) => PRIMARY,
            Field(..) => MEMBER,
            Call(..) => CALL,
            Minus(..) => UNARY,
            BinOp(_, _, op) => match op {
                Mult | Div | Rem => MULTIPLICATIVE,
                Add | Sub => ADDITIVE,
                Lt | Lte | Gt | Gte => RELATIONAL,
                Eq | Neq => EQUALITY,
            },
            Ternary(..) => CONDITIONAL,
            Assignment(..) => ASSIGN,
            ArrowFunc(..) => ASSIGN,
            Comma(..) => EXPR,
            Print(..) => CALL,
        }
    }

    fn first(&self) -> Token {
        use Expr2::*;
        use Token::*;
        match self {
            Paren(..) => PAREN,
            Literal(..) => OTHER,
            Obj(..) => BRACE,
            Var(..) => OTHER,
            Field(lhs, ..) => lhs.first(),
            Call(lhs, ..) => lhs.first(),
            Minus(..) => OTHER,
            BinOp(lhs, ..) => lhs.first(),
            Ternary(lhs, ..) => lhs.first(),
            Assignment(lhs, ..) => lhs.first(),
            ArrowFunc(..) => PAREN,
            Comma(lhs, ..) => lhs.first(),
            Print(..) => OTHER,
        }
    }

    fn write(&self, out: &mut String) {
        match self {
            Self::Paren(e) => {
                *out += "(";
                e.write(out);
                *out += ")";
            }
            Self::Literal(code) => {
                *out += code;
            }
            Self::Obj(fields) => {
                *out += "{";
                for prop_def in fields {
                    use PropertyDefinition::*;
                    match prop_def {
                        Named(name, val) => {
                            *out += "'";
                            *out += name;
                            *out += "': ";
                            val.write(out);
                        }
                        Spread(val) => {
                            *out += "...";
                            val.write(out);
                        }
                    }

                    *out += ", ";
                }
                *out += "}";
            }
            Self::Var(name) => {
                *out += name;
            }
            Self::Field(lhs, rhs) => {
                lhs.write(out);
                *out += ".";
                *out += rhs;
            }
            Self::Call(lhs, rhs) => {
                lhs.write(out);
                *out += "(";
                rhs.write(out);
                *out += ")";
            }
            Self::Minus(e) => {
                *out += "-";
                e.write(out);
            }
            Self::BinOp(lhs, rhs, op) => {
                use Op::*;
                let opstr = match op {
                    Add => "+",
                    Sub => "- ",
                    Mult => "*",
                    Div => "/",
                    Rem => "%",

                    Lt => "<",
                    Lte => "<=",
                    Gt => ">",
                    Gte => ">=",

                    Eq => "===",
                    Neq => "!==",
                };

                lhs.write(out);
                *out += opstr;
                rhs.write(out);
            }
            Self::Ternary(cond, e1, e2) => {
                cond.write(out);
                *out += " ? ";
                e1.write(out);
                *out += " : ";
                e2.write(out);
            }
            Self::Assignment(lhs, rhs) => {
                lhs.write(out);
                *out += " = ";
                rhs.write(out);
            }
            Self::ArrowFunc(arg, scope_arg, body) => {
                *out += "(";
                arg.write(out);
                *out += ", ";
                *out += scope_arg;
                *out += "={}) => ";
                body.write(out);
            }
            Self::Comma(lhs, rhs) => {
                lhs.write(out);
                *out += ", ";
                rhs.write(out);
            }
            Self::Print(exprs) => {
                *out += "p.println(";
                for ex in exprs {
                    ex.write(out);
                    *out += ", ";
                }
                *out += ")";
            }
        }
    }

    fn wrap_in_parens(&mut self) {
        use Expr2::*;
        let dummy = Literal("".to_string());
        let temp = std::mem::replace(self, dummy);
        *self = Paren(Box::new(temp));
    }

    fn ensure(&mut self, required: Precedence) {
        if self.precedence() > required {
            self.wrap_in_parens();
        }
    }

    fn add_parens(&mut self) {
        use Precedence::*;
        match self {
            Self::Paren(e) => {
                e.add_parens();
            }
            Self::Literal(code) => {}
            Self::Obj(fields) => {
                for prop_def in fields {
                    use PropertyDefinition::*;
                    match prop_def {
                        Named(name, val) => {
                            val.add_parens();
                            val.ensure(ASSIGN);
                        }
                        Spread(val) => {
                            val.add_parens();
                            val.ensure(ASSIGN);
                        }
                    }
                }
            }
            Self::Var(name) => {}
            Self::Field(lhs, rhs) => {
                lhs.add_parens();
                lhs.ensure(MEMBER);
            }
            Self::Call(lhs, rhs) => {
                lhs.add_parens();
                lhs.ensure(MEMBER);
                rhs.add_parens();
                rhs.ensure(ASSIGN);
            }
            Self::Minus(e) => {
                e.add_parens();
                e.ensure(UNARY);
            }
            Self::BinOp(lhs, rhs, op) => {
                use Op::*;
                let req = match op {
                    Mult | Div | Rem => (MULTIPLICATIVE, EXPONENT),
                    Add | Sub => (ADDITIVE, MULTIPLICATIVE),
                    Lt | Lte | Gt | Gte => (RELATIONAL, SHIFT),
                    Eq | Neq => (EQUALITY, RELATIONAL),
                };

                lhs.add_parens();
                lhs.ensure(req.0);
                rhs.add_parens();
                rhs.ensure(req.1);
            }
            Self::Ternary(cond, e1, e2) => {
                cond.add_parens();
                e1.add_parens();
                e1.ensure(ASSIGN);
                e2.add_parens();
                e2.ensure(ASSIGN);
            }
            Self::Assignment(lhs, rhs) => {
                lhs.add_parens();
                lhs.ensure(LHS);
                rhs.add_parens();
                rhs.ensure(ASSIGN);
            }
            Self::ArrowFunc(arg, scope_arg, body) => {
                arg.add_parens();
                body.add_parens();
                body.ensure(ASSIGN);
                // body can't be an expression starting with "{"
                if body.first() == Token::BRACE {
                    body.wrap_in_parens();
                }
            }
            Self::Comma(lhs, rhs) => {
                lhs.add_parens();
                rhs.add_parens();
                rhs.ensure(ASSIGN);
            }
            Self::Print(exprs) => {
                for ex in exprs {
                    ex.add_parens();
                    ex.ensure(PRIMARY);
                }
            }
        }
    }
}


================================================
FILE: src/lib.rs
================================================
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]

mod ast;
mod codegen;
mod core;
mod grammar;
mod js;
mod reachability;
mod spans;
mod typeck;
mod utils;

use wasm_bindgen::prelude::*;

use std::fmt::Display;
use std::mem;

use lalrpop_util::ParseError;

use self::codegen::ModuleBuilder;
use self::grammar::ScriptParser;
use self::spans::{SpanMaker, SpanManager, SpannedError};
use self::typeck::TypeckState;

fn convert_parse_error<T: Display>(mut sm: SpanMaker, e: ParseError<usize, T, (&'static str, spans::Span)>) -> SpannedError {
    match e {
        ParseError::InvalidToken { location } => {
            SpannedError::new1("SyntaxError: Invalid token", sm.span(location, location))
        }
        ParseError::UnrecognizedEof { location, expected } => SpannedError::new1(
            format!(
                "SyntaxError: Unexpected end of input.\nNote: expected tokens: [{}]\nParse error occurred here:",
                expected.join(", ")
            ),
            sm.span(location, location),
        ),
        ParseError::UnrecognizedToken { token, expected } => SpannedError::new1(
            format!(
                "SyntaxError: Unexpected token {}\nNote: expected tokens: [{}]\nParse error occurred here:",
                token.1,
                expected.join(", ")
            ),
            sm.span(token.0, token.2),
        ),
        ParseError::ExtraToken { token } => {
            SpannedError::new1("SyntaxError: Unexpected extra token", sm.span(token.0, token.2))
        }
        ParseError::User { error: (msg, span) } => SpannedError::new1(msg, span),
    }
}

#[wasm_bindgen]
pub struct State {
    parser: ScriptParser,
    spans: SpanManager,

    checker: TypeckState,
    compiler: ModuleBuilder,

    out: Option<String>,
    err: Option<String>,
}
#[wasm_bindgen]
impl State {
    pub fn new() -> Self {
        State {
            parser: ScriptParser::new(),
            spans: SpanManager::default(),

            checker: TypeckState::new(),
            compiler: ModuleBuilder::new(),

            out: None,
            err: None,
        }
    }

    fn process_sub(&mut self, source: &str) -> Result<String, SpannedError> {
        let mut span_maker = self.spans.add_source(source.to_owned());

        let ast = self
            .parser
            .parse(&mut span_maker, source)
            .map_err(|e| convert_parse_error(span_maker, e))?;
        let _t = self.checker.check_script(&ast)?;
        let js_ast = self.compiler.compile_script(&ast);
        Ok(js_ast.to_source())
    }

    pub fn process(&mut self, source: &str) -> bool {
        let res = self.process_sub(source);
        match res {
            Ok(s) => {
                self.out = Some(s);
                true
            }
            Err(e) => {
                self.err = Some(e.print(&self.spans));
                false
            }
        }
    }

    pub fn get_output(&mut self) -> Option<String> {
        self.out.take()
    }
    pub fn get_err(&mut self) -> Option<String> {
        self.err.take()
    }

    pub fn reset(&mut self) {
        mem::swap(&mut self.checker, &mut TypeckState::new());
        mem::swap(&mut self.compiler, &mut ModuleBuilder::new());
    }
}


================================================
FILE: src/main.rs
================================================
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]

use std::env;
use std::fs;
use std::time::Instant;

use cubiml_demo::State;

fn main() {
    let mut state = State::new();
    for fname in env::args().skip(1) {
        println!("Processing {}", fname);
        let data = fs::read_to_string(fname).unwrap();
        // println!(">> {}", data);

        let t0 = Instant::now();
        let res = if state.process(&data) {
            state.get_output().unwrap()
        } else {
            state.get_err().unwrap()
        };
        dbg!(t0.elapsed());

        println!("{}", res);
    }
}


================================================
FILE: src/reachability.rs
================================================
use std::collections::HashSet;

#[derive(Debug, Default, Clone)]
struct OrderedSet<T> {
    v: Vec<T>,
    s: HashSet<T>,
}
impl<T: Eq + std::hash::Hash + Clone> OrderedSet<T> {
    fn insert(&mut self, value: T) -> bool {
        if self.s.insert(value.clone()) {
            self.v.push(value);
            true
        } else {
            false
        }
    }

    fn iter(&self) -> std::slice::Iter<T> {
        self.v.iter()
    }
}

type ID = usize;

#[derive(Debug, Default, Clone)]
pub struct Reachability {
    upsets: Vec<OrderedSet<ID>>,
    downsets: Vec<OrderedSet<ID>>,
}
impl Reachability {
    pub fn add_node(&mut self) -> ID {
        let i = self.upsets.len();
        self.upsets.push(Default::default());
        self.downsets.push(Default::default());
        i
    }

    pub fn add_edge(&mut self, lhs: ID, rhs: ID, out: &mut Vec<(ID, ID)>) {
        let mut work = vec![(lhs, rhs)];

        while let Some((lhs, rhs)) = work.pop() {
            // Insert returns false if the edge is already present
            if !self.downsets[lhs].insert(rhs) {
                continue;
            }
            self.upsets[rhs].insert(lhs);
            // Inform the caller that a new edge was added
            out.push((lhs, rhs));

            for &lhs2 in self.upsets[lhs].iter() {
                work.push((lhs2, rhs));
            }
            for &rhs2 in self.downsets[rhs].iter() {
                work.push((lhs, rhs2));
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test() {
        let mut r = Reachability::default();
        for i in 0..10 {
            r.add_node();
        }

        let mut out = Vec::new();
        r.add_edge(0, 8, &mut out);
        assert_eq!(out, vec![(0, 8)]);
        out.clear();
        r.add_edge(0, 8, &mut out);
        assert_eq!(out, vec![]);

        r.add_edge(0, 3, &mut out);
        r.add_edge(1, 3, &mut out);
        r.add_edge(2, 3, &mut out);
        r.add_edge(4, 5, &mut out);
        r.add_edge(4, 6, &mut out);
        r.add_edge(4, 7, &mut out);
        r.add_edge(6, 7, &mut out);
        r.add_edge(9, 1, &mut out);
        r.add_edge(9, 8, &mut out);

        out.clear();
        r.add_edge(3, 4, &mut out);

        let mut expected = Vec::new();
        for &lhs in &[0, 1, 2, 3, 9] {
            for &rhs in &[4, 5, 6, 7] {
                expected.push((lhs, rhs));
            }
        }

        out.sort_unstable();
        expected.sort_unstable();
        assert_eq!(out, expected);
    }
}


================================================
FILE: src/spans.rs
================================================
use std::collections::{HashMap, HashSet};
use std::error;
use std::fmt;

#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct Span(usize);

pub type Spanned<T> = (T, Span);

#[derive(Debug, Default)]
pub struct SpanManager {
    sources: Vec<String>,
    spans: Vec<(usize, usize, usize)>,
}
impl SpanManager {
    pub fn add_source(&mut self, source: String) -> SpanMaker {
        let i = self.sources.len();
        self.sources.push(source);
        SpanMaker {
            parent: self,
            source_ind: i,
            pool: Default::default(),
        }
    }

    pub fn print(&self, span: Span) -> String {
        let (source_ind, l, r) = self.spans[span.0];
        let source = &self.sources[source_ind];

        let mut out = String::new();

        assert!(l <= r);
        let (before, source) = source.split_at(l);

        // The matched span may contain newlines, so cut it off at the first newline if applicable
        let tok = source.split_at(r - l).0.split('\n').next().unwrap();
        let after = source.split_at(tok.len()).1;
        // let (source, after) = source.split_at(r);

        let mut b_iter = before.rsplit('\n');
        let line_before = b_iter.next().unwrap();
        let mut a_iter = after.split('\n');
        let line_after = a_iter.next().unwrap();

        // Lines before the span
        if let Some(line) = b_iter.next() {
            if let Some(line) = b_iter.next() {
                out += line;
                out += "\n";
            }
            out += line;
            out += "\n";
        }

        // Line of the span
        out += line_before;
        out += tok;
        out += line_after;
        out += "\n";

        // highlight line
        out += &" ".repeat(line_before.len());
        out += "^";
        out += &"~".repeat(std::cmp::max(1, tok.len()) - 1);
        out += &" ".repeat(line_after.len());
        out += "\n";

        // Lines after the span
        for _ in 0..2 {
            if let Some(line) = a_iter.next() {
                out += line;
                out += "\n";
            }
        }

        out
    }

    fn new_span(&mut self, source_ind: usize, l: usize, r: usize) -> Span {
        let i = self.spans.len();
        self.spans.push((source_ind, l, r));
        Span(i)
    }
}

#[derive(Debug)]
pub struct SpanMaker<'a> {
    parent: &'a mut SpanManager,
    source_ind: usize,
    pool: HashMap<(usize, usize), Span>,
}
impl<'a> SpanMaker<'a> {
    pub fn span(&mut self, l: usize, r: usize) -> Span {
        // Make the borrow checker happy
        let source_ind = self.source_ind;
        let parent = &mut self.parent;

        *self.pool.entry((l, r)).or_insert_with(|| parent.new_span(source_ind, l, r))
    }
}

#[derive(Debug)]
pub struct SpannedError {
    pairs: Vec<(String, Span)>,
}

impl SpannedError {
    pub fn new1(s1: impl Into<String>, s2: Span) -> Self {
        let p1 = (s1.into(), s2);
        SpannedError { pairs: vec![p1] }
    }

    pub fn new2(s1: impl Into<String>, s2: Span, s3: impl Into<String>, s4: Span) -> Self {
        let p1 = (s1.into(), s2);
        let p2 = (s3.into(), s4);
        SpannedError { pairs: vec![p1, p2] }
    }

    pub fn print(&self, sm: &SpanManager) -> String {
        let mut out = String::new();
        for (msg, span) in self.pairs.iter() {
            out += &msg;
            out += "\n";
            out += &sm.print(*span);
        }
        out
    }
}
impl fmt::Display for SpannedError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        Ok(())
    }
}
impl error::Error for SpannedError {}


================================================
FILE: src/typeck.rs
================================================
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, HashSet};
use std::error;
use std::fmt;
use std::rc::Rc;

use crate::ast;
use crate::core::*;
use crate::spans::{Span, SpannedError as SyntaxError};

type Result<T> = std::result::Result<T, SyntaxError>;

#[derive(Clone)]
enum Scheme {
    Mono(Value),
    PolyLet(Rc<RefCell<PolyLet>>),
    PolyLetRec(Rc<RefCell<PolyLetRec>>, usize),
}

struct PolyLet {
    saved_bindings: Bindings,
    saved_expr: ast::Expr,
    cached: Option<Value>,
}
impl PolyLet {
    fn new(saved_bindings: Bindings, saved_expr: ast::Expr, engine: &mut TypeCheckerCore) -> Result<Self> {
        let mut s = Self {
            saved_bindings,
            saved_expr,
            cached: None,
        };
        s.cached = Some(s.check(engine)?);
        Ok(s)
    }

    fn check(&mut self, engine: &mut TypeCheckerCore) -> Result<Value> {
        if let Some(v) = self.cached.take() {
            return Ok(v);
        }
        check_expr(engine, &mut self.saved_bindings, &self.saved_expr)
    }
}

struct PolyLetRec {
    saved_bindings: Bindings,
    saved_defs: Vec<(String, Box<ast::Expr>)>,
    cached: Option<Vec<(Value, Use)>>,
}
impl PolyLetRec {
    fn new(
        saved_bindings: Bindings,
        saved_defs: Vec<(String, Box<ast::Expr>)>,
        engine: &mut TypeCheckerCore,
    ) -> Result<Self> {
        let mut s = Self {
            saved_bindings,
            saved_defs,
            cached: None,
        };
        s.cached = Some(s.check(engine)?);
        Ok(s)
    }

    fn check(&mut self, engine: &mut TypeCheckerCore) -> Result<Vec<(Value, Use)>> {
        if let Some(v) = self.cached.take() {
            return Ok(v);
        }

        let saved_defs = &self.saved_defs;
        self.saved_bindings.in_child_scope(|bindings| {
            let mut temp_vars = Vec::with_capacity(saved_defs.len());
            for (name, _) in saved_defs.iter() {
                let (temp_type, temp_bound) = engine.var();
                bindings.insert(name.clone(), temp_type);
                temp_vars.push((temp_type, temp_bound));
            }

            for ((_, expr), (_, bound)) in saved_defs.iter().zip(&temp_vars) {
                let var_type = check_expr(engine, bindings, expr)?;
                engine.flow(var_type, *bound)?;
            }

            Ok(temp_vars)
        })
    }
}

struct UnwindPoint(usize);
struct Bindings {
    m: HashMap<String, Scheme>,
    changes: Vec<(String, Option<Scheme>)>,
}
impl Bindings {
    fn new() -> Self {
        Self {
            m: HashMap::new(),
            changes: Vec::new(),
        }
    }

    fn get(&self, k: &str) -> Option<&Scheme> {
        self.m.get(k)
    }

    fn insert_scheme(&mut self, k: String, v: Scheme) {
        let old = self.m.insert(k.clone(), v);
        self.changes.push((k, old));
    }

    fn insert(&mut self, k: String, v: Value) {
        self.insert_scheme(k, Scheme::Mono(v))
    }

    fn unwind_point(&mut self) -> UnwindPoint {
        UnwindPoint(self.changes.len())
    }

    fn unwind(&mut self, n: UnwindPoint) {
        let n = n.0;
        while self.changes.len() > n {
            let (k, old) = self.changes.pop().unwrap();
            match old {
                Some(v) => self.m.insert(k, v),
                None => self.m.remove(&k),
            };
        }
    }

    fn in_child_scope<T>(&mut self, cb: impl FnOnce(&mut Self) -> T) -> T {
        let n = self.unwind_point();
        let res = cb(self);
        self.unwind(n);
        res
    }
}

struct TypeVarBindings {
    level: u32,
    // Only valid if u32 <= self.level
    m: HashMap<String, ((Value, Use), u32, Span)>,
}
impl TypeVarBindings {
    fn new() -> Self {
        Self {
            level: 0,
            m: HashMap::new(),
        }
    }

    fn insert(&mut self, name: String, v: (Value, Use), span: Span) -> Option<Span> {
        self.m.insert(name, (v, self.level + 1, span)).map(|t| t.2)
    }
}

fn parse_type(engine: &mut TypeCheckerCore, bindings: &mut TypeVarBindings, tyexpr: &ast::TypeExpr) -> Result<(Value, Use)> {
    use ast::TypeExpr::*;
    match tyexpr {
        Alias(lhs, (name, span)) => {
            let (utype_value, utype) = engine.var();
            let (vtype, vtype_bound) = engine.var();

            let old = bindings.insert(name.to_string(), (vtype, utype), *span);
            if let Some(old_span) = old {
                return Err(SyntaxError::new2(
                    format!("SyntaxError: Redefinition of type variable '{}", name),
                    *span,
                    "Note: Type variable was already defined here",
                    old_span,
                ));
            }

            let lhs_type = parse_type(engine, bindings, lhs)?;
            engine.flow(lhs_type.0, vtype_bound)?;
            engine.flow(utype_value, lhs_type.1)?;
            // Make alias permanent by setting level to 0 now that definition is complete
            bindings.m.get_mut(name).unwrap().1 = 0;
            Ok((vtype, utype))
        }
        Case(ext, cases, span) => {
            // Create a dummy variable to use as the lazy flow values
            let dummy = engine.var();
            let (vtype, vtype_bound) = engine.var();

            let utype_wildcard = if let Some(ext) = ext {
                let ext_type = parse_type(engine, bindings, ext)?;
                engine.flow(ext_type.0, vtype_bound)?;
                Some((ext_type.1, dummy))
            } else {
                None
            };

            // Must do this *after* parsing wildcard as wildcards are unguarded
            bindings.level += 1;
            let mut utype_case_arms = Vec::new();
            for ((tag, tag_span), wrapped_expr) in cases {
                let wrapped_type = parse_type(engine, bindings, wrapped_expr)?;

                let case_value = engine.case((tag.clone(), wrapped_type.0), *tag_span);
                engine.flow(case_value, vtype_bound)?;
                utype_case_arms.push((tag.clone(), (wrapped_type.1, dummy)));
            }
            bindings.level -= 1;

            let utype = engine.case_use(utype_case_arms, utype_wildcard, *span);
            Ok((vtype, utype))
        }
        Func(((lhs, rhs), span)) => {
            bindings.level += 1;
            let lhs_type = parse_type(engine, bindings, lhs)?;
            let rhs_type = parse_type(engine, bindings, rhs)?;
            bindings.level -= 1;

            let utype = engine.func_use(lhs_type.0, rhs_type.1, *span);
            let vtype = engine.func(lhs_type.1, rhs_type.0, *span);
            Ok((vtype, utype))
        }
        Ident((s, span)) => match s.as_str() {
            "bool" => Ok((engine.bool(*span), engine.bool_use(*span))),
            "float" => Ok((engine.float(*span), engine.float_use(*span))),
            "int" => Ok((engine.int(*span), engine.int_use(*span))),
            "null" => Ok((engine.null(*span), engine.null_use(*span))),
            "str" => Ok((engine.str(*span), engine.str_use(*span))),
            "number" => {
                let (vtype, vtype_bound) = engine.var();
                let float_lit = engine.float(*span);
                let int_lit = engine.int(*span);
                engine.flow(float_lit, vtype_bound)?;
                engine.flow(int_lit, vtype_bound)?;
                Ok((vtype, engine.int_or_float_use(*span)))
            }
            "top" => {
                let (_, utype) = engine.var();
                let (vtype, vtype_bound) = engine.var();
                let float_lit = engine.float(*span);
                let bool_lit = engine.bool(*span);
                engine.flow(float_lit, vtype_bound)?;
                engine.flow(bool_lit, vtype_bound)?;
                Ok((vtype, utype))
            }
            "bot" => {
                let (vtype, _) = engine.var();
                let (utype_value, utype) = engine.var();
                let float_lit = engine.float_use(*span);
                let bool_lit = engine.bool_use(*span);
                engine.flow(utype_value, float_lit)?;
                engine.flow(utype_value, bool_lit)?;
                Ok((vtype, utype))
            }
            "_" => Ok(engine.var()),
            _ => Err(SyntaxError::new1(
                "SyntaxError: Unrecognized simple type (choices are bool, float, int, str, number, null, top, bot, or _)",
                *span,
            )),
        },
        Nullable(lhs, span) => {
            let lhs_type = parse_type(engine, bindings, lhs)?;
            let utype = engine.null_check_use(lhs_type.1, *span);

            let (vtype, vtype_bound) = engine.var();
            let null_lit = engine.null(*span);
            engine.flow(lhs_type.0, vtype_bound)?;
            engine.flow(null_lit, vtype_bound)?;
            Ok((vtype, utype))
        }
        Record(ext, fields, span) => {
            let (utype_value, utype) = engine.var();

            let vtype_wildcard = if let Some(ext) = ext {
                let ext_type = parse_type(engine, bindings, ext)?;
                engine.flow(utype_value, ext_type.1)?;
                Some(ext_type.0)
            } else {
                None
            };

            // Must do this *after* parsing wildcard as wildcards are unguarded
            bindings.level += 1;
            let mut vtype_fields = Vec::new();
            for ((name, name_span), wrapped_expr) in fields {
                let wrapped_type = parse_type(engine, bindings, wrapped_expr)?;

                let obj_use = engine.obj_use((name.clone(), wrapped_type.1), *name_span);
                engine.flow(utype_value, obj_use)?;
                vtype_fields.push((name.clone(), wrapped_type.0));
            }
            bindings.level -= 1;

            let vtype = engine.obj(vtype_fields, vtype_wildcard, *span);
            Ok((vtype, utype))
        }
        Ref(lhs, (rw, span)) => {
            use ast::Readability::*;
            bindings.level += 1;
            let lhs_type = parse_type(engine, bindings, lhs)?;
            bindings.level -= 1;

            let write = if *rw == ReadOnly {
                (None, None)
            } else {
                (Some(lhs_type.1), Some(lhs_type.0))
            };
            let read = if *rw == WriteOnly {
                (None, None)
            } else {
                (Some(lhs_type.0), Some(lhs_type.1))
            };

            let vtype = engine.reference(write.0, read.0, *span);
            let utype = engine.reference_use(write.1, read.1, *span);
            Ok((vtype, utype))
        }
        TypeVar((name, span)) => {
            if let Some((res, lvl, _)) = bindings.m.get(name.as_str()).copied() {
                if lvl <= bindings.level {
                    Ok(res)
                } else {
                    Err(SyntaxError::new1(
                    format!("SyntaxError: Unguarded type variable {}. Recursive type variables must be nested within a case, record field, function, or ref", name),
                    *span))
                }
            } else {
                Err(SyntaxError::new1(
                    format!("SyntaxError: Undefined type variable {}", name),
                    *span,
                ))
            }
        }
    }
}

fn parse_type_signature(engine: &mut TypeCheckerCore, tyexpr: &ast::TypeExpr) -> Result<(Value, Use)> {
    let mut bindings = TypeVarBindings::new();
    parse_type(engine, &mut bindings, tyexpr)
}

fn process_let_pattern(engine: &mut TypeCheckerCore, bindings: &mut Bindings, pat: &ast::LetPattern) -> Result<Use> {
    use ast::LetPattern::*;

    let (arg_type, arg_bound) = engine.var();
    match pat {
        Var(name) => {
            bindings.insert(name.clone(), arg_type);
        }
        Record(pairs) => {
            let mut field_names = HashMap::with_capacity(pairs.len());

            for ((name, name_span), sub_pattern) in pairs {
                if let Some(old_span) = field_names.insert(&*name, *name_span) {
                    return Err(SyntaxError::new2(
                        "SyntaxError: Repeated field pattern name",
                        *name_span,
                        "Note: Field was already bound here",
                        old_span,
                    ));
                }

                let field_bound = process_let_pattern(engine, bindings, &*sub_pattern)?;
                let bound = engine.obj_use((name.clone(), field_bound), *name_span);
                engine.flow(arg_type, bound)?;
            }
        }
    };
    Ok(arg_bound)
}

fn check_expr(engine: &mut TypeCheckerCore, bindings: &mut Bindings, expr: &ast::Expr) -> Result<Value> {
    use ast::Expr::*;

    match expr {
        BinOp((lhs_expr, lhs_span), (rhs_expr, rhs_span), op_type, op, full_span) => {
            use ast::OpType::*;
            let lhs_type = check_expr(engine, bindings, lhs_expr)?;
            let rhs_type = check_expr(engine, bindings, rhs_expr)?;

            Ok(match op_type {
                IntOp => {
                    let lhs_bound = engine.int_use(*lhs_span);
                    let rhs_bound = engine.int_use(*rhs_span);
                    engine.flow(lhs_type, lhs_bound)?;
                    engine.flow(rhs_type, rhs_bound)?;
                    engine.int(*full_span)
                }
                FloatOp => {
                    let lhs_bound = engine.float_use(*lhs_span);
                    let rhs_bound = engine.float_use(*rhs_span);
                    engine.flow(lhs_type, lhs_bound)?;
                    engine.flow(rhs_type, rhs_bound)?;
                    engine.float(*full_span)
                }
                StrOp => {
                    let lhs_bound = engine.str_use(*lhs_span);
                    let rhs_bound = engine.str_use(*rhs_span);
                    engine.flow(lhs_type, lhs_bound)?;
                    engine.flow(rhs_type, rhs_bound)?;
                    engine.str(*full_span)
                }
                IntOrFloatCmp => {
                    let lhs_bound = engine.int_or_float_use(*lhs_span);
                    let rhs_bound = engine.int_or_float_use(*rhs_span);
                    engine.flow(lhs_type, lhs_bound)?;
                    engine.flow(rhs_type, rhs_bound)?;
                    engine.bool(*full_span)
                }
                AnyCmp => engine.bool(*full_span),
            })
        }
        Block(statements, rest_expr) => {
            assert!(statements.len() >= 1);
            let mark = bindings.unwind_point();

            for stmt in statements.iter() {
                check_statement(engine, bindings, stmt)?;
            }

            let result_type = check_expr(engine, bindings, rest_expr)?;
            bindings.unwind(mark);
            Ok(result_type)
        }
        Call(func_expr, arg_expr, span) => {
            let func_type = check_expr(engine, bindings, func_expr)?;
            let arg_type = check_expr(engine, bindings, arg_expr)?;

            let (ret_type, ret_bound) = engine.var();
            let bound = engine.func_use(arg_type, ret_bound, *span);
            engine.flow(func_type, bound)?;
            Ok(ret_type)
        }
        Case((tag, span), val_expr) => {
            let val_type = check_expr(engine, bindings, val_expr)?;
            Ok(engine.case((tag.clone(), val_type), *span))
        }
        FieldAccess(lhs_expr, name, span) => {
            let lhs_type = check_expr(engine, bindings, lhs_expr)?;

            let (field_type, field_bound) = engine.var();
            let bound = engine.obj_use((name.clone(), field_bound), *span);
            engine.flow(lhs_type, bound)?;
            Ok(field_type)
        }
        FuncDef(((arg_pattern, body_expr), span)) => {
            let (arg_bound, body_type) = bindings.in_child_scope(|bindings| {
                let arg_bound = process_let_pattern(engine, bindings, arg_pattern)?;
                let body_type = check_expr(engine, bindings, body_expr)?;
                Ok((arg_bound, body_type))
            })?;
            Ok(engine.func(arg_bound, body_type, *span))
        }
        If((cond_expr, span), then_expr, else_expr) => {
            // Handle conditions of the form foo == null and foo != null specially
            if let BinOp((lhs, _), (rhs, _), ast::OpType::AnyCmp, op, ..) = &**cond_expr {
                if let Variable((name, _)) = &**lhs {
                    if let Literal(ast::Literal::Null, ..) = **rhs {
                        if let Some(scheme) = bindings.get(name.as_str()) {
                            if let Scheme::Mono(lhs_type) = scheme {
                                // Flip order of branches if they wrote if foo == null instead of !=
                                let (ok_expr, else_expr) = match op {
                                    ast::Op::Neq => (then_expr, else_expr),
                                    ast::Op::Eq => (else_expr, then_expr),
                                    _ => unreachable!(),
                                };

                                let (nnvar_type, nnvar_bound) = engine.var();
                                let bound = engine.null_check_use(nnvar_bound, *span);
                                engine.flow(*lhs_type, bound)?;

                                let ok_type = bindings.in_child_scope(|bindings| {
                                    bindings.insert(name.clone(), nnvar_type);
                                    check_expr(engine, bindings, ok_expr)
                                })?;
                                let else_type = check_expr(engine, bindings, else_expr)?;

                                let (merged, merged_bound) = engine.var();
                                engine.flow(ok_type, merged_bound)?;
                                engine.flow(else_type, merged_bound)?;
                                return Ok(merged);
                            }
                        }
                    }
                }
            }

            let cond_type = check_expr(engine, bindings, cond_expr)?;
            let bound = engine.bool_use(*span);
            engine.flow(cond_type, bound)?;

            let then_type = check_expr(engine, bindings, then_expr)?;
            let else_type = check_expr(engine, bindings, else_expr)?;

            let (merged, merged_bound) = engine.var();
            engine.flow(then_type, merged_bound)?;
            engine.flow(else_type, merged_bound)?;
            Ok(merged)
        }
        Literal(type_, (code, span)) => {
            use ast::Literal::*;
            let span = *span;
            Ok(match type_ {
                Bool => engine.bool(span),
                Float => engine.float(span),
                Int => engine.int(span),
                Null => engine.null(span),
                Str => engine.str(span),
            })
        }
        Match(match_expr, cases, span) => {
            let match_type = check_expr(engine, bindings, match_expr)?;
            let (result_type, result_bound) = engine.var();

            // Result types from the match arms
            let mut case_type_pairs = Vec::with_capacity(cases.len());
            let mut wildcard_type = None;

            // Pattern reachability checking
            let mut case_names = HashMap::with_capacity(cases.len());
            let mut wildcard = None;

            for ((pattern, pattern_span), rhs_expr) in cases {
                if let Some(old_span) = wildcard {
                    return Err(SyntaxError::new2(
                        "SyntaxError: Unreachable match pattern",
                        *pattern_span,
                        "Note: Unreachable due to previous wildcard pattern here",
                        old_span,
                    ));
                }

                use ast::MatchPattern::*;
                match pattern {
                    Case(tag, name) => {
                        if let Some(old_span) = case_names.insert(&*tag, *pattern_span) {
                            return Err(SyntaxError::new2(
                                "SyntaxError: Unreachable match pattern",
                                *pattern_span,
                                "Note: Unreachable due to previous case pattern here",
                                old_span,
                            ));
                        }

                        let (wrapped_type, wrapped_bound) = engine.var();
                        let rhs_type = bindings.in_child_scope(|bindings| {
                            bindings.insert(name.clone(), wrapped_type);
                            check_expr(engine, bindings, rhs_expr)
                        })?;

                        case_type_pairs.push((tag.clone(), (wrapped_bound, (rhs_type, result_bound))));
                    }
                    Wildcard(name) => {
                        wildcard = Some(*pattern_span);

                        let (wrapped_type, wrapped_bound) = engine.var();
                        let rhs_type = bindings.in_child_scope(|bindings| {
                            bindings.insert(name.clone(), wrapped_type);
                            check_expr(engine, bindings, rhs_expr)
                        })?;

                        wildcard_type = Some((wrapped_bound, (rhs_type, result_bound)));
                    }
                }
            }

            let bound = engine.case_use(case_type_pairs, wildcard_type, *span);
            engine.flow(match_type, bound)?;

            Ok(result_type)
        }
        NewRef(expr, span) => {
            let expr_type = check_expr(engine, bindings, expr)?;
            let (read, write) = engine.var();
            engine.flow(expr_type, write)?;
            Ok(engine.reference(Some(write), Some(read), *span))
        }
        Record(proto, fields, span) => {
            let proto_type = match proto {
                Some(expr) => Some(check_expr(engine, bindings, expr)?),
                None => None,
            };

            let mut field_names = HashMap::with_capacity(fields.len());
            let mut field_type_pairs = Vec::with_capacity(fields.len());
            for ((name, name_span), expr) in fields {
                if let Some(old_span) = field_names.insert(&*name, *name_span) {
                    return Err(SyntaxError::new2(
                        "SyntaxError: Repeated field name",
                        *name_span,
                        "Note: Field was already defined here",
                        old_span,
                    ));
                }

                let t = check_expr(engine, bindings, expr)?;
                field_type_pairs.push((name.clone(), t));
            }
            Ok(engine.obj(field_type_pairs, proto_type, *span))
        }
        RefGet((expr, span)) => {
            let expr_type = check_expr(engine, bindings, expr)?;

            let (cell_type, cell_bound) = engine.var();
            let bound = engine.reference_use(None, Some(cell_bound), *span);
            engine.flow(expr_type, bound)?;
            Ok(cell_type)
        }
        RefSet((lhs_expr, lhs_span), rhs_expr) => {
            let lhs_type = check_expr(engine, bindings, lhs_expr)?;
            let rhs_type = check_expr(engine, bindings, rhs_expr)?;

            let bound = engine.reference_use(Some(rhs_type), None, *lhs_span);
            engine.flow(lhs_type, bound)?;
            Ok(rhs_type)
        }
        Typed(expr, sig) => {
            let expr_type = check_expr(engine, bindings, expr)?;
            let sig_type = parse_type_signature(engine, sig)?;
            engine.flow(expr_type, sig_type.1)?;
            Ok(sig_type.0)
        }
        Variable((name, span)) => {
            if let Some(scheme) = bindings.get(name.as_str()) {
                match scheme {
                    Scheme::Mono(v) => Ok(*v),
                    Scheme::PolyLet(cb) => cb.borrow_mut().check(engine),
                    Scheme::PolyLetRec(cb, i) => Ok(cb.borrow_mut().check(engine)?[*i].0),
                }
            } else {
                Err(SyntaxError::new1(format!("SyntaxError: Undefined variable {}", name), *span))
            }
        }
    }
}

fn check_let_def(
    engine: &mut TypeCheckerCore,
    bindings: &mut Bindings,
    lhs: &ast::LetPattern,
    expr: &ast::Expr,
) -> Result<()> {
    use ast::LetPattern::*;
    if let ast::Expr::FuncDef((_, span)) = expr {
        let name = match lhs {
            Var(name) => name.to_owned(),
            _ => return Err(SyntaxError::new1(format!("TypeError: Cannot destructure function"), *span)),
        };

        let saved_bindings = Bindings {
            m: bindings.m.clone(),
            changes: Vec::new(),
        };
        let saved_expr = expr.clone();

        let f = PolyLet::new(saved_bindings, saved_expr, engine)?;
        bindings.insert_scheme(name, Scheme::PolyLet(Rc::new(RefCell::new(f))));
    } else {
        let var_type = check_expr(engine, bindings, expr)?;
        let bound = process_let_pattern(engine, bindings, lhs)?;
        engine.flow(var_type, bound)?;
    }
    Ok(())
}

fn check_let_rec_defs(
    engine: &mut TypeCheckerCore,
    bindings: &mut Bindings,
    defs: &Vec<(String, Box<ast::Expr>)>,
) -> Result<()> {
    let saved_bindings = Bindings {
        m: bindings.m.clone(),
        changes: Vec::new(),
    };
    let saved_defs = defs.clone();

    let f = PolyLetRec::new(saved_bindings, saved_defs, engine)?;
    let f = Rc::new(RefCell::new(f));

    for (i, (name, _)) in defs.iter().enumerate() {
        bindings.insert_scheme(name.clone(), Scheme::PolyLetRec(f.clone(), i));
    }
    Ok(())
}

fn check_statement(engine: &mut TypeCheckerCore, bindings: &mut Bindings, def: &ast::Statement) -> Result<()> {
    use ast::Statement::*;
    match def {
        Empty => {}
        Expr(expr) => {
            check_expr(engine, bindings, expr)?;
        }
        LetDef((pattern, var_expr)) => {
            check_let_def(engine, bindings, pattern, var_expr)?;
        }
        LetRecDef(defs) => {
            check_let_rec_defs(engine, bindings, defs)?;
        }
        Println(exprs) => {
            for expr in exprs {
                check_expr(engine, bindings, expr)?;
            }
        }
    };
    Ok(())
}

pub struct TypeckState {
    core: TypeCheckerCore,
    bindings: Bindings,
}
impl TypeckState {
    pub fn new() -> Self {
        Self {
            core: TypeCheckerCore::new(),
            bindings: Bindings::new(),
        }
    }

    pub fn check_script(&mut self, parsed: &[ast::Statement]) -> Result<()> {
        // Create temporary copy of the entire type state so we can roll
        // back all the changes if the script contains an error.
        let temp = self.core.save();
        assert!(self.bindings.changes.is_empty());
        let mark = self.bindings.unwind_point();

        for item in parsed {
            if let Err(e) = check_statement(&mut self.core, &mut self.bindings, item) {
                // Roll back changes to the type state and bindings
                self.core.restore(temp);
                self.bindings.unwind(mark);
                return Err(e);
            }
        }

        // Now that script type-checked successfully, make the global definitions permanent
        // by removing them from the changes rollback list
        self.bindings.changes.clear();
        Ok(())
    }
}


================================================
FILE: src/utils.rs
================================================
#![allow(dead_code)]
pub fn set_panic_hook() {
    // When the `console_error_panic_hook` feature is enabled, we can call the
    // `set_panic_hook` function at least once during initialization, and then
    // we will get better error messages if our code ever panics.
    //
    // For more details see
    // https://github.com/rustwasm/console_error_panic_hook#readme
    #[cfg(feature = "console_error_panic_hook")]
    console_error_panic_hook::set_once();
}


================================================
FILE: tests/web.rs
================================================
//! Test suite for the Web and headless browsers.

#![cfg(target_arch = "wasm32")]

extern crate wasm_bindgen_test;
use wasm_bindgen_test::*;

wasm_bindgen_test_configure!(run_in_browser);

#[wasm_bindgen_test]
fn pass() {
    assert_eq!(1 + 1, 2);
}
Download .txt
gitextract_6axljtt2/

├── .gitignore
├── Cargo.toml
├── LICENSE_APACHE
├── LICENSE_MIT
├── README.md
├── demo.html
├── demo.js
├── rustfmt.toml
├── src/
│   ├── ast.rs
│   ├── codegen.rs
│   ├── core.rs
│   ├── grammar.lalr
│   ├── js.rs
│   ├── lib.rs
│   ├── main.rs
│   ├── reachability.rs
│   ├── spans.rs
│   ├── typeck.rs
│   └── utils.rs
└── tests/
    └── web.rs
Download .txt
SYMBOL INDEX (162 symbols across 12 files)

FILE: demo.js
  constant HTML (line 10) | const HTML = `
  class CubimlDemo (line 135) | class CubimlDemo extends HTMLElement {
    method constructor (line 136) | constructor() {
  function initializeRepl (line 153) | function initializeRepl(root, compiler, Printer) {
  class Printer (line 261) | class Printer {
    method constructor (line 262) | constructor() {
    method visit (line 267) | visit(e) {
    method println (line 308) | println(...args) {

FILE: src/ast.rs
  type Literal (line 4) | pub enum Literal {
  type Op (line 13) | pub enum Op {
  type OpType (line 30) | pub enum OpType {
  type LetDefinition (line 39) | type LetDefinition = (LetPattern, Box<Expr>);
  type VarDefinition (line 40) | type VarDefinition = (String, Box<Expr>);
  type LetPattern (line 43) | pub enum LetPattern {
  type MatchPattern (line 49) | pub enum MatchPattern {
  type Expr (line 55) | pub enum Expr {
  type Readability (line 74) | pub enum Readability {
  type TypeExpr (line 81) | pub enum TypeExpr {
  type Statement (line 93) | pub enum Statement {

FILE: src/codegen.rs
  type ModuleBuilder (line 7) | pub struct ModuleBuilder {
    method new (line 16) | pub fn new() -> Self {
    method compile_script (line 27) | pub fn compile_script(&mut self, def: &[ast::Statement]) -> js::Expr {
    method ml_scope (line 31) | fn ml_scope<T>(&mut self, cb: impl FnOnce(&mut Self) -> T) -> T {
    method fn_scope (line 44) | fn fn_scope<T>(&mut self, cb: impl FnOnce(&mut Self) -> T) -> T {
    method set_binding (line 58) | fn set_binding(&mut self, k: String, v: js::Expr) {
    method new_var_name (line 63) | fn new_var_name(&mut self) -> String {
    method new_temp_var (line 69) | fn new_temp_var(&mut self) -> js::Expr {
    method new_var (line 76) | fn new_var(&mut self, ml_name: &str) -> js::Expr {
    method new_scope_name (line 83) | fn new_scope_name(&mut self) -> String {
    method new_param_name (line 89) | fn new_param_name(&mut self) -> String {
  function compile (line 96) | fn compile(ctx: &mut ModuleBuilder, expr: &ast::Expr) -> js::Expr {
  function compile_let_pattern_flat (line 235) | fn compile_let_pattern_flat(ctx: &mut ModuleBuilder, out: &mut Vec<js::E...
  function compile_let_pattern (line 254) | fn compile_let_pattern(ctx: &mut ModuleBuilder, pat: &ast::LetPattern) -...
  function compile_statement (line 272) | fn compile_statement(ctx: &mut ModuleBuilder, exprs: &mut Vec<js::Expr>,...
  function compile_script (line 302) | fn compile_script(ctx: &mut ModuleBuilder, parsed: &[ast::Statement]) ->...
  function test (line 321) | fn test() {

FILE: src/core.rs
  type ID (line 8) | type ID = usize;
  type Value (line 11) | pub struct Value(ID);
  type Use (line 13) | pub struct Use(ID);
  type LazyFlow (line 15) | pub type LazyFlow = (Value, Use);
  type VTypeHead (line 18) | enum VTypeHead {
  type UTypeHead (line 41) | enum UTypeHead {
  function check_heads (line 68) | fn check_heads(
  type TypeNode (line 214) | enum TypeNode {
  type TypeCheckerCore (line 220) | pub struct TypeCheckerCore {
    method new (line 225) | pub fn new() -> Self {
    method flow (line 232) | pub fn flow(&mut self, lhs: Value, rhs: Use) -> Result<(), TypeError> {
    method new_val (line 251) | fn new_val(&mut self, val_type: VTypeHead, span: Span) -> Value {
    method new_use (line 258) | fn new_use(&mut self, constraint: UTypeHead, span: Span) -> Use {
    method var (line 265) | pub fn var(&mut self) -> (Value, Use) {
    method bool (line 272) | pub fn bool(&mut self, span: Span) -> Value {
    method float (line 275) | pub fn float(&mut self, span: Span) -> Value {
    method int (line 278) | pub fn int(&mut self, span: Span) -> Value {
    method null (line 281) | pub fn null(&mut self, span: Span) -> Value {
    method str (line 284) | pub fn str(&mut self, span: Span) -> Value {
    method bool_use (line 288) | pub fn bool_use(&mut self, span: Span) -> Use {
    method float_use (line 291) | pub fn float_use(&mut self, span: Span) -> Use {
    method int_use (line 294) | pub fn int_use(&mut self, span: Span) -> Use {
    method null_use (line 297) | pub fn null_use(&mut self, span: Span) -> Use {
    method str_use (line 300) | pub fn str_use(&mut self, span: Span) -> Use {
    method int_or_float_use (line 303) | pub fn int_or_float_use(&mut self, span: Span) -> Use {
    method func (line 307) | pub fn func(&mut self, arg: Use, ret: Value, span: Span) -> Value {
    method func_use (line 310) | pub fn func_use(&mut self, arg: Value, ret: Use, span: Span) -> Use {
    method obj (line 314) | pub fn obj(&mut self, fields: Vec<(String, Value)>, proto: Option<Valu...
    method obj_use (line 318) | pub fn obj_use(&mut self, field: (String, Use), span: Span) -> Use {
    method case (line 322) | pub fn case(&mut self, case: (String, Value), span: Span) -> Value {
    method case_use (line 325) | pub fn case_use(&mut self, cases: Vec<(String, (Use, LazyFlow))>, wild...
    method reference (line 330) | pub fn reference(&mut self, write: Option<Use>, read: Option<Value>, s...
    method reference_use (line 333) | pub fn reference_use(&mut self, write: Option<Value>, read: Option<Use...
    method null_check_use (line 337) | pub fn null_check_use(&mut self, nonnull: Use, span: Span) -> Use {
    method save (line 341) | pub fn save(&self) -> SavePoint {
    method restore (line 344) | pub fn restore(&mut self, mut save: SavePoint) {
  type SavePoint (line 350) | type SavePoint = (usize, reachability::Reachability);

FILE: src/js.rs
  type Op (line 4) | pub enum Op {
  function assign (line 21) | pub fn assign(lhs: Expr, rhs: Expr) -> Expr {
  function binop (line 24) | pub fn binop(lhs: Expr, rhs: Expr, op: Op) -> Expr {
  function call (line 27) | pub fn call(lhs: Expr, rhs: Expr) -> Expr {
  function comma_pair (line 30) | pub fn comma_pair(lhs: Expr, rhs: Expr) -> Expr {
  function unary_minus (line 33) | pub fn unary_minus(rhs: Expr) -> Expr {
  function eqop (line 36) | pub fn eqop(lhs: Expr, rhs: Expr) -> Expr {
  function field (line 39) | pub fn field(lhs: Expr, rhs: String) -> Expr {
  function lit (line 42) | pub fn lit(code: String) -> Expr {
  function ternary (line 45) | pub fn ternary(cond: Expr, e1: Expr, e2: Expr) -> Expr {
  function var (line 48) | pub fn var(s: String) -> Expr {
  function comma_list (line 52) | pub fn comma_list(mut exprs: Vec<Expr>) -> Expr {
  function println (line 62) | pub fn println(exprs: Vec<Expr>) -> Expr {
  function func (line 66) | pub fn func(arg: Expr, scope: String, body: Expr) -> Expr {
  function obj (line 70) | pub fn obj(spread: Option<Expr>, fields: Vec<(String, Expr)>) -> Expr {
  type Expr (line 83) | pub struct Expr(Expr2);
    method to_source (line 89) | pub fn to_source(mut self) -> String {
  type Token (line 99) | enum Token {
  type Precedence (line 106) | enum Precedence {
  type PropertyDefinition (line 125) | enum PropertyDefinition {
  type Expr2 (line 131) | enum Expr2 {
    method precedence (line 156) | fn precedence(&self) -> Precedence {
    method first (line 182) | fn first(&self) -> Token {
    method write (line 202) | fn write(&self, out: &mut String) {
    method wrap_in_parens (line 309) | fn wrap_in_parens(&mut self) {
    method ensure (line 316) | fn ensure(&mut self, required: Precedence) {
    method add_parens (line 322) | fn add_parens(&mut self) {

FILE: src/lib.rs
  function convert_parse_error (line 27) | fn convert_parse_error<T: Display>(mut sm: SpanMaker, e: ParseError<usiz...
  type State (line 55) | pub struct State {
    method new (line 67) | pub fn new() -> Self {
    method process_sub (line 80) | fn process_sub(&mut self, source: &str) -> Result<String, SpannedError> {
    method process (line 92) | pub fn process(&mut self, source: &str) -> bool {
    method get_output (line 106) | pub fn get_output(&mut self) -> Option<String> {
    method get_err (line 109) | pub fn get_err(&mut self) -> Option<String> {
    method reset (line 113) | pub fn reset(&mut self) {

FILE: src/main.rs
  function main (line 11) | fn main() {

FILE: src/reachability.rs
  type OrderedSet (line 4) | struct OrderedSet<T> {
  function insert (line 9) | fn insert(&mut self, value: T) -> bool {
  function iter (line 18) | fn iter(&self) -> std::slice::Iter<T> {
  type ID (line 23) | type ID = usize;
  type Reachability (line 26) | pub struct Reachability {
    method add_node (line 31) | pub fn add_node(&mut self) -> ID {
    method add_edge (line 38) | pub fn add_edge(&mut self, lhs: ID, rhs: ID, out: &mut Vec<(ID, ID)>) {
  function test (line 65) | fn test() {

FILE: src/spans.rs
  type Span (line 6) | pub struct Span(usize);
  type Spanned (line 8) | pub type Spanned<T> = (T, Span);
  type SpanManager (line 11) | pub struct SpanManager {
    method add_source (line 16) | pub fn add_source(&mut self, source: String) -> SpanMaker {
    method print (line 26) | pub fn print(&self, span: Span) -> String {
    method new_span (line 79) | fn new_span(&mut self, source_ind: usize, l: usize, r: usize) -> Span {
  type SpanMaker (line 87) | pub struct SpanMaker<'a> {
  function span (line 93) | pub fn span(&mut self, l: usize, r: usize) -> Span {
  type SpannedError (line 103) | pub struct SpannedError {
    method new1 (line 108) | pub fn new1(s1: impl Into<String>, s2: Span) -> Self {
    method new2 (line 113) | pub fn new2(s1: impl Into<String>, s2: Span, s3: impl Into<String>, s4...
    method print (line 119) | pub fn print(&self, sm: &SpanManager) -> String {
    method fmt (line 130) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {

FILE: src/typeck.rs
  type Result (line 11) | type Result<T> = std::result::Result<T, SyntaxError>;
  type Scheme (line 14) | enum Scheme {
  type PolyLet (line 20) | struct PolyLet {
    method new (line 26) | fn new(saved_bindings: Bindings, saved_expr: ast::Expr, engine: &mut T...
    method check (line 36) | fn check(&mut self, engine: &mut TypeCheckerCore) -> Result<Value> {
  type PolyLetRec (line 44) | struct PolyLetRec {
    method new (line 50) | fn new(
    method check (line 64) | fn check(&mut self, engine: &mut TypeCheckerCore) -> Result<Vec<(Value...
  type UnwindPoint (line 88) | struct UnwindPoint(usize);
  type Bindings (line 89) | struct Bindings {
    method new (line 94) | fn new() -> Self {
    method get (line 101) | fn get(&self, k: &str) -> Option<&Scheme> {
    method insert_scheme (line 105) | fn insert_scheme(&mut self, k: String, v: Scheme) {
    method insert (line 110) | fn insert(&mut self, k: String, v: Value) {
    method unwind_point (line 114) | fn unwind_point(&mut self) -> UnwindPoint {
    method unwind (line 118) | fn unwind(&mut self, n: UnwindPoint) {
    method in_child_scope (line 129) | fn in_child_scope<T>(&mut self, cb: impl FnOnce(&mut Self) -> T) -> T {
  type TypeVarBindings (line 137) | struct TypeVarBindings {
    method new (line 143) | fn new() -> Self {
    method insert (line 150) | fn insert(&mut self, name: String, v: (Value, Use), span: Span) -> Opt...
  function parse_type (line 155) | fn parse_type(engine: &mut TypeCheckerCore, bindings: &mut TypeVarBindin...
  function parse_type_signature (line 331) | fn parse_type_signature(engine: &mut TypeCheckerCore, tyexpr: &ast::Type...
  function process_let_pattern (line 336) | fn process_let_pattern(engine: &mut TypeCheckerCore, bindings: &mut Bind...
  function check_expr (line 366) | fn check_expr(engine: &mut TypeCheckerCore, bindings: &mut Bindings, exp...
  function check_let_def (line 631) | fn check_let_def(
  function check_let_rec_defs (line 660) | fn check_let_rec_defs(
  function check_statement (line 680) | fn check_statement(engine: &mut TypeCheckerCore, bindings: &mut Bindings...
  type TypeckState (line 702) | pub struct TypeckState {
    method new (line 707) | pub fn new() -> Self {
    method check_script (line 714) | pub fn check_script(&mut self, parsed: &[ast::Statement]) -> Result<()> {

FILE: src/utils.rs
  function set_panic_hook (line 2) | pub fn set_panic_hook() {

FILE: tests/web.rs
  function pass (line 11) | fn pass() {
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (127K chars).
[
  {
    "path": ".gitignore",
    "chars": 104,
    "preview": "/target\n**/*.rs.bk\nCargo.lock\nbin/\npkg/\nwasm-pack.log\n\nlocal.html\nsrc/grammar.rs\n\ntest*.ml\nperf.*\n*.svg\n"
  },
  {
    "path": "Cargo.toml",
    "chars": 800,
    "preview": "[package]\nname = \"cubiml-demo\"\nversion = \"0.0.0\"\nauthors = [\"Robert Grosse <n210241048576@gmail.com>\"]\nlicense = \"Apache"
  },
  {
    "path": "LICENSE_APACHE",
    "chars": 9723,
    "preview": "                              Apache License\n                        Version 2.0, January 2004\n                     http"
  },
  {
    "path": "LICENSE_MIT",
    "chars": 1083,
    "preview": "Copyright (c) 2020 Robert Grosse <n210241048576@gmail.com>\n\nPermission is hereby granted, free of charge, to any\nperson "
  },
  {
    "path": "README.md",
    "chars": 16915,
    "preview": "\nCubiml is a simple ML-like programming language with subtyping and full type inference. You can try it out online in yo"
  },
  {
    "path": "demo.html",
    "chars": 293,
    "preview": "<html>\n    <meta charset=\"utf-8\">\n    <head>\n        <title>CubiML demo</title>\n    </head>\n    <body>\n        <noscript"
  },
  {
    "path": "demo.js",
    "chars": 8686,
    "preview": "'use strict';\n\n\nlet mod = null;\nconst mod_promise = import('./pkg/cubiml_demo.js').then(\n    m => (mod = m, mod.default("
  },
  {
    "path": "rustfmt.toml",
    "chars": 16,
    "preview": "max_width = 125\n"
  },
  {
    "path": "src/ast.rs",
    "chars": 2115,
    "preview": "use crate::spans::{Span, Spanned};\n\n#[derive(Debug, Clone)]\npub enum Literal {\n    Bool,\n    Float,\n    Int,\n    Null,\n "
  },
  {
    "path": "src/codegen.rs",
    "chars": 11606,
    "preview": "use std::collections::HashMap;\nuse std::mem::swap;\n\nuse crate::ast;\nuse crate::js;\n\npub struct ModuleBuilder {\n    scope"
  },
  {
    "path": "src/core.rs",
    "chars": 10719,
    "preview": "use std::collections::HashMap;\nuse std::error;\nuse std::fmt;\n\nuse crate::reachability;\nuse crate::spans::{Span, SpannedE"
  },
  {
    "path": "src/grammar.lalr",
    "chars": 10028,
    "preview": "use lalrpop_util::ParseError;\n\nuse super::ast; // super instead of self because lalrpop wraps this in an internal module"
  },
  {
    "path": "src/js.rs",
    "chars": 11095,
    "preview": "#![allow(dead_code)]\n\n#[derive(Debug, Clone, Copy)]\npub enum Op {\n    Add,\n    Sub,\n    Mult,\n    Div,\n    Rem,\n\n    Lt,"
  },
  {
    "path": "src/lib.rs",
    "chars": 3251,
    "preview": "#![allow(dead_code)]\n#![allow(unused_imports)]\n#![allow(unused_variables)]\n\nmod ast;\nmod codegen;\nmod core;\nmod grammar;"
  },
  {
    "path": "src/main.rs",
    "chars": 620,
    "preview": "#![allow(dead_code)]\n#![allow(unused_imports)]\n#![allow(unused_variables)]\n\nuse std::env;\nuse std::fs;\nuse std::time::In"
  },
  {
    "path": "src/reachability.rs",
    "chars": 2538,
    "preview": "use std::collections::HashSet;\n\n#[derive(Debug, Default, Clone)]\nstruct OrderedSet<T> {\n    v: Vec<T>,\n    s: HashSet<T>"
  },
  {
    "path": "src/spans.rs",
    "chars": 3604,
    "preview": "use std::collections::{HashMap, HashSet};\nuse std::error;\nuse std::fmt;\n\n#[derive(Copy, Clone, Debug, Eq, PartialEq)]\npu"
  },
  {
    "path": "src/typeck.rs",
    "chars": 27407,
    "preview": "use std::cell::{Cell, RefCell};\nuse std::collections::{HashMap, HashSet};\nuse std::error;\nuse std::fmt;\nuse std::rc::Rc;"
  },
  {
    "path": "src/utils.rs",
    "chars": 466,
    "preview": "#![allow(dead_code)]\npub fn set_panic_hook() {\n    // When the `console_error_panic_hook` feature is enabled, we can cal"
  },
  {
    "path": "tests/web.rs",
    "chars": 251,
    "preview": "//! Test suite for the Web and headless browsers.\n\n#![cfg(target_arch = \"wasm32\")]\n\nextern crate wasm_bindgen_test;\nuse "
  }
]

About this extraction

This page contains the full source code of the Storyyeller/cubiml-demo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (118.5 KB), approximately 30.0k tokens, and a symbol index with 162 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!