├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── compiler ├── Makefile ├── compiler.wasm ├── index.html ├── main.w └── project.wasp ├── examples ├── canvas │ ├── Makefile │ ├── canvas.wasm │ ├── index.html │ └── main.w ├── dynamic_dispatch │ ├── Makefile │ ├── dynamic_dispatch.wasm │ ├── index.html │ └── main.w ├── helloworld │ ├── Makefile │ ├── helloworld.wasm │ ├── index.html │ └── main.w ├── simplest │ ├── Makefile │ ├── index.html │ ├── main.w │ └── simplest.wasm ├── testing │ ├── Makefile │ ├── index.html │ ├── main.w │ └── testing.wasm └── webgl │ ├── Makefile │ ├── index.html │ ├── main.w │ └── webgl.wasm ├── static_heap.svg ├── test ├── wasp-core ├── Cargo.toml └── src │ ├── ast.rs │ ├── compiler.rs │ ├── lib.rs │ └── parser.rs └── wasp ├── Cargo.toml └── src ├── main.rs └── static ├── index.html ├── main.w └── project.wasp /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | **/*.rs.bk 3 | Cargo.lock 4 | 5 | /target 6 | **/*.rs.bk 7 | Cargo.lock 8 | 9 | /target 10 | **/*.rs.bk 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Richard Anaya 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | cd wasp && cargo build 3 | cd examples/helloworld && make 4 | cd examples/canvas && make 5 | cd examples/testing && make 6 | cd examples/dynamic_dispatch && make 7 | cd examples/simplest && make 8 | cd compiler && make 9 | serve: 10 | http-server -p 8080 11 | publish-std: 12 | cd compiler/vendor/std && git add . && git commit -m 'publishing' && git push 13 | publish: 14 | git add . && git commit -m 'publishing' && git push 15 | cargo publish 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wasp 🐝 2 | a programming language for extremely concise web assembly modules 3 | 4 | **warning:** this compiler is very alpha and error messages aren't the best, but it works and language is simple! 5 | 6 | ```rust 7 | extern console_log(message) 8 | 9 | pub fn main(){ 10 | console_log("Hello World!") 11 | } 12 | ``` 13 | 14 | # Features 15 | * [x] encourages immutability 16 | * [x] immutable c-strings, memory manipulation, global variables, imported functions, 1st class functions 17 | * [x] optional standard library runtime 18 | * [x] functions with inline web assembly 19 | * [x] test framework support 20 | * [x] easy project dependency management 21 | * [ ] self hosting 22 | 23 | # Quickstart 24 | 25 | Wasp depends on `git` and `rust`. Make sure you have them installed before beginning. 26 | 27 | ```console 28 | cargo install wasp 29 | wasp init myproject 30 | cd myproject 31 | wasp build 32 | python3 -m http.server 33 | ``` 34 | Open up http://localhost:8000 and look in console. At this point we will have a web assembly module that has access to the [standard libraries](https://github.com/wasplang/std) functions. More to come in this area! 35 | 36 | If you don't have need for the standard library (or want to write your own!). This is also an option. 37 | 38 | ```console 39 | wasp init myproject --no-std 40 | ``` 41 | 42 | At this point we will have a web assembly module with a single exported main function and nothing else. 43 | 44 | If you think your standard library is out of date, just run `wasp vendor` 45 | 46 | # Simple Data Structures 47 | 48 | Wasp is an extremely basic language and standard library. 49 | 50 | ## Linked List 51 | 52 | ```rust 53 | cons(42,nil) // returns the memory location of cons 54 | ``` 55 | ```rust 56 | head(cons(42,nil)) // return the head value 42 57 | ``` 58 | ```rust 59 | tail(cons(42,nil)) // returns the memory location of tail 60 | ``` 61 | 62 | ```rust 63 | cons(1,cons(2,cons(3,nil))) // returns a linked list 64 | ``` 65 | 66 | ## Structs 67 | 68 | Structs are dictionaries 69 | 70 | ```rust 71 | struct point { :x :y } 72 | 73 | pub fn create_point(){ 74 | foo = malloc(size_of(point)) 75 | set(foo,:x,1) 76 | set(foo,:y,1) 77 | foo 78 | } 79 | ``` 80 | 81 | # Drawing 82 | 83 | Using [web-dom](https://github.com/web-dom/web-dom) we can easily draw something to screen. Loops in wasp work differently than other languages, observe how this example uses recursion to rebind variables. 84 | 85 | ```rust 86 | extern console_log(msg) 87 | extern global_get_window() 88 | extern window_get_document(window) 89 | extern document_query_selector(document,query) 90 | extern htmlcanvas_get_context(element,context) 91 | extern canvas_set_fill_style(canvas,color) 92 | extern canvas_fill_rect(canvas,x,y,w,h) 93 | 94 | static colors = ("black","grey","red") 95 | 96 | pub fn main(){ 97 | // setup a drawing context 98 | window = global_get_window() 99 | document = window_get_document(window) 100 | canvas = document_query_selector(document,"#screen") 101 | ctx = htmlcanvas_get_context(canvas,"2d") 102 | 103 | x = 0 104 | loop { 105 | // get the offset for the color to use 106 | color_offset = (colors + (x * size_num)) 107 | // set current color to string at that position 108 | canvas_set_fill_style(ctx,mem(color_offset)) 109 | // draw the rect 110 | canvas_fill_rect(ctx,(x * 10),(x * 10),50,50) 111 | // recur until 3 squares are drawn 112 | x = (x + 1) 113 | if (x < 3) { 114 | recur 115 | } 116 | } 117 | } 118 | ``` 119 | 120 | See it working [here](https://wasplang.github.io/wasp/examples/canvas/index.html) 121 | 122 | # Mutable Global Data 123 | 124 | It's often important for a web assembly modules to have some sort of global data that can be changed. For instance in a game we might have a high score. 125 | 126 | ```rust 127 | high_score = 0 128 | 129 | fn run_my_game(){ 130 | ... 131 | mem(high_score,(mem(high_score) + 100)) 132 | ... 133 | } 134 | ``` 135 | 136 | # Project Management 137 | **warning: this may change but it works** 138 | Code dependencies are kept in a special folder called `vendor` which is populated by specific checkouts of git repositories. 139 | 140 | For example a `project.wasp` containing: 141 | 142 | ``` 143 | bar git@github.com:richardanaya/bar.git@specific-bar 144 | ``` 145 | 146 | would result in these commands (roughly) 147 | 148 | ``` 149 | rm -rf vendor 150 | mkdir vendor 151 | git clone git@github.com:richardanaya/bar.git@specific-bar vendor/bar 152 | ``` 153 | 154 | when `wasp vendor` is called 155 | 156 | Now, when wasp compiles your code, it does a few things. 157 | 158 | * In order specified by your `project.wasp`, one folder at a time all files ending in .w are loaded from each `vendor/` and its subfolders. 159 | * all files in the current directory and sub directories not in `vendor` are loaded 160 | * then everything is compiled in order 161 | 162 | Please try to use non conflicting names in meantime while this is fleshed out. 163 | 164 | # Technical Details 165 | ## Types 166 | It's easiest to think that everything is a `f64` number in wasp. 167 | 168 | * **number** - a 64 bit float 169 | * **string** - a number to a location in memory of the start of of a c-string (e.g. `"hello world!"`) 170 | * **symbol** - a number to a location in memory of the start of of a c-string (e.g. `:hello_world`) 171 | * **bool** - a number representing boolean values. True is 1, false is 0. (e.g. `true` `false`) 172 | * **(...)** - a global only type this is a a number pointer to sequence of values in memory (e.g. `(another_global 1 true :hey (:more-data)`). Use this for embedding raw data into your application memory on startup. 173 | 174 | ## Globals 175 | * **nil** - a number that represents nothingness (0). Note that it is also the same value as false and the number 0. 176 | * **size_num** - the length of a number in bytes (8). This is a global variable in wasp to cut down in magic numbers floating around in code. 177 | 178 | ## Functions 179 | * **[pub] fn name (x,...){ ... })** - create a function that executes a list of expressions returning the result of the last one. Optionally provide an export name to make visible to host. 180 | * **function_name(...)** - call a function with arguments 181 | * **mem_byte(x:integer)** - get 8-bit value from memory location x 182 | * **mem_byte(x:integer y)** - set 8-bit value at memory location x to value y 183 | * **mem(x:integer)** - get 64-bit float value from memory location x 184 | * **mem(x:integer y)** - set 64-bit float value at memory location x to value y 185 | 186 | * **mem_heap_start()** - get number that represents the start of the heap 187 | * **mem_heap_end()** - get number that represents the end of the heap 188 | * **mem_heap_end(x)** - set number value that represents the end of the heap 189 | * **if x { y } )** - if x is true return expression y otherwise return 0 190 | * **if x { y } else { z })** - if x is true return expression y otherwise return expression z 191 | * **x = y** - bind the value of an expression y to an identifier x 192 | * **loop { ... x } ** - executes a list of expressions and returns the last expression x. loop can be restarted with a recur. 193 | * **recur** - restarts a loop 194 | * **fn(x,x1 ..)->y** - gets the value of a function signature with inputs x0, x1, etc and output y 195 | * **call(x,f,y0,y1 ...)** call a function with signature x and function handle f with parameters y0, y1, ... 196 | 197 | ### Common Operators 198 | These oprators work pretty much how you'd expect if you've used C 199 | 200 | * **(x + y)** - sums a list of values and returns result 201 | * **(x - y)** - subtracts a list of values and returns result 202 | * **(x \* y)** - multiplies a list of values and returns result 203 | * **(x / y)** - divides a list of values and returns result 204 | * **(x % y)** - modulos a list of values and returns result 205 | * **(x == y)** - returns true if values are equal, false if otherwise 206 | * **(x != y)** - returns true if values are not equal, false if otherwise 207 | * **(x < y)** - returns true if x is less than y, false if otherwise 208 | * **(x > y)** - returns true if x is greater than y, false if otherwise 209 | * **(x <= y)** - returns true if x is less than or equal y, false if otherwise 210 | * **(x >= y)** - returns true if x is greater than or equal y, false if otherwise 211 | * **(x and y)** - returns true if x and y are true, false if otherwise 212 | * **(x or y)** - returns true if x or y are true, false if otherwise 213 | * **(x & y)** - returns bitwise and of x and y 214 | * **(x | y)** - returns bitwise or of x and y 215 | * **!x** - returns true if zero and false if not zero 216 | * **^x** - bitwise exclusive or of x 217 | * **~x** - bitwise complement of x 218 | * **(x << y)** - shift x left by y bits 219 | * **(x >> y)** - shift x right by y bits 220 | 221 | ## Testing 222 | ```rust 223 | pub test_addition(){ 224 | assert(4,(2+2),"2 + 2 should be 4") 225 | assert(7,(3+4),"3 + 4 should be 7") 226 | } 227 | ``` 228 | See it working [here](https://wasplang.github.io/wasp/examples/testing/index.html) 229 | 230 | ## Why so few functions? 231 | Wasp prefers to keep as little in the core functionality as possible, letting the [standard library](https://github.com/wasplang/std) evolve faster and more independent community driven manner. This project currently follows a principle that if a feature can be implemented with our primitive functions, don't include it in the core compiled language and let the standard library implement it. Also that no heap based concepts be added to the core language. 232 | 233 | ## Notes 234 |

235 | 236 |

237 | 238 | * all functions (including extern functions) return a value, if no obvious return, it returns () 239 | * Web assembly global 0 is initialized to the end of the static data section (which might also be the start of a heap for a memory allocator). This value is immutable. 240 | * Web assembly global lobal 1 also is initialized to the end of the static data section. This value is mutable and might be used to represent the end of your heap. Check out the [simple allocator example](https://github.com/richardanaya/wasp/blob/master/examples/malloc/main.w). 241 | * Literal strings create initialize data of a c-string at the front of your memory, and can be passed around as pointers to the very start in memory to your text. A \0 is automatically added at compile time, letting you easily have a marker to denote the end of your text. 242 | -------------------------------------------------------------------------------- /compiler/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | cd ../&& cargo build 3 | ../target/debug/wasp build 4 | watch: 5 | while true; do \ 6 | make build; \ 7 | inotifywait -qre close_write .; \ 8 | done 9 | serve: 10 | http-server -p 8080 11 | -------------------------------------------------------------------------------- /compiler/compiler.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasplang/wasp/9cc072c663f6f9ff7cf95e526be2083dd5b4ae32/compiler/compiler.wasm -------------------------------------------------------------------------------- /compiler/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wasp 5 | 6 | 7 | 8 |
9 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /compiler/main.w: -------------------------------------------------------------------------------- 1 | pub fn main(msg){ 2 | 42 3 | } 4 | -------------------------------------------------------------------------------- /compiler/project.wasp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasplang/wasp/9cc072c663f6f9ff7cf95e526be2083dd5b4ae32/compiler/project.wasp -------------------------------------------------------------------------------- /examples/canvas/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | cd ../../ && cargo build 3 | ../../target/debug/wasp build 4 | serve: 5 | http-server -p 8080 6 | -------------------------------------------------------------------------------- /examples/canvas/canvas.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasplang/wasp/9cc072c663f6f9ff7cf95e526be2083dd5b4ae32/examples/canvas/canvas.wasm -------------------------------------------------------------------------------- /examples/canvas/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wasp 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/canvas/main.w: -------------------------------------------------------------------------------- 1 | extern console_log(msg) 2 | extern global_get_window() 3 | extern window_get_document(window) 4 | extern document_query_selector(document,query) 5 | extern htmlcanvas_get_context(element,context) 6 | extern canvas_set_fill_style(canvas,color) 7 | extern canvas_fill_rect(canvas,x,y,w,h) 8 | 9 | static colors = ("black","grey","red") 10 | 11 | pub fn main(){ 12 | // setup a drawing context 13 | window = global_get_window() 14 | document = window_get_document(window) 15 | canvas = document_query_selector(document,"#screen") 16 | ctx = htmlcanvas_get_context(canvas,"2d") 17 | x = 0 18 | loop { 19 | // get the offset for the color to use 20 | color_offset = (colors + (x * size_num)) 21 | // set current color to string at that position 22 | canvas_set_fill_style(ctx,mem(color_offset)) 23 | // draw the rect 24 | canvas_fill_rect(ctx,(x * 10),(x * 10),50,50) 25 | // recur until 3 squares are drawn 26 | x = (x + 1) 27 | if (x < 3) { 28 | recur 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/dynamic_dispatch/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | cd ../../ && cargo build 3 | ../../target/debug/wasp build 4 | serve: 5 | http-server -p 8080 6 | -------------------------------------------------------------------------------- /examples/dynamic_dispatch/dynamic_dispatch.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasplang/wasp/9cc072c663f6f9ff7cf95e526be2083dd5b4ae32/examples/dynamic_dispatch/dynamic_dispatch.wasm -------------------------------------------------------------------------------- /examples/dynamic_dispatch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wasp 5 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/dynamic_dispatch/main.w: -------------------------------------------------------------------------------- 1 | extern console_log(message) 2 | fn heads(){ console_log("heads!") } 3 | fn tails(){ console_log("tails!") } 4 | pub fn main(h){ 5 | function_to_call = if(h==1){heads}else{tails} 6 | call(fn()->f64,function_to_call) 7 | } 8 | -------------------------------------------------------------------------------- /examples/helloworld/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | cd ../../ && cargo build 3 | ../../target/debug/wasp build 4 | serve: 5 | http-server -p 8080 6 | -------------------------------------------------------------------------------- /examples/helloworld/helloworld.wasm: -------------------------------------------------------------------------------- 1 | asm 2 | `|`||env console_logp 3 |  A A memorymain A  4 |  D@ A hello world! -------------------------------------------------------------------------------- /examples/helloworld/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wasp 5 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/helloworld/main.w: -------------------------------------------------------------------------------- 1 | extern console_log(message) 2 | pub fn main(){ 3 | console_log("hello world!") 4 | } 5 | -------------------------------------------------------------------------------- /examples/simplest/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | cd ../../ && cargo build 3 | ../../target/debug/wasp build 4 | serve: 5 | http-server -p 8080 6 | -------------------------------------------------------------------------------- /examples/simplest/index.html: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /examples/simplest/main.w: -------------------------------------------------------------------------------- 1 | pub fn main(){ 2 | 42 3 | } 4 | -------------------------------------------------------------------------------- /examples/simplest/simplest.wasm: -------------------------------------------------------------------------------- 1 | asm`|p 2 |  A A memorymain A  3 |  DE@  -------------------------------------------------------------------------------- /examples/testing/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | cd ../../ && cargo build 3 | ../../target/debug/wasp build 4 | serve: 5 | http-server -p 8080 6 | -------------------------------------------------------------------------------- /examples/testing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 58 | 59 | -------------------------------------------------------------------------------- /examples/testing/main.w: -------------------------------------------------------------------------------- 1 | pub fn test_multiplication() { 2 | assert(4,(2*2),"2 * 2 should be 4") 3 | assert(1,(1*1),"1 * 1 should be 1") 4 | } 5 | 6 | pub fn test_addition() { 7 | assert(5,(2+2),"2 + 2 should be 4") 8 | assert(2,(1+1),"1 + 1 should be 2") 9 | } 10 | -------------------------------------------------------------------------------- /examples/testing/testing.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasplang/wasp/9cc072c663f6f9ff7cf95e526be2083dd5b4ae32/examples/testing/testing.wasm -------------------------------------------------------------------------------- /examples/webgl/Makefile: -------------------------------------------------------------------------------- 1 | build: 2 | cd ../../ && cargo build 3 | ../../target/debug/wasp build 4 | serve: 5 | http-server -p 8080 6 | -------------------------------------------------------------------------------- /examples/webgl/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wasp 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/webgl/main.w: -------------------------------------------------------------------------------- 1 | (def COLOR_BUFFER_BIT 16384) 2 | (def VERTEX_SHADER 35633) 3 | (def FRAGMENT_SHADER 35632) 4 | (def vertex_shader_src " 5 | precision mediump float; 6 | attribute vec2 vertPosition; 7 | attribute vec3 vertColor; 8 | varying vec3 fragColor; 9 | void main() 10 | { 11 | fragColor = vertColor; 12 | gl_Position = vec4(vertPosition, 0.0, 1.0); 13 | } 14 | ") 15 | (def fragment_shader_src " 16 | precision mediump float; 17 | 18 | varying vec3 fragColor; 19 | void main() 20 | { 21 | gl_FragColor = vec4(fragColor, 1.0); 22 | } 23 | ") 24 | 25 | (extern global_get_window []) 26 | (extern window_get_document [window]) 27 | (extern document_query_selector [document query]) 28 | (extern htmlcanvas_get_context [element context]) 29 | (extern webgl_create_shader [ctx shader_type] ) 30 | (extern webgl_shader_source [ctx shader source] ) 31 | (extern webgl_compile_shader [ctx shader] ) 32 | (extern webgl_create_shader [ctx shader_type vertex_shader_src] ) 33 | (extern webgl_create_program [ctx] ) 34 | (extern webgl_attach_shader [ctx program shader] ) 35 | (extern webgl_link_program [ctx program] ) 36 | (extern webgl_use_program [ctx program] ) 37 | (extern webgl_clear_color [ctx r g b a] ) 38 | (extern webgl_clear [ctx buffer_bit] ) 39 | (extern webgl_get_attrib_location [ctx program attrib_name] ) 40 | 41 | (defn create_shader [ctx shader_type source] 42 | (let [shader (webgl_create_shader ctx shader_type)] 43 | (webgl_shader_source ctx shader source) 44 | (webgl_compile_shader ctx shader) 45 | shader)) 46 | 47 | (defn start_program [ctx] 48 | (let [vertex_shader (create_shader ctx VERTEX_SHADER vertex_shader_src) 49 | fragment_shader (create_shader ctx FRAGMENT_SHADER fragment_shader_src) 50 | program (webgl_create_program ctx)] 51 | (webgl_attach_shader ctx program vertex_shader) 52 | (webgl_attach_shader ctx program fragment_shader) 53 | (webgl_link_program ctx program) 54 | (webgl_use_program ctx program) 55 | program)) 56 | 57 | (pub defn main [] 58 | (let [win (global_get_window) 59 | doc (window_get_document win) 60 | canvas (document_query_selector doc "#screen") 61 | ctx (htmlcanvas_get_context canvas "webgl")] 62 | (webgl_clear_color ctx 0.75 0.85 0.8 1.0) 63 | (webgl_clear ctx COLOR_BUFFER_BIT) 64 | (let [program (start_program ctx) 65 | position_location (webgl_get_attrib_location ctx program "vertPosition") 66 | color_location (webgl_get_attrib_location ctx program "vertColor")] 67 | 123))) 68 | 69 | 70 | ; // create a program and get its attribute and uniforms 71 | ; let program = start_program(ctx); 72 | ; let position_location = webgl::get_attrib_location(ctx, program, "vertPosition"); 73 | ; let color_location = webgl::get_attrib_location(ctx, program, "vertColor"); 74 | ; webgl::use_program(ctx, NULL); 75 | ; 76 | ; // setup data buffer 77 | ; let vertices: Vec = vec![ 78 | ; // X, Y, R, G, B 79 | ; 0.0, 0.5, 1.0, 1.0, 0.0, -0.5, -0.5, 0.7, 0.0, 1.0, 0.5, -0.5, 0.1, 1.0, 0.6, 80 | ; ]; 81 | ; let vertices = create_f32array(&vertices.into_bytes()); 82 | ; let vertex_buffer = webgl::create_buffer(ctx); 83 | ; webgl::bind_buffer(ctx, webgl::ARRAY_BUFFER, vertex_buffer); 84 | ; webgl::buffer_data(ctx, webgl::ARRAY_BUFFER, vertices, webgl::STATIC_DRAW); 85 | ; webgl::bind_buffer(ctx, webgl::ARRAY_BUFFER, NULL); 86 | ; 87 | ; // setup for drawing 88 | ; webgl::use_program(ctx, program); 89 | ; 90 | ; // draw 91 | ; webgl::bind_buffer(ctx, webgl::ARRAY_BUFFER, vertex_buffer); 92 | ; webgl::enable_vertex_attrib_array(ctx, position_location); 93 | ; webgl::enable_vertex_attrib_array(ctx, color_location); 94 | ; webgl::vertex_attrib_pointer( 95 | ; ctx, 96 | ; position_location, 97 | ; 2.0, 98 | ; webgl::FLOAT, 99 | ; false, 100 | ; 5.0 * 4.0, 101 | ; 0.0, 102 | ; ); 103 | ; webgl::vertex_attrib_pointer( 104 | ; ctx, 105 | ; color_location, 106 | ; 3.0, 107 | ; webgl::FLOAT, 108 | ; false, 109 | ; 5.0 * 4.0, 110 | ; 2.0 * 4.0, 111 | ; ); 112 | ; webgl::bind_buffer(ctx, webgl::ARRAY_BUFFER, NULL); 113 | ; 114 | ; webgl::draw_arrays(ctx, webgl::TRIANGLES, 0.0, 3.0); 115 | ; } 116 | -------------------------------------------------------------------------------- /examples/webgl/webgl.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasplang/wasp/9cc072c663f6f9ff7cf95e526be2083dd5b4ae32/examples/webgl/webgl.wasm -------------------------------------------------------------------------------- /static_heap.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasplang/wasp/9cc072c663f6f9ff7cf95e526be2083dd5b4ae32/test -------------------------------------------------------------------------------- /wasp-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasp-core" 3 | version = "0.0.0" 4 | authors = ["Richard Anaya"] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "Core compiling logic of wasp" 8 | 9 | [dependencies] 10 | failure = "0.1.5" 11 | wasmly = "0.2.0" 12 | 13 | [dependencies.nom] 14 | version = "4" 15 | features = ["verbose-errors"] -------------------------------------------------------------------------------- /wasp-core/src/ast.rs: -------------------------------------------------------------------------------- 1 | use wasmly::DataType; 2 | 3 | #[derive(Debug)] 4 | pub struct App { 5 | pub children: Vec, 6 | } 7 | 8 | #[derive(Debug, Clone)] 9 | pub enum TopLevelOperation { 10 | Comment(String), 11 | DefineGlobal(Global), 12 | DefineFunction(FunctionDefinition), 13 | ExternalFunction(ExternalFunction), 14 | } 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct Global { 18 | pub name: String, 19 | pub value: GlobalValue, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub enum GlobalValue { 24 | Symbol(String), 25 | Number(f64), 26 | Text(String), 27 | Data(Vec), 28 | Identifier(String), 29 | Struct(StructDefinition), 30 | } 31 | 32 | #[derive(Debug, Clone)] 33 | pub struct ExternalFunction { 34 | pub name: String, 35 | pub params: Vec, 36 | } 37 | 38 | #[derive(Debug, Clone)] 39 | pub struct FunctionDefinition { 40 | pub name: String, 41 | pub exported: bool, 42 | pub params: Vec, 43 | pub output: Option, 44 | pub children: Vec, 45 | } 46 | 47 | #[derive(Debug, Clone)] 48 | pub struct StructMember { 49 | pub name: String, 50 | } 51 | 52 | #[derive(Debug, Clone)] 53 | pub struct StructDefinition { 54 | pub members: Vec, 55 | } 56 | 57 | #[derive(Debug, Clone)] 58 | pub struct OperationFunctionCall { 59 | pub function_name: String, 60 | pub params: Vec, 61 | } 62 | 63 | #[derive(Debug, Clone)] 64 | pub struct OperationRecur {} 65 | 66 | #[derive(Debug, Clone)] 67 | pub struct OperationAssignment { 68 | pub id: String, 69 | pub value: Box, 70 | } 71 | 72 | #[derive(Debug, Clone)] 73 | pub struct OperationIfStatement { 74 | pub condition: Box, 75 | pub if_true: Vec, 76 | pub if_false: Option>, 77 | } 78 | 79 | #[derive(Debug, Clone)] 80 | pub struct OperationLoop { 81 | pub expressions: Vec, 82 | } 83 | 84 | #[derive(Debug, Clone)] 85 | pub struct OperationFnSig { 86 | pub inputs: Vec, 87 | pub output: Option, 88 | } 89 | 90 | #[derive(Debug, Clone)] 91 | pub enum Expression { 92 | IfStatement(OperationIfStatement), 93 | Assignment(OperationAssignment), 94 | TextLiteral(String), 95 | SymbolLiteral(String), 96 | Identifier(String), 97 | FunctionCall(OperationFunctionCall), 98 | Number(f64), 99 | Recur(OperationRecur), 100 | Loop(OperationLoop), 101 | FnSig(OperationFnSig), 102 | } 103 | -------------------------------------------------------------------------------- /wasp-core/src/compiler.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::*; 2 | use failure::Error; 3 | use wasmly::WebAssembly::*; 4 | use wasmly::*; 5 | 6 | #[derive(PartialEq)] 7 | enum IdentifierType { 8 | Global, 9 | Local, 10 | Function, 11 | } 12 | 13 | struct Compiler { 14 | wasm: wasmly::App, 15 | ast: crate::ast::App, 16 | symbols: Vec, 17 | global_names: Vec, 18 | global_values: Vec, 19 | local_names: Vec, 20 | heap_position: f64, 21 | function_defs: Vec, 22 | function_names: Vec, 23 | function_implementations: Vec, 24 | non_imported_functions: Vec, 25 | recur_depth: u32, 26 | return_depth: u32, 27 | } 28 | 29 | impl Compiler { 30 | fn new(app: crate::ast::App) -> Compiler { 31 | let mut c = Compiler { 32 | wasm: wasmly::App::new(vec![]), 33 | ast: app, 34 | symbols: vec![], 35 | global_names: vec![], 36 | global_values: vec![], 37 | local_names: vec![], 38 | heap_position: 4.0, //start at 4 so nothing has 0 address 39 | function_defs: vec![], 40 | function_names: vec![], 41 | function_implementations: vec![], 42 | non_imported_functions: vec![], 43 | recur_depth: 0, 44 | return_depth: 1, 45 | }; 46 | c.initialize(); 47 | c 48 | } 49 | 50 | fn initialize(&mut self) { 51 | //Get imports so we can start creating app 52 | let import_defs = self 53 | .ast 54 | .children 55 | .iter() 56 | .filter_map(|x| match x { 57 | TopLevelOperation::ExternalFunction(x) => Some(x), 58 | _ => None, 59 | }) 60 | .collect::>(); 61 | 62 | let mut imports = vec![]; 63 | for def in import_defs { 64 | self.function_names.push(def.name.clone()); 65 | imports.push(Import::ImportFunction(ImportFunction::new( 66 | def.name.clone(), 67 | def.params.iter().map(|_| DataType::F64).collect(), 68 | Some(DataType::F64), 69 | ))) 70 | } 71 | self.wasm = wasmly::App::new(imports); 72 | self.function_defs = self 73 | .ast 74 | .children 75 | .iter() 76 | .filter_map(|x| match x { 77 | TopLevelOperation::DefineFunction(_) => Some(x.clone()), 78 | _ => None, 79 | }) 80 | .collect::>(); 81 | } 82 | 83 | fn process_globals(&mut self) { 84 | let global_defs = self 85 | .ast 86 | .children 87 | .iter() 88 | .filter_map(|x| match x { 89 | TopLevelOperation::DefineGlobal(x) => Some(x.clone()), 90 | _ => None, 91 | }) 92 | .collect::>(); 93 | for def in global_defs { 94 | self.global_names.push(def.name.clone()); 95 | let v = self.get_global_value(&def.value); 96 | self.global_values.push(v); 97 | } 98 | } 99 | 100 | fn float_to_bytes(&self, i: f64) -> Vec { 101 | let raw_bytes: [u8; 8] = unsafe { std::mem::transmute(i) }; 102 | let bytes: Vec = raw_bytes.to_vec(); 103 | bytes 104 | } 105 | 106 | fn create_global_data(&mut self, v: Vec) -> f64 { 107 | let mut bytes = vec![]; 108 | for i in 0..v.len() { 109 | let v = self.get_global_value(&v[i]); 110 | let b = self.float_to_bytes(v); 111 | bytes.extend_from_slice(&b); 112 | } 113 | self.create_data(bytes) 114 | } 115 | 116 | fn get_symbol_value(&mut self, t: &str) -> f64 { 117 | // no symbol has the value 0 118 | let v = self.symbols.iter().enumerate().find(|x| &x.1 == &t); 119 | if let Some(i) = v { 120 | return i.0 as f64 + 1.0; 121 | } else { 122 | self.symbols.push(t.to_string()); 123 | return self.symbols.len() as f64; 124 | } 125 | } 126 | 127 | fn get_global_value(&mut self, v: &GlobalValue) -> f64 { 128 | match v { 129 | GlobalValue::Symbol(t) => self.get_symbol_value(t), 130 | GlobalValue::Number(t) => *t, 131 | GlobalValue::Text(t) => self.get_or_create_text_data(&t), 132 | GlobalValue::Data(t) => self.create_global_data(t.clone()), 133 | GlobalValue::Struct(s) => { 134 | let mut t: Vec = vec![]; 135 | for i in 0..s.members.len() { 136 | t.push(GlobalValue::Symbol(s.members[i].name.clone())); 137 | } 138 | t.push(GlobalValue::Number(0.0)); 139 | self.create_global_data(t) 140 | } 141 | GlobalValue::Identifier(t) => { 142 | self.resolve_identifier(t) 143 | .expect(&format!("{} is not a valid identifier", &t)) 144 | .0 145 | } 146 | } 147 | } 148 | 149 | fn pre_process_functions(&mut self) { 150 | // gather all the function names and positions we shall use 151 | self.non_imported_functions = vec![]; 152 | for i in 0..self.function_defs.len() { 153 | if let TopLevelOperation::DefineFunction(function_def) = &self.function_defs[i] { 154 | self.function_names.push(function_def.name.clone()); 155 | self.non_imported_functions.push(function_def.name.clone()); 156 | } 157 | } 158 | 159 | // get the basics about our functions loaded into memory 160 | for i in 0..self.function_defs.len() { 161 | if let TopLevelOperation::DefineFunction(function_def) = &self.function_defs[i] { 162 | let mut function = Function::new(); 163 | if function_def.exported { 164 | function.with_name(&function_def.name); 165 | } 166 | function.with_inputs(function_def.params.iter().map(|_| DataType::F64).collect()); 167 | function.with_output(DataType::F64); 168 | self.function_implementations.push(function); 169 | } 170 | } 171 | 172 | self.wasm.add_table(wasmly::Table::new( 173 | self.function_names.len() as u32, 174 | self.function_names.len() as u32, 175 | )); 176 | } 177 | 178 | fn set_heap_start(&mut self) { 179 | //set global heap once we know what it should be 180 | let final_heap_pos = { 181 | if self.heap_position % 4.0 != 0.0 { 182 | (self.heap_position / 4.0) * 4.0 + 4.0 183 | } else { 184 | self.heap_position 185 | } 186 | }; 187 | self.wasm 188 | .add_global(wasmly::Global::new(final_heap_pos as i32, false)); 189 | self.wasm 190 | .add_global(wasmly::Global::new(final_heap_pos as i32, true)); 191 | } 192 | 193 | fn get_or_create_text_data(&mut self, str: &str) -> f64 { 194 | let mut bytes: Vec = str.as_bytes().into(); 195 | bytes.push(0); 196 | self.create_data(bytes) 197 | } 198 | 199 | fn create_data(&mut self, bytes: Vec) -> f64 { 200 | let pos = self.heap_position; 201 | let size = bytes.len(); 202 | self.wasm.add_data(Data::new(pos as i32, bytes)); 203 | let mut final_heap_pos = self.heap_position + (size as f64); 204 | // align data to 4 205 | // TODO: verify if this actually matters 206 | if final_heap_pos % 4.0 != 0.0 { 207 | final_heap_pos = (final_heap_pos / 4.0) * 4.0 + 4.0; 208 | } 209 | self.heap_position = final_heap_pos; 210 | pos 211 | } 212 | 213 | fn resolve_identifier(&self, id: &str) -> Option<(f64, IdentifierType)> { 214 | if id == "nil" { 215 | return Some((0.0, IdentifierType::Global)); 216 | } 217 | if id == "size_num" { 218 | return Some((8.0, IdentifierType::Global)); 219 | } 220 | // look this up in reverse so shadowing works 221 | let mut p = self.local_names.iter().rev().position(|r| r == id); 222 | if p.is_some() { 223 | return Some(( 224 | self.local_names.len() as f64 - 1.0 - p.unwrap() as f64, 225 | IdentifierType::Local, 226 | )); 227 | } 228 | p = self.function_names.iter().position(|r| r == id); 229 | if p.is_some() { 230 | return Some((p.unwrap() as f64, IdentifierType::Function)); 231 | } 232 | p = self.global_names.iter().position(|r| r == id); 233 | if p.is_some() { 234 | return Some((self.global_values[p.unwrap()], IdentifierType::Global)); 235 | } 236 | None 237 | } 238 | 239 | #[allow(clippy::cyclomatic_complexity)] 240 | fn process_expression(&mut self, i: usize, e: &Expression) { 241 | match e { 242 | Expression::SymbolLiteral(x) => { 243 | let v = self.get_symbol_value(x); 244 | self.function_implementations[i].with_instructions(vec![F64_CONST, v.into()]); 245 | } 246 | Expression::FnSig(x) => { 247 | let t = self 248 | .wasm 249 | .add_type(FunctionType::new(x.inputs.clone(), x.output.clone())); 250 | self.function_implementations[i] 251 | .with_instructions(vec![F64_CONST, (t as f64).into()]); 252 | } 253 | Expression::Loop(x) => { 254 | self.recur_depth = 0; 255 | if !x.expressions.is_empty() { 256 | self.function_implementations[i].with_instructions(vec![LOOP, F64]); 257 | for k in 0..x.expressions.len() { 258 | self.process_expression(i, &x.expressions[k]); 259 | if k != x.expressions.len() - 1 { 260 | self.function_implementations[i].with_instructions(vec![DROP]); 261 | } 262 | } 263 | self.function_implementations[i].with_instructions(vec![END]); 264 | } else { 265 | panic!("useless infinite loop detected") 266 | } 267 | } 268 | Expression::Recur(_) => { 269 | self.function_implementations[i].with_instructions(vec![ 270 | F64_CONST, 271 | 0.0.into(), 272 | BR, 273 | self.recur_depth.into(), 274 | ]); 275 | } 276 | Expression::IfStatement(x) => { 277 | self.recur_depth += 1; 278 | self.process_expression(i, &x.condition); 279 | self.function_implementations[i].with_instructions(vec![ 280 | F64_CONST, 281 | 0.0.into(), 282 | F64_EQ, 283 | I32_CONST, 284 | 0.into(), 285 | I32_EQ, 286 | ]); 287 | self.function_implementations[i].with_instructions(vec![IF, F64]); 288 | for k in 0..x.if_true.len() { 289 | self.process_expression(i, &x.if_true[k]); 290 | if k != x.if_true.len() - 1 { 291 | self.function_implementations[i].with_instructions(vec![DROP]); 292 | } 293 | } 294 | self.function_implementations[i].with_instructions(vec![ELSE]); 295 | if x.if_false.is_some() { 296 | for k in 0..x.if_false.as_ref().unwrap().len() { 297 | self.process_expression(i, &x.if_false.as_ref().unwrap()[k]); 298 | if k != x.if_false.as_ref().unwrap().len() - 1 { 299 | self.function_implementations[i].with_instructions(vec![DROP]); 300 | } 301 | } 302 | } else { 303 | self.function_implementations[i].with_instructions(vec![F64_CONST, 0.0.into()]); 304 | } 305 | self.function_implementations[i].with_instructions(vec![END]); 306 | } 307 | Expression::Assignment(x) => { 308 | self.process_expression(i, &x.value); 309 | self.function_implementations[i].with_local(DataType::F64); 310 | let p = self.resolve_identifier(&x.id); 311 | let idx = if p.is_some() { 312 | let ident = p.unwrap(); 313 | if ident.1 == IdentifierType::Local { 314 | ident.0 as u32 315 | } else { 316 | let l = self.local_names.len() as u32; 317 | self.local_names.push((&x.id).to_string()); 318 | l 319 | } 320 | } else { 321 | let l = self.local_names.len() as u32; 322 | self.local_names.push((&x.id).to_string()); 323 | l 324 | }; 325 | self.function_implementations[i].with_instructions(vec![ 326 | LOCAL_SET, 327 | idx.into(), 328 | LOCAL_GET, 329 | idx.into(), 330 | ]); 331 | } 332 | Expression::FunctionCall(x) => { 333 | if &x.function_name == "assert" { 334 | if x.params.len() == 3 { 335 | self.process_expression(i, &x.params[0]); 336 | self.process_expression(i, &x.params[1]); 337 | self.function_implementations[i].with_instructions(vec![F64_EQ]); 338 | self.function_implementations[i].with_instructions(vec![IF, F64]); 339 | self.function_implementations[i] 340 | .with_instructions(vec![F64_CONST, 0.0.into()]); 341 | self.function_implementations[i].with_instructions(vec![ELSE]); 342 | self.process_expression(i, &x.params[2]); 343 | self.function_implementations[i].with_instructions(vec![ 344 | BR, 345 | self.return_depth.into(), 346 | END, 347 | ]); 348 | } else { 349 | panic!("assert has 3 parameters") 350 | } 351 | } else if &x.function_name == "call" { 352 | if x.params.len() >= 2 { 353 | if let Expression::FnSig(sig) = &x.params[0] { 354 | for k in 2..x.params.len() { 355 | self.process_expression(i, &x.params[k]); 356 | } 357 | self.process_expression(i, &x.params[1]); 358 | self.function_implementations[i] 359 | .with_instructions(vec![I32_TRUNC_S_F64]); 360 | let t = self.wasm.add_type(FunctionType::new( 361 | sig.inputs.clone(), 362 | sig.output.clone(), 363 | )); 364 | self.function_implementations[i].with_instructions(vec![ 365 | CALL_INDIRECT, 366 | t.into(), 367 | 0.into(), 368 | ]); 369 | if sig.output.is_none() { 370 | self.function_implementations[i] 371 | .with_instructions(vec![F64_CONST, 0.0.into()]); 372 | } 373 | } else { 374 | panic!("call must begin with a function signature not an expression") 375 | } 376 | } else { 377 | panic!("call must have at least function signature and function index") 378 | } 379 | } else if &x.function_name == "mem_byte" { 380 | if x.params.len() == 1 { 381 | self.process_expression(i, &x.params[0]); 382 | self.function_implementations[i].with_instructions(vec![I32_TRUNC_S_F64]); 383 | self.function_implementations[i].with_instructions(vec![ 384 | I32_LOAD8_U, 385 | 0.into(), 386 | 0.into(), 387 | F64_CONVERT_S_I32, 388 | ]); 389 | } else if x.params.len() == 2 { 390 | for k in 0..x.params.len() { 391 | self.process_expression(i, &x.params[k]); 392 | self.function_implementations[i] 393 | .with_instructions(vec![I32_TRUNC_S_F64]); 394 | } 395 | self.function_implementations[i].with_instructions(vec![ 396 | I32_STORE8, 397 | 0.into(), 398 | 0.into(), 399 | ]); 400 | self.function_implementations[i] 401 | .with_instructions(vec![F64_CONST, 0.0.into()]); 402 | } else { 403 | panic!("invalid number params for mem_byte") 404 | } 405 | } else if &x.function_name == "mem_heap_start" { 406 | if x.params.len() == 0 { 407 | self.function_implementations[i].with_instructions(vec![ 408 | GLOBAL_GET, 409 | 0.into(), 410 | F64_CONVERT_S_I32, 411 | ]); 412 | } else { 413 | panic!("invalid number params for mem_heap_start") 414 | } 415 | } else if &x.function_name == "mem_heap_end" { 416 | if x.params.len() == 0 { 417 | self.function_implementations[i].with_instructions(vec![ 418 | GLOBAL_GET, 419 | 1.into(), 420 | F64_CONVERT_S_I32, 421 | ]); 422 | } else if x.params.len() == 1 { 423 | self.process_expression(i, &x.params[0]); 424 | self.function_implementations[i].with_instructions(vec![I32_TRUNC_S_F64]); 425 | self.function_implementations[i].with_instructions(vec![ 426 | GLOBAL_SET, 427 | 1.into(), 428 | I32_CONST, 429 | 0.into(), 430 | ]); 431 | } else { 432 | panic!("invalid number params for mem_heap_start") 433 | } 434 | } else if &x.function_name == "mem" { 435 | if x.params.len() == 1 { 436 | self.process_expression(i, &x.params[0]); 437 | self.function_implementations[i].with_instructions(vec![ 438 | I32_TRUNC_S_F64, 439 | F64_LOAD, 440 | (0 as i32).into(), 441 | (0 as i32).into(), 442 | ]); 443 | } else if x.params.len() == 2 { 444 | self.process_expression(i, &x.params[0]); 445 | self.function_implementations[i].with_instructions(vec![I32_TRUNC_S_F64]); 446 | self.process_expression(i, &x.params[1]); 447 | self.function_implementations[i].with_instructions(vec![ 448 | F64_STORE, 449 | (0 as i32).into(), 450 | (0 as i32).into(), 451 | ]); 452 | self.function_implementations[i] 453 | .with_instructions(vec![F64_CONST, 0.0.into()]); 454 | } else { 455 | panic!("invalid number params for mem") 456 | } 457 | } else if &x.function_name == "==" 458 | || &x.function_name == "!=" 459 | || &x.function_name == "<=" 460 | || &x.function_name == ">=" 461 | || &x.function_name == "<" 462 | || &x.function_name == ">" 463 | { 464 | if x.params.len() != 2 { 465 | panic!( 466 | "operator {} expected 2 parameters", 467 | (&x.function_name).as_str() 468 | ); 469 | } 470 | self.process_expression(i, &x.params[0]); 471 | self.process_expression(i, &x.params[1]); 472 | let mut f = match (&x.function_name).as_str() { 473 | "==" => vec![F64_EQ], 474 | "!=" => vec![F64_NE], 475 | "<=" => vec![F64_LE], 476 | ">=" => vec![F64_GE], 477 | "<" => vec![F64_LT], 478 | ">" => vec![F64_GT], 479 | _ => panic!("unexpected operator"), 480 | }; 481 | f.extend(vec![F64_CONVERT_S_I32]); 482 | self.function_implementations[i].with_instructions(f); 483 | } else if &x.function_name == "&" 484 | || &x.function_name == "|" 485 | || &x.function_name == "^" 486 | || &x.function_name == "<<" 487 | || &x.function_name == ">>" 488 | { 489 | if x.params.len() != 2 { 490 | panic!( 491 | "operator {} expected 2 parameters", 492 | (&x.function_name).as_str() 493 | ); 494 | } 495 | self.process_expression(i, &x.params[0]); 496 | self.function_implementations[i].with_instructions(vec![I64_TRUNC_S_F64]); 497 | self.process_expression(i, &x.params[1]); 498 | self.function_implementations[i].with_instructions(vec![I64_TRUNC_S_F64]); 499 | let mut f = match (&x.function_name).as_str() { 500 | "&" => vec![I64_AND], 501 | "|" => vec![I64_OR], 502 | "^" => vec![I64_XOR], 503 | "<<" => vec![I64_SHL], 504 | ">>" => vec![I64_SHR_S], 505 | _ => panic!("unexpected operator"), 506 | }; 507 | f.extend(vec![F64_CONVERT_S_I64]); 508 | self.function_implementations[i].with_instructions(f); 509 | } else if &x.function_name == "+" 510 | || &x.function_name == "-" 511 | || &x.function_name == "*" 512 | || &x.function_name == "/" 513 | || &x.function_name == "%" 514 | { 515 | if x.params.len() < 2 { 516 | panic!( 517 | "operator {} expected at least 2 parameters", 518 | (&x.function_name).as_str() 519 | ); 520 | } 521 | for p in 0..x.params.len() { 522 | self.process_expression(i, &x.params[p]); 523 | 524 | if &x.function_name == "%" { 525 | self.function_implementations[i] 526 | .with_instructions(vec![I64_TRUNC_S_F64]); 527 | } 528 | if p != 0 { 529 | let f = match (&x.function_name).as_str() { 530 | "+" => vec![F64_ADD], 531 | "-" => vec![F64_SUB], 532 | "*" => vec![F64_MUL], 533 | "/" => vec![F64_DIV], 534 | "%" => vec![I64_REM_S, F64_CONVERT_S_I64], 535 | _ => panic!("unexpected operator"), 536 | }; 537 | self.function_implementations[i].with_instructions(f); 538 | } 539 | } 540 | } else if &x.function_name == "!" { 541 | if x.params.len() != 1 { 542 | panic!( 543 | "operator {} expected 1 parameters", 544 | (&x.function_name).as_str() 545 | ); 546 | } 547 | 548 | self.process_expression(i, &x.params[0]); 549 | self.function_implementations[i].with_instructions(vec![ 550 | F64_CONST, 551 | 0.0.into(), 552 | F64_EQ, 553 | F64_CONVERT_S_I32, 554 | ]); 555 | } else if &x.function_name == "~" { 556 | if x.params.len() != 1 { 557 | panic!( 558 | "operator {} expected 1 parameters", 559 | (&x.function_name).as_str() 560 | ); 561 | } 562 | 563 | self.process_expression(i, &x.params[0]); 564 | self.function_implementations[i].with_instructions(vec![ 565 | I64_TRUNC_S_F64, 566 | I64_CONST, 567 | (-1 as i32).into(), 568 | I64_XOR, 569 | F64_CONVERT_S_I64, 570 | ]); 571 | } else if &x.function_name == "and" { 572 | if x.params.len() != 2 { 573 | panic!( 574 | "operator {} expected 2 parameters", 575 | (&x.function_name).as_str() 576 | ); 577 | } 578 | 579 | self.process_expression(i, &x.params[0]); 580 | self.function_implementations[i].with_instructions(vec![ 581 | I64_TRUNC_S_F64, 582 | I64_CONST, 583 | 0.into(), 584 | I64_NE, 585 | ]); 586 | self.process_expression(i, &x.params[1]); 587 | self.function_implementations[i].with_instructions(vec![ 588 | I64_TRUNC_S_F64, 589 | I64_CONST, 590 | 0.into(), 591 | I64_NE, 592 | I32_AND, 593 | F64_CONVERT_S_I32, 594 | ]); 595 | } else if &x.function_name == "or" { 596 | if x.params.len() != 2 { 597 | panic!( 598 | "operator {} expected 2 parameters", 599 | (&x.function_name).as_str() 600 | ); 601 | } 602 | 603 | self.process_expression(i, &x.params[0]); 604 | self.function_implementations[i].with_instructions(vec![I64_TRUNC_S_F64]); 605 | self.process_expression(i, &x.params[1]); 606 | self.function_implementations[i].with_instructions(vec![ 607 | I64_TRUNC_S_F64, 608 | I64_OR, 609 | I64_CONST, 610 | 0.into(), 611 | I64_NE, 612 | F64_CONVERT_S_I32, 613 | ]); 614 | } else { 615 | let (function_handle, _) = self 616 | .resolve_identifier(&x.function_name) 617 | .expect(&format!("{} is not a valid function", &x.function_name)); 618 | for k in 0..x.params.len() { 619 | self.process_expression(i, &x.params[k]) 620 | } 621 | self.function_implementations[i] 622 | .with_instructions(vec![CALL, (function_handle as i32).into()]); 623 | } 624 | } 625 | Expression::TextLiteral(x) => { 626 | let pos = self.get_or_create_text_data(&x); 627 | self.function_implementations[i] 628 | .with_instructions(vec![F64_CONST, (pos as f64).into()]); 629 | } 630 | Expression::Identifier(x) => { 631 | let val = self 632 | .resolve_identifier(&x) 633 | .expect(&format!("{} is not a valid identifier", &x)); 634 | match val.1 { 635 | IdentifierType::Global => { 636 | self.function_implementations[i] 637 | .with_instructions(vec![F64_CONST, val.0.into()]); 638 | } 639 | IdentifierType::Local => { 640 | self.function_implementations[i] 641 | .with_instructions(vec![LOCAL_GET, (val.0 as i32).into()]); 642 | } 643 | IdentifierType::Function => { 644 | self.function_implementations[i] 645 | .with_instructions(vec![F64_CONST, val.0.into()]); 646 | } 647 | } 648 | } 649 | Expression::Number(x) => { 650 | self.function_implementations[i].with_instructions(vec![F64_CONST, (*x).into()]); 651 | } 652 | } 653 | } 654 | 655 | fn process_functions(&mut self) { 656 | // now lets process the insides of our functions 657 | for i in 0..self.function_defs.len() { 658 | if let TopLevelOperation::DefineFunction(f) = self.function_defs[i].clone() { 659 | self.local_names = f.params.clone(); 660 | for j in 0..f.children.len() { 661 | self.process_expression(i, &f.children[j].clone()); 662 | if j != f.children.len() - 1 { 663 | self.function_implementations[i].with_instructions(vec![DROP]); 664 | } 665 | } 666 | //end the function 667 | self.function_implementations[i].with_instructions(vec![END]); 668 | } 669 | } 670 | 671 | //now that we are done with everything, put funcions in the app 672 | let num_funcs = self.function_defs.len(); 673 | for _ in 0..num_funcs { 674 | let f = self.function_implementations.remove(0); 675 | self.wasm.add_function(f); 676 | } 677 | 678 | self.wasm.add_elements( 679 | 0, 680 | self.function_names 681 | .iter() 682 | .enumerate() 683 | .map(|(i, _)| Element::new(i as u32)) 684 | .collect::>(), 685 | ) 686 | } 687 | 688 | fn complete(&mut self) -> Vec { 689 | self.wasm.to_bytes() 690 | } 691 | } 692 | 693 | pub fn compile(app: crate::ast::App) -> Result, Error> { 694 | let mut compiler = Compiler::new(app); 695 | compiler.pre_process_functions(); 696 | compiler.process_globals(); 697 | compiler.process_functions(); 698 | compiler.set_heap_start(); 699 | Ok(compiler.complete()) 700 | } 701 | -------------------------------------------------------------------------------- /wasp-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate failure; 3 | #[macro_use] 4 | extern crate nom; 5 | pub mod ast; 6 | pub mod compiler; 7 | pub mod parser; -------------------------------------------------------------------------------- /wasp-core/src/parser.rs: -------------------------------------------------------------------------------- 1 | use crate::ast::*; 2 | use failure::Error; 3 | use nom::types::CompleteStr; 4 | use std::str; 5 | use wasmly::DataType; 6 | 7 | fn to_string(s: CompleteStr) -> String { 8 | s.to_string() 9 | } 10 | 11 | fn is_start_identifier_char(c: char) -> bool { 12 | c == '_' || c == '$' || c.is_alphabetic() 13 | } 14 | 15 | fn is_identifier_char(c: char) -> bool { 16 | c == '_' || c == '!' || c == '-' || c == '$' || c.is_alphanumeric() 17 | } 18 | 19 | fn is_text_char(c: char) -> bool { 20 | c != '"' 21 | } 22 | 23 | fn is_digit(c: char) -> bool { 24 | c.is_digit(10) 25 | } 26 | 27 | fn is_comment_char(c: char) -> bool { 28 | c != '\r' && c != '\n' 29 | } 30 | 31 | fn to_data_type(c: &str) -> DataType { 32 | match c { 33 | "i32" => DataType::I32, 34 | "i64" => DataType::I64, 35 | "f32" => DataType::F32, 36 | "f64" => DataType::F64, 37 | _ => panic!("invalid type"), 38 | } 39 | } 40 | 41 | named!( 42 | token_comment, 43 | do_parse!( 44 | pair: pair!(tag!("//"),take_while!(is_comment_char))>> 45 | (pair.0.to_string()) 46 | ) 47 | ); 48 | 49 | named!( 50 | token_identifier, 51 | do_parse!( 52 | start: map!(take_while1!(is_start_identifier_char), to_string) >> 53 | end: map!(take_while!(is_identifier_char), to_string) >> 54 | (format!("{}{}",start,end).to_string()) 55 | ) 56 | ); 57 | 58 | named!( 59 | operator_identifiers, 60 | do_parse!( 61 | id: alt!(map!(tag!(">>"),to_string)|map!(tag!("<<"),to_string)|map!(tag!(">="),to_string)|map!(tag!("<="),to_string)|map!(tag!(">"),to_string)|map!(tag!("<"),to_string)|map!(tag!("or"),to_string)|map!(tag!("and"),to_string)|map!(tag!("!="),to_string)|map!(tag!("=="),to_string)|map!(tag!("+"),to_string)|map!(tag!("-"),to_string)|map!(tag!("*"),to_string)|map!(tag!("/"),to_string)|map!(tag!("%"),to_string)|map!(tag!("|"),to_string)|map!(tag!("&"),to_string))>> 62 | (id) 63 | ) 64 | ); 65 | 66 | named!( 67 | unary_operator_identifiers, 68 | do_parse!( 69 | id: alt!(map!(tag!("^"),to_string)|map!(tag!("~"),to_string)|map!(tag!("!"),to_string))>> 70 | (id) 71 | ) 72 | ); 73 | 74 | named!( 75 | function_identifiers, 76 | do_parse!( 77 | id: alt!(map!(tag!("assert"),to_string)|map!(tag!("call"),to_string)|token_identifier)>> 78 | (id) 79 | ) 80 | ); 81 | 82 | named!( 83 | token_data_type, 84 | do_parse!( 85 | t: map!(alt!(tag!("()")|tag!("i32")|tag!("i64")|tag!("f32")|tag!("f64")), to_string) >> 86 | (to_data_type(&t)) 87 | ) 88 | ); 89 | 90 | named!( 91 | token_text, 92 | do_parse!( 93 | tag!("\"") 94 | >> text: map!(take_while!(is_text_char), to_string) 95 | >> tag!("\"") 96 | >> (text) 97 | ) 98 | ); 99 | 100 | named!( 101 | token_symbol, 102 | do_parse!( 103 | tag!(":") 104 | >> text: map!(take_while!(is_identifier_char), to_string) 105 | >> (text) 106 | ) 107 | ); 108 | 109 | named!( 110 | base_float, 111 | do_parse!( 112 | num: map!(take_while1!(is_digit), to_string) >> 113 | tag!(".") >> 114 | den: map!(take_while1!(is_digit), to_string) >> 115 | (format!("{}.{}",num,den).to_owned()) 116 | ) 117 | ); 118 | 119 | named!( 120 | base_int, 121 | do_parse!( 122 | num: map!(take_while1!(is_digit), to_string) >> 123 | (num.to_owned()) 124 | ) 125 | ); 126 | 127 | named!( 128 | negative_number, 129 | do_parse!( 130 | tag!("-") 131 | >> num: alt!(base_float|base_int) 132 | >> (-num.parse::().unwrap()) 133 | ) 134 | ); 135 | 136 | named!( 137 | positive_number, 138 | do_parse!( 139 | num: alt!(base_float|base_int) 140 | >> (num.parse::().unwrap()) 141 | ) 142 | ); 143 | 144 | named!( 145 | token_number, 146 | alt!(positive_number|negative_number) 147 | ); 148 | 149 | named!(external_function, 150 | do_parse!( 151 | ws!(tag!("extern")) >> 152 | function_name: ws!(token_identifier) >> 153 | ws!(tag!("(")) >> 154 | params: ws!(separated_list!(tag!(","),ws!(token_identifier))) >> 155 | ws!(tag!(")")) >> 156 | (TopLevelOperation::ExternalFunction(ExternalFunction{name:function_name,params:params})) 157 | ) 158 | ); 159 | 160 | named!(expression_literal_string, 161 | do_parse!( 162 | text: ws!(token_text) >> 163 | (Expression::TextLiteral(text)) 164 | ) 165 | ); 166 | 167 | named!(expression_literal_token, 168 | do_parse!( 169 | text: ws!(token_symbol) >> 170 | (Expression::SymbolLiteral(text)) 171 | ) 172 | ); 173 | 174 | named!(expression_identifier, 175 | do_parse!( 176 | text: ws!(token_identifier) >> 177 | (Expression::Identifier(text)) 178 | ) 179 | ); 180 | 181 | named!(expression_number, 182 | do_parse!( 183 | num: ws!(token_number) >> 184 | (Expression::Number(num)) 185 | ) 186 | ); 187 | 188 | named!(boolean_true, 189 | do_parse!( 190 | tag!("true") >> 191 | (Expression::Number(1.0)) 192 | ) 193 | ); 194 | 195 | named!(boolean_false, 196 | do_parse!( 197 | tag!("false") >> 198 | (Expression::Number(0.0)) 199 | ) 200 | ); 201 | 202 | named!(expression_let_pair, 203 | do_parse!( 204 | id: ws!(token_identifier) >> 205 | exp: ws!(expression) >> 206 | ((id,exp)) 207 | ) 208 | ); 209 | 210 | named!(expression_loop, 211 | do_parse!( 212 | ws!(tag!("loop")) >> 213 | many0!(ws!(token_comment)) >> 214 | ws!(tag!("{")) >> 215 | expressions: expression_list >> 216 | tag!("}") >> 217 | (Expression::Loop(OperationLoop{expressions:expressions})) 218 | ) 219 | ); 220 | 221 | named!(expression_recur, 222 | do_parse!( 223 | tag!("recur") >> 224 | (Expression::Recur(OperationRecur{})) 225 | ) 226 | ); 227 | 228 | named!(expression_fnsig, 229 | do_parse!( 230 | ws!(tag!("fn")) >> 231 | many0!(ws!(token_comment)) >> 232 | ws!(tag!("(")) >> 233 | many0!(ws!(token_comment)) >> 234 | inputs: ws!(separated_list!(tag!(","),ws!(token_data_type))) >> 235 | many0!(ws!(token_comment)) >> 236 | ws!(tag!(")")) >> 237 | ws!(tag!("->")) >> 238 | many0!(ws!(token_comment)) >> 239 | output: opt!(ws!(token_data_type)) >> 240 | (Expression::FnSig(OperationFnSig{inputs:inputs, output:output})) 241 | ) 242 | ); 243 | 244 | named!(expression, 245 | alt!(expression_if_statement|expression_fnsig|expression_operator_call|expression_unary_operator_call|expression_assignment|expression_function_call|expression_loop|expression_recur|expression_number|boolean_true|boolean_false|expression_literal_token|expression_literal_string|expression_identifier) 246 | ); 247 | 248 | named!(expression_list_item, 249 | do_parse!( 250 | many0!(ws!(token_comment)) >> 251 | expr: ws!(expression) >> 252 | (expr) 253 | ) 254 | ); 255 | 256 | named!(expression_list>, 257 | do_parse!( 258 | exprs: ws!(ws!(many1!(ws!(expression_list_item)))) >> 259 | (exprs) 260 | ) 261 | ); 262 | 263 | named!(function_params>, 264 | do_parse!( 265 | op: ws!(separated_list!(tag!(","),ws!(expression))) >> 266 | (op) 267 | ) 268 | ); 269 | 270 | named!(expression_operator_call, 271 | do_parse!( 272 | tag!("(") >> 273 | expr_a: ws!(expression) >> 274 | function_name: ws!(operator_identifiers) >> 275 | expr_b: ws!(expression) >> 276 | tag!(")") >> 277 | (Expression::FunctionCall(OperationFunctionCall{function_name:function_name,params:vec![expr_a,expr_b]})) 278 | ) 279 | ); 280 | 281 | named!(expression_assignment, 282 | do_parse!( 283 | id: ws!(token_identifier) >> 284 | ws!(tag!("=")) >> 285 | expr: ws!(expression) >> 286 | (Expression::Assignment(OperationAssignment{id:id,value:Box::new(expr)})) 287 | ) 288 | ); 289 | 290 | named!(expression_else_statement>, 291 | do_parse!( 292 | ws!(tag!("else")) >> 293 | ws!(tag!("{")) >> 294 | expr_c: expression_list >> 295 | tag!("}") >> 296 | (expr_c) 297 | ) 298 | ); 299 | 300 | named!(expression_if_statement, 301 | do_parse!( 302 | ws!(tag!("if")) >> 303 | expr_a: ws!(expression) >> 304 | ws!(tag!("{")) >> 305 | expr_b: expression_list >> 306 | tag!("}") >> 307 | expr_c: ws!(opt!(expression_else_statement)) >> 308 | (Expression::IfStatement(OperationIfStatement{condition:Box::new(expr_a),if_true:expr_b,if_false:expr_c})) 309 | ) 310 | ); 311 | 312 | named!(expression_unary_operator_call, 313 | do_parse!( 314 | function_name: ws!(unary_operator_identifiers) >> 315 | expr_a: ws!(expression) >> 316 | (Expression::FunctionCall(OperationFunctionCall{function_name:function_name,params:vec![expr_a]})) 317 | ) 318 | ); 319 | 320 | named!(expression_function_call, 321 | do_parse!( 322 | function_name: ws!(function_identifiers) >> 323 | tag!("(") >> 324 | params: ws!(function_params) >> 325 | ws!(tag!(")")) >> 326 | (Expression::FunctionCall(OperationFunctionCall{function_name:function_name,params:params})) 327 | ) 328 | ); 329 | 330 | named!(define_function, 331 | do_parse!( 332 | external_name:opt!( ws!(tag!("pub"))) >> 333 | many0!(ws!(token_comment)) >> 334 | ws!(tag!("fn")) >> 335 | many0!(ws!(token_comment)) >> 336 | function_name: ws!(token_identifier) >> 337 | many0!(ws!(token_comment)) >> 338 | ws!(tag!("(")) >> 339 | many0!(ws!(token_comment)) >> 340 | params: ws!(separated_list!(tag!(","),ws!(token_identifier))) >> 341 | many0!(ws!(token_comment)) >> 342 | ws!(tag!(")")) >> 343 | many0!(ws!(token_comment)) >> 344 | ws!(tag!("{")) >> 345 | children: expression_list >> 346 | tag!("}") >> 347 | (TopLevelOperation::DefineFunction(FunctionDefinition{name: function_name, 348 | exported: external_name.is_some(), 349 | params: params, 350 | output: None, 351 | children: children})) 352 | ) 353 | ); 354 | 355 | named!(struct_pair, 356 | do_parse!( 357 | name: token_symbol >> 358 | many0!(ws!(token_comment)) >> 359 | (StructMember{name: name}) 360 | ) 361 | ); 362 | 363 | named!(define_struct, 364 | do_parse!( 365 | ws!(tag!("struct")) >> 366 | many0!(ws!(token_comment)) >> 367 | name: ws!(token_identifier) >> 368 | many0!(ws!(token_comment)) >> 369 | tag!("{") >> 370 | many0!(ws!(token_comment)) >> 371 | members: many0!(ws!(struct_pair)) >> 372 | many0!(ws!(token_comment)) >> 373 | tag!("}") >> 374 | (TopLevelOperation::DefineGlobal(Global{name:name,value:GlobalValue::Struct(StructDefinition{ 375 | members: members})})) 376 | ) 377 | ); 378 | 379 | named!(value_number, 380 | do_parse!( 381 | value: token_number >> 382 | (GlobalValue::Number(value)) 383 | ) 384 | ); 385 | 386 | named!(value_text, 387 | do_parse!( 388 | value: token_text >> 389 | (GlobalValue::Text(value)) 390 | ) 391 | ); 392 | 393 | named!(value_symbol, 394 | do_parse!( 395 | value: token_symbol >> 396 | (GlobalValue::Symbol(value)) 397 | ) 398 | ); 399 | 400 | named!(global_bool_true, 401 | do_parse!( 402 | tag!("true") >> 403 | (GlobalValue::Number(1.0)) 404 | ) 405 | ); 406 | 407 | named!(global_bool_false, 408 | do_parse!( 409 | tag!("false") >> 410 | (GlobalValue::Number(0.0)) 411 | ) 412 | ); 413 | 414 | named!(global_identifier, 415 | do_parse!( 416 | value: token_identifier >> 417 | (GlobalValue::Identifier(value)) 418 | ) 419 | ); 420 | 421 | named!(global_data, 422 | do_parse!( 423 | tag!("(") >> 424 | values: ws!(separated_list!(tag!(","),ws!(alt!(global_value|global_identifier)))) >> 425 | tag!(")") >> 426 | (GlobalValue::Data(values)) 427 | ) 428 | ); 429 | 430 | named!(global_value, 431 | do_parse!( 432 | value: ws!(alt!(global_bool_true|global_bool_false|value_number|value_symbol|value_text|global_data)) >> 433 | (value) 434 | ) 435 | ); 436 | 437 | named!(define_global, 438 | do_parse!( 439 | ws!(tag!("static")) >> 440 | name: ws!(token_identifier) >> 441 | ws!(tag!("=")) >> 442 | value: global_value >> 443 | (TopLevelOperation::DefineGlobal(Global{name: name,value:value})) 444 | ) 445 | ); 446 | 447 | named!(comment, 448 | do_parse!( 449 | tag!("//") >> 450 | comment: map!(take_while!(is_comment_char),to_string) >> 451 | (TopLevelOperation::Comment(comment)) 452 | ) 453 | ); 454 | 455 | named!(app, 456 | do_parse!( 457 | op: many0!(ws!(alt!(comment|external_function|define_function|define_struct|define_global))) >> 458 | eof!() >> 459 | (App{children:op}) 460 | ) 461 | ); 462 | 463 | pub fn parse(content: &str) -> Result { 464 | let result = app(CompleteStr(content)); 465 | match result { 466 | Ok((_, value)) => Ok(value), 467 | Err(nom::Err::Incomplete(needed)) => Err(format_err!("{:?}", needed)), 468 | Err(nom::Err::Error(e)) | Err(nom::Err::Failure(e)) => Err(format_err!("{:?}", e)), 469 | } 470 | } 471 | -------------------------------------------------------------------------------- /wasp/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasp" 3 | version = "0.5.1" 4 | authors = ["Richard Anaya"] 5 | edition = "2018" 6 | license = "MIT" 7 | description = "a web assembly lisp programming language" 8 | 9 | [dependencies] 10 | failure = "0.1.5" 11 | wasmly = "0.2.0" 12 | clap = "2" 13 | walkdir = "2" 14 | wasp-core = {path="../wasp-core",version="0"} 15 | -------------------------------------------------------------------------------- /wasp/src/main.rs: -------------------------------------------------------------------------------- 1 | use failure::Error; 2 | use std::env; 3 | use std::fs::metadata; 4 | use std::fs::File; 5 | use std::io::prelude::*; 6 | use std::str; 7 | extern crate clap; 8 | use clap::{App, AppSettings, Arg, SubCommand}; 9 | use std::fs::OpenOptions; 10 | use std::io::{BufRead, BufReader}; 11 | use wasp_core::{compiler,parser}; 12 | 13 | const VERSION: &'static str = env!("CARGO_PKG_VERSION"); 14 | 15 | fn write_output(bytes: &[u8], output_file: Option<&str>) -> std::io::Result<()> { 16 | if output_file.is_none() { 17 | let path = env::current_dir().unwrap(); 18 | let output_file = format!( 19 | "{}.wasm", 20 | String::from(path.file_name().unwrap().to_str().unwrap()) 21 | ); 22 | let mut buffer = File::create(output_file)?; 23 | buffer.write_all(bytes)?; 24 | } 25 | Ok(()) 26 | } 27 | 28 | 29 | 30 | fn run(content: &str) -> Result, Error> { 31 | let app = parser::parse(content)?; 32 | compiler::compile(app) 33 | } 34 | 35 | fn main() -> Result<(), Error> { 36 | let matches = App::new("wasp") 37 | .setting(AppSettings::ArgRequiredElseHelp) 38 | .version(VERSION) 39 | .about("A lisp for web assembly") 40 | .author("Richard Anaya") 41 | .subcommand( 42 | SubCommand::with_name("build") 43 | .about("compile a wasp file") 44 | .arg( 45 | Arg::with_name("verbose") 46 | .long("verbose") 47 | .short("v") 48 | .help("Sets the level of verbosity"), 49 | ) 50 | .arg( 51 | Arg::with_name("emscripten") 52 | .long("emscripten") 53 | .short("e") 54 | .help("Sets the level of verbosity"), 55 | ), 56 | ) 57 | .subcommand( 58 | SubCommand::with_name("init") 59 | .about("initialize a directory to be a wasp project") 60 | .arg( 61 | Arg::with_name("NAME") 62 | .help("Name of project to create folder for") 63 | .required(true), 64 | ) 65 | .arg( 66 | Arg::with_name("no-std") 67 | .long("no-std") 68 | .help("don't add the standard library"), 69 | ), 70 | ) 71 | .subcommand(SubCommand::with_name("vendor").about("fetch dependencies")) 72 | .subcommand( 73 | SubCommand::with_name("add") 74 | .about("adds a dependency package to this project") 75 | .arg( 76 | Arg::with_name("NAME") 77 | .help("Sets the name of the dependency package") 78 | .required(true), 79 | ) 80 | .arg( 81 | Arg::with_name("LOCATION") 82 | .help("A git repo url or folder path") 83 | .required(true), 84 | ), 85 | ) 86 | .get_matches(); 87 | 88 | if let Some(_matches) = matches.subcommand_matches("build") { 89 | use walkdir::WalkDir; 90 | 91 | let mut files = vec![]; 92 | for entry in WalkDir::new("./") { 93 | let entry = entry.unwrap(); 94 | let f = entry.path().display().to_string(); 95 | if f.ends_with(".w") { 96 | let md = metadata(f.clone()).unwrap(); 97 | if md.is_file() { 98 | files.push(f); 99 | } 100 | } 101 | } 102 | 103 | let mut packages = vec![]; 104 | 105 | if std::path::Path::new("project.wasp").exists() { 106 | let file = File::open("project.wasp")?; 107 | for line in BufReader::new(file).lines() { 108 | let l = line?; 109 | let v: Vec<&str> = l.split(' ').collect(); 110 | packages.push(v[0].to_string()) 111 | } 112 | } 113 | 114 | files.sort_by(|a, b| { 115 | if a.starts_with("./vendor/") { 116 | if b.starts_with("./vendor/") { 117 | let sa = a.split('/').collect::>()[2]; 118 | let sb = b.split('/').collect::>()[2]; 119 | let pa = packages 120 | .iter() 121 | .position(|r| r == sa) 122 | .unwrap_or(std::usize::MAX); 123 | let pb = packages 124 | .iter() 125 | .position(|r| r == sb) 126 | .unwrap_or(std::usize::MAX); 127 | return pa.cmp(&pb); 128 | } 129 | return std::cmp::Ordering::Less; 130 | } 131 | std::cmp::Ordering::Equal 132 | }); 133 | 134 | let mut contents = "".to_string(); 135 | for file in files { 136 | let c = std::fs::read_to_string(&file).unwrap(); 137 | contents = format!("{}\n{}", &contents, &c).to_string(); 138 | } 139 | 140 | let output = run(&contents)?; 141 | write_output(&output, None)?; 142 | return Ok(()); 143 | }; 144 | 145 | if let Some(matches) = matches.subcommand_matches("init") { 146 | let folder = matches.value_of("NAME"); 147 | if let Some(f) = folder { 148 | if !std::path::Path::new(&f).exists() { 149 | std::fs::create_dir(f)?; 150 | let mut file = File::create(format!("{}/{}", f, "main.w"))?; 151 | file.write_all(include_bytes!("static/main.w"))?; 152 | let mut file = File::create(format!("{}/{}", f, "project.wasp"))?; 153 | file.write_all(include_bytes!("static/project.wasp"))?; 154 | let mut file = File::create(format!("{}/{}", f, "index.html"))?; 155 | let mut idx = include_str!("static/index.html").to_string(); 156 | idx = idx.replace("PROJECT_NAME", &f); 157 | file.write_all((&idx).as_bytes())?; 158 | let no_std = matches.is_present("no-std"); 159 | if !no_std { 160 | std::process::Command::new("git") 161 | .args(&[ 162 | "clone", 163 | "git@github.com:wasplang/std.git", 164 | &format!("{}/vendor/{}", f, "std"), 165 | ]) 166 | .output() 167 | .expect("failed to execute process"); 168 | println!("added standard library"); 169 | } 170 | println!("created package"); 171 | } else { 172 | println!("directory \"{}\" already exists", f); 173 | std::process::exit(1); 174 | } 175 | } 176 | return Ok(()); 177 | }; 178 | 179 | if let Some(matches) = matches.subcommand_matches("add") { 180 | let name = matches.value_of("NAME").expect("no name"); 181 | let location = matches.value_of("LOCATION").expect("no location"); 182 | let mut file = OpenOptions::new() 183 | .write(true) 184 | .append(true) 185 | .open("project.wasp") 186 | .unwrap(); 187 | 188 | if let Err(e) = writeln!(file, "{} {}", name, location) { 189 | eprintln!("Couldn't write to file: {}", e); 190 | } 191 | std::process::Command::new("git") 192 | .args(&["clone", location, &format!("vendor/{}", name)]) 193 | .output() 194 | .expect("failed to execute process"); 195 | println!("added dependency"); 196 | } 197 | 198 | if matches.subcommand_matches("vendor").is_some() { 199 | std::fs::remove_dir_all("vendor")?; 200 | let file = File::open("project.wasp")?; 201 | for line in BufReader::new(file).lines() { 202 | let l = line?; 203 | let v: Vec<&str> = l.split(' ').collect(); 204 | std::process::Command::new("git") 205 | .args(&["clone", v[1], &format!("vendor/{}", v[0])]) 206 | .output() 207 | .expect("failed to execute process"); 208 | println!("vendoring \"{}\"", v[0]); 209 | } 210 | } 211 | 212 | Ok(()) 213 | } 214 | -------------------------------------------------------------------------------- /wasp/src/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wasp 5 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /wasp/src/static/main.w: -------------------------------------------------------------------------------- 1 | (defn main "main" [] 2 | 42) 3 | -------------------------------------------------------------------------------- /wasp/src/static/project.wasp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wasplang/wasp/9cc072c663f6f9ff7cf95e526be2083dd5b4ae32/wasp/src/static/project.wasp --------------------------------------------------------------------------------