├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── examples ├── binary-tree │ ├── bin │ │ ├── Int.vm │ │ ├── Main.vm │ │ └── Tree.vm │ └── src │ │ ├── Main.jill │ │ └── Tree.jill ├── complex-average │ ├── bin │ │ ├── Bool.vm │ │ ├── Fn.vm │ │ ├── Int.vm │ │ ├── List.vm │ │ └── Main.vm │ └── src │ │ └── Main.jill ├── hello-world │ ├── bin │ │ └── Main.vm │ └── src │ │ └── Main.jill ├── higher-lower │ ├── bin │ │ ├── Game.vm │ │ ├── Globals.vm │ │ ├── Guess.vm │ │ ├── Int.vm │ │ ├── Main.vm │ │ ├── Random.vm │ │ └── Sys.vm │ └── src │ │ ├── Game.jill │ │ ├── Guess.jill │ │ └── Main.jill ├── simple-average │ ├── bin │ │ ├── Bool.vm │ │ ├── Int.vm │ │ └── Main.vm │ └── src │ │ └── Main.jill └── username-validation │ ├── bin │ ├── Bool.vm │ ├── Fn.vm │ ├── List.vm │ ├── Main.vm │ └── StdUtils.vm │ └── src │ └── Main.jill └── src ├── codegen ├── common │ ├── compiler_internal_call.rs │ ├── expression.rs │ ├── function_call.rs │ ├── function_declaration.rs │ ├── function_reference.rs │ ├── helpers │ │ ├── array.rs │ │ ├── capture.rs │ │ ├── function.rs │ │ ├── function_override.rs │ │ ├── jack_api.rs │ │ ├── mod.rs │ │ └── variable.rs │ ├── literal.rs │ ├── mod.rs │ ├── variable.rs │ └── variable_name.rs ├── context │ ├── mod.rs │ ├── module.rs │ └── program.rs ├── error.rs ├── functions.rs ├── globals.rs ├── mod.rs ├── post_compilation │ ├── function_dispatch.rs │ ├── globals.rs │ ├── jillstd │ │ ├── Bool │ │ │ ├── and.vm │ │ │ ├── eq.vm │ │ │ ├── ge.vm │ │ │ ├── gt.vm │ │ │ ├── le.vm │ │ │ ├── lt.vm │ │ │ ├── ne.vm │ │ │ ├── not.vm │ │ │ └── or.vm │ │ ├── Int │ │ │ ├── add.vm │ │ │ ├── dec.vm │ │ │ ├── div.vm │ │ │ ├── inc.vm │ │ │ ├── max.vm │ │ │ ├── min.vm │ │ │ ├── mod.vm │ │ │ ├── mult.vm │ │ │ ├── neg.vm │ │ │ ├── sqrt.vm │ │ │ └── sub.vm │ │ ├── List │ │ │ ├── Empty.vm │ │ │ ├── List.vm │ │ │ ├── List_head.vm │ │ │ ├── List_tag.vm │ │ │ ├── List_tail.vm │ │ │ ├── all.vm │ │ │ ├── any.vm │ │ │ ├── dispose.Jill │ │ │ ├── dispose.vm │ │ │ ├── disposeWith.jill │ │ │ ├── disposeWith.vm │ │ │ ├── filter.jill │ │ │ ├── filter.vm │ │ │ ├── fold.vm │ │ │ ├── isEmpty.vm │ │ │ ├── length.vm │ │ │ ├── map.jill │ │ │ ├── map.vm │ │ │ ├── range.vm │ │ │ ├── repeat.vm │ │ │ ├── reverse.jill │ │ │ └── reverse.vm │ │ ├── Random │ │ │ ├── Random.vm │ │ │ ├── fromRange.vm │ │ │ └── next.vm │ │ ├── StdUtils │ │ │ └── identity.vm │ │ ├── Sys.vm │ │ └── mod.rs │ └── mod.rs ├── types.rs └── vm.rs ├── common ├── ast.rs └── mod.rs ├── fileio.rs ├── main.rs └── parser.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | /.dev 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "jillc" 3 | version = "0.1.0" 4 | description = "Compiler for the Jill programming language (an alternative to Jack from the Nand2Tetris course/platform)" 5 | edition = "2021" 6 | 7 | [dependencies] 8 | chumsky = "0.9" 9 | strum = { version = "0.26", features = ["derive"] } 10 | strum_macros = { version = "0.26" } 11 | heck = "0.5" 12 | ariadne = "0.4" 13 | phf = { version = "0.11", features = ["macros"] } 14 | anyhow = "1" 15 | clap = { version = "4.5", features = ["derive"] } 16 | 17 | 18 | [lints.clippy] 19 | pedantic = { level = "warn", priority = -1 } 20 | nursery = { level = "warn", priority = -1 } 21 | unwrap_used = "warn" 22 | module_name_repetitions = "allow" 23 | future_not_send = "allow" 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Jill programming language 2 | 3 | Jill is a functional programming language built for the [Nand2Tetris](https://www.nand2tetris.org/) platform, as an alternative to the original *Jack* high-level language. 4 | 5 | It is designed as a drop-in replacement for Jack, as it uses the same VM instruction set and underlying *HACK* architecture, and follows similar design principles (willing to sacrifice ease of use to favour ease of implementation), while offering an alternative to Jack's very object-oriented, verbose style (*I like to think of Jill as Jack's more elegant, modern sister*). 6 | 7 | ### Some notable features: 8 | - functions as first-class citizens (ability to store them in variables, pass them on to other functions as arguments, and return from functions as a result) 9 | - optimized tail-call recursion to use constant stack space (single stack frame) 10 | - data modeling using algebraic data types with primitive pattern-matching (per type variant) 11 | - note that, as with Jack, all variables are still effectively 16-bit integers, therefore Jill is dynamically typed 12 | - minimal language design 13 | - only 3 main concepts (types, variables and functions) 14 | - expressions can only be literals, variables or function calls 15 | - expanded standard library which is lazily-generated (instructions are generated only for modules and functions which were used in codebase) 16 | - common design choices of functional languages (no loops, variables are immutable, code is organized into modules etc.) 17 | 18 | 19 | ## Usage 20 | 21 | For an overview of Jill's concepts and syntax, take a look at the [language tour](https://github.com/mpatajac/jillc/wiki/Language-tour). 22 | 23 | ### Project structure 24 | 25 | At the root of your project you should have two directories - `/src`, which will contain all of your Jill code, and `/bin`, which will contain the generated `.vm` files. 26 | 27 | All of your project's Jill code should be written in `.jill` files, located in the `/src` directory at the root of your project, or one of its subdirectories. 28 | 29 | Once compiled, your projects' VM instructions will be located in the `bin` directory, at the root of your project. 30 | 31 | An example project structure: 32 | ``` 33 | project_root 34 | | README.md 35 | | 36 | |__src 37 | | | Main.jill 38 | | | Foo.jill 39 | | | ... 40 | | | 41 | | |__Model 42 | | | | Bar.jill 43 | | | | Baz.jill 44 | | | | ... 45 | | 46 | |__bin 47 | | | Main.vm 48 | | | Foo.vm 49 | | | Model_Bar.vm 50 | | | Model_Baz.vm 51 | | | ... 52 | ``` 53 | 54 | ### Examples 55 | You can find several examples of Jill code/projects in the `examples` directory. 56 | 57 | ## Setup 58 | 59 | ### Pre-built 60 | You can find a pre-built Windows executable in the latest [release](https://github.com/mpatajac/jillc/releases). 61 | 62 | ### Manual 63 | 64 | #### Prerequisites 65 | Make sure you have [the Rust toolchain](https://www.rust-lang.org/tools/install) installed and updated to a recent version. 66 | 67 | #### Installation 68 | After cloning this repository, you can either build and run the compiler in a single step: 69 | ```shell 70 | $ cargo run -- path_to_jill_project_root 71 | ``` 72 | 73 | or build the compiler using 74 | ```shell 75 | $ cargo build 76 | ``` 77 | and use the binary in `./target/debug` to compile your Jill codebase 78 | ```shell 79 | $ jillc [path_to_jill_project_root] 80 | ``` 81 | 82 | For more information on the compiler itself, you can always check the provided manual with `jillc --help`. 83 | -------------------------------------------------------------------------------- /examples/binary-tree/bin/Int.vm: -------------------------------------------------------------------------------- 1 | function Int.inc 0 2 | push argument 0 3 | push constant 1 4 | add 5 | return -------------------------------------------------------------------------------- /examples/binary-tree/bin/Main.vm: -------------------------------------------------------------------------------- 1 | function Main.printResult 0 2 | push argument 0 3 | call Output.printString 1 4 | pop temp 0 5 | push argument 1 6 | call Output.printInt 1 7 | pop temp 0 8 | call Output.println 0 9 | return 10 | function Main.printBoolResult 1 11 | push argument 1 12 | push constant 0 13 | eq 14 | if-goto SKIP_TRUE_0 15 | push constant 1 16 | goto SKIP_FALSE_0 17 | label SKIP_TRUE_0 18 | push constant 0 19 | label SKIP_FALSE_0 20 | pop local 0 21 | push argument 0 22 | push local 0 23 | call Main.printResult 2 24 | return 25 | function Main.main 1 26 | push constant 2 27 | call Tree.leaf 1 28 | push constant 7 29 | push constant 5 30 | call Tree.leaf 1 31 | push constant 6 32 | push constant 11 33 | call Tree.leaf 1 34 | call Tree.Node 3 35 | call Tree.Node 3 36 | push constant 1 37 | call Tree.Null 0 38 | push constant 9 39 | push constant 5 40 | call Tree.leaf 1 41 | push constant 9 42 | call Tree.Null 0 43 | call Tree.Node 3 44 | call Tree.Node 3 45 | call Tree.Node 3 46 | pop local 0 47 | push local 0 48 | call Tree.printValues 1 49 | pop temp 0 50 | call Output.println 0 51 | pop temp 0 52 | push constant 15 53 | call String.new 1 54 | push constant 78 55 | call String.appendChar 2 56 | push constant 111 57 | call String.appendChar 2 58 | push constant 100 59 | call String.appendChar 2 60 | push constant 101 61 | call String.appendChar 2 62 | push constant 115 63 | call String.appendChar 2 64 | push constant 32 65 | call String.appendChar 2 66 | push constant 105 67 | call String.appendChar 2 68 | push constant 110 69 | call String.appendChar 2 70 | push constant 32 71 | call String.appendChar 2 72 | push constant 116 73 | call String.appendChar 2 74 | push constant 114 75 | call String.appendChar 2 76 | push constant 101 77 | call String.appendChar 2 78 | push constant 101 79 | call String.appendChar 2 80 | push constant 58 81 | call String.appendChar 2 82 | push constant 32 83 | call String.appendChar 2 84 | push local 0 85 | call Tree.count 1 86 | call Main.printResult 2 87 | pop temp 0 88 | push constant 13 89 | call String.new 1 90 | push constant 83 91 | call String.appendChar 2 92 | push constant 117 93 | call String.appendChar 2 94 | push constant 109 95 | call String.appendChar 2 96 | push constant 32 97 | call String.appendChar 2 98 | push constant 111 99 | call String.appendChar 2 100 | push constant 102 101 | call String.appendChar 2 102 | push constant 32 103 | call String.appendChar 2 104 | push constant 116 105 | call String.appendChar 2 106 | push constant 114 107 | call String.appendChar 2 108 | push constant 101 109 | call String.appendChar 2 110 | push constant 101 111 | call String.appendChar 2 112 | push constant 58 113 | call String.appendChar 2 114 | push constant 32 115 | call String.appendChar 2 116 | push local 0 117 | call Tree.sum 1 118 | call Main.printResult 2 119 | pop temp 0 120 | push constant 13 121 | call String.new 1 122 | push constant 77 123 | call String.appendChar 2 124 | push constant 105 125 | call String.appendChar 2 126 | push constant 110 127 | call String.appendChar 2 128 | push constant 32 129 | call String.appendChar 2 130 | push constant 111 131 | call String.appendChar 2 132 | push constant 102 133 | call String.appendChar 2 134 | push constant 32 135 | call String.appendChar 2 136 | push constant 116 137 | call String.appendChar 2 138 | push constant 114 139 | call String.appendChar 2 140 | push constant 101 141 | call String.appendChar 2 142 | push constant 101 143 | call String.appendChar 2 144 | push constant 58 145 | call String.appendChar 2 146 | push constant 32 147 | call String.appendChar 2 148 | push local 0 149 | call Tree.min 1 150 | call Main.printResult 2 151 | pop temp 0 152 | push constant 13 153 | call String.new 1 154 | push constant 77 155 | call String.appendChar 2 156 | push constant 97 157 | call String.appendChar 2 158 | push constant 120 159 | call String.appendChar 2 160 | push constant 32 161 | call String.appendChar 2 162 | push constant 111 163 | call String.appendChar 2 164 | push constant 102 165 | call String.appendChar 2 166 | push constant 32 167 | call String.appendChar 2 168 | push constant 116 169 | call String.appendChar 2 170 | push constant 114 171 | call String.appendChar 2 172 | push constant 101 173 | call String.appendChar 2 174 | push constant 101 175 | call String.appendChar 2 176 | push constant 58 177 | call String.appendChar 2 178 | push constant 32 179 | call String.appendChar 2 180 | push local 0 181 | call Tree.max 1 182 | call Main.printResult 2 183 | pop temp 0 184 | push constant 12 185 | call String.new 1 186 | push constant 67 187 | call String.appendChar 2 188 | push constant 111 189 | call String.appendChar 2 190 | push constant 110 191 | call String.appendChar 2 192 | push constant 116 193 | call String.appendChar 2 194 | push constant 97 195 | call String.appendChar 2 196 | push constant 105 197 | call String.appendChar 2 198 | push constant 110 199 | call String.appendChar 2 200 | push constant 115 201 | call String.appendChar 2 202 | push constant 32 203 | call String.appendChar 2 204 | push constant 55 205 | call String.appendChar 2 206 | push constant 58 207 | call String.appendChar 2 208 | push constant 32 209 | call String.appendChar 2 210 | push local 0 211 | push constant 7 212 | call Tree.contains 2 213 | call Main.printBoolResult 2 214 | pop temp 0 215 | push constant 12 216 | call String.new 1 217 | push constant 67 218 | call String.appendChar 2 219 | push constant 111 220 | call String.appendChar 2 221 | push constant 110 222 | call String.appendChar 2 223 | push constant 116 224 | call String.appendChar 2 225 | push constant 97 226 | call String.appendChar 2 227 | push constant 105 228 | call String.appendChar 2 229 | push constant 110 230 | call String.appendChar 2 231 | push constant 115 232 | call String.appendChar 2 233 | push constant 32 234 | call String.appendChar 2 235 | push constant 51 236 | call String.appendChar 2 237 | push constant 58 238 | call String.appendChar 2 239 | push constant 32 240 | call String.appendChar 2 241 | push local 0 242 | push constant 3 243 | call Tree.contains 2 244 | call Main.printBoolResult 2 245 | return -------------------------------------------------------------------------------- /examples/binary-tree/bin/Tree.vm: -------------------------------------------------------------------------------- 1 | function Tree.BinaryTree_tag 0 2 | push argument 0 3 | pop pointer 0 4 | push this 0 5 | return 6 | function Tree.Null 0 7 | push constant 1 8 | call Memory.alloc 1 9 | pop pointer 0 10 | push constant 0 11 | pop this 0 12 | push pointer 0 13 | return 14 | function Tree.Node 0 15 | push constant 4 16 | call Memory.alloc 1 17 | pop pointer 0 18 | push argument 0 19 | pop this 1 20 | push argument 1 21 | pop this 2 22 | push argument 2 23 | pop this 3 24 | push constant 1 25 | pop this 0 26 | push pointer 0 27 | return 28 | function Tree.Node_left 0 29 | push argument 0 30 | pop pointer 0 31 | push this 1 32 | return 33 | function Tree.Node_value 0 34 | push argument 0 35 | pop pointer 0 36 | push this 2 37 | return 38 | function Tree.Node_right 0 39 | push argument 0 40 | pop pointer 0 41 | push this 3 42 | return 43 | function Tree.Node_updateLeft 0 44 | push argument 0 45 | pop pointer 0 46 | push argument 1 47 | push this 2 48 | push this 3 49 | call Tree.Node 3 50 | return 51 | function Tree.Node_updateValue 0 52 | push argument 0 53 | pop pointer 0 54 | push this 1 55 | push argument 1 56 | push this 3 57 | call Tree.Node 3 58 | return 59 | function Tree.Node_updateRight 0 60 | push argument 0 61 | pop pointer 0 62 | push this 1 63 | push this 2 64 | push argument 1 65 | call Tree.Node 3 66 | return 67 | function Tree.leaf 0 68 | call Tree.Null 0 69 | push argument 0 70 | call Tree.Null 0 71 | call Tree.Node 3 72 | return 73 | function Tree.printValues_inorderPrint 0 74 | label REC_CALL_0 75 | push argument 0 76 | call Tree.BinaryTree_tag 1 77 | pop temp 0 78 | push temp 0 79 | push constant 0 80 | eq 81 | if-goto VARIANT_0_0 82 | label VARIANT_1_0 83 | push argument 0 84 | call Tree.Node_left 1 85 | call Tree.printValues_inorderPrint 1 86 | pop temp 0 87 | push argument 0 88 | call Tree.Node_value 1 89 | call Output.printInt 1 90 | pop temp 0 91 | push constant 1 92 | call String.new 1 93 | push constant 32 94 | call String.appendChar 2 95 | call Output.printString 1 96 | pop temp 0 97 | push argument 0 98 | call Tree.Node_right 1 99 | pop argument 0 100 | goto REC_CALL_0 101 | goto MATCH_END_0 102 | label VARIANT_0_0 103 | push constant 0 104 | call String.new 1 105 | call Output.printString 1 106 | goto MATCH_END_0 107 | label MATCH_END_0 108 | return 109 | function Tree.printValues 0 110 | push argument 0 111 | call Tree.printValues_inorderPrint 1 112 | pop temp 0 113 | call Output.println 0 114 | return 115 | function Tree.count 0 116 | push argument 0 117 | call Tree.BinaryTree_tag 1 118 | pop temp 0 119 | push temp 0 120 | push constant 0 121 | eq 122 | if-goto VARIANT_0_0 123 | label VARIANT_1_0 124 | push argument 0 125 | call Tree.Node_left 1 126 | call Tree.count 1 127 | push argument 0 128 | call Tree.Node_right 1 129 | call Tree.count 1 130 | add 131 | call Int.inc 1 132 | goto MATCH_END_0 133 | label VARIANT_0_0 134 | push constant 0 135 | goto MATCH_END_0 136 | label MATCH_END_0 137 | return 138 | function Tree.sum 0 139 | push argument 0 140 | call Tree.BinaryTree_tag 1 141 | pop temp 0 142 | push temp 0 143 | push constant 0 144 | eq 145 | if-goto VARIANT_0_0 146 | label VARIANT_1_0 147 | push argument 0 148 | call Tree.Node_value 1 149 | push argument 0 150 | call Tree.Node_left 1 151 | call Tree.sum 1 152 | push argument 0 153 | call Tree.Node_right 1 154 | call Tree.sum 1 155 | add 156 | add 157 | goto MATCH_END_0 158 | label VARIANT_0_0 159 | push constant 0 160 | goto MATCH_END_0 161 | label MATCH_END_0 162 | return 163 | function Tree.min 0 164 | push argument 0 165 | call Tree.BinaryTree_tag 1 166 | pop temp 0 167 | push temp 0 168 | push constant 0 169 | eq 170 | if-goto VARIANT_0_0 171 | label VARIANT_1_0 172 | push argument 0 173 | call Tree.Node_value 1 174 | push argument 0 175 | call Tree.Node_left 1 176 | call Tree.min 1 177 | push argument 0 178 | call Tree.Node_right 1 179 | call Tree.min 1 180 | call Math.min 2 181 | call Math.min 2 182 | goto MATCH_END_0 183 | label VARIANT_0_0 184 | push constant 32767 185 | goto MATCH_END_0 186 | label MATCH_END_0 187 | return 188 | function Tree.max 0 189 | push argument 0 190 | call Tree.BinaryTree_tag 1 191 | pop temp 0 192 | push temp 0 193 | push constant 0 194 | eq 195 | if-goto VARIANT_0_0 196 | label VARIANT_1_0 197 | push argument 0 198 | call Tree.Node_value 1 199 | push argument 0 200 | call Tree.Node_left 1 201 | call Tree.max 1 202 | push argument 0 203 | call Tree.Node_right 1 204 | call Tree.max 1 205 | call Math.max 2 206 | call Math.max 2 207 | goto MATCH_END_0 208 | label VARIANT_0_0 209 | push constant 32767 210 | neg 211 | goto MATCH_END_0 212 | label MATCH_END_0 213 | return 214 | function Tree.contains 0 215 | push argument 0 216 | call Tree.BinaryTree_tag 1 217 | pop temp 0 218 | push temp 0 219 | push constant 0 220 | eq 221 | if-goto VARIANT_0_0 222 | label VARIANT_1_0 223 | push argument 0 224 | call Tree.Node_value 1 225 | push argument 1 226 | eq 227 | push argument 0 228 | call Tree.Node_left 1 229 | push argument 1 230 | call Tree.contains 2 231 | push argument 0 232 | call Tree.Node_right 1 233 | push argument 1 234 | call Tree.contains 2 235 | or 236 | or 237 | goto MATCH_END_0 238 | label VARIANT_0_0 239 | push constant 0 240 | goto MATCH_END_0 241 | label MATCH_END_0 242 | return -------------------------------------------------------------------------------- /examples/binary-tree/src/Main.jill: -------------------------------------------------------------------------------- 1 | -- @type String, Int -> () 2 | fn printResult label value = do( 3 | Output::printString(label), 4 | Output::printInt(value), 5 | Output::println(), 6 | ). 7 | 8 | -- @type String, Bool -> () 9 | fn printBoolResult label b = 10 | let value = ifElse(b, 1, 0), 11 | 12 | printResult(label, value). 13 | 14 | 15 | fn main = 16 | -- @type Tree::BinaryTree(Int) 17 | let tree = Tree::Node( 18 | Tree::Node ( 19 | Tree::leaf(2), 20 | 7, 21 | Tree::Node( 22 | Tree::leaf(5), 23 | 6, 24 | Tree::leaf(11) 25 | ) 26 | ), 27 | 1, 28 | Tree::Node( 29 | Tree::Null(), 30 | 9, 31 | Tree::Node( 32 | Tree::leaf(5), 33 | 9, 34 | Tree::Null(), 35 | ) 36 | ) 37 | ), 38 | 39 | do( 40 | Tree::printValues(tree), 41 | Output::println(), 42 | 43 | printResult("Nodes in tree: ", Tree::count(tree)), 44 | printResult("Sum of tree: ", Tree::sum(tree)), 45 | printResult("Min of tree: ", Tree::min(tree)), 46 | printResult("Max of tree: ", Tree::max(tree)), 47 | printBoolResult("Contains 7: ", Tree::contains(tree, 7)), 48 | printBoolResult("Contains 3: ", Tree::contains(tree, 3)), 49 | ). 50 | 51 | -------------------------------------------------------------------------------- /examples/binary-tree/src/Tree.jill: -------------------------------------------------------------------------------- 1 | type BinaryTree = 2 | -- @type Null 3 | Null, 4 | -- @type Node(BinaryTree(a), a, BinaryTree(a)) 5 | Node(left, value, right). 6 | 7 | -- Create a "leaf" node (with no sub-trees) 8 | -- @type a -> BinaryTree(a) 9 | fn leaf value = Node(Null(), value, Null()). 10 | 11 | -- @type BinaryTree(Int) -> () 12 | fn printValues tree = 13 | fn inorderPrint tree = BinaryTree:match( 14 | tree, 15 | -- Null 16 | Output::printString(""), 17 | -- Node(..) 18 | do( 19 | inorderPrint(Node:left(tree)), 20 | Output::printInt(Node:value(tree)), 21 | Output::printString(" "), 22 | inorderPrint(Node:right(tree)), 23 | ) 24 | ). 25 | 26 | do( 27 | inorderPrint(tree), 28 | Output::println(), 29 | ). 30 | 31 | 32 | -- @type BinaryTree(a) -> Int 33 | fn count tree = BinaryTree:match( 34 | tree, 35 | -- Null 36 | 0, 37 | -- Node(..) 38 | Int::inc(Int::add( 39 | count(Node:left(tree)), 40 | count(Node:right(tree)), 41 | )) 42 | ). 43 | 44 | 45 | -- @type BinaryTree(a) -> Int 46 | fn sum tree = BinaryTree:match( 47 | tree, 48 | -- Null 49 | 0, 50 | -- Node(..) 51 | Int::add( 52 | Node:value(tree), 53 | Int::add( 54 | sum(Node:left(tree)), 55 | sum(Node:right(tree)), 56 | ) 57 | ) 58 | ). 59 | 60 | 61 | -- @type BinaryTree(a) -> Int 62 | fn min tree = BinaryTree:match( 63 | tree, 64 | -- Null 65 | 32767, 66 | -- Node(..) 67 | Int::min( 68 | Node:value(tree), 69 | Int::min( 70 | min(Node:left(tree)), 71 | min(Node:right(tree)), 72 | ) 73 | ) 74 | ). 75 | 76 | 77 | -- @type BinaryTree(a) -> Int 78 | fn max tree = BinaryTree:match( 79 | tree, 80 | -- Null 81 | Int::neg(32767), 82 | -- Node(..) 83 | Int::max( 84 | Node:value(tree), 85 | Int::max( 86 | max(Node:left(tree)), 87 | max(Node:right(tree)), 88 | ) 89 | ) 90 | ). 91 | 92 | 93 | -- @type BinaryTree(a) -> Bool 94 | fn contains tree elem = BinaryTree:match( 95 | tree, 96 | -- Null 97 | False, 98 | -- Node(..) 99 | Bool::or( 100 | Bool::eq(Node:value(tree), elem), 101 | Bool::or( 102 | contains(Node:left(tree), elem), 103 | contains(Node:right(tree), elem), 104 | ) 105 | ) 106 | ). 107 | -------------------------------------------------------------------------------- /examples/complex-average/bin/Bool.vm: -------------------------------------------------------------------------------- 1 | function Bool.le 0 2 | push argument 0 3 | push argument 1 4 | gt 5 | not 6 | return -------------------------------------------------------------------------------- /examples/complex-average/bin/Fn.vm: -------------------------------------------------------------------------------- 1 | function Fn._new 0 2 | push constant 2 3 | call Memory.alloc 1 4 | pop pointer 0 5 | push argument 0 6 | pop this 0 7 | push argument 1 8 | pop this 1 9 | push pointer 0 10 | return 11 | function Fn.dispose 0 12 | push argument 0 13 | pop pointer 0 14 | push this 1 15 | push constant 0 16 | eq 17 | if-goto SKIP_CAPTURES_DEALLOC 18 | push this 1 19 | call Array.dispose 1 20 | pop temp 7 21 | label SKIP_CAPTURES_DEALLOC 22 | push argument 0 23 | call Memory.deAlloc 1 24 | return 25 | function Fn._call 0 26 | push argument 0 27 | pop pointer 0 28 | push constant 0 29 | pop temp 7 30 | label ARGS_INIT_START 31 | push temp 7 32 | push argument 1 33 | eq 34 | if-goto ARGS_INIT_END 35 | push temp 7 36 | push argument 2 37 | add 38 | pop pointer 1 39 | push that 0 40 | push temp 7 41 | push constant 1 42 | add 43 | pop temp 7 44 | goto ARGS_INIT_START 45 | label ARGS_INIT_END 46 | push argument 2 47 | call Array.dispose 1 48 | pop temp 7 49 | push this 1 50 | push constant 0 51 | eq 52 | if-goto SKIP_CAPTURES 53 | push this 1 54 | label SKIP_CAPTURES 55 | push this 0 56 | push constant 3 57 | eq 58 | if-goto FN_0 59 | push this 0 60 | push constant 4 61 | eq 62 | if-goto FN_1 63 | push this 0 64 | push constant 0 65 | eq 66 | if-goto FN_2 67 | push this 0 68 | push constant 5 69 | eq 70 | if-goto FN_3 71 | push this 0 72 | push constant 6 73 | eq 74 | if-goto FN_4 75 | push this 0 76 | push constant 1 77 | eq 78 | if-goto FN_5 79 | push this 0 80 | push constant 2 81 | eq 82 | if-goto FN_6 83 | push constant 0 84 | return 85 | label FN_0 86 | call Int.max 2 87 | return 88 | label FN_1 89 | call Int.min 2 90 | return 91 | label FN_2 92 | call Int.add 2 93 | return 94 | label FN_3 95 | call Main.centeredAverage_total 1 96 | return 97 | label FN_4 98 | call Main.centeredAverage_count 1 99 | return 100 | label FN_5 101 | call Main.listSum 1 102 | return 103 | label FN_6 104 | call List.length 1 105 | return -------------------------------------------------------------------------------- /examples/complex-average/bin/Int.vm: -------------------------------------------------------------------------------- 1 | function Int.dec 0 2 | push argument 0 3 | push constant 1 4 | sub 5 | return 6 | function Int.min 0 7 | push argument 0 8 | push argument 1 9 | call Math.min 2 10 | return 11 | function Int.max 0 12 | push argument 0 13 | push argument 1 14 | call Math.max 2 15 | return 16 | function Int.inc 0 17 | push argument 0 18 | push constant 1 19 | add 20 | return 21 | function Int.add 0 22 | push argument 0 23 | push argument 1 24 | add 25 | return -------------------------------------------------------------------------------- /examples/complex-average/bin/List.vm: -------------------------------------------------------------------------------- 1 | function List.Empty 0 2 | push constant 1 3 | call Memory.alloc 1 4 | pop pointer 0 5 | push constant 0 6 | pop this 0 7 | push pointer 0 8 | return 9 | function List.length_lengthRec 0 10 | label REC_CALL_0 11 | push argument 1 12 | call List.List_tag 1 13 | push constant 0 14 | eq 15 | if-goto VARIANT_0_0 16 | push argument 0 17 | call Int.inc 1 18 | push argument 1 19 | call List.List_tail 1 20 | pop argument 1 21 | pop argument 0 22 | goto REC_CALL_0 23 | label VARIANT_0_0 24 | push argument 0 25 | return 26 | function List.length 0 27 | push constant 0 28 | push argument 0 29 | call List.length_lengthRec 2 30 | return 31 | function List.reverse_reverseRec 0 32 | label REC_CALL_0 33 | push argument 1 34 | call List.List_tag 1 35 | push constant 0 36 | eq 37 | if-goto VARIANT_0_0 38 | push argument 1 39 | call List.List_head 1 40 | push argument 0 41 | call List.List 2 42 | push argument 1 43 | call List.List_tail 1 44 | pop argument 1 45 | pop argument 0 46 | goto REC_CALL_0 47 | label VARIANT_0_0 48 | push argument 0 49 | return 50 | function List.reverse 0 51 | call List.Empty 0 52 | push argument 0 53 | call List.reverse_reverseRec 2 54 | return 55 | function List.List_tag 0 56 | push argument 0 57 | pop pointer 0 58 | push this 0 59 | return 60 | function List.List 0 61 | push constant 3 62 | call Memory.alloc 1 63 | pop pointer 0 64 | push argument 0 65 | pop this 1 66 | push argument 1 67 | pop this 2 68 | push constant 1 69 | pop this 0 70 | push pointer 0 71 | return 72 | function List.isEmpty 0 73 | push argument 0 74 | call List.List_tag 1 75 | push constant 0 76 | eq 77 | return 78 | function List.List_head 0 79 | push argument 0 80 | pop pointer 0 81 | push this 1 82 | return 83 | function List.List_tail 0 84 | push argument 0 85 | pop pointer 0 86 | push this 2 87 | return 88 | function List.fold 0 89 | label REC_CALL_0 90 | push argument 0 91 | call List.List_tag 1 92 | push constant 0 93 | eq 94 | if-goto VARIANT_0_0 95 | push argument 0 96 | call List.List_tail 1 97 | push constant 2 98 | call Array.new 1 99 | pop temp 1 100 | push constant 0 101 | push temp 1 102 | add 103 | push argument 0 104 | call List.List_head 1 105 | pop temp 0 106 | pop pointer 1 107 | push temp 0 108 | pop that 0 109 | push constant 1 110 | push temp 1 111 | add 112 | push argument 1 113 | pop temp 0 114 | pop pointer 1 115 | push temp 0 116 | pop that 0 117 | push argument 2 118 | push constant 2 119 | push temp 1 120 | call Fn._call 3 121 | pop argument 1 122 | pop argument 0 123 | goto REC_CALL_0 124 | label VARIANT_0_0 125 | push argument 1 126 | return -------------------------------------------------------------------------------- /examples/complex-average/bin/Main.vm: -------------------------------------------------------------------------------- 1 | function Main._baseAverage 0 2 | push constant 1 3 | call Array.new 1 4 | pop temp 1 5 | push constant 0 6 | push temp 1 7 | add 8 | push argument 0 9 | pop temp 0 10 | pop pointer 1 11 | push temp 0 12 | pop that 0 13 | push argument 1 14 | push constant 1 15 | push temp 1 16 | call Fn._call 3 17 | push constant 1 18 | call Array.new 1 19 | pop temp 1 20 | push constant 0 21 | push temp 1 22 | add 23 | push argument 0 24 | pop temp 0 25 | pop pointer 1 26 | push temp 0 27 | pop that 0 28 | push argument 2 29 | push constant 1 30 | push temp 1 31 | call Fn._call 3 32 | call Math.divide 2 33 | return 34 | function Main.listSum 0 35 | push argument 0 36 | push constant 0 37 | push constant 0 38 | push constant 0 39 | call Fn._new 2 40 | call List.fold 3 41 | return 42 | function Main.average 0 43 | push argument 0 44 | push constant 1 45 | push constant 0 46 | call Fn._new 2 47 | push constant 2 48 | push constant 0 49 | call Fn._new 2 50 | call Main._baseAverage 3 51 | return 52 | function Main.centeredAverage_count 0 53 | push argument 0 54 | call List.length 1 55 | push constant 2 56 | sub 57 | return 58 | function Main.centeredAverage_total__listOptimum 0 59 | push argument 0 60 | push argument 0 61 | call List.List_head 1 62 | push argument 1 63 | call List.fold 3 64 | return 65 | function Main.centeredAverage_total 3 66 | push argument 0 67 | call Main.listSum 1 68 | pop local 0 69 | push argument 0 70 | push constant 3 71 | push constant 0 72 | call Fn._new 2 73 | call Main.centeredAverage_total__listOptimum 2 74 | pop local 1 75 | push argument 0 76 | push constant 4 77 | push constant 0 78 | call Fn._new 2 79 | call Main.centeredAverage_total__listOptimum 2 80 | pop local 2 81 | push local 0 82 | push local 1 83 | push local 2 84 | add 85 | sub 86 | return 87 | function Main.centeredAverage 0 88 | push argument 0 89 | push constant 5 90 | push constant 0 91 | call Fn._new 2 92 | push constant 6 93 | push constant 0 94 | call Fn._new 2 95 | call Main._baseAverage 3 96 | return 97 | function Main.main_promptNum 0 98 | push constant 16 99 | call String.new 1 100 | push constant 69 101 | call String.appendChar 2 102 | push constant 110 103 | call String.appendChar 2 104 | push constant 116 105 | call String.appendChar 2 106 | push constant 101 107 | call String.appendChar 2 108 | push constant 114 109 | call String.appendChar 2 110 | push constant 32 111 | call String.appendChar 2 112 | push constant 97 113 | call String.appendChar 2 114 | push constant 32 115 | call String.appendChar 2 116 | push constant 110 117 | call String.appendChar 2 118 | push constant 117 119 | call String.appendChar 2 120 | push constant 109 121 | call String.appendChar 2 122 | push constant 98 123 | call String.appendChar 2 124 | push constant 101 125 | call String.appendChar 2 126 | push constant 114 127 | call String.appendChar 2 128 | push constant 58 129 | call String.appendChar 2 130 | push constant 32 131 | call String.appendChar 2 132 | call Keyboard.readInt 1 133 | return 134 | function Main.main_readNumbers 0 135 | label REC_CALL_0 136 | push argument 1 137 | push constant 0 138 | call Bool.le 2 139 | push constant 0 140 | eq 141 | if-goto SKIP_TRUE_0 142 | push argument 0 143 | call List.reverse 1 144 | goto SKIP_FALSE_0 145 | label SKIP_TRUE_0 146 | call Main.main_promptNum 0 147 | push argument 0 148 | call List.List 2 149 | push argument 1 150 | call Int.dec 1 151 | pop argument 1 152 | pop argument 0 153 | goto REC_CALL_0 154 | label SKIP_FALSE_0 155 | return 156 | function Main.main_printNumbers 0 157 | label REC_CALL_0 158 | push argument 0 159 | call List.isEmpty 1 160 | not 161 | push constant 0 162 | eq 163 | if-goto SKIP_IF_0 164 | push argument 0 165 | call List.List_head 1 166 | call Output.printInt 1 167 | pop temp 0 168 | push constant 2 169 | call String.new 1 170 | push constant 44 171 | call String.appendChar 2 172 | push constant 32 173 | call String.appendChar 2 174 | call Output.printString 1 175 | pop temp 0 176 | push argument 0 177 | call List.List_tail 1 178 | pop argument 0 179 | goto REC_CALL_0 180 | label SKIP_IF_0 181 | push constant 0 182 | return 183 | function Main.main 2 184 | push constant 18 185 | call String.new 1 186 | push constant 72 187 | call String.appendChar 2 188 | push constant 111 189 | call String.appendChar 2 190 | push constant 119 191 | call String.appendChar 2 192 | push constant 32 193 | call String.appendChar 2 194 | push constant 109 195 | call String.appendChar 2 196 | push constant 97 197 | call String.appendChar 2 198 | push constant 110 199 | call String.appendChar 2 200 | push constant 121 201 | call String.appendChar 2 202 | push constant 32 203 | call String.appendChar 2 204 | push constant 110 205 | call String.appendChar 2 206 | push constant 117 207 | call String.appendChar 2 208 | push constant 109 209 | call String.appendChar 2 210 | push constant 98 211 | call String.appendChar 2 212 | push constant 101 213 | call String.appendChar 2 214 | push constant 114 215 | call String.appendChar 2 216 | push constant 115 217 | call String.appendChar 2 218 | push constant 63 219 | call String.appendChar 2 220 | push constant 32 221 | call String.appendChar 2 222 | call Keyboard.readInt 1 223 | pop local 0 224 | call List.Empty 0 225 | push local 0 226 | call Main.main_readNumbers 2 227 | pop local 1 228 | call Output.println 0 229 | pop temp 0 230 | push constant 9 231 | call String.new 1 232 | push constant 78 233 | call String.appendChar 2 234 | push constant 117 235 | call String.appendChar 2 236 | push constant 109 237 | call String.appendChar 2 238 | push constant 98 239 | call String.appendChar 2 240 | push constant 101 241 | call String.appendChar 2 242 | push constant 114 243 | call String.appendChar 2 244 | push constant 115 245 | call String.appendChar 2 246 | push constant 58 247 | call String.appendChar 2 248 | push constant 32 249 | call String.appendChar 2 250 | call Output.printString 1 251 | pop temp 0 252 | push local 1 253 | call Main.main_printNumbers 1 254 | pop temp 0 255 | call Output.println 0 256 | pop temp 0 257 | push constant 9 258 | call String.new 1 259 | push constant 65 260 | call String.appendChar 2 261 | push constant 118 262 | call String.appendChar 2 263 | push constant 101 264 | call String.appendChar 2 265 | push constant 114 266 | call String.appendChar 2 267 | push constant 97 268 | call String.appendChar 2 269 | push constant 103 270 | call String.appendChar 2 271 | push constant 101 272 | call String.appendChar 2 273 | push constant 58 274 | call String.appendChar 2 275 | push constant 32 276 | call String.appendChar 2 277 | call Output.printString 1 278 | pop temp 0 279 | push local 1 280 | call Main.average 1 281 | call Output.printInt 1 282 | pop temp 0 283 | call Output.println 0 284 | pop temp 0 285 | push constant 18 286 | call String.new 1 287 | push constant 67 288 | call String.appendChar 2 289 | push constant 101 290 | call String.appendChar 2 291 | push constant 110 292 | call String.appendChar 2 293 | push constant 116 294 | call String.appendChar 2 295 | push constant 101 296 | call String.appendChar 2 297 | push constant 114 298 | call String.appendChar 2 299 | push constant 101 300 | call String.appendChar 2 301 | push constant 100 302 | call String.appendChar 2 303 | push constant 32 304 | call String.appendChar 2 305 | push constant 97 306 | call String.appendChar 2 307 | push constant 118 308 | call String.appendChar 2 309 | push constant 101 310 | call String.appendChar 2 311 | push constant 114 312 | call String.appendChar 2 313 | push constant 97 314 | call String.appendChar 2 315 | push constant 103 316 | call String.appendChar 2 317 | push constant 101 318 | call String.appendChar 2 319 | push constant 58 320 | call String.appendChar 2 321 | push constant 32 322 | call String.appendChar 2 323 | call Output.printString 1 324 | pop temp 0 325 | push local 1 326 | call Main.centeredAverage 1 327 | call Output.printInt 1 328 | pop temp 0 329 | call Output.println 0 330 | return -------------------------------------------------------------------------------- /examples/complex-average/src/Main.jill: -------------------------------------------------------------------------------- 1 | -- Generic average calculation (allows for normal average, weighted average etc.) 2 | -- @type List(Int), Fn(List(Int) -> Int), Fn(List(Int) -> Int) -> Int 3 | fn _baseAverage numbers determineTotal determineCount = 4 | Int::div( 5 | determineTotal(numbers), 6 | determineCount(numbers) 7 | ). 8 | 9 | -- Helper function for calculating the sum of a list. 10 | -- @type List(Int) -> Int 11 | fn listSum numbers = 12 | List::fold(numbers, 0, &Int::add). 13 | 14 | -- Classic average 15 | -- @type List(Int) -> Int 16 | fn average numbers = 17 | _baseAverage(numbers, &listSum, &List::length). 18 | 19 | -- Average without the smallest and largest element 20 | -- @type List(Int) -> Int 21 | fn centeredAverage numbers = 22 | fn count numbers = Int::sub(List::length(numbers), 2). 23 | 24 | fn total numbers = 25 | fn _listOptimum list optimumCmp = 26 | List::fold(list, List::List:head(list), optimumCmp). 27 | 28 | let sum = listSum(numbers), 29 | let listMax = _listOptimum(numbers, &Int::max), 30 | let listMin = _listOptimum(numbers, &Int::min), 31 | 32 | Int::sub(sum, Int::add(listMax, listMin)). 33 | 34 | 35 | _baseAverage(numbers, &total, &count). 36 | 37 | 38 | fn main = 39 | -- @type () -> Int 40 | fn promptNum = Keyboard::readInt("Enter a number: "). 41 | 42 | -- @type List(Int), Int -> List(Int) 43 | fn readNumbers numbers count = 44 | ifElse( 45 | Bool::le(count, 0), 46 | List::reverse(numbers), 47 | readNumbers( 48 | List::List(promptNum(), numbers), 49 | Int::dec(count) 50 | ) 51 | ). 52 | 53 | -- @type List(Int) -> () 54 | fn printNumbers numbers = 55 | if( 56 | Bool::not(List::isEmpty(numbers)), 57 | do( 58 | Output::printInt(List::List:head(numbers)), 59 | Output::printString(", "), 60 | printNumbers( 61 | List::List:tail(numbers) 62 | ) 63 | ) 64 | ). 65 | 66 | 67 | let length = Keyboard::readInt("How many numbers? "), 68 | let numbers = readNumbers(List::Empty(), length), 69 | 70 | do( 71 | Output::println(), 72 | 73 | Output::printString("Numbers: "), 74 | printNumbers(numbers), 75 | Output::println(), 76 | 77 | Output::printString("Average: "), 78 | Output::printInt(average(numbers)), 79 | Output::println(), 80 | 81 | Output::printString("Centered average: "), 82 | Output::printInt(centeredAverage(numbers)), 83 | Output::println() 84 | ). 85 | -------------------------------------------------------------------------------- /examples/hello-world/bin/Main.vm: -------------------------------------------------------------------------------- 1 | function Main.main 0 2 | push constant 13 3 | call String.new 1 4 | push constant 72 5 | call String.appendChar 2 6 | push constant 101 7 | call String.appendChar 2 8 | push constant 108 9 | call String.appendChar 2 10 | push constant 108 11 | call String.appendChar 2 12 | push constant 111 13 | call String.appendChar 2 14 | push constant 44 15 | call String.appendChar 2 16 | push constant 32 17 | call String.appendChar 2 18 | push constant 119 19 | call String.appendChar 2 20 | push constant 111 21 | call String.appendChar 2 22 | push constant 114 23 | call String.appendChar 2 24 | push constant 108 25 | call String.appendChar 2 26 | push constant 100 27 | call String.appendChar 2 28 | push constant 33 29 | call String.appendChar 2 30 | call Output.printString 1 31 | return -------------------------------------------------------------------------------- /examples/hello-world/src/Main.jill: -------------------------------------------------------------------------------- 1 | fn main = Output::printString("Hello, world!"). 2 | -------------------------------------------------------------------------------- /examples/higher-lower/bin/Game.vm: -------------------------------------------------------------------------------- 1 | function Game.enterGuess 0 2 | push constant 14 3 | call String.new 1 4 | push constant 69 5 | call String.appendChar 2 6 | push constant 110 7 | call String.appendChar 2 8 | push constant 116 9 | call String.appendChar 2 10 | push constant 101 11 | call String.appendChar 2 12 | push constant 114 13 | call String.appendChar 2 14 | push constant 32 15 | call String.appendChar 2 16 | push constant 110 17 | call String.appendChar 2 18 | push constant 117 19 | call String.appendChar 2 20 | push constant 109 21 | call String.appendChar 2 22 | push constant 98 23 | call String.appendChar 2 24 | push constant 101 25 | call String.appendChar 2 26 | push constant 114 27 | call String.appendChar 2 28 | push constant 58 29 | call String.appendChar 2 30 | push constant 32 31 | call String.appendChar 2 32 | call Keyboard.readInt 1 33 | return 34 | function Game.playTurn 2 35 | label REC_CALL_0 36 | push constant 100 37 | call Sys.wait 1 38 | pop temp 0 39 | call Game.enterGuess 0 40 | pop local 0 41 | push argument 0 42 | push local 0 43 | call Guess.determineGuessResult 2 44 | pop local 1 45 | push local 1 46 | call Guess.guessResultDisplay 1 47 | call Output.printString 1 48 | pop temp 0 49 | call Output.println 0 50 | pop temp 0 51 | call Output.println 0 52 | pop temp 0 53 | push local 1 54 | call Guess.isCorrectGuess 1 55 | push constant 0 56 | eq 57 | if-goto SKIP_TRUE_0 58 | push argument 1 59 | goto SKIP_FALSE_0 60 | label SKIP_TRUE_0 61 | push argument 0 62 | push argument 1 63 | call Int.inc 1 64 | pop argument 1 65 | pop argument 0 66 | goto REC_CALL_0 67 | label SKIP_FALSE_0 68 | return -------------------------------------------------------------------------------- /examples/higher-lower/bin/Globals.vm: -------------------------------------------------------------------------------- 1 | function Globals.init 0 2 | call Main._init__globals 0 3 | pop temp 0 4 | push constant 0 5 | return -------------------------------------------------------------------------------- /examples/higher-lower/bin/Guess.vm: -------------------------------------------------------------------------------- 1 | function Guess.GuessResult_tag 0 2 | push argument 0 3 | pop pointer 0 4 | push this 0 5 | return 6 | function Guess.Correct 0 7 | push constant 1 8 | call Memory.alloc 1 9 | pop pointer 0 10 | push constant 0 11 | pop this 0 12 | push pointer 0 13 | return 14 | function Guess.Higher 0 15 | push constant 1 16 | call Memory.alloc 1 17 | pop pointer 0 18 | push constant 1 19 | pop this 0 20 | push pointer 0 21 | return 22 | function Guess.Lower 0 23 | push constant 1 24 | call Memory.alloc 1 25 | pop pointer 0 26 | push constant 2 27 | pop this 0 28 | push pointer 0 29 | return 30 | function Guess.determineGuessResult 0 31 | push argument 0 32 | push argument 1 33 | eq 34 | push constant 0 35 | eq 36 | if-goto SKIP_TRUE_0 37 | call Guess.Correct 0 38 | goto SKIP_FALSE_0 39 | label SKIP_TRUE_0 40 | push argument 0 41 | push argument 1 42 | lt 43 | push constant 0 44 | eq 45 | if-goto SKIP_TRUE_1 46 | call Guess.Lower 0 47 | goto SKIP_FALSE_1 48 | label SKIP_TRUE_1 49 | call Guess.Higher 0 50 | label SKIP_FALSE_1 51 | label SKIP_FALSE_0 52 | return 53 | function Guess.guessResultDisplay 0 54 | push argument 0 55 | call Guess.GuessResult_tag 1 56 | pop temp 0 57 | push temp 0 58 | push constant 0 59 | eq 60 | if-goto VARIANT_0_0 61 | push temp 0 62 | push constant 1 63 | eq 64 | if-goto VARIANT_1_0 65 | label VARIANT_2_0 66 | push constant 6 67 | call String.new 1 68 | push constant 76 69 | call String.appendChar 2 70 | push constant 111 71 | call String.appendChar 2 72 | push constant 119 73 | call String.appendChar 2 74 | push constant 101 75 | call String.appendChar 2 76 | push constant 114 77 | call String.appendChar 2 78 | push constant 33 79 | call String.appendChar 2 80 | goto MATCH_END_0 81 | label VARIANT_1_0 82 | push constant 7 83 | call String.new 1 84 | push constant 72 85 | call String.appendChar 2 86 | push constant 105 87 | call String.appendChar 2 88 | push constant 103 89 | call String.appendChar 2 90 | push constant 104 91 | call String.appendChar 2 92 | push constant 101 93 | call String.appendChar 2 94 | push constant 114 95 | call String.appendChar 2 96 | push constant 33 97 | call String.appendChar 2 98 | goto MATCH_END_0 99 | label VARIANT_0_0 100 | push constant 8 101 | call String.new 1 102 | push constant 67 103 | call String.appendChar 2 104 | push constant 111 105 | call String.appendChar 2 106 | push constant 114 107 | call String.appendChar 2 108 | push constant 114 109 | call String.appendChar 2 110 | push constant 101 111 | call String.appendChar 2 112 | push constant 99 113 | call String.appendChar 2 114 | push constant 116 115 | call String.appendChar 2 116 | push constant 33 117 | call String.appendChar 2 118 | goto MATCH_END_0 119 | label MATCH_END_0 120 | return 121 | function Guess.isCorrectGuess 0 122 | push argument 0 123 | call Guess.GuessResult_tag 1 124 | pop temp 0 125 | push temp 0 126 | push constant 0 127 | eq 128 | if-goto VARIANT_0_0 129 | push temp 0 130 | push constant 1 131 | eq 132 | if-goto VARIANT_1_0 133 | label VARIANT_2_0 134 | push constant 0 135 | goto MATCH_END_0 136 | label VARIANT_1_0 137 | push constant 0 138 | goto MATCH_END_0 139 | label VARIANT_0_0 140 | push constant 1 141 | neg 142 | goto MATCH_END_0 143 | label MATCH_END_0 144 | return -------------------------------------------------------------------------------- /examples/higher-lower/bin/Int.vm: -------------------------------------------------------------------------------- 1 | function Int.inc 0 2 | push argument 0 3 | push constant 1 4 | add 5 | return 6 | function Int.mod 0 7 | push argument 0 8 | push argument 0 9 | push argument 1 10 | call Math.divide 2 11 | push argument 1 12 | call Math.multiply 2 13 | sub 14 | return -------------------------------------------------------------------------------- /examples/higher-lower/bin/Main.vm: -------------------------------------------------------------------------------- 1 | function Main._init__globals 0 2 | push constant 128 3 | pop static 0 4 | push constant 0 5 | return 6 | function Main.printStringLn 0 7 | push argument 0 8 | call Output.printString 1 9 | pop temp 0 10 | call Output.println 0 11 | return 12 | function Main.pickRandomSeed_isEnterPressed 0 13 | push argument 0 14 | push static 0 15 | eq 16 | return 17 | function Main.pickRandomSeed_getPlayerInput 1 18 | label REC_CALL_0 19 | call Keyboard.keyPressed 0 20 | pop local 0 21 | push local 0 22 | call Main.pickRandomSeed_isEnterPressed 1 23 | push constant 0 24 | eq 25 | if-goto SKIP_TRUE_0 26 | push argument 0 27 | goto SKIP_FALSE_0 28 | label SKIP_TRUE_0 29 | push argument 0 30 | call Int.inc 1 31 | pop argument 0 32 | goto REC_CALL_0 33 | label SKIP_FALSE_0 34 | return 35 | function Main.pickRandomSeed 0 36 | push constant 0 37 | call Main.pickRandomSeed_getPlayerInput 1 38 | return 39 | function Main.main 4 40 | push constant 14 41 | call String.new 1 42 | push constant 72 43 | call String.appendChar 2 44 | push constant 73 45 | call String.appendChar 2 46 | push constant 71 47 | call String.appendChar 2 48 | push constant 72 49 | call String.appendChar 2 50 | push constant 69 51 | call String.appendChar 2 52 | push constant 82 53 | call String.appendChar 2 54 | push constant 32 55 | call String.appendChar 2 56 | push constant 45 57 | call String.appendChar 2 58 | push constant 32 59 | call String.appendChar 2 60 | push constant 76 61 | call String.appendChar 2 62 | push constant 79 63 | call String.appendChar 2 64 | push constant 87 65 | call String.appendChar 2 66 | push constant 69 67 | call String.appendChar 2 68 | push constant 82 69 | call String.appendChar 2 70 | call Main.printStringLn 1 71 | pop temp 0 72 | push constant 51 73 | call String.new 1 74 | push constant 84 75 | call String.appendChar 2 76 | push constant 114 77 | call String.appendChar 2 78 | push constant 121 79 | call String.appendChar 2 80 | push constant 32 81 | call String.appendChar 2 82 | push constant 116 83 | call String.appendChar 2 84 | push constant 111 85 | call String.appendChar 2 86 | push constant 32 87 | call String.appendChar 2 88 | push constant 103 89 | call String.appendChar 2 90 | push constant 117 91 | call String.appendChar 2 92 | push constant 101 93 | call String.appendChar 2 94 | push constant 115 95 | call String.appendChar 2 96 | push constant 115 97 | call String.appendChar 2 98 | push constant 32 99 | call String.appendChar 2 100 | push constant 97 101 | call String.appendChar 2 102 | push constant 32 103 | call String.appendChar 2 104 | push constant 110 105 | call String.appendChar 2 106 | push constant 117 107 | call String.appendChar 2 108 | push constant 109 109 | call String.appendChar 2 110 | push constant 98 111 | call String.appendChar 2 112 | push constant 101 113 | call String.appendChar 2 114 | push constant 114 115 | call String.appendChar 2 116 | push constant 32 117 | call String.appendChar 2 118 | push constant 40 119 | call String.appendChar 2 120 | push constant 49 121 | call String.appendChar 2 122 | push constant 45 123 | call String.appendChar 2 124 | push constant 49 125 | call String.appendChar 2 126 | push constant 48 127 | call String.appendChar 2 128 | push constant 48 129 | call String.appendChar 2 130 | push constant 41 131 | call String.appendChar 2 132 | push constant 32 133 | call String.appendChar 2 134 | push constant 119 135 | call String.appendChar 2 136 | push constant 105 137 | call String.appendChar 2 138 | push constant 116 139 | call String.appendChar 2 140 | push constant 104 141 | call String.appendChar 2 142 | push constant 32 143 | call String.appendChar 2 144 | push constant 102 145 | call String.appendChar 2 146 | push constant 101 147 | call String.appendChar 2 148 | push constant 119 149 | call String.appendChar 2 150 | push constant 101 151 | call String.appendChar 2 152 | push constant 115 153 | call String.appendChar 2 154 | push constant 116 155 | call String.appendChar 2 156 | push constant 32 157 | call String.appendChar 2 158 | push constant 97 159 | call String.appendChar 2 160 | push constant 116 161 | call String.appendChar 2 162 | push constant 116 163 | call String.appendChar 2 164 | push constant 101 165 | call String.appendChar 2 166 | push constant 109 167 | call String.appendChar 2 168 | push constant 112 169 | call String.appendChar 2 170 | push constant 116 171 | call String.appendChar 2 172 | push constant 115 173 | call String.appendChar 2 174 | push constant 33 175 | call String.appendChar 2 176 | call Main.printStringLn 1 177 | pop temp 0 178 | push constant 31 179 | call String.new 1 180 | push constant 80 181 | call String.appendChar 2 182 | push constant 114 183 | call String.appendChar 2 184 | push constant 101 185 | call String.appendChar 2 186 | push constant 115 187 | call String.appendChar 2 188 | push constant 115 189 | call String.appendChar 2 190 | push constant 32 191 | call String.appendChar 2 192 | push constant 39 193 | call String.appendChar 2 194 | push constant 69 195 | call String.appendChar 2 196 | push constant 110 197 | call String.appendChar 2 198 | push constant 116 199 | call String.appendChar 2 200 | push constant 101 201 | call String.appendChar 2 202 | push constant 114 203 | call String.appendChar 2 204 | push constant 39 205 | call String.appendChar 2 206 | push constant 32 207 | call String.appendChar 2 208 | push constant 116 209 | call String.appendChar 2 210 | push constant 111 211 | call String.appendChar 2 212 | push constant 32 213 | call String.appendChar 2 214 | push constant 115 215 | call String.appendChar 2 216 | push constant 116 217 | call String.appendChar 2 218 | push constant 97 219 | call String.appendChar 2 220 | push constant 114 221 | call String.appendChar 2 222 | push constant 116 223 | call String.appendChar 2 224 | push constant 32 225 | call String.appendChar 2 226 | push constant 116 227 | call String.appendChar 2 228 | push constant 104 229 | call String.appendChar 2 230 | push constant 101 231 | call String.appendChar 2 232 | push constant 32 233 | call String.appendChar 2 234 | push constant 103 235 | call String.appendChar 2 236 | push constant 97 237 | call String.appendChar 2 238 | push constant 109 239 | call String.appendChar 2 240 | push constant 101 241 | call String.appendChar 2 242 | call Main.printStringLn 1 243 | pop temp 0 244 | call Main.pickRandomSeed 0 245 | pop local 0 246 | push local 0 247 | call Random.Random 1 248 | pop local 1 249 | push local 1 250 | push constant 1 251 | push constant 101 252 | call Random.fromRange 3 253 | pop local 2 254 | push local 2 255 | push constant 1 256 | call Game.playTurn 2 257 | pop local 3 258 | call Output.println 0 259 | pop temp 0 260 | push constant 12 261 | call String.new 1 262 | push constant 73 263 | call String.appendChar 2 264 | push constant 116 265 | call String.appendChar 2 266 | push constant 32 267 | call String.appendChar 2 268 | push constant 116 269 | call String.appendChar 2 270 | push constant 111 271 | call String.appendChar 2 272 | push constant 111 273 | call String.appendChar 2 274 | push constant 107 275 | call String.appendChar 2 276 | push constant 32 277 | call String.appendChar 2 278 | push constant 121 279 | call String.appendChar 2 280 | push constant 111 281 | call String.appendChar 2 282 | push constant 117 283 | call String.appendChar 2 284 | push constant 32 285 | call String.appendChar 2 286 | call Output.printString 1 287 | pop temp 0 288 | push local 3 289 | call Output.printInt 1 290 | pop temp 0 291 | push constant 30 292 | call String.new 1 293 | push constant 32 294 | call String.appendChar 2 295 | push constant 97 296 | call String.appendChar 2 297 | push constant 116 298 | call String.appendChar 2 299 | push constant 116 300 | call String.appendChar 2 301 | push constant 101 302 | call String.appendChar 2 303 | push constant 109 304 | call String.appendChar 2 305 | push constant 112 306 | call String.appendChar 2 307 | push constant 116 308 | call String.appendChar 2 309 | push constant 115 310 | call String.appendChar 2 311 | push constant 32 312 | call String.appendChar 2 313 | push constant 116 314 | call String.appendChar 2 315 | push constant 111 316 | call String.appendChar 2 317 | push constant 32 318 | call String.appendChar 2 319 | push constant 103 320 | call String.appendChar 2 321 | push constant 117 322 | call String.appendChar 2 323 | push constant 101 324 | call String.appendChar 2 325 | push constant 115 326 | call String.appendChar 2 327 | push constant 115 328 | call String.appendChar 2 329 | push constant 32 330 | call String.appendChar 2 331 | push constant 116 332 | call String.appendChar 2 333 | push constant 104 334 | call String.appendChar 2 335 | push constant 101 336 | call String.appendChar 2 337 | push constant 32 338 | call String.appendChar 2 339 | push constant 110 340 | call String.appendChar 2 341 | push constant 117 342 | call String.appendChar 2 343 | push constant 109 344 | call String.appendChar 2 345 | push constant 98 346 | call String.appendChar 2 347 | push constant 101 348 | call String.appendChar 2 349 | push constant 114 350 | call String.appendChar 2 351 | push constant 33 352 | call String.appendChar 2 353 | call Output.printString 1 354 | pop temp 0 355 | call Output.println 0 356 | return -------------------------------------------------------------------------------- /examples/higher-lower/bin/Random.vm: -------------------------------------------------------------------------------- 1 | function Random.fromRange 1 2 | push argument 0 3 | call Random.next 1 4 | push argument 2 5 | push argument 1 6 | sub 7 | call Int.mod 2 8 | push argument 1 9 | add 10 | return 11 | function Random.next 2 12 | push argument 0 13 | pop pointer 0 14 | push this 2 15 | push this 1 16 | call Math.divide 2 17 | pop local 0 18 | push this 2 19 | push this 1 20 | call Int.mod 2 21 | pop local 1 22 | push this 1 23 | push this 0 24 | push local 0 25 | call Int.mod 2 26 | call Math.multiply 2 27 | push local 1 28 | push this 0 29 | push local 0 30 | call Math.divide 2 31 | call Math.multiply 2 32 | sub 33 | pop this 0 34 | push this 0 35 | push constant 0 36 | lt 37 | if-goto IF_TRUE0 38 | goto IF_FALSE0 39 | label IF_TRUE0 40 | push this 0 41 | push this 2 42 | add 43 | pop this 0 44 | label IF_FALSE0 45 | push this 0 46 | return 47 | function Random.Random 0 48 | push constant 3 49 | call Memory.alloc 1 50 | pop pointer 0 51 | push argument 0 52 | pop this 0 53 | push constant 219 54 | pop this 1 55 | push constant 32749 56 | pop this 2 57 | push pointer 0 58 | return -------------------------------------------------------------------------------- /examples/higher-lower/bin/Sys.vm: -------------------------------------------------------------------------------- 1 | function Sys.init 0 2 | call Memory.init 0 3 | pop temp 0 4 | call Math.init 0 5 | pop temp 0 6 | call Screen.init 0 7 | pop temp 0 8 | call Output.init 0 9 | pop temp 0 10 | call Keyboard.init 0 11 | pop temp 0 12 | call Globals.init 0 13 | pop temp 0 14 | call Main.main 0 15 | pop temp 0 16 | call Sys.halt 0 17 | pop temp 0 18 | push constant 0 19 | return 20 | function Sys.halt 0 21 | label WHILE_EXP0 22 | push constant 0 23 | not 24 | not 25 | if-goto WHILE_END0 26 | goto WHILE_EXP0 27 | label WHILE_END0 28 | push constant 0 29 | return 30 | function Sys.wait 1 31 | push argument 0 32 | push constant 0 33 | lt 34 | if-goto IF_TRUE0 35 | goto IF_FALSE0 36 | label IF_TRUE0 37 | push constant 1 38 | call Sys.error 1 39 | pop temp 0 40 | label IF_FALSE0 41 | label WHILE_EXP0 42 | push argument 0 43 | push constant 0 44 | gt 45 | not 46 | if-goto WHILE_END0 47 | push constant 50 48 | pop local 0 49 | label WHILE_EXP1 50 | push local 0 51 | push constant 0 52 | gt 53 | not 54 | if-goto WHILE_END1 55 | push local 0 56 | push constant 1 57 | sub 58 | pop local 0 59 | goto WHILE_EXP1 60 | label WHILE_END1 61 | push argument 0 62 | push constant 1 63 | sub 64 | pop argument 0 65 | goto WHILE_EXP0 66 | label WHILE_END0 67 | push constant 0 68 | return 69 | function Sys.error 0 70 | push constant 3 71 | call String.new 1 72 | push constant 69 73 | call String.appendChar 2 74 | push constant 82 75 | call String.appendChar 2 76 | push constant 82 77 | call String.appendChar 2 78 | call Output.printString 1 79 | pop temp 0 80 | push argument 0 81 | call Output.printInt 1 82 | pop temp 0 83 | call Sys.halt 0 84 | pop temp 0 85 | push constant 0 86 | return -------------------------------------------------------------------------------- /examples/higher-lower/src/Game.jill: -------------------------------------------------------------------------------- 1 | -- @type () -> Int 2 | fn enterGuess = Keyboard::readInt("Enter number: "). 3 | 4 | fn playTurn selectedNumber totalAttempts = 5 | let guess = do( 6 | -- start with a slight delay (to prevent accidental duplicate input) 7 | Sys::wait(100), 8 | enterGuess() 9 | ), 10 | 11 | let guessResult = Guess::determineGuessResult(selectedNumber, guess), 12 | 13 | do( 14 | -- output guess status 15 | Output::printString(Guess::guessResultDisplay(guessResult)), 16 | Output::println(), 17 | -- spacer line 18 | Output::println(), 19 | ifElse( 20 | -- if the guess is correct 21 | Guess::isCorrectGuess(guessResult), 22 | -- stop and return how many attempts it took 23 | totalAttempts, 24 | -- otherwise increment attempt count and repeat 25 | -- NOTE: tail-recursive, so only uses one stack frame 26 | playTurn(selectedNumber, Int::inc(totalAttempts)) 27 | ) 28 | ). 29 | -------------------------------------------------------------------------------- /examples/higher-lower/src/Guess.jill: -------------------------------------------------------------------------------- 1 | type GuessResult = 2 | Correct, 3 | Higher, 4 | Lower. 5 | 6 | -- @type Int, Int -> GuessResult 7 | fn determineGuessResult selectedNumber guess = 8 | ifElse( 9 | Bool::eq(selectedNumber, guess), 10 | Correct(), 11 | ifElse( 12 | Bool::lt(selectedNumber, guess), 13 | Lower(), 14 | Higher(), 15 | ) 16 | ). 17 | 18 | -- @type GuessResult -> String 19 | fn guessResultDisplay guessResult = 20 | -- `match` enables us to perform action 21 | -- based on the value's type variant 22 | GuessResult:match( 23 | -- value whoose type variant we are checking 24 | guessResult, 25 | -- action if first variant 26 | "Correct!", 27 | -- action if second variant 28 | "Higher!", 29 | -- action if third variant 30 | "Lower!", 31 | ). 32 | 33 | fn isCorrectGuess guessResult = 34 | GuessResult:match( 35 | guessResult, 36 | True, 37 | False, 38 | False, 39 | ). 40 | -------------------------------------------------------------------------------- /examples/higher-lower/src/Main.jill: -------------------------------------------------------------------------------- 1 | let enterKeyCode = 128. 2 | 3 | -- Print a string and follow it with a newline. 4 | -- @type String -> () 5 | fn printStringLn text = 6 | do( 7 | Output::printString(text), 8 | Output::println() 9 | ). 10 | 11 | 12 | -- Wait for user to input the "enter" keycode - 13 | -- used to generate a "seed" for further random generation. 14 | -- @type () -> Int 15 | fn pickRandomSeed = 16 | fn isEnterPressed input = Bool::eq(input, enterKeyCode). 17 | 18 | fn getPlayerInput seed = 19 | let userInput = Keyboard::keyPressed(), 20 | 21 | ifElse( 22 | -- if enter is pressed 23 | isEnterPressed(userInput), 24 | -- return current (random) seed 25 | seed, 26 | -- otherwise restart check with an updated seed 27 | getPlayerInput(Int::inc(seed)) 28 | ). 29 | 30 | -- start "seed loop" with a seed value of 0 31 | getPlayerInput(0). 32 | 33 | 34 | 35 | fn main = 36 | -- `do` block allows us to perform a series of actions 37 | -- it also returns the result of the last action 38 | let randomSeed = do( 39 | printStringLn("HIGHER - LOWER"), 40 | printStringLn("Try to guess a number (1-100) with fewest attempts!"), 41 | printStringLn("Press 'Enter' to start the game"), 42 | pickRandomSeed() 43 | ), 44 | 45 | -- create a random generator (provided in the JillStd) 46 | let random = Random::Random(randomSeed), 47 | 48 | -- pick a random number (for player to guess) 49 | -- NOTE: `fromRange` uses range [a, b> 50 | let selectedNumber = Random::fromRange(random, 1, 101), 51 | 52 | -- start a first guessing turn 53 | let totalAttempts = Game::playTurn(selectedNumber, 1), 54 | 55 | do( 56 | Output::println(), 57 | Output::printString("It took you "), 58 | Output::printInt(totalAttempts), 59 | Output::printString(" attempts to guess the number!"), 60 | Output::println() 61 | ). 62 | -------------------------------------------------------------------------------- /examples/simple-average/bin/Bool.vm: -------------------------------------------------------------------------------- 1 | function Bool.le 0 2 | push argument 0 3 | push argument 1 4 | gt 5 | not 6 | return -------------------------------------------------------------------------------- /examples/simple-average/bin/Int.vm: -------------------------------------------------------------------------------- 1 | function Int.dec 0 2 | push argument 0 3 | push constant 1 4 | sub 5 | return -------------------------------------------------------------------------------- /examples/simple-average/bin/Main.vm: -------------------------------------------------------------------------------- 1 | function Main.sumNumbers_readNum 0 2 | push constant 16 3 | call String.new 1 4 | push constant 69 5 | call String.appendChar 2 6 | push constant 110 7 | call String.appendChar 2 8 | push constant 116 9 | call String.appendChar 2 10 | push constant 101 11 | call String.appendChar 2 12 | push constant 114 13 | call String.appendChar 2 14 | push constant 32 15 | call String.appendChar 2 16 | push constant 97 17 | call String.appendChar 2 18 | push constant 32 19 | call String.appendChar 2 20 | push constant 110 21 | call String.appendChar 2 22 | push constant 117 23 | call String.appendChar 2 24 | push constant 109 25 | call String.appendChar 2 26 | push constant 98 27 | call String.appendChar 2 28 | push constant 101 29 | call String.appendChar 2 30 | push constant 114 31 | call String.appendChar 2 32 | push constant 58 33 | call String.appendChar 2 34 | push constant 32 35 | call String.appendChar 2 36 | call Keyboard.readInt 1 37 | return 38 | function Main.sumNumbers 0 39 | label REC_CALL_0 40 | push argument 1 41 | push constant 0 42 | call Bool.le 2 43 | push constant 0 44 | eq 45 | if-goto SKIP_TRUE_0 46 | push argument 0 47 | goto SKIP_FALSE_0 48 | label SKIP_TRUE_0 49 | call Main.sumNumbers_readNum 0 50 | push argument 0 51 | add 52 | push argument 1 53 | call Int.dec 1 54 | pop argument 1 55 | pop argument 0 56 | goto REC_CALL_0 57 | label SKIP_FALSE_0 58 | return 59 | function Main.main 3 60 | push constant 18 61 | call String.new 1 62 | push constant 72 63 | call String.appendChar 2 64 | push constant 111 65 | call String.appendChar 2 66 | push constant 119 67 | call String.appendChar 2 68 | push constant 32 69 | call String.appendChar 2 70 | push constant 109 71 | call String.appendChar 2 72 | push constant 97 73 | call String.appendChar 2 74 | push constant 110 75 | call String.appendChar 2 76 | push constant 121 77 | call String.appendChar 2 78 | push constant 32 79 | call String.appendChar 2 80 | push constant 110 81 | call String.appendChar 2 82 | push constant 117 83 | call String.appendChar 2 84 | push constant 109 85 | call String.appendChar 2 86 | push constant 98 87 | call String.appendChar 2 88 | push constant 101 89 | call String.appendChar 2 90 | push constant 114 91 | call String.appendChar 2 92 | push constant 115 93 | call String.appendChar 2 94 | push constant 63 95 | call String.appendChar 2 96 | push constant 32 97 | call String.appendChar 2 98 | call Keyboard.readInt 1 99 | pop local 0 100 | push constant 0 101 | push local 0 102 | call Main.sumNumbers 2 103 | pop local 1 104 | push local 1 105 | push local 0 106 | call Math.divide 2 107 | pop local 2 108 | push constant 15 109 | call String.new 1 110 | push constant 84 111 | call String.appendChar 2 112 | push constant 104 113 | call String.appendChar 2 114 | push constant 101 115 | call String.appendChar 2 116 | push constant 32 117 | call String.appendChar 2 118 | push constant 97 119 | call String.appendChar 2 120 | push constant 118 121 | call String.appendChar 2 122 | push constant 101 123 | call String.appendChar 2 124 | push constant 114 125 | call String.appendChar 2 126 | push constant 97 127 | call String.appendChar 2 128 | push constant 103 129 | call String.appendChar 2 130 | push constant 101 131 | call String.appendChar 2 132 | push constant 32 133 | call String.appendChar 2 134 | push constant 105 135 | call String.appendChar 2 136 | push constant 115 137 | call String.appendChar 2 138 | push constant 32 139 | call String.appendChar 2 140 | call Output.printString 1 141 | pop temp 0 142 | push local 2 143 | call Output.printInt 1 144 | return -------------------------------------------------------------------------------- /examples/simple-average/src/Main.jill: -------------------------------------------------------------------------------- 1 | -- @type Int, Int -> Int 2 | fn sumNumbers total count = 3 | -- @type () -> Int 4 | fn readNum = Keyboard::readInt("Enter a number: "). 5 | 6 | ifElse( 7 | Bool::le(count, 0), 8 | total, 9 | sumNumbers( 10 | Int::add(readNum(), total), 11 | Int::dec(count) 12 | ) 13 | ). 14 | 15 | fn main = 16 | let length = Keyboard::readInt("How many numbers? "), 17 | let total = sumNumbers(0, length), 18 | let average = Int::div(total, length), 19 | 20 | do( 21 | Output::printString("The average is "), 22 | Output::printInt(average), 23 | ). 24 | -------------------------------------------------------------------------------- /examples/username-validation/bin/Bool.vm: -------------------------------------------------------------------------------- 1 | function Bool.ge 0 2 | push argument 0 3 | push argument 1 4 | lt 5 | not 6 | return 7 | function Bool.le 0 8 | push argument 0 9 | push argument 1 10 | gt 11 | not 12 | return -------------------------------------------------------------------------------- /examples/username-validation/bin/Fn.vm: -------------------------------------------------------------------------------- 1 | function Fn._new 0 2 | push constant 2 3 | call Memory.alloc 1 4 | pop pointer 0 5 | push argument 0 6 | pop this 0 7 | push argument 1 8 | pop this 1 9 | push pointer 0 10 | return 11 | function Fn.dispose 0 12 | push argument 0 13 | pop pointer 0 14 | push this 1 15 | push constant 0 16 | eq 17 | if-goto SKIP_CAPTURES_DEALLOC 18 | push this 1 19 | call Array.dispose 1 20 | pop temp 7 21 | label SKIP_CAPTURES_DEALLOC 22 | push argument 0 23 | call Memory.deAlloc 1 24 | return 25 | function Fn._call 0 26 | push argument 0 27 | pop pointer 0 28 | push constant 0 29 | pop temp 7 30 | label ARGS_INIT_START 31 | push temp 7 32 | push argument 1 33 | eq 34 | if-goto ARGS_INIT_END 35 | push temp 7 36 | push argument 2 37 | add 38 | pop pointer 1 39 | push that 0 40 | push temp 7 41 | push constant 1 42 | add 43 | pop temp 7 44 | goto ARGS_INIT_START 45 | label ARGS_INIT_END 46 | push argument 2 47 | call Array.dispose 1 48 | pop temp 7 49 | push this 1 50 | push constant 0 51 | eq 52 | if-goto SKIP_CAPTURES 53 | push this 1 54 | label SKIP_CAPTURES 55 | push this 0 56 | push constant 2 57 | eq 58 | if-goto FN_0 59 | push this 0 60 | push constant 0 61 | eq 62 | if-goto FN_1 63 | push this 0 64 | push constant 1 65 | eq 66 | if-goto FN_2 67 | push this 0 68 | push constant 3 69 | eq 70 | if-goto FN_3 71 | push constant 0 72 | return 73 | label FN_0 74 | call Main.isMinLength_checkIsMinLength 2 75 | return 76 | label FN_1 77 | call Main.isValidUsername_applyValidation 2 78 | return 79 | label FN_2 80 | call StdUtils.identity 1 81 | return 82 | label FN_3 83 | call Main.isMaxLength_checkIsMaxLength 2 84 | return -------------------------------------------------------------------------------- /examples/username-validation/bin/List.vm: -------------------------------------------------------------------------------- 1 | function List.all 0 2 | label REC_CALL_0 3 | push argument 0 4 | call List.List_tag 1 5 | push constant 0 6 | eq 7 | if-goto VARIANT_0_0 8 | push constant 1 9 | call Array.new 1 10 | pop temp 1 11 | push constant 0 12 | push temp 1 13 | add 14 | push argument 0 15 | call List.List_head 1 16 | pop temp 0 17 | pop pointer 1 18 | push temp 0 19 | pop that 0 20 | push argument 1 21 | push constant 1 22 | push temp 1 23 | call Fn._call 3 24 | not 25 | push constant 0 26 | eq 27 | if-goto SKIP_TRUE_0 28 | push constant 0 29 | return 30 | label SKIP_TRUE_0 31 | push argument 0 32 | call List.List_tail 1 33 | pop argument 0 34 | goto REC_CALL_0 35 | label VARIANT_0_0 36 | push constant 1 37 | neg 38 | return 39 | function List.map_reverseAndCleanup 1 40 | push argument 0 41 | call List.reverse 1 42 | pop local 0 43 | push argument 0 44 | call List.dispose 1 45 | pop temp 0 46 | push local 0 47 | return 48 | function List.map_mapRec 0 49 | label REC_CALL_0 50 | push argument 1 51 | call List.List_tag 1 52 | push constant 0 53 | eq 54 | if-goto VARIANT_0_0 55 | push constant 1 56 | call Array.new 1 57 | pop temp 1 58 | push constant 0 59 | push temp 1 60 | add 61 | push argument 1 62 | call List.List_head 1 63 | pop temp 0 64 | pop pointer 1 65 | push temp 0 66 | pop that 0 67 | push argument 2 68 | push constant 1 69 | push temp 1 70 | call Fn._call 3 71 | push argument 0 72 | call List.List 2 73 | push argument 1 74 | call List.List_tail 1 75 | pop argument 1 76 | pop argument 0 77 | goto REC_CALL_0 78 | label VARIANT_0_0 79 | push argument 0 80 | call List.map_reverseAndCleanup 1 81 | return 82 | function List.map 0 83 | call List.Empty 0 84 | push argument 0 85 | push argument 1 86 | call List.map_mapRec 3 87 | return 88 | function List.Empty 0 89 | push constant 1 90 | call Memory.alloc 1 91 | pop pointer 0 92 | push constant 0 93 | pop this 0 94 | push pointer 0 95 | return 96 | function List.List_tail 0 97 | push argument 0 98 | pop pointer 0 99 | push this 2 100 | return 101 | function List.List_head 0 102 | push argument 0 103 | pop pointer 0 104 | push this 1 105 | return 106 | function List.reverse_reverseRec 0 107 | label REC_CALL_0 108 | push argument 1 109 | call List.List_tag 1 110 | push constant 0 111 | eq 112 | if-goto VARIANT_0_0 113 | push argument 1 114 | call List.List_head 1 115 | push argument 0 116 | call List.List 2 117 | push argument 1 118 | call List.List_tail 1 119 | pop argument 1 120 | pop argument 0 121 | goto REC_CALL_0 122 | label VARIANT_0_0 123 | push argument 0 124 | return 125 | function List.reverse 0 126 | call List.Empty 0 127 | push argument 0 128 | call List.reverse_reverseRec 2 129 | return 130 | function List.List 0 131 | push constant 3 132 | call Memory.alloc 1 133 | pop pointer 0 134 | push argument 0 135 | pop this 1 136 | push argument 1 137 | pop this 2 138 | push constant 1 139 | pop this 0 140 | push pointer 0 141 | return 142 | function List.List_tag 0 143 | push argument 0 144 | pop pointer 0 145 | push this 0 146 | return -------------------------------------------------------------------------------- /examples/username-validation/bin/Main.vm: -------------------------------------------------------------------------------- 1 | function Main.UsernameValidator_tag 0 2 | push argument 0 3 | pop pointer 0 4 | push this 0 5 | return 6 | function Main.UsernameValidator 0 7 | push constant 2 8 | call Memory.alloc 1 9 | pop pointer 0 10 | push argument 0 11 | pop this 1 12 | push constant 0 13 | pop this 0 14 | push pointer 0 15 | return 16 | function Main.UsernameValidator_validations 0 17 | push argument 0 18 | pop pointer 0 19 | push this 1 20 | return 21 | function Main.UsernameValidator_updateValidations 0 22 | push argument 0 23 | pop pointer 0 24 | push argument 1 25 | call Main.UsernameValidator 1 26 | return 27 | function Main.isValidUsername_applyValidation 0 28 | push constant 1 29 | call Array.new 1 30 | pop temp 1 31 | push constant 0 32 | push temp 1 33 | add 34 | push constant 0 35 | push argument 1 36 | add 37 | pop pointer 1 38 | push that 0 39 | pop temp 0 40 | pop pointer 1 41 | push temp 0 42 | pop that 0 43 | push argument 0 44 | push constant 1 45 | push temp 1 46 | call Fn._call 3 47 | return 48 | function Main.isValidUsername 1 49 | push argument 0 50 | call Main.UsernameValidator_validations 1 51 | push constant 0 52 | push constant 1 53 | call Array.new 1 54 | pop temp 1 55 | push constant 0 56 | push temp 1 57 | add 58 | push argument 1 59 | pop temp 0 60 | pop pointer 1 61 | push temp 0 62 | pop that 0 63 | push temp 1 64 | call Fn._new 2 65 | call List.map 2 66 | pop local 0 67 | push local 0 68 | push constant 1 69 | push constant 0 70 | call Fn._new 2 71 | call List.all 2 72 | return 73 | function Main.isMinLength_checkIsMinLength 0 74 | push argument 0 75 | call String.length 1 76 | push constant 0 77 | push argument 1 78 | add 79 | pop pointer 1 80 | push that 0 81 | call Bool.ge 2 82 | return 83 | function Main.isMinLength 0 84 | push constant 2 85 | push constant 1 86 | call Array.new 1 87 | pop temp 1 88 | push constant 0 89 | push temp 1 90 | add 91 | push argument 0 92 | pop temp 0 93 | pop pointer 1 94 | push temp 0 95 | pop that 0 96 | push temp 1 97 | call Fn._new 2 98 | return 99 | function Main.isMaxLength_checkIsMaxLength 0 100 | push argument 0 101 | call String.length 1 102 | push constant 0 103 | push argument 1 104 | add 105 | pop pointer 1 106 | push that 0 107 | call Bool.le 2 108 | return 109 | function Main.isMaxLength 0 110 | push constant 3 111 | push constant 1 112 | call Array.new 1 113 | pop temp 1 114 | push constant 0 115 | push temp 1 116 | add 117 | push argument 0 118 | pop temp 0 119 | pop pointer 1 120 | push temp 0 121 | pop that 0 122 | push temp 1 123 | call Fn._new 2 124 | return 125 | function Main.main_buildUsernameValidator 2 126 | push constant 3 127 | call Main.isMinLength 1 128 | pop local 0 129 | push constant 10 130 | call Main.isMaxLength 1 131 | pop local 1 132 | call List.Empty 0 133 | pop temp 0 134 | push local 1 135 | push temp 0 136 | call List.List 2 137 | pop temp 0 138 | push local 0 139 | push temp 0 140 | call List.List 2 141 | call Main.UsernameValidator 1 142 | return 143 | function Main.main 4 144 | call Main.main_buildUsernameValidator 0 145 | pop local 0 146 | push constant 16 147 | call String.new 1 148 | push constant 69 149 | call String.appendChar 2 150 | push constant 110 151 | call String.appendChar 2 152 | push constant 116 153 | call String.appendChar 2 154 | push constant 101 155 | call String.appendChar 2 156 | push constant 114 157 | call String.appendChar 2 158 | push constant 32 159 | call String.appendChar 2 160 | push constant 117 161 | call String.appendChar 2 162 | push constant 115 163 | call String.appendChar 2 164 | push constant 101 165 | call String.appendChar 2 166 | push constant 114 167 | call String.appendChar 2 168 | push constant 110 169 | call String.appendChar 2 170 | push constant 97 171 | call String.appendChar 2 172 | push constant 109 173 | call String.appendChar 2 174 | push constant 101 175 | call String.appendChar 2 176 | push constant 58 177 | call String.appendChar 2 178 | push constant 32 179 | call String.appendChar 2 180 | call Keyboard.readLine 1 181 | pop local 1 182 | push local 0 183 | push local 1 184 | call Main.isValidUsername 2 185 | pop local 2 186 | push local 2 187 | push constant 0 188 | eq 189 | if-goto SKIP_TRUE_0 190 | push constant 18 191 | call String.new 1 192 | push constant 85 193 | call String.appendChar 2 194 | push constant 115 195 | call String.appendChar 2 196 | push constant 101 197 | call String.appendChar 2 198 | push constant 114 199 | call String.appendChar 2 200 | push constant 110 201 | call String.appendChar 2 202 | push constant 97 203 | call String.appendChar 2 204 | push constant 109 205 | call String.appendChar 2 206 | push constant 101 207 | call String.appendChar 2 208 | push constant 32 209 | call String.appendChar 2 210 | push constant 105 211 | call String.appendChar 2 212 | push constant 115 213 | call String.appendChar 2 214 | push constant 32 215 | call String.appendChar 2 216 | push constant 118 217 | call String.appendChar 2 218 | push constant 97 219 | call String.appendChar 2 220 | push constant 108 221 | call String.appendChar 2 222 | push constant 105 223 | call String.appendChar 2 224 | push constant 100 225 | call String.appendChar 2 226 | push constant 33 227 | call String.appendChar 2 228 | goto SKIP_FALSE_0 229 | label SKIP_TRUE_0 230 | push constant 22 231 | call String.new 1 232 | push constant 85 233 | call String.appendChar 2 234 | push constant 115 235 | call String.appendChar 2 236 | push constant 101 237 | call String.appendChar 2 238 | push constant 114 239 | call String.appendChar 2 240 | push constant 110 241 | call String.appendChar 2 242 | push constant 97 243 | call String.appendChar 2 244 | push constant 109 245 | call String.appendChar 2 246 | push constant 101 247 | call String.appendChar 2 248 | push constant 32 249 | call String.appendChar 2 250 | push constant 105 251 | call String.appendChar 2 252 | push constant 115 253 | call String.appendChar 2 254 | push constant 32 255 | call String.appendChar 2 256 | push constant 78 257 | call String.appendChar 2 258 | push constant 79 259 | call String.appendChar 2 260 | push constant 84 261 | call String.appendChar 2 262 | push constant 32 263 | call String.appendChar 2 264 | push constant 118 265 | call String.appendChar 2 266 | push constant 97 267 | call String.appendChar 2 268 | push constant 108 269 | call String.appendChar 2 270 | push constant 105 271 | call String.appendChar 2 272 | push constant 100 273 | call String.appendChar 2 274 | push constant 33 275 | call String.appendChar 2 276 | label SKIP_FALSE_0 277 | pop local 3 278 | push local 3 279 | call Output.printString 1 280 | return -------------------------------------------------------------------------------- /examples/username-validation/bin/StdUtils.vm: -------------------------------------------------------------------------------- 1 | function StdUtils.identity 0 2 | push argument 0 3 | return -------------------------------------------------------------------------------- /examples/username-validation/src/Main.jill: -------------------------------------------------------------------------------- 1 | -- VALIDATOR 2 | 3 | -- @type UsernameValidator(List(Fn(String) -> Bool)) 4 | type UsernameValidator = UsernameValidator(validations). 5 | 6 | 7 | -- @type UsernameValidator, String -> Bool 8 | fn isValidUsername validator username = 9 | -- `List::map` expects a one-argument function (only validation from list), 10 | -- so we have to capture username from parent 11 | fn applyValidation validation [username] = validation(username). 12 | 13 | -- apply all validations to the given username 14 | let validationResults = List::map(UsernameValidator:validations(validator), &applyValidation), 15 | 16 | -- check if all results are "valid" (`True`) 17 | List::all(validationResults, &StdUtils::identity). 18 | 19 | 20 | -- VALIDATIONS 21 | 22 | -- @type Int -> Fn(String -> Bool) 23 | fn isMinLength minLength = 24 | -- NOTE: validator expects one-argument functions (only takes in username), 25 | -- so we have to capture minimum length data and "embed" it in the closure 26 | -- @type String, [Int] -> Bool 27 | fn checkIsMinLength username [minLength] = 28 | Bool::ge( 29 | String::length(username), 30 | minLength 31 | ). 32 | 33 | &checkIsMinLength. 34 | 35 | -- @type Int -> Fn(String -> Bool) 36 | fn isMaxLength maxLength = 37 | -- NOTE: validator expects one-argument functions (only takes in username), 38 | -- so we have to capture maximum length data and "embed" it in the closure 39 | -- @type String, [Int] -> Bool 40 | fn checkIsMaxLength username [maxLength] = 41 | Bool::le( 42 | String::length(username), 43 | maxLength 44 | ). 45 | 46 | &checkIsMaxLength. 47 | 48 | 49 | -- MAIN 50 | 51 | fn main = 52 | -- @type () -> UsernameValidator 53 | fn buildUsernameValidator = 54 | -- @type Fn(String) -> Bool 55 | let minLengthValidator = isMinLength(3), 56 | 57 | -- @type Fn(String) -> Bool 58 | let maxLengthValidator = isMaxLength(10), 59 | 60 | UsernameValidator([ 61 | -- NOTE: no need to "take a reference" because 62 | -- these are already closures (returned from functions) 63 | minLengthValidator, 64 | maxLengthValidator, 65 | ]). 66 | 67 | let usernameValidator = buildUsernameValidator(), 68 | 69 | let username = Keyboard::readLine("Enter username: "), 70 | 71 | let usernameIsValid = isValidUsername(usernameValidator, username), 72 | let validationResponseMessage = 73 | ifElse( 74 | usernameIsValid, 75 | "Username is valid!", 76 | "Username is NOT valid!", 77 | ), 78 | 79 | Output::printString(validationResponseMessage). 80 | -------------------------------------------------------------------------------- /src/codegen/common/expression.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::{ 3 | context::{ModuleContext, ProgramContext}, 4 | error::FallableInstructions, 5 | }, 6 | common::ast, 7 | }; 8 | 9 | use super::{function_call, function_reference, literal, variable_name}; 10 | 11 | pub fn construct( 12 | expression: &ast::JillExpression, 13 | module_context: &mut ModuleContext, 14 | program_context: &mut ProgramContext, 15 | ) -> FallableInstructions { 16 | match expression { 17 | ast::JillExpression::Literal(literal) => { 18 | literal::construct(literal, module_context, program_context) 19 | } 20 | ast::JillExpression::VariableName(variable_name) => { 21 | variable_name::construct(variable_name, module_context, program_context) 22 | } 23 | ast::JillExpression::FunctionReference(function_reference) => { 24 | function_reference::construct(function_reference, module_context, program_context) 25 | } 26 | ast::JillExpression::FunctionCall(function_call) => { 27 | function_call::construct(function_call, module_context, program_context) 28 | } 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use crate::codegen::vm; 35 | 36 | use super::*; 37 | 38 | #[test] 39 | fn test_expression_construction() { 40 | let mut program_context = ProgramContext::new(); 41 | let mut module_context = ModuleContext::new("Test".to_owned()); 42 | 43 | let expression = ast::JillExpression::Literal(ast::JillLiteral::Integer(5)); 44 | 45 | let expected = "push constant 5"; 46 | 47 | assert!( 48 | construct(&expression, &mut module_context, &mut program_context).is_ok_and( 49 | |instructions| vm::VMInstructionBlock::from(instructions).compile() == expected 50 | ) 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/codegen/common/function_reference.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::{ 3 | common::helpers::{self, function::JillFunctionReferenceExtensions}, 4 | context::{module::FunctionContext, ModuleContext, ProgramContext}, 5 | error::{Error, FallableInstructions}, 6 | post_compilation::jillstd, 7 | vm, 8 | }, 9 | common::ast, 10 | }; 11 | 12 | pub fn construct( 13 | function_reference: &ast::JillFunctionReference, 14 | module_context: &ModuleContext, 15 | program_context: &mut ProgramContext, 16 | ) -> FallableInstructions { 17 | let function_context = module_context 18 | .scope 19 | .search_function(&function_reference.type_associated_function_name()); 20 | 21 | if !is_valid_function_reference(function_reference, function_context.as_ref()) { 22 | return invalid_function_reference(function_reference); 23 | } 24 | 25 | // track (potential) `JillStd` function occurences 26 | let std_function_usage_note_outcome = program_context 27 | .std_usage_tracker 28 | .note_usage(function_reference); 29 | if std_function_usage_note_outcome 30 | == jillstd::JillStdFunctionUsageNoteOutcome::FunctionNotPresentInModule 31 | { 32 | return invalid_function_reference(function_reference); 33 | } 34 | 35 | let function_prefix = function_context 36 | .as_ref() 37 | .map(|ctx| ctx.prefix.clone()) 38 | .unwrap_or_default(); 39 | let vm_function_name = function_reference 40 | .to_fully_qualified_hack_name(&module_context.module_name, function_prefix); 41 | 42 | // we handle function references by pushing their 43 | // associated ID onto the stack as a constant 44 | let function_id = program_context 45 | .function_dispatch 46 | // have to use fully qualified (HACK) names to avoid misrepresentation 47 | .encounter(vm_function_name); 48 | 49 | // collect function captures (if any are present) 50 | let function_captures = function_context.map_or(Vec::new(), |ctx| ctx.captures); 51 | let captures_instructions = helpers::capture::construct_captures_array( 52 | &function_captures, 53 | module_context, 54 | program_context, 55 | )?; 56 | 57 | let instructions = [ 58 | vec![vm::push(vm::Segment::Constant, function_id)], 59 | captures_instructions, 60 | vec![vm::call(vm::VMFunctionName::from_literal("Fn._new"), 2)], 61 | ] 62 | .concat(); 63 | 64 | Ok(instructions) 65 | } 66 | 67 | /// Function reference is valid if it is a reference to another module 68 | /// or an existing (found module-local) function 69 | fn is_valid_function_reference( 70 | function_reference: &ast::JillFunctionReference, 71 | function_context: Option<&FunctionContext>, 72 | ) -> bool { 73 | function_reference.is_fully_qualified() || function_context.is_some() 74 | } 75 | 76 | fn invalid_function_reference( 77 | function_reference: &ast::JillFunctionReference, 78 | ) -> FallableInstructions { 79 | Err(Error::InvalidFunctionReference( 80 | function_reference.reconstruct_source_name(), 81 | )) 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use crate::codegen::context::module::{FunctionContextArguments, VariableContextArguments}; 87 | 88 | use super::*; 89 | 90 | #[test] 91 | fn test_successful_function_reference_construction() { 92 | let mut program_context = ProgramContext::new(); 93 | let mut module_context = ModuleContext::new("Test".to_owned()); 94 | 95 | assert!(module_context 96 | .scope 97 | .enter_function("foo".to_string(), FunctionContextArguments::new(0)) 98 | .is_ok()); 99 | 100 | module_context.scope.leave_function(); 101 | 102 | assert!(module_context 103 | .scope 104 | .enter_function("bar".to_string(), FunctionContextArguments::new(0)) 105 | .is_ok()); 106 | 107 | let function_reference = ast::JillFunctionReference { 108 | modules_path: vec![], 109 | associated_type: None, 110 | function_name: ast::JillIdentifier(String::from("foo")), 111 | }; 112 | 113 | let expected_instructions = [ 114 | "push constant 0", 115 | // null 116 | "push constant 0", 117 | "call Fn._new 2", 118 | ] 119 | .join("\n"); 120 | 121 | assert!( 122 | construct(&function_reference, &module_context, &mut program_context).is_ok_and( 123 | |instructions| vm::VMInstructionBlock::from(instructions).compile() 124 | == expected_instructions 125 | ) 126 | ); 127 | } 128 | 129 | #[test] 130 | fn test_unsuccessful_function_reference_construction() { 131 | let mut program_context = ProgramContext::new(); 132 | let mut module_context = ModuleContext::new("Test".to_owned()); 133 | 134 | assert!(module_context 135 | .scope 136 | .enter_function("foo".to_string(), FunctionContextArguments::new(0)) 137 | .is_ok()); 138 | 139 | module_context.scope.leave_function(); 140 | 141 | let function_reference = ast::JillFunctionReference { 142 | modules_path: vec![], 143 | associated_type: None, 144 | function_name: ast::JillIdentifier(String::from("bar")), 145 | }; 146 | 147 | assert!( 148 | construct(&function_reference, &module_context, &mut program_context) 149 | .is_err_and(|error| matches!(error, Error::InvalidFunctionReference(_))) 150 | ); 151 | } 152 | 153 | #[test] 154 | fn test_construction_of_function_reference_with_captures() { 155 | let mut program_context = ProgramContext::new(); 156 | let mut module_context = ModuleContext::new("Test".to_owned()); 157 | 158 | // `fn foo a b c` (top level fn) 159 | assert!(module_context 160 | .scope 161 | .enter_function("foo".to_string(), FunctionContextArguments::new(3)) 162 | .is_ok()); 163 | 164 | // argument `a` 165 | assert!(module_context 166 | .scope 167 | .add_variable( 168 | "a".to_string(), 169 | VariableContextArguments::new(vm::Segment::Argument) 170 | ) 171 | .is_ok()); 172 | 173 | // argument `b` 174 | assert!(module_context 175 | .scope 176 | .add_variable( 177 | "b".to_string(), 178 | VariableContextArguments::new(vm::Segment::Argument) 179 | ) 180 | .is_ok()); 181 | 182 | // argument `c` 183 | assert!(module_context 184 | .scope 185 | .add_variable( 186 | "c".to_string(), 187 | VariableContextArguments::new(vm::Segment::Argument) 188 | ) 189 | .is_ok()); 190 | 191 | // `fn bar x [a c] = _.` (nested fn) 192 | assert!(module_context 193 | .scope 194 | .enter_function( 195 | "bar".to_string(), 196 | FunctionContextArguments::new(1) 197 | .with_captures(vec!["a".to_string(), "c".to_string()]) 198 | ) 199 | .is_ok()); 200 | 201 | // argument `x` 202 | assert!(module_context 203 | .scope 204 | .add_variable( 205 | "x".to_string(), 206 | VariableContextArguments::new(vm::Segment::Argument) 207 | ) 208 | .is_ok()); 209 | 210 | module_context.scope.leave_function(); 211 | 212 | // reference to the nested function (i.e. returning a function from a function) 213 | let function_reference = ast::JillFunctionReference { 214 | modules_path: vec![], 215 | associated_type: None, 216 | function_name: ast::JillIdentifier(String::from("bar")), 217 | }; 218 | 219 | let expected_instructions = [ 220 | // fid 221 | "push constant 0", 222 | // captures 223 | // create array 224 | "push constant 2", 225 | "call Array.new 1", 226 | "pop temp 1", 227 | // add `a` 228 | "push constant 0", 229 | "push temp 1", 230 | "add", 231 | "push argument 0", 232 | "pop temp 0", 233 | "pop pointer 1", 234 | "push temp 0", 235 | "pop that 0", 236 | // add `c` 237 | "push constant 1", 238 | "push temp 1", 239 | "add", 240 | "push argument 2", 241 | "pop temp 0", 242 | "pop pointer 1", 243 | "push temp 0", 244 | "pop that 0", 245 | // push to stack 246 | "push temp 1", 247 | // build closure 248 | "call Fn._new 2", 249 | ] 250 | .join("\n"); 251 | 252 | assert!( 253 | construct(&function_reference, &module_context, &mut program_context).is_ok_and( 254 | |instructions| vm::VMInstructionBlock::from(instructions).compile() 255 | == expected_instructions 256 | ) 257 | ); 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/codegen/common/helpers/array.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::{context::module::VariableContext, error::FallableInstructions, vm}; 2 | 3 | pub struct ArrayBuildConfiguration { 4 | pub push_resulting_array: bool, 5 | pub array_temp_segment_index: usize, 6 | pub array_elem_temp_segment_index: usize, 7 | } 8 | 9 | pub fn build_array_instructions( 10 | array_items: &[T], 11 | mut item_instructions: F, 12 | build_configuration: &ArrayBuildConfiguration, 13 | ) -> FallableInstructions 14 | where 15 | F: FnMut(&T) -> FallableInstructions, 16 | { 17 | if array_items.is_empty() { 18 | return Ok(vec![vm::null()]); 19 | } 20 | 21 | let array = VariableContext { 22 | segment: vm::Segment::Temp, 23 | // NOTE: have to use different `temp` segment index here 24 | // than in array construction because we store array 25 | // pointer in `temp` instead of `local` 26 | index: build_configuration.array_temp_segment_index, 27 | }; 28 | 29 | let array_init_instructions = vec![ 30 | vm::push(vm::Segment::Constant, array_items.len()), 31 | vm::call(vm::VMFunctionName::from_literal("Array.new"), 1), 32 | array.pop(), 33 | ]; 34 | 35 | let array_elem_temp_storage = VariableContext { 36 | segment: vm::Segment::Temp, 37 | index: build_configuration.array_elem_temp_segment_index, 38 | }; 39 | let array_items_instructions = array_items 40 | .iter() 41 | .enumerate() 42 | .map(|(index, item)| { 43 | Ok([ 44 | vec![vm::push(vm::Segment::Constant, index)], 45 | array.push(), 46 | vec![vm::command(vm::VMCommand::Add)], 47 | item_instructions(item)?, 48 | vec![ 49 | array_elem_temp_storage.pop(), 50 | vm::pop(vm::Segment::Pointer, 1), 51 | ], 52 | array_elem_temp_storage.push(), 53 | vec![vm::pop(vm::Segment::That, 0)], 54 | ] 55 | .concat()) 56 | }) 57 | .collect::, _>>()? 58 | .concat(); 59 | 60 | let mut instructions = vec![array_init_instructions, array_items_instructions]; 61 | 62 | if build_configuration.push_resulting_array { 63 | instructions.push(array.push()); 64 | } 65 | 66 | Ok(instructions.concat()) 67 | } 68 | -------------------------------------------------------------------------------- /src/codegen/common/helpers/capture.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::{ 2 | context::{ModuleContext, ProgramContext}, 3 | error::{Error, FallableInstructions}, 4 | }; 5 | 6 | pub fn construct_captures_array( 7 | function_captures: &[String], 8 | module_context: &ModuleContext, 9 | program_context: &mut ProgramContext, 10 | ) -> FallableInstructions { 11 | let array_instructions_build_config = super::array::ArrayBuildConfiguration { 12 | push_resulting_array: true, 13 | // don't need `temp` segment values here, so we can just inline them 14 | array_elem_temp_segment_index: program_context.temp_segment_index.request(), 15 | array_temp_segment_index: program_context.temp_segment_index.request(), 16 | }; 17 | 18 | // have to evaluate before releasing `temp` indices 19 | let array_build_instructions = super::array::build_array_instructions( 20 | function_captures, 21 | |capture_name| { 22 | module_context 23 | .scope 24 | .search_variable(capture_name) 25 | .map_or_else( 26 | || Err(Error::CaptureNotInScope(capture_name.to_string())), 27 | |capture_variable_context| Ok(capture_variable_context.push()), 28 | ) 29 | }, 30 | &array_instructions_build_config, 31 | ); 32 | 33 | // array 34 | program_context.temp_segment_index.release(); 35 | // array elem storage 36 | program_context.temp_segment_index.release(); 37 | 38 | array_build_instructions 39 | } 40 | -------------------------------------------------------------------------------- /src/codegen/common/helpers/function.rs: -------------------------------------------------------------------------------- 1 | use crate::{codegen::vm, common::ast}; 2 | 3 | pub trait JillFunctionReferenceExtensions { 4 | fn is_fully_qualified(&self) -> bool; 5 | fn reconstruct_source_name(&self) -> String; 6 | fn to_fully_qualified_hack_name( 7 | &self, 8 | module_name: &str, 9 | function_prefix: String, 10 | ) -> vm::VMFunctionName; 11 | fn from_function_definition(function_definition: &ast::JillFunction) -> Self; 12 | fn type_associated_function_name(&self) -> String; 13 | } 14 | 15 | impl JillFunctionReferenceExtensions for ast::JillFunctionReference { 16 | fn is_fully_qualified(&self) -> bool { 17 | !self.modules_path.is_empty() 18 | } 19 | 20 | fn reconstruct_source_name(&self) -> String { 21 | let module_path = self 22 | .modules_path 23 | .iter() 24 | .fold(String::new(), |s, module| s + &module.0 + "::"); 25 | 26 | let type_path = self 27 | .associated_type 28 | .clone() 29 | .map(|t| format!("{t}:")) 30 | .unwrap_or_default(); 31 | 32 | let function_name = &self.function_name.0; 33 | 34 | format!("{module_path}{type_path}{function_name}") 35 | } 36 | 37 | // TODO: try to do this more elegantly (and give a nicer name) 38 | fn to_fully_qualified_hack_name( 39 | &self, 40 | local_module_name: &str, 41 | local_function_prefix: String, 42 | ) -> vm::VMFunctionName { 43 | // format: `Module_Path_Elements.[OptionalType_][optionalFunction_prefixes_]functionName` 44 | let module_path = if self.is_fully_qualified() { 45 | self.modules_path 46 | .iter() 47 | .map(|module_identifier| module_identifier.0.clone()) 48 | .collect::>() 49 | .join("_") 50 | } else { 51 | // if it is local, use the name of the module it is defined in 52 | local_module_name.to_string() 53 | }; 54 | 55 | let type_name = self 56 | .associated_type 57 | .clone() 58 | .map(|t| t.0 + "_") 59 | .unwrap_or_default(); 60 | 61 | let function_name = if self.is_fully_qualified() { 62 | self.function_name.0.clone() 63 | } else { 64 | // if it is local, prepend the function prefix 65 | local_function_prefix + &self.function_name.0 66 | }; 67 | 68 | vm::VMFunctionName::construct(&module_path, &type_name, &function_name) 69 | } 70 | 71 | fn from_function_definition(function_definition: &ast::JillFunction) -> Self { 72 | Self { 73 | modules_path: vec![], 74 | associated_type: None, 75 | function_name: function_definition.name.clone(), 76 | } 77 | } 78 | 79 | fn type_associated_function_name(&self) -> String { 80 | let associated_type_prefix = self 81 | .associated_type 82 | .clone() 83 | .map(|t| format!("{t}_")) 84 | .unwrap_or_default(); 85 | 86 | associated_type_prefix + &self.function_name.0 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod tests { 92 | use super::*; 93 | 94 | #[test] 95 | fn test_function_reference_reconstruction() { 96 | // case 1 97 | let function_reference = ast::JillFunctionReference { 98 | modules_path: vec![], 99 | associated_type: None, 100 | function_name: ast::JillIdentifier(String::from("test")), 101 | }; 102 | 103 | assert_eq!(&function_reference.reconstruct_source_name(), "test"); 104 | 105 | // case 2 106 | let function_reference = ast::JillFunctionReference { 107 | modules_path: vec![ast::JillIdentifier(String::from("List"))], 108 | associated_type: None, 109 | function_name: ast::JillIdentifier(String::from("map")), 110 | }; 111 | 112 | assert_eq!(&function_reference.reconstruct_source_name(), "List::map"); 113 | 114 | // case 3 115 | let function_reference = ast::JillFunctionReference { 116 | modules_path: vec![ 117 | ast::JillIdentifier(String::from("Utils")), 118 | ast::JillIdentifier(String::from("Primitives")), 119 | ast::JillIdentifier(String::from("Option")), 120 | ], 121 | associated_type: Some(ast::JillIdentifier(String::from("Some"))), 122 | function_name: ast::JillIdentifier(String::from("value")), 123 | }; 124 | 125 | assert_eq!( 126 | &function_reference.reconstruct_source_name(), 127 | "Utils::Primitives::Option::Some:value" 128 | ); 129 | } 130 | 131 | #[test] 132 | fn test_fully_qualified_name_generation() { 133 | // case 1 134 | let function_reference = ast::JillFunctionReference { 135 | modules_path: vec![], 136 | associated_type: None, 137 | function_name: ast::JillIdentifier(String::from("test")), 138 | }; 139 | let module_name = &String::from("Test"); 140 | let function_prefix = String::new(); 141 | 142 | assert_eq!( 143 | function_reference.to_fully_qualified_hack_name(module_name, function_prefix), 144 | vm::VMFunctionName::from_literal("Test.test") 145 | ); 146 | 147 | // case 2 148 | let function_reference = ast::JillFunctionReference { 149 | modules_path: vec![ast::JillIdentifier(String::from("List"))], 150 | associated_type: None, 151 | function_name: ast::JillIdentifier(String::from("map")), 152 | }; 153 | let module_name = &String::new(); 154 | let function_prefix = String::new(); 155 | 156 | assert_eq!( 157 | function_reference.to_fully_qualified_hack_name(module_name, function_prefix), 158 | vm::VMFunctionName::from_literal("List.map") 159 | ); 160 | 161 | // case 3 162 | let function_reference = ast::JillFunctionReference { 163 | modules_path: vec![ 164 | ast::JillIdentifier(String::from("Utils")), 165 | ast::JillIdentifier(String::from("Primitives")), 166 | ast::JillIdentifier(String::from("Option")), 167 | ], 168 | associated_type: Some(ast::JillIdentifier(String::from("Some"))), 169 | function_name: ast::JillIdentifier(String::from("value")), 170 | }; 171 | let module_name = &String::new(); 172 | let function_prefix = String::new(); 173 | 174 | assert_eq!( 175 | function_reference.to_fully_qualified_hack_name(module_name, function_prefix), 176 | vm::VMFunctionName::from_literal("Utils_Primitives_Option.Some_value") 177 | ); 178 | 179 | // case 4 180 | let function_reference = ast::JillFunctionReference { 181 | modules_path: vec![], 182 | associated_type: None, 183 | function_name: ast::JillIdentifier(String::from("biz")), 184 | }; 185 | let module_name = &String::from("Foo"); 186 | let function_prefix = String::from("bar_baz_"); 187 | 188 | assert_eq!( 189 | function_reference.to_fully_qualified_hack_name(module_name, function_prefix), 190 | vm::VMFunctionName::from_literal("Foo.bar_baz_biz") 191 | ); 192 | } 193 | 194 | #[test] 195 | fn test_type_associated_function_name() { 196 | // case 1 (no associated type) 197 | let function_reference = ast::JillFunctionReference { 198 | modules_path: vec![], 199 | associated_type: None, 200 | function_name: ast::JillIdentifier(String::from("test")), 201 | }; 202 | 203 | assert_eq!( 204 | function_reference.type_associated_function_name(), 205 | String::from("test") 206 | ); 207 | 208 | // case 2 (associated type) 209 | let function_reference = ast::JillFunctionReference { 210 | modules_path: vec![], 211 | associated_type: Some(ast::JillIdentifier(String::from("Type"))), 212 | function_name: ast::JillIdentifier(String::from("test")), 213 | }; 214 | 215 | assert_eq!( 216 | function_reference.type_associated_function_name(), 217 | String::from("Type_test") 218 | ); 219 | 220 | // case 3 (module present - doesn't change a thing) 221 | let function_reference = ast::JillFunctionReference { 222 | modules_path: vec![ast::JillIdentifier(String::from("Mod"))], 223 | associated_type: Some(ast::JillIdentifier(String::from("Type"))), 224 | function_name: ast::JillIdentifier(String::from("test")), 225 | }; 226 | 227 | assert_eq!( 228 | function_reference.type_associated_function_name(), 229 | String::from("Type_test") 230 | ); 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /src/codegen/common/helpers/function_override.rs: -------------------------------------------------------------------------------- 1 | use phf::phf_map; 2 | 3 | use crate::{codegen::vm, common::ast}; 4 | 5 | use super::function::JillFunctionReferenceExtensions; 6 | 7 | type ModuleName = &'static str; 8 | type FunctionName = &'static str; 9 | 10 | #[derive(Debug, PartialEq, Eq, Clone)] 11 | pub enum FunctionOverrideKind { 12 | VM(vm::VMCommand), 13 | JackStd(ModuleName, FunctionName), 14 | } 15 | 16 | static OVERRIDES: phf::Map<&'static str, FunctionOverrideKind> = phf_map! { 17 | // VM 18 | "Int.add" => FunctionOverrideKind::VM(vm::VMCommand::Add), 19 | "Int.sub" => FunctionOverrideKind::VM(vm::VMCommand::Sub), 20 | "Int.neg" => FunctionOverrideKind::VM(vm::VMCommand::Neg), 21 | "Bool.eq" => FunctionOverrideKind::VM(vm::VMCommand::Eq), 22 | "Bool.gt" => FunctionOverrideKind::VM(vm::VMCommand::Gt), 23 | "Bool.lt" => FunctionOverrideKind::VM(vm::VMCommand::Lt), 24 | "Bool.and" => FunctionOverrideKind::VM(vm::VMCommand::And), 25 | "Bool.or" => FunctionOverrideKind::VM(vm::VMCommand::Or), 26 | "Bool.not" => FunctionOverrideKind::VM(vm::VMCommand::Not), 27 | 28 | // JackStd 29 | "Int.mult" => FunctionOverrideKind::JackStd("Math", "multiply"), 30 | "Int.div" => FunctionOverrideKind::JackStd("Math", "divide"), 31 | "Int.min" => FunctionOverrideKind::JackStd("Math", "min"), 32 | "Int.max" => FunctionOverrideKind::JackStd("Math", "max"), 33 | "Int.sqrt" => FunctionOverrideKind::JackStd("Math", "sqrt"), 34 | }; 35 | 36 | pub fn find_override( 37 | function_reference: &ast::JillFunctionReference, 38 | ) -> Option { 39 | let vm_function_name = function_reference.to_fully_qualified_hack_name("", String::new()); 40 | 41 | OVERRIDES.get(&vm_function_name).cloned() 42 | } 43 | -------------------------------------------------------------------------------- /src/codegen/common/helpers/jack_api.rs: -------------------------------------------------------------------------------- 1 | use phf::phf_map; 2 | 3 | use crate::codegen::{context::program::JillFunctionMetadata, vm}; 4 | 5 | static ARITIES: phf::Map<&'static str, usize> = phf_map! { 6 | // Math 7 | "Math.multiply" => 2, 8 | "Math.divide" => 2, 9 | "Math.min" => 2, 10 | "Math.max" => 2, 11 | "Math.sqrt" => 1, 12 | 13 | // String 14 | "String.new" => 1, 15 | "String.dispose" => 1, 16 | "String.length" => 1, 17 | "String.charAt" => 2, 18 | "String.setCharAt" => 3, 19 | "String.appendChar" => 2, 20 | "String.eraseLastChar" => 1, 21 | "String.intValue" => 1, 22 | "String.setInt" => 2, 23 | "String.backSpace" => 0, 24 | "String.doubleQuote" => 0, 25 | "String.newLine" => 0, 26 | 27 | // Array 28 | "Array.new" => 1, 29 | "Array.dispose" => 1, 30 | 31 | // Output 32 | "Output.moveCursor" => 2, 33 | "Output.printChar" => 1, 34 | "Output.printString" => 1, 35 | "Output.printInt" => 1, 36 | "Output.println" => 0, 37 | "Output.backSpace" => 0, 38 | 39 | // Screen 40 | "Screen.clearScreen" => 0, 41 | "Screen.setColor" => 1, 42 | "Screen.drawPixel" => 2, 43 | "Screen.drawLine" => 4, 44 | "Screen.drawRectangle" => 4, 45 | "Screen.drawCircle" => 3, 46 | 47 | // Keyboard 48 | "Keyboard.keyPressed" => 0, 49 | "Keyboard.readChar" => 0, 50 | "Keyboard.readLine" => 1, 51 | "Keyboard.readInt" => 1, 52 | 53 | // Memory 54 | "Memory.peek" => 1, 55 | "Memory.poke" => 2, 56 | "Memory.alloc" => 1, 57 | "Memory.deAlloc" => 1, 58 | 59 | // Sys 60 | "Sys.halt" => 0, 61 | "Sys.error" => 1, 62 | "Sys.wait" => 1, 63 | }; 64 | 65 | pub fn function_arity(vm_function_name: &vm::VMFunctionName) -> Option { 66 | ARITIES 67 | .get(&vm_function_name.to_string()) 68 | .copied() 69 | .map(|arity| JillFunctionMetadata { 70 | arity, 71 | // Jack API functions have no captures 72 | has_captures: false, 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /src/codegen/common/helpers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod array; 2 | pub mod capture; 3 | pub mod function; 4 | pub mod function_override; 5 | pub mod jack_api; 6 | pub mod variable; 7 | -------------------------------------------------------------------------------- /src/codegen/common/helpers/variable.rs: -------------------------------------------------------------------------------- 1 | use crate::common::ast; 2 | 3 | pub trait JillVariableExtensions { 4 | fn is_discard(&self) -> bool; 5 | } 6 | 7 | impl JillVariableExtensions for ast::JillVariable { 8 | fn is_discard(&self) -> bool { 9 | self.name.0.is_empty() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/codegen/common/literal.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::{ 3 | context::{module::VariableContext, ModuleContext, ProgramContext}, 4 | error::FallableInstructions, 5 | vm, 6 | }, 7 | common::ast, 8 | }; 9 | 10 | pub fn construct( 11 | literal: &ast::JillLiteral, 12 | module_context: &mut ModuleContext, 13 | program_context: &mut ProgramContext, 14 | ) -> FallableInstructions { 15 | let instructions = match literal { 16 | ast::JillLiteral::Integer(i) => construct_integer(*i), 17 | ast::JillLiteral::String(s) => construct_string(s), 18 | ast::JillLiteral::Bool(b) => construct_bool(*b), 19 | ast::JillLiteral::List(l) => construct_list(l, module_context, program_context)?, 20 | }; 21 | 22 | Ok(instructions) 23 | } 24 | 25 | fn construct_integer(i: usize) -> Vec { 26 | vec![vm::push(vm::Segment::Constant, i)] 27 | } 28 | 29 | fn construct_string(s: &str) -> Vec { 30 | let string_init = vec![ 31 | vm::push(vm::Segment::Constant, s.len()), 32 | vm::call(vm::VMFunctionName::from_literal("String.new"), 1), 33 | ]; 34 | 35 | let string_population = s 36 | .chars() 37 | .flat_map(|c| { 38 | vec![ 39 | vm::push(vm::Segment::Constant, to_ascii(c)), 40 | vm::call(vm::VMFunctionName::from_literal("String.appendChar"), 2), 41 | ] 42 | }) 43 | .collect(); 44 | 45 | [string_init, string_population].concat() 46 | } 47 | 48 | fn construct_bool(b: bool) -> Vec { 49 | if b { 50 | vm::r#true() 51 | } else { 52 | vec![vm::r#false()] 53 | } 54 | } 55 | 56 | fn construct_list( 57 | list: &[ast::JillExpression], 58 | module_context: &mut ModuleContext, 59 | program_context: &mut ProgramContext, 60 | ) -> FallableInstructions { 61 | note_std_list_constructor_usage("Empty", program_context); 62 | 63 | // start with an empty list 64 | let empty_list = vec![vm::call(vm::VMFunctionName::from_literal("List.Empty"), 0)]; 65 | 66 | if list.is_empty() { 67 | // no elements, just return the empty list 68 | return Ok(empty_list); 69 | }; 70 | 71 | note_std_list_constructor_usage("List", program_context); 72 | 73 | let temp_index = program_context.temp_segment_index.request(); 74 | let temp_storage = VariableContext { 75 | segment: vm::Segment::Temp, 76 | index: temp_index, 77 | }; 78 | let instructions = list 79 | .iter() 80 | // need to add elements in reverse order 81 | .rev() 82 | .map(|elem| { 83 | Ok([ 84 | // move last result to temporary storage 85 | vec![temp_storage.pop()], 86 | // evaluate next elem 87 | super::expression::construct(elem, module_context, program_context)?, 88 | // re-push previous result (to maintain proper element order) 89 | temp_storage.push(), 90 | // construct new list "head" 91 | vec![vm::call(vm::VMFunctionName::from_literal("List.List"), 2)], 92 | ] 93 | .concat()) 94 | }) 95 | .collect::>, _>>()? 96 | .concat(); 97 | 98 | program_context.temp_segment_index.release(); 99 | 100 | Ok([empty_list, instructions].concat()) 101 | } 102 | 103 | fn note_std_list_constructor_usage(constructor: &str, program_context: &mut ProgramContext) { 104 | let function_reference = ast::JillFunctionReference { 105 | modules_path: vec![ast::JillIdentifier(String::from("List"))], 106 | associated_type: None, 107 | function_name: ast::JillIdentifier(constructor.to_owned()), 108 | }; 109 | 110 | program_context 111 | .std_usage_tracker 112 | .note_usage(&function_reference); 113 | } 114 | 115 | fn to_ascii(c: char) -> usize { 116 | (c as u8).into() 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use crate::{ 122 | codegen::{ 123 | context::{ModuleContext, ProgramContext}, 124 | vm, 125 | }, 126 | common::ast, 127 | }; 128 | 129 | #[test] 130 | fn test_positive_integer_construction() { 131 | let i = 17; 132 | 133 | let expected = "push constant 17"; 134 | 135 | assert_eq!( 136 | vm::VMInstructionBlock::from(super::construct_integer(i)).compile(), 137 | expected 138 | ); 139 | } 140 | 141 | #[test] 142 | fn test_string_construction() { 143 | let s = String::from("fin"); 144 | 145 | let expected = [ 146 | "push constant 3", 147 | "call String.new 1", 148 | "push constant 102", 149 | "call String.appendChar 2", 150 | "push constant 105", 151 | "call String.appendChar 2", 152 | "push constant 110", 153 | "call String.appendChar 2", 154 | ] 155 | .join("\n"); 156 | 157 | assert_eq!( 158 | vm::VMInstructionBlock::from(super::construct_string(&s)).compile(), 159 | expected 160 | ); 161 | } 162 | 163 | #[test] 164 | fn test_basic_list_construction() { 165 | let mut program_context = ProgramContext::new(); 166 | let mut module_context = ModuleContext::new("Test".to_owned()); 167 | 168 | let number = |i| ast::JillExpression::Literal(ast::JillLiteral::Integer(i)); 169 | 170 | let list = vec![number(5), number(2), number(7), number(3)]; 171 | 172 | let expected = [ 173 | "call List.Empty 0", 174 | "pop temp 0", 175 | "push constant 3", 176 | "push temp 0", 177 | "call List.List 2", 178 | "pop temp 0", 179 | "push constant 7", 180 | "push temp 0", 181 | "call List.List 2", 182 | "pop temp 0", 183 | "push constant 2", 184 | "push temp 0", 185 | "call List.List 2", 186 | "pop temp 0", 187 | "push constant 5", 188 | "push temp 0", 189 | "call List.List 2", 190 | ] 191 | .join("\n"); 192 | 193 | assert!( 194 | super::construct_list(&list, &mut module_context, &mut program_context).is_ok_and( 195 | |instructions| vm::VMInstructionBlock::from(instructions).compile() == expected 196 | ) 197 | ); 198 | } 199 | 200 | #[test] 201 | fn test_nested_list_construction() { 202 | let mut program_context = ProgramContext::new(); 203 | let mut module_context = ModuleContext::new("Test".to_owned()); 204 | 205 | let number = |i| ast::JillExpression::Literal(ast::JillLiteral::Integer(i)); 206 | let list = |l| ast::JillExpression::Literal(ast::JillLiteral::List(l)); 207 | 208 | // [[5, 2], [7, 3]] 209 | let lists = vec![ 210 | list(vec![number(5), number(2)]), 211 | list(vec![number(7), number(3)]), 212 | ]; 213 | 214 | let inner_list_1 = vec![ 215 | "call List.Empty 0", 216 | "pop temp 1", 217 | "push constant 2", 218 | "push temp 1", 219 | "call List.List 2", 220 | "pop temp 1", 221 | "push constant 5", 222 | "push temp 1", 223 | "call List.List 2", 224 | ]; 225 | 226 | let inner_list_2 = vec![ 227 | "call List.Empty 0", 228 | "pop temp 1", 229 | "push constant 3", 230 | "push temp 1", 231 | "call List.List 2", 232 | "pop temp 1", 233 | "push constant 7", 234 | "push temp 1", 235 | "call List.List 2", 236 | ]; 237 | 238 | let expected = [ 239 | vec!["call List.Empty 0", "pop temp 0"], 240 | inner_list_2, 241 | vec!["push temp 0", "call List.List 2", "pop temp 0"], 242 | inner_list_1, 243 | vec!["push temp 0", "call List.List 2"], 244 | ] 245 | .concat() 246 | .join("\n"); 247 | 248 | assert!( 249 | super::construct_list(&lists, &mut module_context, &mut program_context).is_ok_and( 250 | |instructions| vm::VMInstructionBlock::from(instructions).compile() == expected 251 | ) 252 | ); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/codegen/common/mod.rs: -------------------------------------------------------------------------------- 1 | mod compiler_internal_call; 2 | mod expression; 3 | mod function_call; 4 | pub(super) mod function_declaration; 5 | mod function_reference; 6 | pub(super) mod helpers; 7 | mod literal; 8 | pub(super) mod variable; 9 | mod variable_name; 10 | -------------------------------------------------------------------------------- /src/codegen/common/variable.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::{ 3 | context::{ 4 | module::{VariableContext, VariableContextArguments}, 5 | ModuleContext, ProgramContext, 6 | }, 7 | error::FallableInstructions, 8 | vm, 9 | }, 10 | common::ast, 11 | }; 12 | 13 | use super::{expression, helpers::variable::JillVariableExtensions}; 14 | 15 | pub fn construct( 16 | variable: &ast::JillVariable, 17 | // pass segment as argument so we can share 18 | // this logic between global and local variables 19 | segment: vm::Segment, 20 | module_context: &mut ModuleContext, 21 | program_context: &mut ProgramContext, 22 | ) -> FallableInstructions { 23 | // (try to) evaluate assigned expression 24 | let expression_instructions = 25 | expression::construct(&variable.value, module_context, program_context)?; 26 | 27 | // discard pattern (let `_` = ...) is mapped to a variable with an empty name 28 | let is_discard = variable.is_discard(); 29 | 30 | let variable_context = if is_discard { 31 | // create dummy context to pop into 32 | VariableContext { 33 | segment: vm::Segment::Temp, 34 | index: program_context.temp_segment_index.request(), 35 | } 36 | } else { 37 | // (try to) register variable 38 | module_context.scope.add_variable( 39 | variable.name.0.clone(), 40 | VariableContextArguments::new(segment), 41 | )? 42 | }; 43 | 44 | let instruction_components = [expression_instructions, vec![variable_context.pop()]]; 45 | 46 | if is_discard { 47 | program_context.temp_segment_index.release(); 48 | } 49 | 50 | Ok(instruction_components.concat()) 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use crate::codegen::context::module::FunctionContextArguments; 56 | 57 | use super::*; 58 | 59 | #[test] 60 | fn test_assignment_construction() { 61 | let mut program_context = ProgramContext::new(); 62 | let mut module_context = ModuleContext::new("Test".to_owned()); 63 | 64 | assert!(module_context 65 | .scope 66 | .enter_function(String::from("f"), FunctionContextArguments::new(0)) 67 | .is_ok()); 68 | 69 | let name = ast::JillIdentifier(String::from("foo")); 70 | let expression = ast::JillExpression::Literal(ast::JillLiteral::Integer(5)); 71 | let variable = ast::JillVariable { 72 | name: name.clone(), 73 | value: expression, 74 | }; 75 | 76 | let segment = vm::Segment::Local; 77 | 78 | let expected = ["push constant 5", "pop local 0"].join("\n"); 79 | 80 | assert!(construct( 81 | &variable, 82 | segment, 83 | &mut module_context, 84 | &mut program_context 85 | ) 86 | .is_ok_and( 87 | |instructions| vm::VMInstructionBlock::from(instructions).compile() == expected 88 | )); 89 | 90 | assert!(module_context.scope.search_variable(&name.0).is_some()); 91 | } 92 | 93 | #[test] 94 | fn test_discard_construction() { 95 | let mut program_context = ProgramContext::new(); 96 | let mut module_context = ModuleContext::new("Test".to_owned()); 97 | 98 | assert!(module_context 99 | .scope 100 | .enter_function(String::from("f"), FunctionContextArguments::new(0)) 101 | .is_ok()); 102 | 103 | let discard_name = ast::JillIdentifier(String::new()); 104 | let expression = ast::JillExpression::Literal(ast::JillLiteral::Integer(5)); 105 | let variable = ast::JillVariable { 106 | name: discard_name.clone(), 107 | value: expression, 108 | }; 109 | 110 | let segment = vm::Segment::Local; 111 | 112 | let expected = ["push constant 5", "pop temp 0"].join("\n"); 113 | 114 | assert!(construct( 115 | &variable, 116 | segment, 117 | &mut module_context, 118 | &mut program_context 119 | ) 120 | .is_ok_and( 121 | |instructions| vm::VMInstructionBlock::from(instructions).compile() == expected 122 | )); 123 | 124 | // "discard" not accidentally added to scope 125 | assert!(module_context 126 | .scope 127 | .search_variable(&discard_name.0) 128 | .is_none()); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/codegen/common/variable_name.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::{ 3 | context::{ModuleContext, ProgramContext}, 4 | error::{Error, FallableInstructions}, 5 | }, 6 | common::ast, 7 | }; 8 | 9 | pub fn construct( 10 | variable: &ast::JillIdentifier, 11 | module_context: &ModuleContext, 12 | _program_context: &ProgramContext, 13 | ) -> FallableInstructions { 14 | let variable_name = &variable.0; 15 | 16 | let Some(variable_context) = module_context.scope.search_variable(variable_name) else { 17 | return Err(Error::VariableNotInScope(variable_name.clone())); 18 | }; 19 | 20 | let instructions = variable_context.push(); 21 | 22 | Ok(instructions) 23 | } 24 | 25 | #[cfg(test)] 26 | mod tests { 27 | use crate::codegen::{ 28 | context::module::{FunctionContextArguments, VariableContextArguments}, 29 | vm, 30 | }; 31 | 32 | use super::*; 33 | 34 | #[test] 35 | fn test_successful_variable_name_construction() { 36 | let program_context = ProgramContext::new(); 37 | let mut module_context = ModuleContext::new("Test".to_owned()); 38 | 39 | assert!(module_context 40 | .scope 41 | .enter_function("foo".to_string(), FunctionContextArguments::new(1)) 42 | .is_ok()); 43 | 44 | assert!(module_context 45 | .scope 46 | .add_variable( 47 | "a".to_string(), 48 | VariableContextArguments::new(vm::Segment::Argument), 49 | ) 50 | .is_ok()); 51 | 52 | let expected_instructions = "push argument 0"; 53 | 54 | assert!(construct( 55 | &ast::JillIdentifier("a".to_string()), 56 | &module_context, 57 | &program_context 58 | ) 59 | .is_ok_and( 60 | |instructions| vm::VMInstructionBlock::from(instructions).compile() 61 | == expected_instructions 62 | )); 63 | } 64 | 65 | #[test] 66 | fn test_unsuccessful_variable_name_construction() { 67 | let program_context = ProgramContext::new(); 68 | let module_context = ModuleContext::new("Test".to_owned()); 69 | 70 | assert!(construct( 71 | &ast::JillIdentifier("a".to_string()), 72 | &module_context, 73 | &program_context 74 | ) 75 | .is_err_and(|err| matches!(err, Error::VariableNotInScope(_)))); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/codegen/context/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod module; 2 | pub mod program; 3 | 4 | pub use module::Context as ModuleContext; 5 | pub use program::Context as ProgramContext; 6 | -------------------------------------------------------------------------------- /src/codegen/context/program.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use crate::codegen::{ 4 | error::{Error, FallableAction}, 5 | post_compilation::jillstd::JillStdUsageTracker, 6 | vm, 7 | }; 8 | 9 | // region: Context 10 | 11 | type ModuleName = String; 12 | 13 | /// Context information regarding the entire program, across modules 14 | /// (e.g. which parts of the `std` need to be generated, 15 | /// which functions need to be dispatched etc.). 16 | #[derive(Debug)] 17 | pub struct Context { 18 | pub function_dispatch: FunctionDispatch, 19 | pub std_usage_tracker: JillStdUsageTracker, 20 | pub program_metadata: JillProgramMetadata, 21 | pub globals: HashSet, 22 | pub temp_segment_index: TempSegmentIndex, 23 | } 24 | 25 | impl Context { 26 | pub fn new() -> Self { 27 | Self { 28 | function_dispatch: FunctionDispatch::new(), 29 | std_usage_tracker: JillStdUsageTracker::new(), 30 | program_metadata: JillProgramMetadata::new(), 31 | globals: HashSet::new(), 32 | temp_segment_index: TempSegmentIndex(0), 33 | } 34 | } 35 | } 36 | 37 | // endregion 38 | 39 | // region: Function Dispatch 40 | 41 | pub type FunctionReferenceIndex = usize; 42 | type FunctionReferenceCount = usize; 43 | 44 | /// Track functions which are used as a first-class object 45 | /// (i.e. function references from AST) for dispatch generation. 46 | #[derive(Debug)] 47 | pub struct FunctionDispatch { 48 | references: HashMap, 49 | } 50 | 51 | impl FunctionDispatch { 52 | pub fn new() -> Self { 53 | Self { 54 | references: HashMap::new(), 55 | } 56 | } 57 | 58 | /// Handle function reference encounter by either 59 | /// adding it to existing references (if not already present) 60 | /// or increasing its count. 61 | pub fn encounter(&mut self, name: vm::VMFunctionName) -> FunctionReferenceIndex { 62 | let num_of_items = self.references.len(); 63 | 64 | self.references 65 | .entry(name) 66 | .and_modify(|(_, count)| *count += 1) 67 | .or_insert((num_of_items, 1)) 68 | .0 69 | } 70 | 71 | /// Return a [Vec] of encountered function references with their indices, 72 | /// ordered by their reference count. 73 | pub fn collect(&self) -> Vec<(vm::VMFunctionName, FunctionReferenceIndex)> { 74 | let mut items: Vec<_> = self.references.iter().collect(); 75 | 76 | // sort items by their count, descending 77 | items.sort_by(|(_, (_, a_count)), (_, (_, b_count))| a_count.cmp(b_count).reverse()); 78 | 79 | // map to (name, index) 80 | items 81 | .into_iter() 82 | .map(|(name, (idx, _))| (name.clone(), *idx)) 83 | .collect() 84 | } 85 | } 86 | 87 | // endregion 88 | 89 | // region: Program metadata 90 | 91 | #[derive(Debug, Clone, Copy)] 92 | pub struct JillFunctionMetadata { 93 | pub arity: usize, 94 | pub has_captures: bool, 95 | } 96 | 97 | #[derive(Debug)] 98 | pub struct JillProgramMetadata { 99 | metadata: HashMap, 100 | } 101 | 102 | impl JillProgramMetadata { 103 | pub fn new() -> Self { 104 | Self { 105 | metadata: HashMap::new(), 106 | } 107 | } 108 | 109 | pub fn log_function_metadata( 110 | &mut self, 111 | name: vm::VMFunctionName, 112 | arity: usize, 113 | has_captures: bool, 114 | ) -> FallableAction { 115 | let function_metadata = JillFunctionMetadata { 116 | arity, 117 | has_captures, 118 | }; 119 | 120 | if self 121 | .metadata 122 | .insert(name.clone(), function_metadata) 123 | .is_some() 124 | { 125 | Err(Error::MultipleFunctionDefinitions(name)) 126 | } else { 127 | Ok(()) 128 | } 129 | } 130 | 131 | pub fn get_function_metadata(&self, name: &vm::VMFunctionName) -> Option { 132 | self.metadata.get(name).copied() 133 | } 134 | } 135 | 136 | // endregion 137 | 138 | #[derive(Debug)] 139 | pub struct TempSegmentIndex(usize); 140 | 141 | impl TempSegmentIndex { 142 | /// Request the first available index for the `temp` segment 143 | /// (incrementing the counter in the process for further usage). 144 | pub fn request(&mut self) -> usize { 145 | let next = self.0 + 1; 146 | 147 | // replace current (free) index with the next one 148 | // and return the current (previous) index 149 | std::mem::replace(&mut self.0, next) 150 | } 151 | 152 | /// Mark the index that was last used as free for further usage 153 | /// (by decrementing the internal counter). 154 | pub fn release(&mut self) { 155 | self.0 -= 1; 156 | } 157 | } 158 | 159 | #[cfg(test)] 160 | mod tests { 161 | use crate::codegen::{error::Error, vm}; 162 | 163 | use super::JillProgramMetadata; 164 | 165 | #[allow(clippy::similar_names)] 166 | #[test] 167 | fn test_function_dispatch() { 168 | let mut fn_dispatch = super::FunctionDispatch::new(); 169 | 170 | let foo = vm::VMFunctionName::from_literal("foo"); 171 | let baz = vm::VMFunctionName::from_literal("baz"); 172 | let bar = vm::VMFunctionName::from_literal("bar"); 173 | let biz = vm::VMFunctionName::from_literal("biz"); 174 | 175 | fn_dispatch.encounter(foo.clone()); 176 | fn_dispatch.encounter(bar.clone()); 177 | fn_dispatch.encounter(foo.clone()); 178 | fn_dispatch.encounter(baz.clone()); 179 | fn_dispatch.encounter(biz.clone()); 180 | fn_dispatch.encounter(bar.clone()); 181 | fn_dispatch.encounter(foo.clone()); 182 | fn_dispatch.encounter(baz.clone()); 183 | fn_dispatch.encounter(foo.clone()); 184 | fn_dispatch.encounter(baz.clone()); 185 | 186 | let collection = fn_dispatch.collect(); 187 | 188 | assert_eq!(collection, vec![(foo, 0), (baz, 2), (bar, 1), (biz, 3),]); 189 | } 190 | 191 | #[test] 192 | fn test_program_metadata() { 193 | let mut program_metadata = JillProgramMetadata::new(); 194 | 195 | // setup 196 | assert!(program_metadata 197 | .log_function_metadata(vm::VMFunctionName::from_literal("Foo.foo"), 2, false) 198 | .is_ok()); 199 | 200 | assert!(program_metadata 201 | .log_function_metadata(vm::VMFunctionName::from_literal("Foo.bar"), 1, true) 202 | .is_ok()); 203 | 204 | assert!(program_metadata 205 | .log_function_metadata(vm::VMFunctionName::from_literal("Bar.bar"), 4, false) 206 | .is_ok()); 207 | 208 | // existing function 209 | assert!(program_metadata 210 | .get_function_metadata(&vm::VMFunctionName::from_literal("Foo.bar")) 211 | .is_some_and(|metadata| metadata.arity == 1 && metadata.has_captures)); 212 | 213 | // non-existing function 214 | assert!(program_metadata 215 | .get_function_metadata(&vm::VMFunctionName::from_literal("Foo.baz")) 216 | .is_none()); 217 | 218 | // duplicate function log 219 | assert!(program_metadata 220 | .log_function_metadata(vm::VMFunctionName::from_literal("Bar.bar"), 4, false) 221 | .is_err_and(|err| matches!(err, Error::MultipleFunctionDefinitions(_)))); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/codegen/error.rs: -------------------------------------------------------------------------------- 1 | use crate::fileio; 2 | 3 | use super::vm; 4 | 5 | /// List of all errors that could possibly occur during code generation. 6 | #[derive(Debug)] 7 | #[allow(dead_code, reason = "still to be used")] 8 | pub enum Error { 9 | VariableAlreadyInScope(String), 10 | FunctionAlreadyInScope(String), 11 | VariableNotInScope(String), 12 | CaptureNotInScope(String), 13 | InvalidFunctionReference(String), 14 | MultipleFunctionDefinitions(vm::VMFunctionName), 15 | InvalidFunctionCall(String), 16 | InvalidCompilerInternalFunctionCall(String), 17 | CaptureInTopLevelFunction(String), 18 | DiscardInGlobal, 19 | } 20 | 21 | impl std::fmt::Display for Error { 22 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 23 | // TODO!: implement 24 | write!(f, "ERROR") 25 | } 26 | } 27 | 28 | impl std::error::Error for Error {} 29 | 30 | pub type FallableAction = Result<(), Error>; 31 | pub type FallableInstructions = Result, Error>; 32 | pub type FallableOutputFile = Result; 33 | -------------------------------------------------------------------------------- /src/codegen/functions.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::context::{ModuleContext, ProgramContext}, 3 | common::ast, 4 | }; 5 | 6 | use super::{ 7 | common, 8 | error::{Error, FallableAction}, 9 | }; 10 | 11 | pub fn construct( 12 | functions: Vec, 13 | module_context: &mut ModuleContext, 14 | program_context: &mut ProgramContext, 15 | ) -> FallableAction { 16 | // check for captures in top-level functions (which don't 17 | // make sense, as there is nothing to capture) 18 | if let Some(function) = functions.iter().find(|f| !f.captures.is_empty()) { 19 | return Err(Error::CaptureInTopLevelFunction(function.name.0.clone())); 20 | } 21 | 22 | for function in functions { 23 | let instructions = 24 | common::function_declaration::construct(&function, module_context, program_context)?; 25 | 26 | module_context.output.add_block(instructions.into()); 27 | } 28 | 29 | Ok(()) 30 | } 31 | 32 | #[cfg(test)] 33 | mod tests { 34 | use super::*; 35 | 36 | #[test] 37 | fn test_function_construction() { 38 | use ast::*; 39 | 40 | let mut program_context = ProgramContext::new(); 41 | let mut module_context = ModuleContext::new("Test".to_owned()); 42 | 43 | // fn f x = Int::add(x, 2). 44 | // fn g x = Module::other(x, 7). 45 | let functions = vec![ 46 | JillFunction { 47 | name: JillIdentifier("f".to_owned()), 48 | arguments: vec![JillIdentifier("x".to_string())], 49 | captures: vec![], 50 | body: JillFunctionBody { 51 | local_functions: vec![], 52 | local_variables: vec![], 53 | return_expression: JillExpression::FunctionCall(JillFunctionCall { 54 | reference: JillFunctionReference { 55 | modules_path: vec![JillIdentifier("Int".to_string())], 56 | associated_type: None, 57 | function_name: JillIdentifier("add".to_owned()), 58 | }, 59 | arguments: vec![ 60 | JillExpression::VariableName(JillIdentifier("x".to_string())), 61 | JillExpression::Literal(JillLiteral::Integer(2)), 62 | ], 63 | }), 64 | }, 65 | }, 66 | JillFunction { 67 | name: JillIdentifier("g".to_owned()), 68 | arguments: vec![JillIdentifier("x".to_string())], 69 | captures: vec![], 70 | body: JillFunctionBody { 71 | local_functions: vec![], 72 | local_variables: vec![], 73 | return_expression: JillExpression::FunctionCall(JillFunctionCall { 74 | reference: JillFunctionReference { 75 | modules_path: vec![JillIdentifier("Module".to_string())], 76 | associated_type: None, 77 | function_name: JillIdentifier("other".to_owned()), 78 | }, 79 | arguments: vec![ 80 | JillExpression::VariableName(JillIdentifier("x".to_string())), 81 | JillExpression::Literal(JillLiteral::Integer(7)), 82 | ], 83 | }), 84 | }, 85 | }, 86 | ]; 87 | 88 | let expected_fn1 = vec![ 89 | "function Test.f 0", 90 | "push argument 0", 91 | "push constant 2", 92 | "add", 93 | "return", 94 | ]; 95 | let expected_fn2 = vec![ 96 | "function Test.g 0", 97 | "push argument 0", 98 | "push constant 7", 99 | "call Module.other 2", 100 | "return", 101 | ]; 102 | let expected = [expected_fn1, expected_fn2].concat().join("\n"); 103 | 104 | // construction successful and correct 105 | assert!(construct(functions, &mut module_context, &mut program_context).is_ok()); 106 | assert_eq!(module_context.output.compile(), expected); 107 | } 108 | 109 | #[test] 110 | fn test_capture_in_top_level_function() { 111 | use ast::*; 112 | 113 | let mut program_context = ProgramContext::new(); 114 | let mut module_context = ModuleContext::new("Test".to_owned()); 115 | 116 | // fn f x = Int::add(x, 2). 117 | // fn g x [test] = Module::other(x, test). 118 | let functions = vec![ 119 | JillFunction { 120 | name: JillIdentifier("f".to_owned()), 121 | arguments: vec![JillIdentifier("x".to_string())], 122 | captures: vec![], 123 | body: JillFunctionBody { 124 | local_functions: vec![], 125 | local_variables: vec![], 126 | return_expression: JillExpression::FunctionCall(JillFunctionCall { 127 | reference: JillFunctionReference { 128 | modules_path: vec![JillIdentifier("Int".to_string())], 129 | associated_type: None, 130 | function_name: JillIdentifier("add".to_owned()), 131 | }, 132 | arguments: vec![ 133 | JillExpression::VariableName(JillIdentifier("x".to_string())), 134 | JillExpression::Literal(JillLiteral::Integer(2)), 135 | ], 136 | }), 137 | }, 138 | }, 139 | JillFunction { 140 | name: JillIdentifier("g".to_owned()), 141 | arguments: vec![JillIdentifier("x".to_string())], 142 | captures: vec![JillIdentifier("test".to_string())], 143 | body: JillFunctionBody { 144 | local_functions: vec![], 145 | local_variables: vec![], 146 | return_expression: JillExpression::FunctionCall(JillFunctionCall { 147 | reference: JillFunctionReference { 148 | modules_path: vec![JillIdentifier("Module".to_string())], 149 | associated_type: None, 150 | function_name: JillIdentifier("other".to_owned()), 151 | }, 152 | arguments: vec![ 153 | JillExpression::VariableName(JillIdentifier("x".to_string())), 154 | JillExpression::VariableName(JillIdentifier("test".to_string())), 155 | ], 156 | }), 157 | }, 158 | }, 159 | ]; 160 | 161 | assert!( 162 | construct(functions, &mut module_context, &mut program_context) 163 | .is_err_and(|err| matches!(err, Error::CaptureInTopLevelFunction(_))) 164 | ); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/codegen/globals.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::{ 3 | context::{ModuleContext, ProgramContext}, 4 | vm, 5 | }, 6 | common::ast, 7 | }; 8 | 9 | use super::{ 10 | common::helpers::{self}, 11 | error::{Error, FallableAction, FallableInstructions}, 12 | GLOBALS_INIT_FN_NAME, 13 | }; 14 | 15 | pub fn construct( 16 | globals: &[ast::JillVariable], 17 | module_context: &mut ModuleContext, 18 | program_context: &mut ProgramContext, 19 | ) -> FallableAction { 20 | // check for discard in globals (which doesn't make sense) 21 | if globals 22 | .iter() 23 | .any(helpers::variable::JillVariableExtensions::is_discard) 24 | { 25 | return Err(Error::DiscardInGlobal); 26 | } 27 | 28 | let globals_assignment_instructions = 29 | construct_globals(globals, module_context, program_context)?; 30 | 31 | if !globals_assignment_instructions.is_empty() { 32 | // log this module as one of those which will be called in `Globals.init` 33 | program_context 34 | .globals 35 | .insert(module_context.module_name.clone()); 36 | 37 | let function_name = 38 | vm::VMFunctionName::construct(&module_context.module_name, "", GLOBALS_INIT_FN_NAME); 39 | 40 | let instructions = [ 41 | vec![vm::function(function_name, 0)], 42 | globals_assignment_instructions, 43 | vec![vm::push(vm::Segment::Constant, 0), vm::vm_return()], 44 | ]; 45 | 46 | module_context 47 | .output 48 | .add_block(instructions.concat().into()); 49 | } 50 | 51 | Ok(()) 52 | } 53 | 54 | fn construct_globals( 55 | globals: &[ast::JillVariable], 56 | module_context: &mut ModuleContext, 57 | program_context: &mut ProgramContext, 58 | ) -> FallableInstructions { 59 | globals 60 | .iter() 61 | .map(|variable| { 62 | super::common::variable::construct( 63 | variable, 64 | vm::Segment::Static, 65 | module_context, 66 | program_context, 67 | ) 68 | }) 69 | .collect::, _>>() 70 | .map(|blocks| blocks.concat()) 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use super::*; 76 | 77 | #[test] 78 | fn test_globals_construction() { 79 | let module_name = "Test"; 80 | let mut program_context = ProgramContext::new(); 81 | let mut module_context = ModuleContext::new(module_name.to_owned()); 82 | 83 | let variables = vec![ 84 | ast::JillVariable { 85 | name: ast::JillIdentifier(String::from("foo")), 86 | value: ast::JillExpression::Literal(ast::JillLiteral::Integer(5)), 87 | }, 88 | ast::JillVariable { 89 | name: ast::JillIdentifier(String::from("bar")), 90 | value: ast::JillExpression::Literal(ast::JillLiteral::Bool(true)), 91 | }, 92 | ]; 93 | 94 | let expected = [ 95 | "function Test._init__globals 0", 96 | // `foo` 97 | "push constant 5", 98 | "pop static 0", 99 | // `bar` 100 | "push constant 1", 101 | "neg", 102 | "pop static 1", 103 | // return 104 | "push constant 0", 105 | "return", 106 | ] 107 | .join("\n"); 108 | 109 | // `foo` not previously present in scope 110 | assert!(module_context 111 | .scope 112 | .search_variable(&"foo".to_string()) 113 | .is_none()); 114 | 115 | // construction successful and correct 116 | assert!(construct(&variables, &mut module_context, &mut program_context).is_ok()); 117 | assert_eq!(module_context.output.compile(), expected); 118 | 119 | // `foo` now added to scope 120 | assert!(module_context 121 | .scope 122 | .search_variable(&"foo".to_string()) 123 | .is_some()); 124 | 125 | // module added to "globals to generate" list 126 | assert!(program_context.globals.contains(module_name)); 127 | } 128 | 129 | #[test] 130 | fn test_no_globals() { 131 | let module_name = "Test"; 132 | let mut program_context = ProgramContext::new(); 133 | let mut module_context = ModuleContext::new(module_name.to_owned()); 134 | 135 | // no globals in module 136 | let variables = vec![]; 137 | 138 | // result should be empty 139 | assert!(construct(&variables, &mut module_context, &mut program_context).is_ok()); 140 | assert_eq!(module_context.output.compile(), ""); 141 | 142 | // module should not be added to "globals to generate" list 143 | assert!(!program_context.globals.contains(module_name)); 144 | } 145 | 146 | #[test] 147 | fn test_discard_in_globals() { 148 | let module_name = "Test"; 149 | let mut program_context = ProgramContext::new(); 150 | let mut module_context = ModuleContext::new(module_name.to_owned()); 151 | 152 | let variables = vec![ 153 | ast::JillVariable { 154 | name: ast::JillIdentifier(String::from("foo")), 155 | value: ast::JillExpression::Literal(ast::JillLiteral::Integer(5)), 156 | }, 157 | ast::JillVariable { 158 | name: ast::JillIdentifier(String::new()), 159 | value: ast::JillExpression::Literal(ast::JillLiteral::Bool(true)), 160 | }, 161 | ]; 162 | 163 | assert!( 164 | construct(&variables, &mut module_context, &mut program_context) 165 | .is_err_and(|err| matches!(err, Error::DiscardInGlobal)) 166 | ); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/codegen/mod.rs: -------------------------------------------------------------------------------- 1 | //! Logic for converting parsed `Jill` code (_AST_) 2 | //! to Hack VM instructions. 3 | 4 | use context::{ModuleContext, ProgramContext}; 5 | use error::FallableOutputFile; 6 | 7 | use crate::{common::ast, fileio::output::OutputFile}; 8 | 9 | pub mod context; 10 | pub mod error; 11 | pub mod post_compilation; 12 | 13 | mod common; 14 | mod functions; 15 | mod globals; 16 | mod types; 17 | mod vm; 18 | 19 | const GLOBALS_INIT_FN_NAME: &str = "_init__globals"; 20 | 21 | pub fn construct_module( 22 | module: ast::JillModule, 23 | program_context: &mut ProgramContext, 24 | ) -> FallableOutputFile { 25 | let mut module_context = ModuleContext::new(module.name); 26 | 27 | types::construct(module.content.types, &mut module_context, program_context)?; 28 | 29 | globals::construct( 30 | &module.content.variables, 31 | &mut module_context, 32 | program_context, 33 | )?; 34 | 35 | functions::construct( 36 | module.content.functions, 37 | &mut module_context, 38 | program_context, 39 | )?; 40 | 41 | Ok(OutputFile::new( 42 | module_context.module_name, 43 | module_context.output.compile(), 44 | )) 45 | } 46 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/function_dispatch.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::{ 3 | common::helpers, 4 | context::{ 5 | module::VariableContext, 6 | program::{FunctionReferenceIndex, JillFunctionMetadata}, 7 | ProgramContext, 8 | }, 9 | error::{Error, FallableInstructions}, 10 | vm, 11 | }, 12 | fileio::output::OutputFile, 13 | }; 14 | 15 | pub fn construct(program_context: &ProgramContext) -> Result, Error> { 16 | let functions_to_dispatch = program_context.function_dispatch.collect(); 17 | 18 | if functions_to_dispatch.is_empty() { 19 | // no functions to dispatch - no need to generate anything 20 | return Ok(None); 21 | } 22 | 23 | let instruction_block: vm::VMInstructionBlock = [ 24 | construct_new(), 25 | construct_dispose(), 26 | construct_call(&functions_to_dispatch, program_context)?, 27 | ] 28 | .concat() 29 | .into(); 30 | 31 | Ok(Some(OutputFile::new( 32 | String::from("Fn"), 33 | instruction_block.compile(), 34 | ))) 35 | } 36 | 37 | // NOTE: since this module is completely unrelated the rest of the program, 38 | // we will use TEMP 7 (last one) to prevent any possible overwrites (just in case) 39 | const FN_TEMP_STORAGE: VariableContext = VariableContext { 40 | segment: vm::Segment::Temp, 41 | index: 7, 42 | }; 43 | 44 | fn construct_new() -> Vec { 45 | vec![ 46 | vm::function(vm::VMFunctionName::from_literal("Fn._new"), 0), 47 | // we store two fields in closure object 48 | vm::push(vm::Segment::Constant, 2), 49 | vm::call(vm::VMFunctionName::from_literal("Memory.alloc"), 1), 50 | vm::pop(vm::Segment::Pointer, 0), 51 | // fid 52 | vm::push(vm::Segment::Argument, 0), 53 | vm::pop(vm::Segment::This, 0), 54 | // captures array 55 | vm::push(vm::Segment::Argument, 1), 56 | vm::pop(vm::Segment::This, 1), 57 | // return constructed object 58 | vm::push(vm::Segment::Pointer, 0), 59 | vm::vm_return(), 60 | ] 61 | } 62 | 63 | fn construct_dispose() -> Vec { 64 | vec![ 65 | vm::function(vm::VMFunctionName::from_literal("Fn.dispose"), 0), 66 | // set closure object as `THIS` 67 | vm::push(vm::Segment::Argument, 0), 68 | vm::pop(vm::Segment::Pointer, 0), 69 | // dispose captures array 70 | // NOTE: must perform a null check (if there were no captures) 71 | vm::push(vm::Segment::This, 1), 72 | vm::null(), 73 | vm::command(vm::VMCommand::Eq), 74 | vm::label(vm::LabelAction::IfGoto, "SKIP_CAPTURES_DEALLOC"), 75 | vm::push(vm::Segment::This, 1), 76 | vm::call(vm::VMFunctionName::from_literal("Array.dispose"), 1), 77 | FN_TEMP_STORAGE.pop(), 78 | vm::label(vm::LabelAction::Label, "SKIP_CAPTURES_DEALLOC"), 79 | // deAlloc closure object 80 | vm::push(vm::Segment::Argument, 0), 81 | vm::call(vm::VMFunctionName::from_literal("Memory.deAlloc"), 1), 82 | vm::vm_return(), 83 | ] 84 | } 85 | 86 | fn construct_call( 87 | functions_to_dispatch: &[(vm::VMFunctionName, FunctionReferenceIndex)], 88 | program_context: &ProgramContext, 89 | ) -> FallableInstructions { 90 | let vm_function_name = vm::VMFunctionName::from_literal("Fn._call"); 91 | 92 | let closure_as_this = vec![ 93 | vm::push(vm::Segment::Argument, 0), 94 | vm::pop(vm::Segment::Pointer, 0), 95 | ]; 96 | 97 | let arguments_construction = vec![ 98 | vm::push(vm::Segment::Constant, 0), 99 | FN_TEMP_STORAGE.pop(), 100 | vm::label(vm::LabelAction::Label, "ARGS_INIT_START"), 101 | // section: condition check 102 | // counter 103 | FN_TEMP_STORAGE.push()[0].clone(), 104 | // arity 105 | vm::push(vm::Segment::Argument, 1), 106 | vm::command(vm::VMCommand::Eq), 107 | vm::label(vm::LabelAction::IfGoto, "ARGS_INIT_END"), 108 | // section: loop body 109 | // counter 110 | FN_TEMP_STORAGE.push()[0].clone(), 111 | // arguments 112 | vm::push(vm::Segment::Argument, 2), 113 | vm::command(vm::VMCommand::Add), 114 | // use as array (THAT segment) 115 | vm::pop(vm::Segment::Pointer, 1), 116 | vm::push(vm::Segment::That, 0), 117 | // increase counter 118 | FN_TEMP_STORAGE.push()[0].clone(), 119 | vm::push(vm::Segment::Constant, 1), 120 | vm::command(vm::VMCommand::Add), 121 | FN_TEMP_STORAGE.pop(), 122 | // repeat loop 123 | vm::label(vm::LabelAction::Goto, "ARGS_INIT_START"), 124 | vm::label(vm::LabelAction::Label, "ARGS_INIT_END"), 125 | // deAlloc arguments array (AFTER all have been pushed to stack) 126 | vm::push(vm::Segment::Argument, 2), 127 | vm::call(vm::VMFunctionName::from_literal("Array.dispose"), 1), 128 | FN_TEMP_STORAGE.pop(), 129 | ]; 130 | 131 | let captures = vec![ 132 | // check if captures array is NULL 133 | vm::push(vm::Segment::This, 1), 134 | vm::null(), 135 | vm::command(vm::VMCommand::Eq), 136 | // if it is NULL, don't push it onto the stack 137 | vm::label(vm::LabelAction::IfGoto, "SKIP_CAPTURES"), 138 | vm::push(vm::Segment::This, 1), 139 | vm::label(vm::LabelAction::Label, "SKIP_CAPTURES"), 140 | ]; 141 | 142 | let enumerated_functions = functions_to_dispatch.iter().enumerate(); 143 | 144 | let dispatch_jumps = enumerated_functions 145 | .clone() 146 | .map(|(i, (_, fid))| { 147 | vec![ 148 | // closure's fid 149 | vm::push(vm::Segment::This, 0), 150 | // currently-checked fid 151 | vm::push(vm::Segment::Constant, *fid), 152 | vm::command(vm::VMCommand::Eq), 153 | // jump to associated call 154 | vm::label(vm::LabelAction::IfGoto, format!("FN_{i}")), 155 | ] 156 | }) 157 | .collect::>() 158 | .concat(); 159 | 160 | let dispatch_calls = enumerated_functions 161 | .map(|(i, (vm_function_name, _))| { 162 | let Some(function_metadata) = get_function_metadata(vm_function_name, program_context) 163 | else { 164 | return Err(Error::InvalidFunctionReference( 165 | vm_function_name.to_string(), 166 | )); 167 | }; 168 | 169 | // if function has captures, call it with 170 | // one extra argument (captures array) 171 | let call_argument_count = 172 | function_metadata.arity + usize::from(function_metadata.has_captures); 173 | 174 | Ok(vec![ 175 | // jump label 176 | vm::label(vm::LabelAction::Label, format!("FN_{i}")), 177 | // directly call associated function 178 | vm::call(vm_function_name.clone(), call_argument_count), 179 | vm::vm_return(), 180 | ]) 181 | }) 182 | .collect::, _>>()? 183 | .concat(); 184 | 185 | Ok([ 186 | vec![vm::function(vm_function_name, 0)], 187 | closure_as_this, 188 | arguments_construction, 189 | captures, 190 | dispatch_jumps, 191 | // "default" (no fid-s matched) 192 | vec![vm::push(vm::Segment::Constant, 0), vm::vm_return()], 193 | dispatch_calls, 194 | ] 195 | .concat()) 196 | } 197 | 198 | fn get_function_metadata( 199 | vm_function_name: &vm::VMFunctionName, 200 | program_context: &ProgramContext, 201 | ) -> Option { 202 | program_context 203 | .program_metadata 204 | .get_function_metadata(vm_function_name) 205 | .map_or_else(|| helpers::jack_api::function_arity(vm_function_name), Some) 206 | } 207 | 208 | #[cfg(test)] 209 | mod tests { 210 | use super::*; 211 | 212 | #[allow(clippy::similar_names, clippy::redundant_clone, clippy::too_many_lines)] 213 | #[test] 214 | fn test_dispatch_call_construction() { 215 | let mut program_context = ProgramContext::new(); 216 | 217 | // region: setup 218 | 219 | let foo = vm::VMFunctionName::from_literal("Test.foo"); 220 | let bar = vm::VMFunctionName::from_literal("Test.bar"); 221 | let baz = vm::VMFunctionName::from_literal("Test.baz"); 222 | let biz = vm::VMFunctionName::from_literal("Test.biz"); 223 | 224 | // arities 225 | assert!(program_context 226 | .program_metadata 227 | .log_function_metadata(foo.clone(), 2, false) 228 | .is_ok()); 229 | 230 | assert!(program_context 231 | .program_metadata 232 | .log_function_metadata(bar.clone(), 1, true) 233 | .is_ok()); 234 | 235 | assert!(program_context 236 | .program_metadata 237 | .log_function_metadata(baz.clone(), 5, false) 238 | .is_ok()); 239 | 240 | assert!(program_context 241 | .program_metadata 242 | .log_function_metadata(biz.clone(), 2, false) 243 | .is_ok()); 244 | 245 | // dispatch 246 | program_context.function_dispatch.encounter(foo.clone()); 247 | program_context.function_dispatch.encounter(bar.clone()); 248 | program_context.function_dispatch.encounter(foo.clone()); 249 | program_context.function_dispatch.encounter(baz.clone()); 250 | program_context.function_dispatch.encounter(biz.clone()); 251 | program_context.function_dispatch.encounter(bar.clone()); 252 | program_context.function_dispatch.encounter(foo.clone()); 253 | program_context.function_dispatch.encounter(baz.clone()); 254 | program_context.function_dispatch.encounter(foo.clone()); 255 | program_context.function_dispatch.encounter(baz.clone()); 256 | 257 | // endregion 258 | 259 | let expected = [ 260 | "function Fn._call 0", 261 | // resolve `this` 262 | "push argument 0", 263 | "pop pointer 0", 264 | // SECTION: push args on stack 265 | "push constant 0", 266 | "pop temp 7", 267 | "label ARGS_INIT_START", 268 | // condition check 269 | "push temp 7", 270 | "push argument 1", 271 | "eq", 272 | "if-goto ARGS_INIT_END", 273 | // loop body 274 | "push temp 7", 275 | "push argument 2", 276 | "add", 277 | "pop pointer 1", 278 | "push that 0", 279 | // i = i + 1 280 | "push temp 7", 281 | "push constant 1", 282 | "add", 283 | "pop temp 7", 284 | "goto ARGS_INIT_START", 285 | "label ARGS_INIT_END", 286 | // args array cleanup 287 | "push argument 2", 288 | "call Array.dispose 1", 289 | "pop temp 7", 290 | // end SECTION: push args on stack 291 | // captures 292 | "push this 1", 293 | "push constant 0", 294 | "eq", 295 | "if-goto SKIP_CAPTURES", 296 | "push this 1", 297 | "label SKIP_CAPTURES", 298 | // SECTION: dispatch jumps 299 | // foo 300 | "push this 0", 301 | "push constant 0", 302 | "eq", 303 | "if-goto FN_0", 304 | // baz 305 | "push this 0", 306 | "push constant 2", 307 | "eq", 308 | "if-goto FN_1", 309 | // bar 310 | "push this 0", 311 | "push constant 1", 312 | "eq", 313 | "if-goto FN_2", 314 | // biz 315 | "push this 0", 316 | "push constant 3", 317 | "eq", 318 | "if-goto FN_3", 319 | // "default" 320 | "push constant 0", 321 | "return", 322 | // end SECTION: dispatch jumps 323 | // SECTION: dispatch calls 324 | // foo 325 | "label FN_0", 326 | "call Test.foo 2", 327 | "return", 328 | // baz 329 | "label FN_1", 330 | "call Test.baz 5", 331 | "return", 332 | // bar 333 | "label FN_2", 334 | "call Test.bar 2", 335 | "return", 336 | // biz 337 | "label FN_3", 338 | "call Test.biz 2", 339 | "return", 340 | // end SECTION: dispatch calls 341 | ] 342 | .join("\n"); 343 | 344 | assert!(construct_call( 345 | &program_context.function_dispatch.collect(), 346 | &program_context, 347 | ) 348 | .is_ok_and( 349 | |instructions| vm::VMInstructionBlock::from(instructions).compile() == expected 350 | )); 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/globals.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::{context::ProgramContext, vm, GLOBALS_INIT_FN_NAME}, 3 | fileio::output::OutputFile, 4 | }; 5 | 6 | pub fn construct(program_context: &ProgramContext) -> Option { 7 | if program_context.globals.is_empty() { 8 | // no globals in any module - no need to generate anything 9 | return None; 10 | } 11 | 12 | let globals_init_call_instructions = program_context 13 | .globals 14 | .iter() 15 | .map(|module_name| { 16 | let vm_function_name = 17 | vm::VMFunctionName::construct(module_name, "", GLOBALS_INIT_FN_NAME); 18 | 19 | vec![vm::call(vm_function_name, 0), vm::pop(vm::Segment::Temp, 0)] 20 | }) 21 | .collect::>() 22 | .concat(); 23 | 24 | let instruction_block: vm::VMInstructionBlock = [ 25 | vec![vm::function( 26 | vm::VMFunctionName::from_literal("Globals.init"), 27 | 0, 28 | )], 29 | globals_init_call_instructions, 30 | vec![vm::push(vm::Segment::Constant, 0), vm::vm_return()], 31 | ] 32 | .concat() 33 | .into(); 34 | 35 | Some(OutputFile::new( 36 | String::from("Globals"), 37 | instruction_block.compile(), 38 | )) 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use std::collections::HashSet; 44 | 45 | use super::*; 46 | 47 | #[test] 48 | fn test_globals_init_construction() { 49 | let mut program_context = ProgramContext::new(); 50 | 51 | program_context.globals.insert(String::from("Foo")); 52 | program_context.globals.insert(String::from("Mod_One")); 53 | program_context.globals.insert(String::from("Mod_Two")); 54 | program_context.globals.insert(String::from("Mod")); 55 | 56 | let expected = [ 57 | "function Globals.init 0", 58 | &format!("call Foo.{GLOBALS_INIT_FN_NAME} 0"), 59 | "pop temp 0", 60 | &format!("call Mod_One.{GLOBALS_INIT_FN_NAME} 0"), 61 | "pop temp 0", 62 | &format!("call Mod_Two.{GLOBALS_INIT_FN_NAME} 0"), 63 | "pop temp 0", 64 | &format!("call Mod.{GLOBALS_INIT_FN_NAME} 0"), 65 | "pop temp 0", 66 | "push constant 0", 67 | "return", 68 | ]; 69 | 70 | let output = construct(&program_context).expect("should be Some(_)"); 71 | 72 | // since the output is built from hashset elements, we can't 73 | // directly compare to expected output (order is not guaranteed) 74 | 75 | let output_items: Vec<_> = output.content().split('\n').collect(); 76 | 77 | // same amount of lines 78 | assert_eq!(output_items.len(), expected.len()); 79 | 80 | let expected_items = HashSet::from(expected); 81 | 82 | let mut matches = HashSet::with_capacity(expected_items.len()); 83 | 84 | // no line that isn't expected 85 | for item in output_items { 86 | assert!(expected_items.contains(&item)); 87 | 88 | matches.insert(item); 89 | } 90 | 91 | // ALL expected lines (no duplicates) 92 | assert_eq!(matches.len(), expected_items.len()); 93 | } 94 | 95 | #[test] 96 | fn test_no_globals() { 97 | let program_context = ProgramContext::new(); 98 | 99 | assert_eq!( 100 | construct(&program_context) 101 | .as_ref() 102 | .map(OutputFile::content), 103 | None 104 | ); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Bool/and.vm: -------------------------------------------------------------------------------- 1 | function Bool.and 0 2 | push argument 0 3 | push argument 1 4 | and 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Bool/eq.vm: -------------------------------------------------------------------------------- 1 | function Bool.eq 0 2 | push argument 0 3 | push argument 1 4 | eq 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Bool/ge.vm: -------------------------------------------------------------------------------- 1 | function Bool.ge 0 2 | push argument 0 3 | push argument 1 4 | lt 5 | not 6 | return 7 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Bool/gt.vm: -------------------------------------------------------------------------------- 1 | function Bool.gt 0 2 | push argument 0 3 | push argument 1 4 | gt 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Bool/le.vm: -------------------------------------------------------------------------------- 1 | function Bool.le 0 2 | push argument 0 3 | push argument 1 4 | gt 5 | not 6 | return 7 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Bool/lt.vm: -------------------------------------------------------------------------------- 1 | function Bool.lt 0 2 | push argument 0 3 | push argument 1 4 | lt 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Bool/ne.vm: -------------------------------------------------------------------------------- 1 | function Bool.ne 0 2 | push argument 0 3 | push argument 1 4 | eq 5 | not 6 | return 7 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Bool/not.vm: -------------------------------------------------------------------------------- 1 | function Bool.not 0 2 | push argument 0 3 | not 4 | return 5 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Bool/or.vm: -------------------------------------------------------------------------------- 1 | function Bool.or 0 2 | push argument 0 3 | push argument 1 4 | or 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/add.vm: -------------------------------------------------------------------------------- 1 | function Int.add 0 2 | push argument 0 3 | push argument 1 4 | add 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/dec.vm: -------------------------------------------------------------------------------- 1 | function Int.dec 0 2 | push argument 0 3 | push constant 1 4 | sub 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/div.vm: -------------------------------------------------------------------------------- 1 | function Int.div 0 2 | push argument 0 3 | push argument 1 4 | call Math.divide 2 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/inc.vm: -------------------------------------------------------------------------------- 1 | function Int.inc 0 2 | push argument 0 3 | push constant 1 4 | add 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/max.vm: -------------------------------------------------------------------------------- 1 | function Int.max 0 2 | push argument 0 3 | push argument 1 4 | call Math.max 2 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/min.vm: -------------------------------------------------------------------------------- 1 | function Int.min 0 2 | push argument 0 3 | push argument 1 4 | call Math.min 2 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/mod.vm: -------------------------------------------------------------------------------- 1 | function Int.mod 0 2 | push argument 0 3 | push argument 0 4 | push argument 1 5 | call Math.divide 2 6 | push argument 1 7 | call Math.multiply 2 8 | sub 9 | return 10 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/mult.vm: -------------------------------------------------------------------------------- 1 | function Int.mult 0 2 | push argument 0 3 | push argument 1 4 | call Math.multiply 2 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/neg.vm: -------------------------------------------------------------------------------- 1 | function Int.neg 0 2 | push argument 0 3 | neg 4 | return 5 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/sqrt.vm: -------------------------------------------------------------------------------- 1 | function Int.sqrt 0 2 | push argument 0 3 | call Math.sqrt 1 4 | return 5 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Int/sub.vm: -------------------------------------------------------------------------------- 1 | function Int.sub 0 2 | push argument 0 3 | push argument 1 4 | sub 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/Empty.vm: -------------------------------------------------------------------------------- 1 | function List.Empty 0 2 | push constant 1 3 | call Memory.alloc 1 4 | pop pointer 0 5 | push constant 0 6 | pop this 0 7 | push pointer 0 8 | return 9 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/List.vm: -------------------------------------------------------------------------------- 1 | function List.List 0 2 | push constant 3 3 | call Memory.alloc 1 4 | pop pointer 0 5 | push argument 0 6 | pop this 1 7 | push argument 1 8 | pop this 2 9 | push constant 1 10 | pop this 0 11 | push pointer 0 12 | return 13 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/List_head.vm: -------------------------------------------------------------------------------- 1 | function List.List_head 0 2 | push argument 0 3 | pop pointer 0 4 | push this 1 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/List_tag.vm: -------------------------------------------------------------------------------- 1 | function List.List_tag 0 2 | push argument 0 3 | pop pointer 0 4 | push this 0 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/List_tail.vm: -------------------------------------------------------------------------------- 1 | function List.List_tail 0 2 | push argument 0 3 | pop pointer 0 4 | push this 2 5 | return 6 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/all.vm: -------------------------------------------------------------------------------- 1 | function List.all 0 2 | label REC_CALL_0 3 | push argument 0 4 | call List.List_tag 1 5 | push constant 0 6 | eq 7 | if-goto VARIANT_0_0 8 | push constant 1 9 | call Array.new 1 10 | pop temp 1 11 | push constant 0 12 | push temp 1 13 | add 14 | push argument 0 15 | call List.List_head 1 16 | pop temp 0 17 | pop pointer 1 18 | push temp 0 19 | pop that 0 20 | push argument 1 21 | push constant 1 22 | push temp 1 23 | call Fn._call 3 24 | not 25 | push constant 0 26 | eq 27 | if-goto SKIP_TRUE_0 28 | push constant 0 29 | return 30 | label SKIP_TRUE_0 31 | push argument 0 32 | call List.List_tail 1 33 | pop argument 0 34 | goto REC_CALL_0 35 | label VARIANT_0_0 36 | push constant 1 37 | neg 38 | return 39 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/any.vm: -------------------------------------------------------------------------------- 1 | function List.any 0 2 | label REC_CALL_0 3 | push argument 0 4 | call List.List_tag 1 5 | push constant 0 6 | eq 7 | if-goto VARIANT_0_0 8 | push constant 1 9 | call Array.new 1 10 | pop temp 1 11 | push constant 0 12 | push temp 1 13 | add 14 | push argument 0 15 | call List.List_head 1 16 | pop temp 0 17 | pop pointer 1 18 | push temp 0 19 | pop that 0 20 | push argument 1 21 | push constant 1 22 | push temp 1 23 | call Fn._call 3 24 | push constant 0 25 | eq 26 | if-goto SKIP_TRUE_0 27 | push constant 1 28 | neg 29 | return 30 | label SKIP_TRUE_0 31 | push argument 0 32 | call List.List_tail 1 33 | pop argument 0 34 | goto REC_CALL_0 35 | label VARIANT_0_0 36 | push constant 0 37 | return 38 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/dispose.Jill: -------------------------------------------------------------------------------- 1 | -- Reference implementation of the `List::dispose` function. 2 | -- @type List(a) -> () 3 | fn dispose list = 4 | -- store list tail to enable tail-recursive call 5 | -- NOTE: potentially invalid (if list has no tail), 6 | -- but only used after "more elements" check 7 | let tail = List::List:tail(list), 8 | let hasMoreElements = Bool::not(List::isEmpty(list)), 9 | 10 | do( 11 | -- shallow free (only the list pointer, not its elements) 12 | Memory::deAlloc(list), 13 | 14 | -- recursive call on the rest of the list 15 | if(hasMoreElements, dispose(tail)) 16 | ). 17 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/dispose.vm: -------------------------------------------------------------------------------- 1 | function List.dispose 2 2 | label REC_CALL 3 | push argument 0 4 | pop pointer 0 5 | push this 0 6 | pop local 0 7 | push this 2 8 | pop local 1 9 | push pointer 0 10 | call Memory.deAlloc 1 11 | pop temp 0 12 | push local 0 13 | push constant 0 14 | eq 15 | if-goto SKIP_IF 16 | push local 1 17 | pop argument 0 18 | goto REC_CALL 19 | label SKIP_IF 20 | push constant 0 21 | return 22 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/disposeWith.jill: -------------------------------------------------------------------------------- 1 | -- Reference implementation of the `List::disposeWith` function. 2 | -- Unlike `List::dispose`, this function applies additional disposing 3 | -- of the stored elements. 4 | -- NOTE: the `headDisposer` is also disposed as a part of this function. 5 | -- @type List(a), Fn(a -> ()) -> () 6 | fn disposeWith list headDisposer = 7 | -- store tail to enable tail-recursive call 8 | let tail = List::List:tail(list), 9 | let isEmpty = List::isEmpty(list), 10 | 11 | do( 12 | -- dispose list head 13 | headDisposer(List::List:head(list)), 14 | 15 | -- shallow free (only the list pointer, not its elements) 16 | Memory::deAlloc(list), 17 | 18 | -- recursive call on the rest of the list 19 | ifElse( 20 | isEmpty, 21 | -- reached the end of the list - dispose closure 22 | Fn::dispose(headDisposer), 23 | disposeWith(tail, headDisposer) 24 | ) 25 | ). 26 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/disposeWith.vm: -------------------------------------------------------------------------------- 1 | function List.disposeWith 2 2 | label REC_CALL 3 | push argument 0 4 | pop pointer 0 5 | push constant 1 6 | call Array.new 1 7 | push this 1 8 | pop temp 1 9 | pop pointer 1 10 | push temp 1 11 | pop that 0 12 | push argument 1 13 | push constant 1 14 | push temp 2 15 | call Fn._call 3 16 | pop temp 0 17 | push this 0 18 | pop local 0 19 | push this 2 20 | pop local 1 21 | push pointer 0 22 | call Memory.deAlloc 1 23 | pop temp 0 24 | push local 0 25 | push constant 0 26 | eq 27 | not 28 | if-goto SKIP_TRUE 29 | push argument 1 30 | call Fn.dispose 1 31 | return 32 | label SKIP_TRUE 33 | push local 1 34 | pop argument 0 35 | goto REC_CALL 36 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/filter.jill: -------------------------------------------------------------------------------- 1 | -- Reference implementation of the `List::filter` function. 2 | -- @type List(a), Fn(a -> Bool) -> List(a) 3 | fn filter list f = 4 | fn reverseAndCleanup list = 5 | let reversedList = List::reverse(list), 6 | 7 | do( 8 | List::dispose(list), 9 | StdUtils::identity(reversedList) 10 | ). 11 | 12 | -- helper for tail-recursive implementation 13 | fn filterRec acc list f = 14 | let listHead = List::List:head(list), 15 | 16 | List::List:match( 17 | list, 18 | -- Empty 19 | -- list was constructed in the reverse order - 20 | -- reverse the result to get the original order 21 | reverseAndCleanup(acc), 22 | -- List(_) 23 | filterRec( 24 | -- prepend the current element (list head) 25 | -- (if condition is met, otherwise ignore) 26 | -- to the rest of the (accumulated) list 27 | ifElse( 28 | f(listHead), 29 | List::List(listHead, acc), 30 | acc 31 | ), 32 | -- and do the same with the rest of the list 33 | List::List:tail(list), 34 | -- don't forget to pass along the function 35 | f 36 | ) 37 | ). 38 | 39 | filterRec(List::Empty(), list, f). 40 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/filter.vm: -------------------------------------------------------------------------------- 1 | function List.filter_reverseAndCleanup 1 2 | push argument 0 3 | call List.reverse 1 4 | pop local 0 5 | push argument 0 6 | call List.dispose 1 7 | pop temp 0 8 | push local 0 9 | return 10 | function List.filter_filterRec 0 11 | label REC_CALL_0 12 | push argument 1 13 | call List.List_tag 1 14 | push constant 0 15 | eq 16 | if-goto VARIANT_0_0 17 | push constant 1 18 | call Array.new 1 19 | pop temp 1 20 | push constant 0 21 | push temp 1 22 | add 23 | push argument 1 24 | call List.List_head 1 25 | pop temp 0 26 | pop pointer 1 27 | push temp 0 28 | pop that 0 29 | push argument 2 30 | push constant 1 31 | push temp 1 32 | call Fn._call 3 33 | push constant 0 34 | eq 35 | if-goto SKIP_TRUE_0 36 | push argument 1 37 | call List.List_head 1 38 | push argument 0 39 | call List.List 2 40 | goto SKIP_FALSE_0 41 | label SKIP_TRUE_0 42 | push argument 0 43 | label SKIP_FALSE_0 44 | push argument 1 45 | call List.List_tail 1 46 | pop argument 1 47 | pop argument 0 48 | goto REC_CALL_0 49 | label VARIANT_0_0 50 | push argument 0 51 | call List.filter_reverseAndCleanup 1 52 | return 53 | function List.filter 0 54 | call List.Empty 0 55 | push argument 0 56 | push argument 1 57 | call List.filter_filterRec 3 58 | return 59 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/fold.vm: -------------------------------------------------------------------------------- 1 | function List.fold 0 2 | label REC_CALL_0 3 | push argument 0 4 | call List.List_tag 1 5 | push constant 0 6 | eq 7 | if-goto VARIANT_0_0 8 | push argument 0 9 | call List.List_tail 1 10 | push constant 2 11 | call Array.new 1 12 | pop temp 1 13 | push constant 0 14 | push temp 1 15 | add 16 | push argument 0 17 | call List.List_head 1 18 | pop temp 0 19 | pop pointer 1 20 | push temp 0 21 | pop that 0 22 | push constant 1 23 | push temp 1 24 | add 25 | push argument 1 26 | pop temp 0 27 | pop pointer 1 28 | push temp 0 29 | pop that 0 30 | push argument 2 31 | push constant 2 32 | push temp 1 33 | call Fn._call 3 34 | pop argument 1 35 | pop argument 0 36 | goto REC_CALL_0 37 | label VARIANT_0_0 38 | push argument 1 39 | return 40 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/isEmpty.vm: -------------------------------------------------------------------------------- 1 | function List.isEmpty 0 2 | push argument 0 3 | call List.List_tag 1 4 | push constant 0 5 | eq 6 | return 7 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/length.vm: -------------------------------------------------------------------------------- 1 | function List.length_lengthRec 0 2 | label REC_CALL_0 3 | push argument 1 4 | call List.List_tag 1 5 | push constant 0 6 | eq 7 | if-goto VARIANT_0_0 8 | push argument 0 9 | call Int.inc 1 10 | push argument 1 11 | call List.List_tail 1 12 | pop argument 1 13 | pop argument 0 14 | goto REC_CALL_0 15 | label VARIANT_0_0 16 | push argument 0 17 | return 18 | function List.length 0 19 | push constant 0 20 | push argument 0 21 | call List.length_lengthRec 2 22 | return 23 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/map.jill: -------------------------------------------------------------------------------- 1 | -- Reference implementation of the `List::map` function. 2 | -- @type List(a), Fn(a -> b) -> List(b) 3 | fn map list f = 4 | fn reverseAndCleanup list = 5 | let reversedList = List::reverse(list), 6 | 7 | do( 8 | List::dispose(list), 9 | StdUtils::identity(reversedList) 10 | ). 11 | 12 | -- helper for tail-recursive implementation 13 | fn mapRec acc list f = 14 | List::List:match( 15 | list, 16 | -- Empty 17 | -- list was constructed in the reverse order - 18 | -- reverse the result to get the original order 19 | reverseAndCleanup(acc), 20 | -- List(_) 21 | mapRec( 22 | -- prepend the mapped current element (list head) 23 | -- to the rest of the (accumulated) list 24 | List::List( 25 | f(List::List:head(list)), 26 | acc 27 | ), 28 | -- and do the same with the rest of the list 29 | List::List:tail(list), 30 | -- don't forget to pass along the function 31 | f 32 | ) 33 | ). 34 | 35 | mapRec(List::Empty(), list, f). 36 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/map.vm: -------------------------------------------------------------------------------- 1 | function List.map_reverseAndCleanup 1 2 | push argument 0 3 | call List.reverse 1 4 | pop local 0 5 | push argument 0 6 | call List.dispose 1 7 | pop temp 0 8 | push local 0 9 | return 10 | function List.map_mapRec 0 11 | label REC_CALL_0 12 | push argument 1 13 | call List.List_tag 1 14 | push constant 0 15 | eq 16 | if-goto VARIANT_0_0 17 | push constant 1 18 | call Array.new 1 19 | pop temp 1 20 | push constant 0 21 | push temp 1 22 | add 23 | push argument 1 24 | call List.List_head 1 25 | pop temp 0 26 | pop pointer 1 27 | push temp 0 28 | pop that 0 29 | push argument 2 30 | push constant 1 31 | push temp 1 32 | call Fn._call 3 33 | push argument 0 34 | call List.List 2 35 | push argument 1 36 | call List.List_tail 1 37 | pop argument 1 38 | pop argument 0 39 | goto REC_CALL_0 40 | label VARIANT_0_0 41 | push argument 0 42 | call List.map_reverseAndCleanup 1 43 | return 44 | function List.map 0 45 | call List.Empty 0 46 | push argument 0 47 | push argument 1 48 | call List.map_mapRec 3 49 | return 50 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/range.vm: -------------------------------------------------------------------------------- 1 | function List.range_rangeRec 0 2 | label REC_CALL_0 3 | push argument 1 4 | push argument 2 5 | gt 6 | push constant 0 7 | eq 8 | if-goto SKIP_TRUE_0 9 | push argument 0 10 | return 11 | label SKIP_TRUE_0 12 | push argument 2 13 | push argument 0 14 | call List.List 2 15 | push argument 2 16 | call Int.dec 1 17 | pop argument 2 18 | pop argument 0 19 | goto REC_CALL_0 20 | function List.range 0 21 | call List.Empty 0 22 | push argument 0 23 | push argument 1 24 | call Int.dec 1 25 | call List.range_rangeRec 3 26 | return 27 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/repeat.vm: -------------------------------------------------------------------------------- 1 | function List.repeat_repeatRec 0 2 | label REC_CALL_0 3 | push argument 2 4 | push constant 0 5 | eq 6 | push constant 0 7 | eq 8 | if-goto SKIP_TRUE_0 9 | push argument 0 10 | return 11 | label SKIP_TRUE_0 12 | push argument 1 13 | push argument 0 14 | call List.List 2 15 | push argument 2 16 | call Int.dec 1 17 | pop argument 2 18 | pop argument 0 19 | goto REC_CALL_0 20 | function List.repeat 0 21 | push argument 1 22 | push constant 0 23 | lt 24 | push constant 0 25 | eq 26 | if-goto SKIP_TRUE_0 27 | push constant 2 28 | call Sys.error 1 29 | return 30 | label SKIP_TRUE_0 31 | call List.Empty 0 32 | push argument 0 33 | push argument 1 34 | call List.repeat_repeatRec 3 35 | return 36 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/reverse.jill: -------------------------------------------------------------------------------- 1 | -- Reference implementation of the `List::reverse` function. 2 | -- @type List(a) -> List(a) 3 | fn reverse list = 4 | -- helper for tail-recursive implementation 5 | fn reverseRec acc list = 6 | List::List:match( 7 | list, 8 | -- Empty 9 | acc, 10 | -- List(_) 11 | reverseRec( 12 | -- prepend current element (list head) to 13 | -- the rest of the (accumulated) list 14 | List::List( 15 | List::List:head(list), 16 | acc 17 | ), 18 | -- and do the same with the rest of the list 19 | List::List:tail(list) 20 | ) 21 | ). 22 | 23 | reverseRec(List::Empty(), list). 24 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/List/reverse.vm: -------------------------------------------------------------------------------- 1 | function List.reverse_reverseRec 0 2 | label REC_CALL_0 3 | push argument 1 4 | call List.List_tag 1 5 | push constant 0 6 | eq 7 | if-goto VARIANT_0_0 8 | push argument 1 9 | call List.List_head 1 10 | push argument 0 11 | call List.List 2 12 | push argument 1 13 | call List.List_tail 1 14 | pop argument 1 15 | pop argument 0 16 | goto REC_CALL_0 17 | label VARIANT_0_0 18 | push argument 0 19 | return 20 | function List.reverse 0 21 | call List.Empty 0 22 | push argument 0 23 | call List.reverse_reverseRec 2 24 | return 25 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Random/Random.vm: -------------------------------------------------------------------------------- 1 | function Random.Random 0 2 | push constant 3 3 | call Memory.alloc 1 4 | pop pointer 0 5 | push argument 0 6 | pop this 0 7 | push constant 219 8 | pop this 1 9 | push constant 32749 10 | pop this 2 11 | push pointer 0 12 | return 13 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Random/fromRange.vm: -------------------------------------------------------------------------------- 1 | function Random.fromRange 1 2 | push argument 0 3 | call Random.next 1 4 | push argument 2 5 | push argument 1 6 | sub 7 | call Int.mod 2 8 | push argument 1 9 | add 10 | return 11 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Random/next.vm: -------------------------------------------------------------------------------- 1 | function Random.next 2 2 | push argument 0 3 | pop pointer 0 4 | push this 2 5 | push this 1 6 | call Math.divide 2 7 | pop local 0 8 | push this 2 9 | push this 1 10 | call Int.mod 2 11 | pop local 1 12 | push this 1 13 | push this 0 14 | push local 0 15 | call Int.mod 2 16 | call Math.multiply 2 17 | push local 1 18 | push this 0 19 | push local 0 20 | call Math.divide 2 21 | call Math.multiply 2 22 | sub 23 | pop this 0 24 | push this 0 25 | push constant 0 26 | lt 27 | if-goto IF_TRUE0 28 | goto IF_FALSE0 29 | label IF_TRUE0 30 | push this 0 31 | push this 2 32 | add 33 | pop this 0 34 | label IF_FALSE0 35 | push this 0 36 | return 37 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/StdUtils/identity.vm: -------------------------------------------------------------------------------- 1 | function StdUtils.identity 0 2 | push argument 0 3 | return 4 | -------------------------------------------------------------------------------- /src/codegen/post_compilation/jillstd/Sys.vm: -------------------------------------------------------------------------------- 1 | function Sys.init 0 2 | call Memory.init 0 3 | pop temp 0 4 | call Math.init 0 5 | pop temp 0 6 | call Screen.init 0 7 | pop temp 0 8 | call Output.init 0 9 | pop temp 0 10 | call Keyboard.init 0 11 | pop temp 0 12 | call Globals.init 0 13 | pop temp 0 14 | call Main.main 0 15 | pop temp 0 16 | call Sys.halt 0 17 | pop temp 0 18 | push constant 0 19 | return 20 | function Sys.halt 0 21 | label WHILE_EXP0 22 | push constant 0 23 | not 24 | not 25 | if-goto WHILE_END0 26 | goto WHILE_EXP0 27 | label WHILE_END0 28 | push constant 0 29 | return 30 | function Sys.wait 1 31 | push argument 0 32 | push constant 0 33 | lt 34 | if-goto IF_TRUE0 35 | goto IF_FALSE0 36 | label IF_TRUE0 37 | push constant 1 38 | call Sys.error 1 39 | pop temp 0 40 | label IF_FALSE0 41 | label WHILE_EXP0 42 | push argument 0 43 | push constant 0 44 | gt 45 | not 46 | if-goto WHILE_END0 47 | push constant 50 48 | pop local 0 49 | label WHILE_EXP1 50 | push local 0 51 | push constant 0 52 | gt 53 | not 54 | if-goto WHILE_END1 55 | push local 0 56 | push constant 1 57 | sub 58 | pop local 0 59 | goto WHILE_EXP1 60 | label WHILE_END1 61 | push argument 0 62 | push constant 1 63 | sub 64 | pop argument 0 65 | goto WHILE_EXP0 66 | label WHILE_END0 67 | push constant 0 68 | return 69 | function Sys.error 0 70 | push constant 3 71 | call String.new 1 72 | push constant 69 73 | call String.appendChar 2 74 | push constant 82 75 | call String.appendChar 2 76 | push constant 82 77 | call String.appendChar 2 78 | call Output.printString 1 79 | pop temp 0 80 | push argument 0 81 | call Output.printInt 1 82 | pop temp 0 83 | call Sys.halt 0 84 | pop temp 0 85 | push constant 0 86 | return -------------------------------------------------------------------------------- /src/codegen/post_compilation/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod function_dispatch; 2 | pub mod globals; 3 | pub mod jillstd; 4 | -------------------------------------------------------------------------------- /src/codegen/vm.rs: -------------------------------------------------------------------------------- 1 | // region: VMModule 2 | 3 | #[derive(Debug)] 4 | pub struct VMModule { 5 | blocks: Vec, 6 | } 7 | 8 | impl VMModule { 9 | pub const fn new() -> Self { 10 | Self { blocks: Vec::new() } 11 | } 12 | 13 | pub fn add_block(&mut self, block: VMInstructionBlock) { 14 | self.blocks.push(block); 15 | } 16 | 17 | pub fn compile(self) -> String { 18 | self.to_string() 19 | } 20 | } 21 | 22 | impl std::fmt::Display for VMModule { 23 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 24 | write!( 25 | f, 26 | "{}", 27 | self.blocks 28 | .iter() 29 | .map(|block| format!("{block}")) 30 | .collect::>() 31 | .join("\n") 32 | ) 33 | } 34 | } 35 | 36 | // endregion 37 | 38 | // region: VMInstructionBlock 39 | 40 | #[derive(Debug)] 41 | pub struct VMInstructionBlock { 42 | instructions: Vec, 43 | } 44 | 45 | impl std::fmt::Display for VMInstructionBlock { 46 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 47 | write!( 48 | f, 49 | "{}", 50 | self.instructions 51 | .iter() 52 | .map(|instruction| format!("{instruction}")) 53 | .collect::>() 54 | .join("\n") 55 | ) 56 | } 57 | } 58 | 59 | impl From> for VMInstructionBlock { 60 | fn from(instructions: Vec) -> Self { 61 | Self { instructions } 62 | } 63 | } 64 | 65 | impl From<&[VMInstruction]> for VMInstructionBlock { 66 | fn from(instructions: &[VMInstruction]) -> Self { 67 | Self { 68 | instructions: instructions.to_vec(), 69 | } 70 | } 71 | } 72 | 73 | impl VMInstructionBlock { 74 | pub fn compile(self) -> String { 75 | self.to_string() 76 | } 77 | } 78 | 79 | // endregion 80 | 81 | // region: VMInstruction 82 | 83 | /// Wrapper type which prevents accidentaly passing non-HACK-formatted 84 | /// function name where one is required. 85 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] 86 | pub struct VMFunctionName(String); 87 | 88 | impl VMFunctionName { 89 | /// Create function name from a string literal. 90 | /// 91 | /// Useful in cases when you know exactly which function 92 | /// you want to call (e.g. a built-in). 93 | pub fn from_literal(name: &str) -> Self { 94 | Self(name.to_owned()) 95 | } 96 | 97 | /// Construct function name from (processed) components. 98 | /// 99 | /// Note: this function assumes that module paths are properly formatted 100 | /// and type name is empty if it was not originally present. 101 | pub fn construct(module_path: &str, type_name: &str, function_name: &str) -> Self { 102 | Self(format!("{module_path}.{type_name}{function_name}")) 103 | } 104 | } 105 | 106 | impl std::ops::Deref for VMFunctionName { 107 | type Target = String; 108 | 109 | fn deref(&self) -> &Self::Target { 110 | &self.0 111 | } 112 | } 113 | 114 | // region: VMInstruction utility functions 115 | 116 | /// Utility function for the `push` VM instruction. 117 | pub const fn push(segment: Segment, i: usize) -> VMInstruction { 118 | VMInstruction::Push(segment, i) 119 | } 120 | 121 | /// Utility function for the `pop` VM instruction. 122 | pub const fn pop(segment: Segment, i: usize) -> VMInstruction { 123 | VMInstruction::Pop(segment, i) 124 | } 125 | 126 | /// Utility function for the `command` VM instruction. 127 | pub const fn command(command: VMCommand) -> VMInstruction { 128 | VMInstruction::Command(command) 129 | } 130 | 131 | /// Utility function for the `return` VM instruction. 132 | pub const fn vm_return() -> VMInstruction { 133 | VMInstruction::Command(VMCommand::Return) 134 | } 135 | 136 | /// Utility function for the `label` VM instruction. 137 | pub fn label>(label_action: LabelAction, label: S) -> VMInstruction { 138 | VMInstruction::Label(label_action, label.into()) 139 | } 140 | 141 | /// Utility function for the `function` VM instruction. 142 | pub const fn function(function_name: VMFunctionName, variable_count: usize) -> VMInstruction { 143 | VMInstruction::Function(function_name, variable_count) 144 | } 145 | 146 | /// Utility function for the `call` VM instruction. 147 | pub const fn call(function_name: VMFunctionName, argument_count: usize) -> VMInstruction { 148 | VMInstruction::Call(function_name, argument_count) 149 | } 150 | 151 | /// Utility function for pushing the `null` value onto the stack. 152 | pub const fn null() -> VMInstruction { 153 | VMInstruction::Push(Segment::Constant, 0) 154 | } 155 | 156 | /// Utility function for pushing the `true` value onto the stack. 157 | pub fn r#true() -> Vec { 158 | vec![ 159 | VMInstruction::Push(Segment::Constant, 1), 160 | VMInstruction::Command(VMCommand::Neg), 161 | ] 162 | } 163 | 164 | /// Utility function for pushing the `false` value onto the stack. 165 | pub const fn r#false() -> VMInstruction { 166 | VMInstruction::Push(Segment::Constant, 0) 167 | } 168 | 169 | // endregion 170 | 171 | type Index = usize; 172 | type Label = String; 173 | type Count = usize; 174 | 175 | #[derive(Debug, Clone, PartialEq, Eq)] 176 | pub enum VMInstruction { 177 | Push(Segment, Index), 178 | Pop(Segment, Index), 179 | Command(VMCommand), 180 | Label(LabelAction, Label), 181 | Function(VMFunctionName, Count), 182 | Call(VMFunctionName, Count), 183 | } 184 | 185 | impl VMInstruction { 186 | fn as_instruction(&self) -> String { 187 | match self { 188 | Self::Push(segment, i) => format!("push {segment} {i}"), 189 | Self::Pop(segment, i) => format!("pop {segment} {i}"), 190 | Self::Command(command) => command.to_string(), 191 | Self::Label(label_action, label) => format!("{label_action} {label}"), 192 | Self::Function(vm_function_name, variable_count) => { 193 | let function_name = &vm_function_name.0; 194 | format!("function {function_name} {variable_count}") 195 | } 196 | Self::Call(vm_function_name, argument_count) => { 197 | let function_name = &vm_function_name.0; 198 | format!("call {function_name} {argument_count}") 199 | } 200 | } 201 | } 202 | } 203 | 204 | impl std::fmt::Display for VMInstruction { 205 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 206 | write!(f, "{}", self.as_instruction()) 207 | } 208 | } 209 | 210 | #[derive(Debug, strum::Display, Clone, PartialEq, Eq)] 211 | #[strum(serialize_all = "kebab-case")] 212 | pub enum VMCommand { 213 | Add, 214 | Sub, 215 | Neg, 216 | Eq, 217 | Gt, 218 | Lt, 219 | And, 220 | Or, 221 | Not, 222 | Return, 223 | } 224 | 225 | #[derive(Debug, strum::Display, Clone, PartialEq, Eq)] 226 | #[strum(serialize_all = "kebab-case")] 227 | pub enum LabelAction { 228 | Label, 229 | Goto, 230 | IfGoto, 231 | } 232 | 233 | /// How many arguments are there before the "captures" array 234 | pub type CapturesArrayArgumentNumber = usize; 235 | 236 | #[derive(Debug, strum::Display, Clone, Copy, PartialEq, Eq, Hash)] 237 | #[strum(serialize_all = "kebab-case")] 238 | pub enum Segment { 239 | Local, 240 | Argument, 241 | Static, 242 | Constant, 243 | This, 244 | That, 245 | Pointer, 246 | Temp, 247 | // NOTE: not a proper VM segment; used internally to work with captures (array) 248 | Capture(CapturesArrayArgumentNumber), 249 | } 250 | 251 | // endregion 252 | -------------------------------------------------------------------------------- /src/common/ast.rs: -------------------------------------------------------------------------------- 1 | //! Types representing the structure of a Jill program. 2 | //! 3 | //! Note: all types are prefixed with `Jill` to avoid potential 4 | //! name collision with existing keywords/phrases (e.g. Type). 5 | 6 | // region: non-terminals 7 | 8 | #[derive(Debug)] 9 | pub struct JillModule { 10 | pub name: String, 11 | pub content: JillModuleContent, 12 | } 13 | 14 | #[derive(Debug)] 15 | pub struct JillModuleContent { 16 | // `type` 17 | pub types: Vec, 18 | 19 | // `let` 20 | pub variables: Vec, 21 | 22 | // `fn` 23 | pub functions: Vec, 24 | } 25 | 26 | #[derive(Debug)] 27 | pub struct JillVariable { 28 | pub name: JillIdentifier, 29 | pub value: JillExpression, 30 | } 31 | 32 | #[derive(Debug)] 33 | pub struct JillFunction { 34 | pub name: JillIdentifier, 35 | pub arguments: Vec, 36 | pub captures: Vec, 37 | pub body: JillFunctionBody, 38 | } 39 | 40 | #[derive(Debug)] 41 | pub struct JillType { 42 | pub name: JillIdentifier, 43 | pub variants: Vec, 44 | } 45 | 46 | #[derive(Debug)] 47 | pub struct JillTypeVariant { 48 | pub name: JillIdentifier, 49 | pub fields: Vec, 50 | } 51 | 52 | #[derive(Debug)] 53 | pub enum JillExpression { 54 | Literal(JillLiteral), 55 | FunctionCall(JillFunctionCall), 56 | FunctionReference(JillFunctionReference), 57 | VariableName(JillIdentifier), 58 | } 59 | 60 | #[derive(Debug)] 61 | pub struct JillFunctionBody { 62 | pub local_functions: Vec, 63 | pub local_variables: Vec, 64 | pub return_expression: JillExpression, 65 | } 66 | 67 | #[derive(Debug)] 68 | pub struct JillFunctionReference { 69 | pub modules_path: Vec, 70 | pub associated_type: Option, 71 | pub function_name: JillIdentifier, 72 | } 73 | 74 | #[derive(Debug)] 75 | pub struct JillFunctionCall { 76 | pub reference: JillFunctionReference, 77 | pub arguments: Vec, 78 | } 79 | 80 | // endregion 81 | 82 | // region: terminals 83 | 84 | #[derive(Debug)] 85 | pub enum JillLiteral { 86 | Integer(usize), 87 | String(String), 88 | Bool(bool), 89 | List(Vec), 90 | } 91 | 92 | #[derive(Debug, Clone)] 93 | pub struct JillIdentifier(pub String); 94 | 95 | impl std::fmt::Display for JillIdentifier { 96 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 97 | write!(f, "{}", self.0) 98 | } 99 | } 100 | 101 | // endregion 102 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | //! Common data & logic shared across the compiler 2 | //! (e.g. AST). 3 | pub mod ast; 4 | 5 | #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::VariantNames, strum::EnumString)] 6 | #[strum(serialize_all = "camelCase")] 7 | pub enum CompilerInternalFunction { 8 | If, 9 | IfElse, 10 | Do, 11 | Match, 12 | Todo, 13 | Free, 14 | } 15 | -------------------------------------------------------------------------------- /src/fileio.rs: -------------------------------------------------------------------------------- 1 | //! Code regarding file input (reading source `.jill` files) 2 | //! and output (writting generated `.vm` files) actions. 3 | //! 4 | //! ## Input 5 | //! `jillc` expects to be run in the root directory of the Jill project, 6 | //! with all the `.jill` files placed in the `./src` directory. 7 | //! 8 | //! Note that, unlike `JackCompiler`, `jillc` allows source files 9 | //! to be in nested directories (so long as they are all in the `./src` directory). 10 | //! 11 | //! ## Output 12 | //! Resulting `.vm` are generated in a separate `./bin` directory, located 13 | //! in the root directory, alongside the `./src` directory (unlike `JackCompiler`, 14 | //! which outputs `.vm` files alongside the source `.jack` files). 15 | //! 16 | //! Since `Hack` only loads `.vm` files from a single directory, 17 | //! resulting `.vm` files are "flattened" in a single directory, rather then 18 | //! keeping the original (potentially nested) source file structure. 19 | 20 | use std::{io, path::Path}; 21 | 22 | /// Checks that the provided path to the root of the project directory 23 | /// is in fact a valid path to an existing directory. 24 | fn check_root_path(root_path: &Path) -> io::Result<()> { 25 | if root_path.is_dir() { 26 | Ok(()) 27 | } else { 28 | Err(io::Error::other( 29 | "provided root path is not a valid directory", 30 | )) 31 | } 32 | } 33 | 34 | pub mod input { 35 | use std::{ 36 | fs, io, 37 | path::{Path, PathBuf}, 38 | }; 39 | 40 | pub struct SourceFile { 41 | src_path: PathBuf, 42 | file_path: PathBuf, 43 | content: String, 44 | } 45 | 46 | impl SourceFile { 47 | pub fn content(&self) -> &str { 48 | &self.content 49 | } 50 | 51 | /// Convert file's path to a Hack-compatible module name 52 | pub fn module_name(&self) -> String { 53 | // get the path from the ./src dir to the source file 54 | let module_path = self 55 | .file_path 56 | .strip_prefix(&self.src_path) 57 | .expect("source file path should start with the ./src path"); 58 | 59 | module_path 60 | // remove extension, so we only have the dir/file names 61 | .with_extension("") 62 | // split into a series of directory/file names 63 | .components() 64 | // convert to ordinary strings 65 | // TODO: re-check string handling 66 | .map(|c| c.as_os_str().to_string_lossy().to_string()) 67 | // concat names using "_" as a separator 68 | .collect::>() 69 | .join("_") 70 | } 71 | } 72 | 73 | pub struct SourceDir { 74 | src_path: PathBuf, 75 | current_file_index: usize, 76 | source_file_paths: Vec, 77 | } 78 | 79 | fn check_src_exists(root_path: &Path) -> io::Result { 80 | let src_path = root_path.join("src"); 81 | if src_path.is_dir() { 82 | Ok(src_path) 83 | } else { 84 | Err(io::Error::other( 85 | "provided root path does not contain a `src` directory", 86 | )) 87 | } 88 | } 89 | 90 | impl SourceDir { 91 | pub fn setup(root_path: &Path) -> io::Result { 92 | super::check_root_path(root_path)?; 93 | 94 | let src_path = check_src_exists(root_path)?; 95 | 96 | let mut source_file_paths = vec![]; 97 | Self::collect_source_files(&src_path, &mut source_file_paths)?; 98 | 99 | if source_file_paths.is_empty() { 100 | return Err(io::Error::other( 101 | "no source files found in the `src` directory", 102 | )); 103 | } 104 | 105 | Ok(Self { 106 | src_path, 107 | current_file_index: 0, 108 | source_file_paths, 109 | }) 110 | } 111 | 112 | fn collect_source_files(dir: &PathBuf, collected: &mut Vec) -> io::Result<()> { 113 | for entry in fs::read_dir(dir)? { 114 | let entry = entry?; 115 | let path = entry.path(); 116 | 117 | // recursively visit nested directories 118 | if path.is_dir() { 119 | Self::collect_source_files(&path, collected)?; 120 | } 121 | 122 | // only add `.jill` files 123 | if path.extension().is_some_and(|ext| ext == "jill") { 124 | collected.push(path); 125 | } 126 | } 127 | 128 | Ok(()) 129 | } 130 | } 131 | 132 | impl Iterator for SourceDir { 133 | type Item = (PathBuf, io::Result); 134 | 135 | fn next(&mut self) -> Option { 136 | // reached the end of the file list - no more files to load 137 | if self.current_file_index == self.source_file_paths.len() { 138 | return None; 139 | } 140 | 141 | // get the path to the current (first unread) file 142 | let file_path = self.source_file_paths[self.current_file_index].clone(); 143 | 144 | // move index to next file (for next iteration) 145 | self.current_file_index += 1; 146 | 147 | // (try to) read the contents of the file 148 | let read_result = fs::read_to_string(&file_path); 149 | 150 | // on successful read, store content (and other metadata) to model 151 | let source_file_result = read_result.map(|content| SourceFile { 152 | src_path: self.src_path.clone(), 153 | file_path: file_path.clone(), 154 | content, 155 | }); 156 | 157 | // keep as result (as any of the file reads could fail), handle on per-file basis 158 | // keep `file_path` separate to the action result so we can denote file at fault in case of failure 159 | Some((file_path, source_file_result)) 160 | } 161 | } 162 | } 163 | 164 | pub mod output { 165 | use std::{ 166 | fs, io, 167 | path::{Path, PathBuf}, 168 | }; 169 | 170 | pub struct OutputFile { 171 | name: String, 172 | content: String, 173 | } 174 | 175 | impl OutputFile { 176 | pub const fn new(name: String, content: String) -> Self { 177 | Self { name, content } 178 | } 179 | 180 | /// NOTE: only actually used in tests 181 | #[cfg(test)] 182 | pub fn content(&self) -> &str { 183 | &self.content 184 | } 185 | } 186 | 187 | pub struct OutputGenerator { 188 | bin_path: PathBuf, 189 | } 190 | 191 | impl OutputGenerator { 192 | pub fn setup(root_path: &Path) -> io::Result { 193 | super::check_root_path(root_path)?; 194 | 195 | let bin_path = ensure_bin_dir_exists(root_path)?; 196 | 197 | Ok(Self { bin_path }) 198 | } 199 | 200 | pub fn generate(&self, output_file: OutputFile) -> io::Result<()> { 201 | let file_path = self.bin_path.join(output_file.name).with_extension("vm"); 202 | 203 | fs::write(file_path, output_file.content) 204 | } 205 | } 206 | 207 | fn ensure_bin_dir_exists(root_path: &Path) -> io::Result { 208 | let bin_path = root_path.join("bin"); 209 | if !bin_path.is_dir() { 210 | fs::create_dir(&bin_path)?; 211 | } 212 | 213 | Ok(bin_path) 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Result}; 2 | use clap::Parser; 3 | use fileio::{input::SourceDir, output::OutputGenerator}; 4 | use std::path::Path; 5 | 6 | mod codegen; 7 | mod common; 8 | mod fileio; 9 | mod parser; 10 | 11 | #[derive(Parser)] 12 | #[command(version, about, long_about = None)] 13 | struct Cli { 14 | #[arg(default_value_t = String::from("./"))] 15 | root_path: String, 16 | } 17 | 18 | fn main() { 19 | let cli = Cli::parse(); 20 | 21 | if let Err(error) = compile(Path::new(&cli.root_path)) { 22 | eprintln!("Compilation error: {error}"); 23 | } 24 | } 25 | 26 | fn compile(root_path: &Path) -> Result<()> { 27 | let source_dir = SourceDir::setup(root_path)?; 28 | let output_generator = OutputGenerator::setup(root_path)?; 29 | 30 | let mut program_context = codegen::context::ProgramContext::new(); 31 | 32 | // (try to) load file by file (per Jill module) 33 | for (file_path, file) in source_dir { 34 | // TODO: refactor to remove/reduce nesting 35 | match file { 36 | Err(error) => bail!("unable to load file at `{file_path:#?}`: {error}"), 37 | 38 | // file content loaded - (try to) parse code into AST 39 | Ok(file_info) => match parser::parse_module(&file_info) { 40 | Ok(ast) => { 41 | // convert AST to VM instructions 42 | match codegen::construct_module(ast, &mut program_context) { 43 | // (try to) output generated VM instructions to designated file 44 | Ok(output_file) => output_generator.generate(output_file)?, 45 | Err(error) => bail!("{error:#?}"), 46 | }; 47 | } 48 | // parsing failed - display the syntax error 49 | Err(errors) => error_report::display( 50 | file_path.to_string_lossy().as_ref(), 51 | file_info.content(), 52 | errors, 53 | ), 54 | }, 55 | } 56 | } 57 | 58 | apply_post_compilation_generation(&output_generator, &mut program_context) 59 | } 60 | 61 | fn apply_post_compilation_generation( 62 | output_generator: &OutputGenerator, 63 | program_context: &mut codegen::context::ProgramContext, 64 | ) -> Result<()> { 65 | use codegen::post_compilation; 66 | 67 | // globals initialization 68 | if let Some(globals_output) = post_compilation::globals::construct(program_context) { 69 | // output globals 70 | output_generator.generate(globals_output)?; 71 | 72 | // add a custom Sys.vm (which is modified to include a call to `Globals.init`) 73 | output_generator.generate(post_compilation::jillstd::sys_output())?; 74 | } 75 | 76 | // jillstd 77 | for jillstd_module_output in post_compilation::jillstd::construct(program_context) { 78 | output_generator.generate(jillstd_module_output)?; 79 | } 80 | 81 | // fn dispatch 82 | if let Some(fn_dispatch_output) = 83 | post_compilation::function_dispatch::construct(program_context)? 84 | { 85 | output_generator.generate(fn_dispatch_output)?; 86 | } 87 | 88 | Ok(()) 89 | } 90 | 91 | mod error_report { 92 | use ariadne::{Label, Report, ReportKind, Source}; 93 | 94 | use crate::parser::JillParseError; 95 | 96 | pub fn display(file_path: &str, file_content: &str, errors: Vec) { 97 | for error in errors { 98 | Report::build(ReportKind::Error, file_path, error.span().start) 99 | .with_message("Parser error") 100 | .with_label( 101 | Label::new((file_path, error.span())) 102 | .with_message(error.label().unwrap_or("error occured here")), 103 | ) 104 | .finish() 105 | .eprint((file_path, Source::from(file_content))) 106 | .expect("error report should be valid"); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | //! Logic for parsing raw `Jill` code 2 | //! into an AST. 3 | 4 | #[allow(clippy::wildcard_imports)] 5 | use crate::common::ast::*; 6 | use chumsky::prelude::*; 7 | 8 | use crate::fileio::input::SourceFile; 9 | 10 | // TODO?: move to error handling 11 | pub type JillParseError = Simple; 12 | 13 | /// Parse a single module (source file). 14 | pub fn parse_module(source_file: &SourceFile) -> Result> { 15 | let module_name = source_file.module_name(); 16 | let module_content = module().parse(source_file.content())?; 17 | 18 | Ok(JillModule { 19 | name: module_name, 20 | content: module_content, 21 | }) 22 | } 23 | 24 | /// Construct the parser for a Jill program module (file). 25 | fn module() -> impl Parser { 26 | let module = r#type() 27 | .then_ignore(just('.')) 28 | .repeated() 29 | .then(variable().then_ignore(just('.')).repeated()) 30 | .then(function().then_ignore(just('.')).repeated()) 31 | .padded() 32 | .map(|((types, variables), functions)| JillModuleContent { 33 | types, 34 | variables, 35 | functions, 36 | }); 37 | 38 | module.then_ignore(end()) 39 | } 40 | 41 | fn comments() -> impl Parser + std::clone::Clone { 42 | let comment = just("--").then(take_until(text::newline())); 43 | comment.padded().repeated().ignored() 44 | } 45 | 46 | fn nested_expression_list( 47 | // NOTE: since this is a part of the (recursive) `expression` parser definition, 48 | // we have to directly pass expression parser 49 | expression: impl Parser, 50 | ) -> impl Parser, Error = JillParseError> { 51 | expression.separated_by(just(',')).allow_trailing().padded() 52 | } 53 | 54 | type JillFunctionReferenceComponents = ( 55 | (Vec, Option), 56 | JillIdentifier, 57 | ); 58 | fn fully_qualified_function_name( 59 | // NOTE: passed as this function is used for both function calls and references, 60 | // where different parsing rules should apply 61 | function_name_parser: impl Parser, 62 | ) -> impl Parser { 63 | let modules_path = identifier::module().then_ignore(just("::")).repeated(); 64 | 65 | let associated_type = identifier::r#type().then_ignore(just(":")); 66 | let function_name = function_name_parser; 67 | 68 | modules_path 69 | .then(associated_type.or_not()) 70 | .then(function_name) 71 | } 72 | 73 | fn literal( 74 | // NOTE: since this is a part of the (recursive) `expression` parser definition, 75 | // we have to directly pass expression parser 76 | expression: impl Parser, 77 | ) -> impl Parser { 78 | // integer 79 | let integer = text::int(10) 80 | .map(|s: String| s.parse::().expect("should be a valid number")) 81 | .map(JillLiteral::Integer); 82 | 83 | // string 84 | let string = just('"') 85 | .ignore_then(none_of('"').repeated()) 86 | .then_ignore(just('"')) 87 | .map(|chars| chars.into_iter().collect()) 88 | .map(JillLiteral::String); 89 | 90 | // bool 91 | let boolean = just("True") 92 | .or(just("False")) 93 | .map(|b| b == "True") 94 | .map(JillLiteral::Bool); 95 | 96 | // list 97 | let list = nested_expression_list(expression) 98 | .delimited_by(just('['), just(']')) 99 | .map(JillLiteral::List); 100 | 101 | integer.or(string).or(boolean).or(list) 102 | } 103 | 104 | fn function_reference() -> impl Parser { 105 | just("&") 106 | .ignore_then(fully_qualified_function_name( 107 | identifier::function_reference(), 108 | )) 109 | .then_ignore( 110 | none_of("(") 111 | .rewind() 112 | .labelled("cannot reference and call a function"), 113 | ) 114 | .map( 115 | |((modules_path, associated_type), function_name)| JillFunctionReference { 116 | modules_path, 117 | associated_type, 118 | function_name, 119 | }, 120 | ) 121 | } 122 | 123 | fn function_call( 124 | // NOTE: since this is a part of the (recursive) `expression` parser definition, 125 | // we have to directly pass expression parser 126 | expression: impl Parser, 127 | ) -> impl Parser { 128 | let function_call_source = fully_qualified_function_name(identifier::function_call()) 129 | .or(identifier::function_call().map(|name| ((vec![], None), name))) 130 | .map( 131 | |((modules_path, associated_type), function_name)| JillFunctionReference { 132 | modules_path, 133 | associated_type, 134 | function_name, 135 | }, 136 | ); 137 | 138 | function_call_source 139 | .then(nested_expression_list(expression).delimited_by(just('('), just(')'))) 140 | .map(|(reference, arguments)| JillFunctionCall { 141 | reference, 142 | arguments, 143 | }) 144 | } 145 | 146 | fn expression() -> impl Parser { 147 | recursive(|expression| { 148 | // since there is possible ambiguity here, order in which the 149 | // options are listed is important (most specific => least specific) 150 | literal(expression.clone()) 151 | .map(JillExpression::Literal) 152 | .or(function_call(expression).map(JillExpression::FunctionCall)) 153 | .or(function_reference().map(JillExpression::FunctionReference)) 154 | .or(identifier::variable().map(JillExpression::VariableName)) 155 | .padded() 156 | .padded_by(comments()) 157 | }) 158 | } 159 | 160 | fn function_body( 161 | nested_function: impl Parser, 162 | ) -> impl Parser { 163 | nested_function 164 | .then_ignore(just('.')) 165 | .repeated() 166 | .then(variable().then_ignore(just(',')).repeated()) 167 | .then(expression()) 168 | .padded() 169 | .map( 170 | |((local_functions, local_variables), return_expression)| JillFunctionBody { 171 | local_functions, 172 | local_variables, 173 | return_expression, 174 | }, 175 | ) 176 | } 177 | 178 | fn variable() -> impl Parser { 179 | text::keyword("let") 180 | .ignore_then(identifier::variable_assignment()) 181 | .then_ignore(just('=')) 182 | .then(expression()) 183 | .padded() 184 | .padded_by(comments()) 185 | .map(|(name, value)| JillVariable { name, value }) 186 | } 187 | 188 | fn function() -> impl Parser { 189 | let captured_arguments = identifier::variable() 190 | .repeated() 191 | // require at least one captured argument if brackets are present 192 | .at_least(1) 193 | .delimited_by(just('['), just(']')) 194 | .padded(); 195 | 196 | recursive(|function| { 197 | text::keyword("fn") 198 | .ignore_then(identifier::function_name()) 199 | .then(identifier::variable().repeated()) 200 | .then(captured_arguments.or_not()) 201 | .then_ignore(just('=')) 202 | .then(function_body(function)) 203 | .padded() 204 | .padded_by(comments()) 205 | .map(|(((name, arguments), captures), body)| JillFunction { 206 | name, 207 | arguments, 208 | captures: captures.unwrap_or(vec![]), 209 | body, 210 | }) 211 | }) 212 | } 213 | 214 | fn r#type() -> impl Parser { 215 | let variant = identifier::r#type() 216 | .then( 217 | identifier::variable() 218 | .separated_by(just(',')) 219 | .allow_trailing() 220 | .delimited_by(just('('), just(')')) 221 | .or_not(), 222 | ) 223 | .padded_by(comments()) 224 | .map(|(name, fields)| JillTypeVariant { 225 | name, 226 | fields: fields.unwrap_or(Vec::new()), 227 | }); 228 | 229 | text::keyword("type") 230 | .ignore_then(identifier::r#type()) 231 | .then_ignore(just('=')) 232 | .then(variant.separated_by(just(','))) 233 | .padded() 234 | .padded_by(comments()) 235 | .map(|(name, variants)| JillType { name, variants }) 236 | } 237 | 238 | mod identifier { 239 | // TODO!: figure out how to "forbid" certain identifiers 240 | 241 | use super::*; 242 | 243 | fn keyword() -> impl Parser { 244 | choice([ 245 | text::keyword("let"), 246 | text::keyword("fn"), 247 | text::keyword("type"), 248 | ]) 249 | } 250 | 251 | fn compiler_internal() -> impl Parser { 252 | choice([ 253 | text::keyword("if"), 254 | text::keyword("ifElse"), 255 | text::keyword("do"), 256 | text::keyword("match"), 257 | text::keyword("todo"), 258 | text::keyword("free"), 259 | ]) 260 | } 261 | 262 | fn underscore() -> impl Parser { 263 | text::keyword("_") 264 | } 265 | 266 | // ({identifier} \ `_`) \ {keyword} 267 | fn non_keyword_identifier() -> impl Parser { 268 | // forbid all keywords, as they are never a valid identifier 269 | let not_keyword = keyword().not().rewind(); 270 | 271 | // also forbid isolated `_`, as it is not a valid identifier 272 | let not_underscore = underscore().not().rewind(); 273 | 274 | let base_identifier = text::ident().padded(); 275 | 276 | not_keyword.ignore_then(not_underscore.ignore_then(base_identifier)) 277 | } 278 | 279 | pub fn function_call() -> impl Parser { 280 | // function call is the only place where we should accept 281 | // compiler internal (i.e. lazy evaluated) functions 282 | non_keyword_identifier().map(JillIdentifier) 283 | } 284 | 285 | // (({identifier} \ `_`) \ {keyword}) \ {compiler_internal} 286 | fn non_compiler_internal_identifier() -> impl Parser { 287 | let not_compiler_internal = compiler_internal().not().rewind(); 288 | 289 | not_compiler_internal.ignore_then(non_keyword_identifier()) 290 | } 291 | 292 | macro_rules! make_non_compiler_internal_identifier { 293 | ($name: ident) => { 294 | pub fn $name() -> impl Parser { 295 | non_compiler_internal_identifier().map(JillIdentifier) 296 | } 297 | }; 298 | } 299 | 300 | make_non_compiler_internal_identifier!(function_reference); 301 | make_non_compiler_internal_identifier!(function_name); 302 | make_non_compiler_internal_identifier!(module); 303 | make_non_compiler_internal_identifier!(r#type); 304 | make_non_compiler_internal_identifier!(variable); 305 | 306 | pub fn variable_assignment() -> impl Parser { 307 | // non-internal, but allows pure `_` 308 | non_compiler_internal_identifier() 309 | .or(underscore().map(|()| String::new())) 310 | .map(JillIdentifier) 311 | } 312 | } 313 | --------------------------------------------------------------------------------