├── tests ├── pass │ ├── empty.plk │ ├── mut-coercion.plk │ ├── pointers.plk │ ├── cat.plk │ ├── hello-world.plk │ └── structs.plk ├── compile-fail │ ├── unbounded-typevar.plk │ ├── unknown-value.plk │ ├── if-no-braces.plk │ ├── missing-return.plk │ ├── recursive-struct.plk │ ├── multiple-definitions.plk │ ├── break-outside-loop.plk │ ├── invalid-num-suffix.plk │ ├── wrong-mut.plk │ └── mut-unsoundness.plk ├── Cargo.toml └── src │ ├── test_parser.rs │ └── main.rs ├── plank-ir ├── Cargo.toml └── src │ ├── lib.rs │ ├── analysis │ ├── mod.rs │ ├── volatility.rs │ ├── liveness.rs │ └── usage.rs │ ├── optimization │ ├── arithmetic.rs │ ├── mod.rs │ ├── simplify_newtypes.rs │ ├── dead_store_elimination.rs │ ├── intermediate_removal.rs │ ├── dead_drop_elimination.rs │ └── cleanup.rs │ └── ir.rs ├── plank-errors ├── Cargo.toml └── src │ ├── lib.rs │ ├── position.rs │ └── reporter.rs ├── plank-interpreter └── Cargo.toml ├── plank-syntax ├── Cargo.toml └── src │ ├── lib.rs │ ├── position.rs │ ├── ast.rs │ └── tokens.rs ├── plank-x86-backend ├── Cargo.toml └── src │ ├── lib.rs │ ├── return_fix.rs │ └── x86.rs ├── Cargo.toml ├── examples ├── generics.plk ├── function_pointers.plk ├── generics2.plk ├── structs.plk ├── pointers.plk ├── basics.plk ├── closures.plk ├── strings.plk └── types.plk ├── plank-frontend ├── Cargo.toml └── src │ ├── type_check │ ├── rollback_map.rs │ └── unify.rs │ ├── symbols.rs │ ├── gen_constructors.rs │ ├── return_check.rs │ ├── lib.rs │ ├── ast │ ├── resolved.rs │ └── typed.rs │ ├── wildcard_check.rs │ ├── dead_code.rs │ ├── cast_check.rs │ ├── literal_size_check.rs │ ├── struct_layout.rs │ ├── assign_check.rs │ ├── struct_check.rs │ └── type_param_check.rs ├── plank └── Cargo.toml ├── plank-server ├── Cargo.toml └── src │ ├── transport.rs │ ├── main.rs │ └── jsonrpc.rs ├── LICENSE ├── .gitignore ├── tasks ├── parser │ └── readme.md ├── grammar │ └── grammar.bnf ├── lexer │ └── readme.md └── semantic-analysis │ └── readme.md └── README.md /tests/pass/empty.plk: -------------------------------------------------------------------------------- 1 | fn main() -> i32 { 2 | return 0; 3 | } 4 | -------------------------------------------------------------------------------- /tests/compile-fail/unbounded-typevar.plk: -------------------------------------------------------------------------------- 1 | fn foo() {} 2 | 3 | fn bar() { 4 | foo(); // ERROR: could not completely infer type 5 | } 6 | -------------------------------------------------------------------------------- /tests/compile-fail/unknown-value.plk: -------------------------------------------------------------------------------- 1 | fn foo() { 2 | let x = 1; 3 | let y = z; // ERROR: unknown value `z` 4 | let z = x + y; 5 | } -------------------------------------------------------------------------------- /plank-ir/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plank-ir" 3 | version = "0.1.0" 4 | authors = ["Domantas Jadenkus "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /plank-errors/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plank-errors" 3 | version = "0.1.0" 4 | authors = ["Domantas Jadenkus "] 5 | 6 | [dependencies] 7 | -------------------------------------------------------------------------------- /tests/compile-fail/if-no-braces.plk: -------------------------------------------------------------------------------- 1 | fn foo() { 2 | if (true) 3 | putc('!'); // ERROR: expected one of 4 | 5 | while (false) 6 | putc('!'); // ERROR: expected one of 7 | } -------------------------------------------------------------------------------- /plank-interpreter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plank-interpreter" 3 | version = "0.1.0" 4 | authors = ["Domantas Jadenkus "] 5 | 6 | [dependencies] 7 | plank-ir = { path = "../plank-ir" } 8 | -------------------------------------------------------------------------------- /plank-syntax/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plank-syntax" 3 | version = "0.1.0" 4 | authors = ["Domantas Jadenkus "] 5 | 6 | [dependencies] 7 | plank-errors = { path = "../plank-errors" } 8 | -------------------------------------------------------------------------------- /plank-syntax/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate plank_errors; 2 | 3 | pub mod ast; 4 | mod lexer; 5 | mod parser; 6 | pub mod position; 7 | pub mod tokens; 8 | 9 | pub use lexer::lex; 10 | pub use parser::parse; 11 | -------------------------------------------------------------------------------- /plank-x86-backend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plank-x86-backend" 3 | version = "0.1.0" 4 | authors = ["Domantas Jadenkus "] 5 | 6 | [dependencies] 7 | plank-ir = { path = "../plank-ir" } 8 | -------------------------------------------------------------------------------- /plank-ir/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod analysis; 2 | pub mod ir; 3 | pub mod optimization; 4 | mod printer; 5 | pub mod validation; 6 | 7 | pub use ir::Program; 8 | pub use printer::emit_program; 9 | pub use validation::validate_ir; 10 | -------------------------------------------------------------------------------- /tests/compile-fail/missing-return.plk: -------------------------------------------------------------------------------- 1 | fn foo() -> bool { // ERROR: not all paths return a value 2 | 3 | } 4 | 5 | fn bar() -> unit { 6 | 7 | } 8 | 9 | fn baz() -> T { // ERROR: not all paths return a value 10 | 11 | } 12 | -------------------------------------------------------------------------------- /tests/compile-fail/recursive-struct.plk: -------------------------------------------------------------------------------- 1 | struct Foo { // ERROR: struct `Foo` is recursive 2 | foo: Foo, 3 | } 4 | 5 | struct Bar { 6 | bar: T, 7 | } 8 | 9 | struct Baz { // ERROR: struct `Baz` is recursive 10 | bar: Bar, 11 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "plank-syntax", 4 | "plank-errors", 5 | "plank-frontend", 6 | "plank-ir", 7 | "plank-x86-backend", 8 | "plank-interpreter", 9 | "plank", 10 | "plank-server", 11 | "tests", 12 | ] 13 | -------------------------------------------------------------------------------- /plank-x86-backend/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate plank_ir; 2 | 3 | mod compiler; 4 | mod printer; 5 | mod return_fix; 6 | mod x86; 7 | 8 | pub use compiler::compile_program; 9 | pub use printer::{print_asm, print_prelude}; 10 | pub use return_fix::fix_function_returns; 11 | -------------------------------------------------------------------------------- /examples/generics.plk: -------------------------------------------------------------------------------- 1 | // try to look at this program using `plank --emit-ir generics.plk` 2 | 3 | fn identity(x: T) -> T { 4 | return x; 5 | } 6 | 7 | fn main() -> i32 { 8 | let x: u32 = identity(12345u32); 9 | let i: i8 = identity(123i8); 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /tests/compile-fail/multiple-definitions.plk: -------------------------------------------------------------------------------- 1 | struct A {} 2 | struct A {} // ERROR: `A` is defined multiple times 3 | fn A() {} // ERROR: `A` is defined multiple times 4 | fn f() {} 5 | fn f() {} // ERROR: `f` is defined multiple times 6 | fn g() {} 7 | fn f() {} // ERROR: `f` is defined multiple times 8 | -------------------------------------------------------------------------------- /plank-errors/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | 3 | //! A library to build and format diagnostics for use in plank compiler. 4 | 5 | pub mod position; 6 | pub mod printer; 7 | pub mod reporter; 8 | 9 | pub use printer::{print_diagnostic, print_diagnostics}; 10 | pub use reporter::Reporter; 11 | -------------------------------------------------------------------------------- /plank-frontend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plank-frontend" 3 | version = "0.1.0" 4 | authors = ["Domantas Jadenkus "] 5 | 6 | [dependencies] 7 | plank-errors = { path = "../plank-errors" } 8 | plank-syntax = { path = "../plank-syntax" } 9 | plank-ir = { path = "../plank-ir" } 10 | -------------------------------------------------------------------------------- /tests/pass/mut-coercion.plk: -------------------------------------------------------------------------------- 1 | fn foo(x: *unit) {} 2 | fn bar() -> *mut unit { loop {} } 3 | 4 | fn main() -> i32 { 5 | let mut x = 0i32; 6 | let a: *i32 = &mut x; 7 | let b: fn(*mut unit) = foo; 8 | let c: fn() -> *unit = bar; 9 | putc('!'); 10 | return 0; 11 | } 12 | 13 | // INPUT: 14 | // OUTPUT: ! 15 | -------------------------------------------------------------------------------- /tests/pass/pointers.plk: -------------------------------------------------------------------------------- 1 | fn modify(ptr: *mut u8) { 2 | *ptr = 'c'; 3 | } 4 | 5 | fn main() -> i32 { 6 | let mut a = 'a'; 7 | putc(a); 8 | let b = &mut a; 9 | *b = 'b'; 10 | putc(a); 11 | putc(*b); 12 | modify(&mut a); 13 | putc(a); 14 | putc(*b); 15 | return 0; 16 | } 17 | 18 | // OUTPUT: abbcc 19 | -------------------------------------------------------------------------------- /tests/pass/cat.plk: -------------------------------------------------------------------------------- 1 | struct S { 2 | a: u8, b: u8, c: u8, d: u8, 3 | } 4 | 5 | fn main() -> i32 { 6 | loop { 7 | let ch = getc(); 8 | if ch == -1 { 9 | return 0; 10 | } else { 11 | putc((ch as S).a); 12 | } 13 | } 14 | } 15 | 16 | // INPUT: do be do be doo 17 | // OUTPUT: do be do be doo 18 | -------------------------------------------------------------------------------- /tests/pass/hello-world.plk: -------------------------------------------------------------------------------- 1 | fn puts(mut string: *u8) { 2 | while *string != 0 { 3 | putc(*string); 4 | // this doesn't look very nice :( 5 | string = (string as u32 + 1) as *u8; 6 | } 7 | putc('\n'); 8 | } 9 | 10 | fn main() -> i32 { 11 | puts("Hello, world!"); 12 | return 0; 13 | } 14 | 15 | // OUTPUT: Hello, world!\x0A 16 | -------------------------------------------------------------------------------- /tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tests" 3 | version = "0.1.0" 4 | authors = ["Domantas Jadenkus "] 5 | 6 | [dependencies] 7 | plank-errors = { path = "../plank-errors" } 8 | plank-syntax = { path = "../plank-syntax" } 9 | plank-frontend = { path = "../plank-frontend" } 10 | plank-ir = { path = "../plank-ir" } 11 | plank-interpreter = { path = "../plank-interpreter" } 12 | -------------------------------------------------------------------------------- /tests/compile-fail/break-outside-loop.plk: -------------------------------------------------------------------------------- 1 | fn foo() { 2 | break; // ERROR: cannot use `break` outside loop 3 | continue; // ERROR: cannot use `continue` outside loop 4 | 5 | loop { 6 | break; 7 | continue; 8 | } 9 | 10 | while true { 11 | break; 12 | continue; 13 | 14 | loop { 15 | continue; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/pass/structs.plk: -------------------------------------------------------------------------------- 1 | struct Foo { 2 | a: u8, 3 | b: i32, 4 | } 5 | 6 | fn main() -> i32 { 7 | let mut foo = Foo('a', 123); 8 | putc(foo.a); 9 | foo.a = 'b'; 10 | putc(foo.a); 11 | if foo.b != 123 { 12 | putc('!'); 13 | } 14 | foo.b = 456; 15 | if foo.b != 456 { 16 | putc('!'); 17 | } 18 | putc(foo.a); 19 | return 0; 20 | } 21 | 22 | // OUTPUT: abb 23 | -------------------------------------------------------------------------------- /tests/compile-fail/invalid-num-suffix.plk: -------------------------------------------------------------------------------- 1 | fn foo() { 2 | 42; 3 | 42a; // ERROR: suffix should be primitive numeric type 4 | 42foobar; // ERROR: suffix should be primitive numeric type 5 | 42i; // ERROR: suffix should be primitive numeric type 6 | 42u; // ERROR: suffix should be primitive numeric type 7 | 42_u8; 8 | 4_2u8; 9 | 42i8; 10 | 42u8; 11 | 42i16; 12 | 42u16; 13 | 42i32; 14 | 42u32; 15 | } 16 | -------------------------------------------------------------------------------- /plank/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plank" 3 | version = "0.1.0" 4 | authors = ["Domantas Jadenkus "] 5 | 6 | [dependencies] 7 | plank-errors = { path = "../plank-errors" } 8 | plank-syntax = { path = "../plank-syntax" } 9 | plank-frontend = { path = "../plank-frontend" } 10 | plank-ir = { path = "../plank-ir" } 11 | plank-interpreter = { path = "../plank-interpreter" } 12 | plank-x86-backend = { path = "../plank-x86-backend" } 13 | clap = "2.26.2" 14 | -------------------------------------------------------------------------------- /plank-server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "plank-server" 3 | version = "0.1.0" 4 | authors = ["Domantas Jadenkus "] 5 | 6 | [dependencies] 7 | languageserver-types = "0.12.0" 8 | serde = "1.0" 9 | serde_derive = "1.0" 10 | serde_json = "1.0" 11 | log = "0.3.8" 12 | simple-logging = "1.0.1" 13 | url = "1.5.1" 14 | plank-errors = { path = "../plank-errors" } 15 | plank-syntax = { path = "../plank-syntax" } 16 | plank-frontend = { path = "../plank-frontend" } 17 | -------------------------------------------------------------------------------- /examples/function_pointers.plk: -------------------------------------------------------------------------------- 1 | fn invoke(f: fn(u8)) { 2 | f('!'); 3 | } 4 | 5 | fn does_nothing(x: T) {} 6 | 7 | fn apply(f: fn(A) -> B, arg: A) -> B { 8 | return f(arg); 9 | } 10 | 11 | fn main() -> i32 { 12 | invoke(putc); 13 | invoke(does_nothing); 14 | putc('\n'); 15 | 16 | // putc is declared (internally) as: 17 | // fn putc(ch: u8); 18 | // which is a shorthand for: 19 | // fn putc(ch: u8) -> unit; 20 | // so we can use apply just fine! 21 | // (explicit types are not necessary here, compiler infers them just fine) 22 | apply::(putc, '?'); 23 | putc('\n'); 24 | 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /examples/generics2.plk: -------------------------------------------------------------------------------- 1 | // structs can be generic too! 2 | struct Pair { 3 | first: A, 4 | second: B, 5 | } 6 | 7 | fn puts(mut string: *u8) { 8 | while *string != 0 { 9 | putc(*string); 10 | // this doesn't look very nice :( 11 | string = (string as u32 + 1) as *u8; 12 | } 13 | putc('\n'); 14 | } 15 | 16 | fn main() -> i32 { 17 | let a: Pair<*u8, i32> = Pair("foobar", 123); 18 | let b: Pair = Pair(1, a.first); 19 | // ^ 20 | // we can ask the compiler to infer parts 21 | // of the type too, just like in Rust 22 | puts(a.first); 23 | puts(b.second); 24 | return 0; 25 | } -------------------------------------------------------------------------------- /tests/compile-fail/wrong-mut.plk: -------------------------------------------------------------------------------- 1 | fn foo() { 2 | *"a" = 'b'; // ERROR: cannot modify non-mut value 3 | 4 | let x = 1; 5 | let a = &mut x; // ERROR: cannot take mutable reference to non-mut value 6 | x = 2; // ERROR: cannot modify non-mut value 7 | a = &x; // ERROR: cannot modify non-mut value 8 | 9 | let x = 1; 10 | let a = &x; 11 | let b = &mut *a; // ERROR: cannot take mutable reference to non-mut value 12 | 13 | let mut x = 1; 14 | let a = &x; 15 | let b = &mut *a; // ERROR: cannot take mutable reference to non-mut value 16 | 17 | // coercing should work 18 | let mut x = 1; 19 | let a: *i32 = &mut x; 20 | *a = 2; // ERROR: cannot modify non-mut value 21 | } 22 | -------------------------------------------------------------------------------- /tests/compile-fail/mut-unsoundness.plk: -------------------------------------------------------------------------------- 1 | // At one point this compiled :( 2 | 3 | // here's a matching closed parentheses, because 4 | // of rainbow brackets extension: ) 5 | 6 | fn puts(mut string: *u8) { 7 | while *string != 0 { 8 | putc(*string); 9 | string = (string as u32 + 1) as *u8; 10 | } 11 | putc('\n'); 12 | } 13 | 14 | struct A { 15 | a: fn(T), 16 | } 17 | 18 | fn foo(x: *mut u8) { 19 | *x = '!'; 20 | } 21 | 22 | fn bar(f: fn(*u8)) { 23 | let str = "abc"; 24 | f(str); 25 | puts(str); 26 | } 27 | 28 | fn main() -> i32 { 29 | let a: A = A(bar); // ERROR: cannot assign `A unit>` to `A unit>` 30 | a.a(foo); 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /plank-frontend/src/type_check/rollback_map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::hash::Hash; 3 | 4 | pub struct Map { 5 | committed: HashMap, 6 | new: HashMap, 7 | } 8 | 9 | impl Map { 10 | pub fn new() -> Self { 11 | Map { 12 | committed: HashMap::new(), 13 | new: HashMap::new(), 14 | } 15 | } 16 | 17 | pub fn insert(&mut self, key: K, value: V) { 18 | self.new.insert(key, value); 19 | } 20 | 21 | pub fn get(&self, key: &K) -> Option<&V> { 22 | self.new.get(key).or_else(|| self.committed.get(key)) 23 | } 24 | 25 | pub fn commit(&mut self) { 26 | self.committed.extend(self.new.drain()) 27 | } 28 | 29 | pub fn rollback(&mut self) { 30 | self.new.clear() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/structs.plk: -------------------------------------------------------------------------------- 1 | struct Pair { 2 | first: u8, 3 | second: u8, 4 | } 5 | 6 | /* When you declare a struct compiler generates a constructor function: 7 | * fn Pair(first: u8, second: u8) -> Pair; 8 | */ 9 | 10 | fn print_byte(x: u8) { 11 | if x >= 10 { 12 | print_byte(x / 10); 13 | } 14 | putc('0' + x % 10); 15 | } 16 | 17 | fn print_pair(pair: Pair) { 18 | putc('('); 19 | print_byte(pair.first); 20 | putc(','); 21 | putc(' '); 22 | print_byte(pair.second); 23 | putc(')'); 24 | putc('\n'); 25 | } 26 | 27 | fn main() -> i32 { 28 | let pair = Pair(1, 2); 29 | print_pair(pair); 30 | 31 | // we can use named parameters for more readable construction 32 | let pair = Pair( 33 | first: 255, 34 | second: 8, 35 | ); 36 | print_pair(pair); 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /plank-ir/src/analysis/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod liveness; 2 | pub mod usage; 3 | pub mod volatility; 4 | 5 | use ir::{BlockId, Instruction, Reg}; 6 | 7 | #[derive(PartialEq, Eq, Debug, Hash, Copy, Clone)] 8 | pub struct Loc { 9 | pub block: BlockId, 10 | pub pos: usize, 11 | } 12 | 13 | pub fn initialized_register(instr: &Instruction) -> Option { 14 | match *instr { 15 | Instruction::Assign(reg, _) 16 | | Instruction::BinaryOp(reg, _, _, _) 17 | | Instruction::Call(reg, _, _) 18 | | Instruction::CallVirt(reg, _, _) 19 | | Instruction::CastAssign(reg, _) 20 | | Instruction::DerefLoad(reg, _, _) 21 | | Instruction::Init(reg) 22 | | Instruction::Load(reg, _, _) 23 | | Instruction::TakeAddress(reg, _, _) 24 | | Instruction::UnaryOp(reg, _, _) => Some(reg), 25 | _ => None, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/pointers.plk: -------------------------------------------------------------------------------- 1 | fn print_byte(x: u8) { 2 | if x >= 10 { 3 | print_byte(x / 10); 4 | } 5 | putc('0' + x % 10); 6 | } 7 | 8 | fn modify(ptr: *mut u8) { 9 | *ptr = *ptr * 10 + 2; 10 | } 11 | 12 | fn basic() { 13 | let mut x = 3; 14 | print_byte(x); 15 | putc('\n'); 16 | 17 | modify(&mut x); 18 | print_byte(x); 19 | putc('\n'); 20 | } 21 | 22 | fn arithmetic() { 23 | // there is no builtin pointer arithmetic, 24 | // but we can use casts! :D 25 | let mut string: *u8 = "Test string!\n"; 26 | while *string != 0 { 27 | putc(*string); 28 | // pointers are currently 32 bits wide, 29 | // so we can cast pointer to u32, add 1, and cast back 30 | string = (string as u32 + 1) as *u8; 31 | } 32 | } 33 | 34 | fn main() -> i32 { 35 | basic(); 36 | arithmetic(); 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /examples/basics.plk: -------------------------------------------------------------------------------- 1 | fn next_letter(x: u8) -> u8 { 2 | if x == 'z' { 3 | return 'a'; 4 | } else { 5 | return x + 1; 6 | } 7 | } 8 | 9 | fn print_letters() { 10 | let mut letter = 'a'; 11 | loop { 12 | putc(letter); 13 | letter = next_letter(letter); 14 | if letter == 'a' { 15 | // we looped around 16 | break; 17 | } 18 | } 19 | putc('\n'); 20 | } 21 | 22 | fn print_byte(x: u8) { 23 | if x >= 10 { 24 | print_byte(x / 10); 25 | } 26 | putc('0' + x % 10); 27 | } 28 | 29 | fn show_math(x: u8, y: u8, z: u8) { 30 | putc('('); 31 | print_byte(x); 32 | putc('+'); 33 | print_byte(y); 34 | putc(')'); 35 | putc('*'); 36 | print_byte(z); 37 | putc('='); 38 | print_byte((x + y) * z); 39 | putc('\n'); 40 | } 41 | 42 | fn main() -> i32 { 43 | print_letters(); 44 | show_math(1, 2, 3); 45 | 46 | // we can also use named parameters 47 | show_math( 48 | y: 3, 49 | x: 10, 50 | z: 4, 51 | ); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Domantas Jadenkus 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/rust,linux,windows,visualstudiocode 3 | 4 | ### Linux ### 5 | *~ 6 | 7 | # temporary files which can be created if a process still has a handle open of a deleted file 8 | .fuse_hidden* 9 | 10 | # KDE directory preferences 11 | .directory 12 | 13 | # Linux trash folder which might appear on any partition or disk 14 | .Trash-* 15 | 16 | # .nfs files are created when an open file is removed but is still being accessed 17 | .nfs* 18 | 19 | ### Rust ### 20 | # Generated by Cargo 21 | # will have compiled files and executables 22 | /target/ 23 | 24 | # These are backup files generated by rustfmt 25 | **/*.rs.bk 26 | 27 | ### VisualStudioCode ### 28 | .vscode/* 29 | !.vscode/settings.json 30 | !.vscode/tasks.json 31 | !.vscode/launch.json 32 | !.vscode/extensions.json 33 | .history 34 | 35 | ### Windows ### 36 | # Windows thumbnail cache files 37 | Thumbs.db 38 | ehthumbs.db 39 | ehthumbs_vista.db 40 | 41 | # Folder config file 42 | Desktop.ini 43 | 44 | # Recycle Bin used on file shares 45 | $RECYCLE.BIN/ 46 | 47 | # Windows Installer files 48 | *.cab 49 | *.msi 50 | *.msm 51 | *.msp 52 | 53 | # Windows shortcuts 54 | *.lnk 55 | 56 | # End of https://www.gitignore.io/api/rust,linux,windows,visualstudiocode 57 | -------------------------------------------------------------------------------- /examples/closures.plk: -------------------------------------------------------------------------------- 1 | // we can abuse pointers, generics, and function 2 | // pointers to create hacky closures 3 | 4 | // because env is behind a pointer we can erase the 5 | // concrete type to unit to remove env type parameter 6 | struct Fn { 7 | env: *mut unit, 8 | function: fn(*mut unit, T) -> O, 9 | } 10 | 11 | fn invoke(f: Fn, arg: T) -> O { 12 | return f.function(f.env, arg); 13 | } 14 | 15 | fn closure(env: *mut E, function: fn(*mut E, T) -> O) -> Fn { 16 | // erase concrete type E to unit, 17 | // compiler can infer these types for us 18 | return Fn(env as _, function as _); 19 | } 20 | 21 | fn print_num(num: u8) { 22 | if num >= 10 { 23 | print_num(num / 10); 24 | } 25 | putc('0' + num % 10); 26 | } 27 | 28 | fn for_each(mut start: u8, end: u8, f: Fn) { 29 | while start <= end { 30 | invoke(f, start); 31 | start = start + 1; 32 | } 33 | } 34 | 35 | fn print_offset(offset: *mut u8, val: u8) { 36 | print_num(*offset + val); 37 | *offset = *offset * 2; 38 | putc(' '); 39 | } 40 | 41 | fn main() -> i32 { 42 | let mut delta = 1; 43 | let f = closure(&mut delta, print_offset); 44 | for_each(3, 8, f); 45 | putc('\n'); 46 | return 0; 47 | } -------------------------------------------------------------------------------- /plank-frontend/src/symbols.rs: -------------------------------------------------------------------------------- 1 | use ast::resolved::Symbol; 2 | use std::collections::HashMap; 3 | 4 | #[derive(Debug)] 5 | pub struct Symbols { 6 | next_symbol: u32, 7 | symbol_names: HashMap, 8 | } 9 | 10 | impl Symbols { 11 | pub fn new() -> Symbols { 12 | let mut names = HashMap::new(); 13 | names.insert(::builtins::SIZE_OF, "size_of".into()); 14 | names.insert(::builtins::ALIGN_OF, "align_of".into()); 15 | names.insert(::builtins::GETC, "@getc".into()); 16 | names.insert(::builtins::PUTC, "@putc".into()); 17 | names.insert(::builtins::SIZE_OF_TYPE_PARAM, "T".into()); 18 | names.insert(::builtins::ALIGN_OF_TYPE_PARAM, "T".into()); 19 | names.insert(::builtins::PUTC_PARAM, "ch".into()); 20 | Symbols { 21 | next_symbol: names.len() as u32, 22 | symbol_names: names, 23 | } 24 | } 25 | 26 | pub fn new_symbol>(&mut self, name: S) -> Symbol { 27 | let symbol = Symbol(self.next_symbol); 28 | self.next_symbol += 1; 29 | self.symbol_names.insert(symbol, name.into()); 30 | symbol 31 | } 32 | 33 | pub fn get_name(&self, symbol: Symbol) -> &str { 34 | &self.symbol_names[&symbol] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/strings.plk: -------------------------------------------------------------------------------- 1 | // a short example of small, stack alocated strings 2 | 3 | struct Quad { a: A, b: A, c: A, d: A } 4 | 5 | // use quads to make string 64 bytes sized 6 | struct String { data: Quad>> } 7 | 8 | fn from_u8_ptr(ptr: *u8) -> String { 9 | let data = Quad(0, 0, 0, 0); 10 | let data = Quad(data, data, data, data); 11 | let data = Quad(data, data, data, data); 12 | let str = String(data); 13 | strcpy( 14 | from: ptr, 15 | to: &str as _, 16 | ); 17 | return str; 18 | } 19 | 20 | fn strcpy(mut to: *mut u8, mut from: *u8) { 21 | while *from != 0 { 22 | *to = *from; 23 | to = (to as _ + 1) as _; 24 | from = (from as _ + 1) as _; 25 | } 26 | *to = 0; 27 | } 28 | 29 | fn append(to: *mut String, str: String) { 30 | let mut ptr = to as *mut u8; 31 | while *ptr != 0 { 32 | ptr = (ptr as _ + 1) as _; 33 | } 34 | strcpy( 35 | from: &str as _, 36 | to: ptr, 37 | ); 38 | } 39 | 40 | fn print(str: String) { 41 | let mut ptr = &str as _; 42 | while *ptr != 0 { 43 | putc(*ptr); 44 | ptr = (ptr as _ + 1) as _; 45 | } 46 | } 47 | 48 | fn main() -> i32 { 49 | let mut a = from_u8_ptr("Stack "); 50 | let b = from_u8_ptr("allocated "); 51 | let c = from_u8_ptr("strings!\n"); 52 | append(&mut a, b); 53 | append(&mut a, c); 54 | print(a); 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /examples/types.plk: -------------------------------------------------------------------------------- 1 | struct Pair { 2 | first: A, 3 | second: B, 4 | } 5 | 6 | struct PairOfInts { 7 | i: i32, 8 | j: i32, 9 | } 10 | 11 | fn print_num(num: u32) { 12 | if num >= 10 { 13 | print_num(num / 10); 14 | } 15 | putc('0' + ((num % 10) as Pair).first); 16 | } 17 | 18 | fn print(mut msg: *u8) { 19 | while *msg != 0 { 20 | putc(*msg); 21 | msg = (msg as u32 + 1) as *u8; 22 | } 23 | } 24 | 25 | fn print_size(name: *u8) { 26 | print("size_of::<"); 27 | print(name); 28 | print(">() = "); 29 | print_num(size_of::()); 30 | putc('\n'); 31 | } 32 | 33 | fn main() -> i32 { 34 | // numeric types 35 | print_size::("u8"); 36 | print_size::("i8"); 37 | print_size::("u16"); 38 | print_size::("i16"); 39 | print_size::("u32"); 40 | print_size::("i32"); 41 | 42 | // bool 43 | print_size::("bool"); 44 | 45 | // unit - a zero sized type with value also called `unit`. 46 | // functions that do not have a specified return type return unit 47 | print_size::("unit"); 48 | 49 | // pointers and function pointers 50 | print_size::<*u8>("*u8"); 51 | print_size::<*Pair>("*Pair"); 52 | print_size:: i32>("fn(u8) -> i32"); 53 | 54 | // structs 55 | print_size::("PairOfInts"); 56 | 57 | // generic structs 58 | print_size::>("Pair"); 59 | print_size::>("Pair"); 60 | 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /plank-syntax/src/position.rs: -------------------------------------------------------------------------------- 1 | pub use plank_errors::position::{Position, Span}; 2 | use std::{borrow, ops}; 3 | 4 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 5 | pub struct Spanned { 6 | value: T, 7 | span: Span, 8 | } 9 | 10 | impl Spanned { 11 | pub fn new(value: T, span: Span) -> Spanned { 12 | Spanned { value, span } 13 | } 14 | 15 | pub fn value(this: &Self) -> &T { 16 | &this.value 17 | } 18 | 19 | pub fn value_mut(this: &mut Self) -> &mut T { 20 | &mut this.value 21 | } 22 | 23 | pub fn into_value(this: Self) -> T { 24 | this.value 25 | } 26 | 27 | pub fn span(this: &Self) -> Span { 28 | this.span 29 | } 30 | 31 | pub fn map U>(this: Self, f: F) -> Spanned { 32 | Spanned { 33 | value: f(this.value), 34 | span: this.span, 35 | } 36 | } 37 | 38 | pub fn map_ref U>(this: &Self, f: F) -> Spanned { 39 | Spanned { 40 | value: f(&this.value), 41 | span: this.span, 42 | } 43 | } 44 | } 45 | 46 | impl ops::Deref for Spanned { 47 | type Target = T; 48 | 49 | fn deref(&self) -> &T { 50 | &self.value 51 | } 52 | } 53 | 54 | impl ops::DerefMut for Spanned { 55 | fn deref_mut(&mut self) -> &mut T { 56 | &mut self.value 57 | } 58 | } 59 | 60 | impl borrow::Borrow for Spanned { 61 | fn borrow(&self) -> &T { 62 | &self.value 63 | } 64 | } 65 | 66 | impl borrow::BorrowMut for Spanned { 67 | fn borrow_mut(&mut self) -> &mut T { 68 | &mut self.value 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /plank-ir/src/optimization/arithmetic.rs: -------------------------------------------------------------------------------- 1 | use analysis::Loc; 2 | use ir::{BinaryOp, Instruction, IntOp, Program, Value}; 3 | use optimization::Rewriter; 4 | 5 | struct Simplifier; 6 | 7 | impl Rewriter for Simplifier { 8 | fn rewrite_instruction(&mut self, _loc: Loc, instr: &mut Instruction) { 9 | let mut result = None; 10 | match *instr { 11 | Instruction::BinaryOp(r, BinaryOp::IntOp(IntOp::Add, sign, size), ref a, ref b) => { 12 | if is_immediate(a) { 13 | result = Some(Instruction::BinaryOp( 14 | r, 15 | BinaryOp::IntOp(IntOp::Add, sign, size), 16 | b.clone(), 17 | a.clone(), 18 | )); 19 | } 20 | } 21 | Instruction::BinaryOp(r, BinaryOp::IntOp(IntOp::Mul, sign, size), ref a, ref b) => { 22 | if is_immediate(a) { 23 | result = Some(Instruction::BinaryOp( 24 | r, 25 | BinaryOp::IntOp(IntOp::Mul, sign, size), 26 | b.clone(), 27 | a.clone(), 28 | )); 29 | } 30 | } 31 | _ => {} 32 | } 33 | if let Some(new) = result { 34 | *instr = new; 35 | } 36 | } 37 | } 38 | 39 | fn is_immediate(val: &Value) -> bool { 40 | match *val { 41 | Value::Bytes(_) | Value::Int(_, _) | Value::Symbol(_) => true, 42 | Value::Reg(_) | Value::Undef => false, 43 | } 44 | } 45 | 46 | pub fn rewrite(program: &mut Program) { 47 | Simplifier.rewrite_program(program); 48 | } 49 | -------------------------------------------------------------------------------- /plank-ir/src/optimization/mod.rs: -------------------------------------------------------------------------------- 1 | mod arithmetic; 2 | mod cleanup; 3 | mod constant_fold; 4 | mod dead_drop_elimination; 5 | mod dead_store_elimination; 6 | mod intermediate_removal; 7 | mod simplify_newtypes; 8 | 9 | use analysis::Loc; 10 | use ir::{Block, BlockId, Function, Instruction, Program}; 11 | 12 | trait Rewriter { 13 | fn rewrite_program(&mut self, program: &mut Program) { 14 | rewrite_program(self, program); 15 | } 16 | 17 | fn rewrite_function(&mut self, f: &mut Function) { 18 | rewrite_function(self, f); 19 | } 20 | 21 | fn rewrite_block(&mut self, id: BlockId, block: &mut Block) { 22 | rewrite_block(self, id, block); 23 | } 24 | 25 | fn rewrite_instruction(&mut self, _loc: Loc, _instr: &mut Instruction) {} 26 | } 27 | 28 | fn rewrite_program(r: &mut R, program: &mut Program) { 29 | for f in program.functions.values_mut() { 30 | r.rewrite_function(f); 31 | } 32 | } 33 | 34 | fn rewrite_function(r: &mut R, f: &mut Function) { 35 | for (&id, block) in &mut f.blocks { 36 | r.rewrite_block(id, block); 37 | } 38 | } 39 | 40 | fn rewrite_block(r: &mut R, id: BlockId, block: &mut Block) { 41 | for (index, i) in block.ops.iter_mut().enumerate() { 42 | let loc = Loc { 43 | block: id, 44 | pos: index, 45 | }; 46 | r.rewrite_instruction(loc, i); 47 | } 48 | } 49 | 50 | pub fn optimize(program: &mut Program) { 51 | simplify_newtypes::rewrite(program); 52 | intermediate_removal::rewrite(program); 53 | constant_fold::rewrite(program); 54 | arithmetic::rewrite(program); 55 | dead_store_elimination::rewrite(program); 56 | dead_drop_elimination::rewrite(program); 57 | cleanup::rewrite(program); 58 | } 59 | -------------------------------------------------------------------------------- /plank-ir/src/optimization/simplify_newtypes.rs: -------------------------------------------------------------------------------- 1 | use analysis::Loc; 2 | use ir::{Function, Instruction, Program, Reg, Value}; 3 | use optimization::{self as opt, Rewriter}; 4 | use std::collections::HashMap; 5 | 6 | #[derive(Default)] 7 | struct Simplifier { 8 | reg_size: HashMap, 9 | } 10 | 11 | impl Simplifier { 12 | fn value_size(&self, val: &Value) -> u32 { 13 | match *val { 14 | Value::Undef => 1, 15 | Value::Bytes(_) => ::ir::POINTER_SIZE, 16 | Value::Int(_, size) => size.in_bytes(), 17 | Value::Reg(reg) => self.reg_size[®], 18 | Value::Symbol(_) => ::ir::FUNCTION_SIZE, 19 | } 20 | } 21 | } 22 | 23 | impl Rewriter for Simplifier { 24 | fn rewrite_function(&mut self, f: &mut Function) { 25 | self.reg_size = f 26 | .registers 27 | .iter() 28 | .map(|(®, &layout)| (reg, layout.size)) 29 | .collect(); 30 | opt::rewrite_function(self, f); 31 | } 32 | 33 | fn rewrite_instruction(&mut self, _loc: Loc, instr: &mut Instruction) { 34 | let mut result = None; 35 | match *instr { 36 | Instruction::Store(reg, 0, ref val) => { 37 | if self.reg_size[®] == self.value_size(val) { 38 | result = Some(Instruction::Assign(reg, val.clone())); 39 | } 40 | } 41 | Instruction::Load(reg, reg2, 0) => { 42 | if self.reg_size[®] == self.reg_size[®2] { 43 | result = Some(Instruction::Assign(reg, Value::Reg(reg2))) 44 | } 45 | } 46 | _ => {} 47 | } 48 | if let Some(new) = result { 49 | *instr = new; 50 | } 51 | } 52 | } 53 | 54 | pub fn rewrite(program: &mut Program) { 55 | Simplifier::default().rewrite_program(program); 56 | } 57 | -------------------------------------------------------------------------------- /plank-ir/src/optimization/dead_store_elimination.rs: -------------------------------------------------------------------------------- 1 | use analysis::{self, usage, volatility, Loc}; 2 | use ir::{Instruction, Program}; 3 | 4 | fn is_call(instr: &Instruction) -> bool { 5 | matches!( 6 | *instr, 7 | Instruction::Call(..) | Instruction::CallProc(..) | Instruction::CallProcVirt(..) 8 | ) 9 | } 10 | 11 | pub fn rewrite(program: &mut Program) { 12 | loop { 13 | let mut changed_anything = false; 14 | for f in program.functions.values_mut() { 15 | let volatile = &volatility::volatile_locations(f); 16 | let mut to_remove = Vec::new(); 17 | { 18 | let ctx = usage::Context::new(f, volatile); 19 | for (&id, block) in &f.blocks { 20 | for (pos, instr) in block.ops.iter().enumerate() { 21 | let loc = Loc { 22 | block: id, 23 | pos: pos + 1, 24 | }; 25 | let written = match *instr { 26 | Instruction::Store(reg, _, _) => Some(reg), 27 | ref other => analysis::initialized_register(other), 28 | }; 29 | if let Some(reg) = written { 30 | if !ctx.is_value_used(loc, reg) { 31 | to_remove.push(Loc { block: id, pos }); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | for loc in to_remove { 38 | let block = f.blocks.get_mut(&loc.block).unwrap(); 39 | if !is_call(&block.ops[loc.pos]) { 40 | changed_anything = true; 41 | block.ops[loc.pos] = Instruction::Nop; 42 | } 43 | } 44 | } 45 | if !changed_anything { 46 | return; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /plank-server/src/transport.rs: -------------------------------------------------------------------------------- 1 | use std::convert::From; 2 | use std::io; 3 | use std::io::prelude::*; 4 | 5 | #[derive(Debug)] 6 | pub enum Error { 7 | Io(io::Error), 8 | BadInput(&'static str), 9 | } 10 | 11 | impl From for Error { 12 | fn from(err: io::Error) -> Self { 13 | Error::Io(err) 14 | } 15 | } 16 | 17 | const LENGTH_HEADER: &'static str = "Content-Length:"; 18 | 19 | #[derive(Debug)] 20 | pub struct Transport { 21 | read_buffer: String, 22 | reader: R, 23 | writer: W, 24 | } 25 | 26 | impl Transport { 27 | pub fn new(reader: R, writer: W) -> Self { 28 | Transport { 29 | read_buffer: String::new(), 30 | reader, 31 | writer, 32 | } 33 | } 34 | 35 | pub fn read_message(&mut self) -> Result { 36 | let mut content_length = None; 37 | loop { 38 | self.read_buffer.clear(); 39 | self.reader.read_line(&mut self.read_buffer)?; 40 | if self.read_buffer.starts_with(LENGTH_HEADER) { 41 | let len_str = self.read_buffer.get(LENGTH_HEADER.len()..).unwrap().trim(); 42 | let length = 43 | str::parse::(len_str).map_err(|_| Error::BadInput("bad length"))?; 44 | content_length = Some(length); 45 | } else if self.read_buffer == "\r\n" { 46 | break; 47 | } else if self.read_buffer.is_empty() { 48 | return Err(Error::BadInput("unexpected eof")); 49 | } 50 | } 51 | let content_length = content_length.ok_or(Error::BadInput("no content length"))?; 52 | self.read_buffer.clear(); 53 | (&mut self.reader) 54 | .take(content_length) 55 | .read_line(&mut self.read_buffer)?; 56 | Ok(self.read_buffer.clone()) 57 | } 58 | 59 | pub fn send_message(&mut self, msg: &str) -> Result<(), Error> { 60 | debug!("sending: {}", msg); 61 | write!(self.writer, "{} {}\r\n\r\n", LENGTH_HEADER, msg.len())?; 62 | self.writer.write_all(msg.as_bytes())?; 63 | self.writer.flush()?; 64 | Ok(()) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /plank-frontend/src/gen_constructors.rs: -------------------------------------------------------------------------------- 1 | use ast::cfg::{Block, BlockEnd, BlockId, BlockLink, Function, Instruction, Program, Reg, Value}; 2 | use ast::typed::{self as t, Struct}; 3 | use plank_syntax::position::Spanned; 4 | use std::collections::HashMap; 5 | 6 | fn generate_constructor(s: &Struct) -> Function { 7 | let mut ops = Vec::new(); 8 | let output = Reg(0); 9 | ops.push(Spanned::new(Instruction::Init(Reg(0)), s.complete_span)); 10 | for i in 0..(s.fields.len()) { 11 | let i = Instruction::FieldStore( 12 | Spanned::new(Reg(0), s.complete_span), 13 | vec![i], 14 | Spanned::new(Value::Reg(Reg(i as u32 + 1)), s.complete_span), 15 | ); 16 | let i = Spanned::new(i, s.complete_span); 17 | ops.push(i); 18 | } 19 | let parameters = (1..(s.fields.len() as u32 + 1)) 20 | .into_iter() 21 | .map(Reg) 22 | .collect(); 23 | let mut registers = (1..(s.fields.len() + 1)) 24 | .into_iter() 25 | .map(|i| (Reg(i as u32), s.fields[i - 1].typ.clone())) 26 | .collect::>(); 27 | let complete_type = t::Type::Concrete( 28 | s.name, 29 | s.type_params 30 | .iter() 31 | .map(|&s| t::Type::Concrete(s, Vec::new().into())) 32 | .collect::>() 33 | .into(), 34 | ); 35 | registers.insert(Reg(0), complete_type.clone()); 36 | let block = Block { 37 | ops, 38 | link: BlockLink::None, 39 | end: BlockEnd::Return(Spanned::new(Value::Reg(output), s.complete_span)), 40 | }; 41 | let mut blocks = HashMap::new(); 42 | blocks.insert(BlockId(0), block); 43 | Function { 44 | complete_span: s.complete_span, 45 | parameters, 46 | registers, 47 | register_symbols: HashMap::new(), 48 | type_params: s.type_params.clone(), 49 | out_type: complete_type, 50 | start_block: Some(BlockId(0)), 51 | blocks, 52 | } 53 | } 54 | 55 | pub(crate) fn add_constructors(program: &mut Program) { 56 | for (id, s) in &program.structs { 57 | let ctor = generate_constructor(s); 58 | program.functions.insert(*id, ctor); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /plank-frontend/src/return_check.rs: -------------------------------------------------------------------------------- 1 | use ast::cfg::{Block, BlockEnd, Function, Instruction, Program, Type, Value}; 2 | use plank_syntax::position::Spanned; 3 | use std::collections::{HashSet, VecDeque}; 4 | use CompileCtx; 5 | 6 | fn has_error_statement(block: &Block) -> bool { 7 | for op in &block.ops { 8 | if let Instruction::Error = **op { 9 | return true; 10 | } 11 | } 12 | false 13 | } 14 | 15 | fn check_function(f: &mut Function, ctx: &mut CompileCtx) { 16 | let mut queue = VecDeque::new(); 17 | let mut visited = HashSet::new(); 18 | if let Some(start_block) = f.start_block { 19 | queue.push_back(start_block); 20 | } 21 | let allow_no_return = match f.out_type { 22 | Type::Unit => true, 23 | _ => false, 24 | }; 25 | 26 | while let Some(block) = queue.pop_front() { 27 | if visited.contains(&block) { 28 | continue; 29 | } 30 | visited.insert(block); 31 | let block = f.blocks.get_mut(&block).unwrap(); 32 | if has_error_statement(block) { 33 | continue; 34 | } 35 | match block.end { 36 | BlockEnd::Error if allow_no_return => { 37 | // TODO: this is not a very good span for this 38 | let spanned = Spanned::new(Value::Unit, f.complete_span); 39 | block.end = BlockEnd::Return(spanned); 40 | } 41 | BlockEnd::Error => { 42 | let span = f.complete_span; 43 | ctx.reporter 44 | .error("not all paths return a value", span) 45 | .span(span) 46 | .build(); 47 | return; 48 | } 49 | BlockEnd::Branch(_, a, b) => { 50 | queue.push_back(a); 51 | queue.push_back(b); 52 | } 53 | BlockEnd::Jump(a) => { 54 | queue.push_back(a); 55 | } 56 | BlockEnd::Return(_) => {} 57 | } 58 | } 59 | } 60 | 61 | pub(crate) fn check_returns(program: &mut Program, ctx: &mut CompileCtx) { 62 | for f in program.functions.values_mut() { 63 | check_function(f, ctx); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /plank-frontend/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate plank_errors; 2 | extern crate plank_ir; 3 | extern crate plank_syntax; 4 | 5 | mod ast { 6 | pub mod cfg; 7 | pub mod resolved; 8 | pub mod typed; 9 | } 10 | mod assign_check; 11 | mod build_cfg; 12 | mod build_ir; 13 | mod cast_check; 14 | mod dead_code; 15 | mod gen_constructors; 16 | mod literal_size_check; 17 | mod resolve_symbols; 18 | mod return_check; 19 | mod struct_check; 20 | mod struct_layout; 21 | mod symbols; 22 | mod type_check; 23 | mod type_param_check; 24 | mod wildcard_check; 25 | 26 | mod builtins { 27 | use ast::resolved::Symbol; 28 | pub const SIZE_OF: Symbol = Symbol(0); 29 | pub const ALIGN_OF: Symbol = Symbol(1); 30 | pub const GETC: Symbol = Symbol(2); 31 | pub const PUTC: Symbol = Symbol(3); 32 | 33 | pub const SIZE_OF_TYPE_PARAM: Symbol = Symbol(4); 34 | pub const ALIGN_OF_TYPE_PARAM: Symbol = Symbol(5); 35 | pub const PUTC_PARAM: Symbol = Symbol(6); 36 | } 37 | 38 | use plank_errors::Reporter; 39 | use plank_syntax::ast::Program; 40 | use symbols::Symbols; 41 | 42 | struct CompileCtx { 43 | symbols: Symbols, 44 | reporter: Reporter, 45 | } 46 | 47 | pub fn compile(program: &Program, reporter: Reporter) -> Result { 48 | let mut ctx = CompileCtx { 49 | symbols: Symbols::new(), 50 | reporter, 51 | }; 52 | 53 | let mut resolved = resolve_symbols::resolve_program(program, &mut ctx); 54 | type_param_check::check_type_params(&mut resolved, &mut ctx); 55 | wildcard_check::check_for_wildcards(&resolved, &mut ctx); 56 | struct_check::check_program(&mut resolved, &mut ctx); 57 | let mut typed = type_check::type_check(&resolved, &mut ctx); 58 | literal_size_check::check_program(&mut typed, &mut ctx); 59 | cast_check::check_casts(&mut typed, &mut ctx); 60 | let mut cfg = build_cfg::build_cfg(&typed, &mut ctx); 61 | dead_code::remove_dead_code(&mut cfg, &mut ctx); 62 | assign_check::check_program(&cfg, &mut ctx); 63 | return_check::check_returns(&mut cfg, &mut ctx); 64 | gen_constructors::add_constructors(&mut cfg); 65 | if ctx.reporter.has_errors() { 66 | Err(()) 67 | } else { 68 | Ok(build_ir::build_ir(&cfg, &ctx)) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /plank-errors/src/position.rs: -------------------------------------------------------------------------------- 1 | //! Types to represent positions inside source file. 2 | 3 | /// Represents a position inside a source file. Both lines and columns start 4 | /// from zero. 5 | #[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone)] 6 | pub struct Position { 7 | #[allow(missing_docs)] 8 | pub line: u32, 9 | #[allow(missing_docs)] 10 | pub column: u32, 11 | } 12 | 13 | impl Position { 14 | /// Create a new position with given line and column. 15 | pub fn new(line: u32, column: u32) -> Position { 16 | Position { line, column } 17 | } 18 | 19 | /// Create a span that starts here, and goes to given position. If 20 | /// `self == to`, then the span is considered empty. 21 | /// 22 | /// # Panics 23 | /// 24 | /// Panics if `to < self`. 25 | pub fn span_to(self, to: Position) -> Span { 26 | Span::new(self, to) 27 | } 28 | 29 | /// Create a new position that is `amount` columns to the right. 30 | pub fn forward(mut self, amount: u32) -> Position { 31 | self.column += amount; 32 | self 33 | } 34 | 35 | /// Create a new position that is `amount` columns to the left. 36 | /// 37 | /// # Panics 38 | /// 39 | /// Panics if resulting column is negative. 40 | pub fn backwards(mut self, amount: u32) -> Position { 41 | assert!(self.column >= amount, "going back too far"); 42 | self.column -= amount; 43 | self 44 | } 45 | } 46 | 47 | /// Represents a range inside source file. You can think of it as a selection 48 | /// inside the editor. 49 | #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] 50 | pub struct Span { 51 | #[allow(missing_docs)] 52 | pub start: Position, 53 | #[allow(missing_docs)] 54 | pub end: Position, 55 | } 56 | 57 | impl Span { 58 | /// Create a span that is between given positions. 59 | /// 60 | /// # Panics 61 | /// 62 | /// Panics if `start > end`. 63 | pub fn new(start: Position, end: Position) -> Span { 64 | assert!(start <= end); 65 | Span { start, end } 66 | } 67 | 68 | /// Return the smallest span that contains both `self` and `other` spans. 69 | pub fn merge(self, other: Span) -> Span { 70 | use std::cmp; 71 | let start = cmp::min(self.start, other.start); 72 | let end = cmp::max(self.end, other.end); 73 | Span::new(start, end) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /plank-ir/src/optimization/intermediate_removal.rs: -------------------------------------------------------------------------------- 1 | use analysis::{self, Loc}; 2 | use ir::{BlockId, Function, Instruction, Program, Value}; 3 | 4 | struct Fix { 5 | block: BlockId, 6 | init_at: usize, 7 | } 8 | 9 | fn inspect_function(f: &Function) -> Vec { 10 | let mut fixes = Vec::new(); 11 | let volatility = &analysis::volatility::volatile_locations(f); 12 | let ctx = analysis::usage::Context::new(f, volatility); 13 | for (&id, block) in &f.blocks { 14 | for (pos, op) in block.ops.iter().enumerate() { 15 | if let Some(reg) = analysis::initialized_register(op) { 16 | match block.ops.get(pos + 1) { 17 | Some(&Instruction::Assign(_, Value::Reg(r2))) if r2 == reg => { 18 | let loc = Loc { 19 | block: id, 20 | pos: pos + 2, 21 | }; 22 | if !ctx.is_value_used(loc, r2) { 23 | fixes.push(Fix { 24 | block: id, 25 | init_at: pos, 26 | }) 27 | } 28 | } 29 | _ => {} 30 | } 31 | } 32 | } 33 | } 34 | fixes 35 | } 36 | 37 | fn rewrite_function(f: &mut Function) { 38 | let fixes = inspect_function(f); 39 | for fix in fixes { 40 | let block = f.blocks.get_mut(&fix.block).unwrap(); 41 | let reg = match block.ops[fix.init_at + 1] { 42 | Instruction::Assign(to, _) => to, 43 | _ => panic!("invalid fix"), 44 | }; 45 | block.ops[fix.init_at + 1] = Instruction::Nop; 46 | match block.ops[fix.init_at] { 47 | Instruction::Assign(ref mut r, _) 48 | | Instruction::BinaryOp(ref mut r, _, _, _) 49 | | Instruction::Call(ref mut r, _, _) 50 | | Instruction::CallVirt(ref mut r, _, _) 51 | | Instruction::CastAssign(ref mut r, _) 52 | | Instruction::DerefLoad(ref mut r, _, _) 53 | | Instruction::Init(ref mut r) 54 | | Instruction::Load(ref mut r, _, _) 55 | | Instruction::TakeAddress(ref mut r, _, _) 56 | | Instruction::UnaryOp(ref mut r, _, _) => *r = reg, 57 | _ => panic!("invalid fix"), 58 | } 59 | } 60 | } 61 | 62 | pub fn rewrite(program: &mut Program) { 63 | for f in program.functions.values_mut() { 64 | rewrite_function(f); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /plank-ir/src/analysis/volatility.rs: -------------------------------------------------------------------------------- 1 | use super::Loc; 2 | use ir::{BlockEnd, BlockId, Function, Instruction, Reg}; 3 | use std::collections::{HashMap, HashSet}; 4 | 5 | struct Volatility<'a> { 6 | function: &'a Function, 7 | reg: Reg, 8 | volatile_locations: HashSet, 9 | checked_blocks: HashSet<(BlockId, bool)>, 10 | } 11 | 12 | impl<'a> Volatility<'a> { 13 | fn new(function: &'a Function, reg: Reg) -> Self { 14 | Volatility { 15 | function, 16 | reg, 17 | volatile_locations: HashSet::new(), 18 | checked_blocks: HashSet::new(), 19 | } 20 | } 21 | 22 | fn walk_block(&mut self, id: BlockId, volatile_start: bool) { 23 | if self.checked_blocks.contains(&(id, volatile_start)) { 24 | return; 25 | } 26 | self.checked_blocks.insert((id, volatile_start)); 27 | let block = &self.function.blocks[&id]; 28 | let mut is_volatile = volatile_start; 29 | for (pos, instr) in block.ops.iter().enumerate() { 30 | if is_volatile { 31 | self.volatile_locations.insert(Loc { block: id, pos }); 32 | } 33 | if referenced_register(instr) == Some(self.reg) { 34 | is_volatile = true; 35 | } 36 | if let Instruction::Drop(reg) = *instr { 37 | if reg == self.reg { 38 | is_volatile = false; 39 | } 40 | } 41 | } 42 | if is_volatile { 43 | self.volatile_locations.insert(Loc { 44 | block: id, 45 | pos: block.ops.len(), 46 | }); 47 | } 48 | match block.end { 49 | BlockEnd::Branch(_, a, b) => { 50 | self.walk_block(a, is_volatile); 51 | self.walk_block(b, is_volatile); 52 | } 53 | BlockEnd::Jump(a) => { 54 | self.walk_block(a, is_volatile); 55 | } 56 | BlockEnd::Return(_) | BlockEnd::ReturnProc | BlockEnd::Unreachable => {} 57 | } 58 | } 59 | } 60 | 61 | fn referenced_register(instr: &Instruction) -> Option { 62 | match *instr { 63 | Instruction::TakeAddress(_, reg, _) => Some(reg), 64 | _ => None, 65 | } 66 | } 67 | 68 | pub fn register_volatile_locations(f: &Function, reg: Reg) -> HashSet { 69 | let mut ctx = Volatility::new(f, reg); 70 | for &id in f.blocks.keys() { 71 | ctx.walk_block(id, false); 72 | } 73 | ctx.volatile_locations 74 | } 75 | 76 | pub fn volatile_locations(f: &Function) -> HashMap> { 77 | let mut locations = HashMap::new(); 78 | for ® in f.registers.keys() { 79 | locations.insert(reg, register_volatile_locations(f, reg)); 80 | } 81 | locations 82 | } 83 | -------------------------------------------------------------------------------- /plank-frontend/src/ast/resolved.rs: -------------------------------------------------------------------------------- 1 | pub use plank_syntax::ast::{ 2 | BinaryOp, FunctionType, Literal, Mutability, Number, Signedness, Size, UnaryOp, 3 | }; 4 | use plank_syntax::position::{Span, Spanned}; 5 | use std::collections::HashMap; 6 | 7 | #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Copy, Clone)] 8 | pub struct Symbol(pub u32); 9 | 10 | #[derive(Debug, Clone)] 11 | pub enum Expr { 12 | Binary(Box>, Spanned, Box>), 13 | Unary(Spanned, Box>), 14 | Call(Box>, Vec>), 15 | Field(Box>, Spanned), 16 | Name(Spanned, Vec>), 17 | Literal(Literal), 18 | Cast(Box>, Spanned), 19 | Error, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub enum Statement { 24 | If( 25 | Spanned, 26 | Box>, 27 | Option>>, 28 | ), 29 | Loop(Box>), 30 | While(Spanned, Box>), 31 | Break, 32 | Continue, 33 | Return(Spanned), 34 | Let( 35 | Mutability, 36 | Spanned, 37 | Spanned, 38 | Option>, 39 | ), 40 | Block(Vec>), 41 | Expr(Spanned), 42 | Error, 43 | } 44 | 45 | #[derive(Debug, Clone)] 46 | pub enum Type { 47 | Wildcard, 48 | I8, 49 | U8, 50 | I16, 51 | U16, 52 | I32, 53 | U32, 54 | Bool, 55 | Unit, 56 | Concrete(Spanned, Vec>), 57 | Pointer(Mutability, Box>), 58 | Function(Vec>, Box>), 59 | Error, 60 | } 61 | 62 | #[derive(Debug, Clone)] 63 | pub struct ItemName { 64 | pub name: Spanned, 65 | pub type_params: Vec>, 66 | } 67 | 68 | #[derive(Debug, Clone)] 69 | pub struct FnParam { 70 | pub mutability: Mutability, 71 | pub name: Spanned, 72 | pub typ: Spanned, 73 | } 74 | 75 | #[derive(Debug, Clone)] 76 | pub struct Function { 77 | pub fn_type: FunctionType, 78 | pub complete_span: Span, 79 | pub name: ItemName, 80 | pub params: Vec, 81 | pub return_type: Spanned, 82 | pub body: Option>, 83 | } 84 | 85 | #[derive(Debug, Clone)] 86 | pub struct Field { 87 | pub name: Spanned, 88 | pub typ: Spanned, 89 | } 90 | 91 | #[derive(Debug, Clone)] 92 | pub struct Struct { 93 | pub complete_span: Span, 94 | pub name: ItemName, 95 | pub fields: Vec, 96 | } 97 | 98 | #[derive(Debug, Clone)] 99 | pub struct Program { 100 | pub structs: HashMap, 101 | pub functions: Vec, 102 | } 103 | -------------------------------------------------------------------------------- /plank-frontend/src/wildcard_check.rs: -------------------------------------------------------------------------------- 1 | use ast::resolved::{Function, Program, Struct, Type}; 2 | use plank_errors::reporter::Builder; 3 | use plank_syntax::position::{Span, Spanned}; 4 | use CompileCtx; 5 | 6 | pub(crate) fn check_for_wildcards(program: &Program, ctx: &mut CompileCtx) { 7 | let mut ctx = Context::new(ctx); 8 | ctx.check_program(program); 9 | } 10 | 11 | struct Context<'a> { 12 | ctx: &'a mut CompileCtx, 13 | builder: Option, 14 | item_kind: Option<&'static str>, 15 | } 16 | 17 | impl<'a> Context<'a> { 18 | fn new(ctx: &'a mut CompileCtx) -> Self { 19 | Context { 20 | ctx, 21 | builder: None, 22 | item_kind: None, 23 | } 24 | } 25 | 26 | fn check_program(&mut self, program: &Program) { 27 | for fn_ in &program.functions { 28 | self.check_function(fn_); 29 | } 30 | 31 | for struct_ in program.structs.values() { 32 | self.check_struct(struct_); 33 | } 34 | } 35 | 36 | fn check_struct(&mut self, struct_: &Struct) { 37 | self.item_kind = Some("struct field"); 38 | for field in &struct_.fields { 39 | self.check_type(&field.typ); 40 | } 41 | self.finish_item_check(); 42 | } 43 | 44 | fn check_function(&mut self, fn_: &Function) { 45 | self.item_kind = Some("function header"); 46 | for param in &fn_.params { 47 | self.check_type(¶m.typ); 48 | } 49 | self.check_type(&fn_.return_type); 50 | self.finish_item_check(); 51 | } 52 | 53 | fn check_type(&mut self, typ: &Spanned) { 54 | match **typ { 55 | Type::Unit 56 | | Type::Bool 57 | | Type::Error 58 | | Type::I8 59 | | Type::I16 60 | | Type::I32 61 | | Type::U8 62 | | Type::U16 63 | | Type::U32 => {} 64 | Type::Pointer(_, ref typ) => self.check_type(typ), 65 | Type::Function(ref params, ref out) => { 66 | for param in params { 67 | self.check_type(param); 68 | } 69 | self.check_type(out); 70 | } 71 | Type::Concrete(_, ref params) => { 72 | for param in params { 73 | self.check_type(param); 74 | } 75 | } 76 | Type::Wildcard => self.report_error(Spanned::span(typ)), 77 | } 78 | } 79 | 80 | fn report_error(&mut self, span: Span) { 81 | self.builder = Some(match self.builder.take() { 82 | Some(builder) => builder.span(span), 83 | None => { 84 | let msg = format!( 85 | "wildcard types are not allowed in {}s", 86 | self.item_kind.unwrap(), 87 | ); 88 | self.ctx.reporter.error(msg, span).span(span) 89 | } 90 | }); 91 | } 92 | 93 | fn finish_item_check(&mut self) { 94 | self.item_kind = None; 95 | if let Some(builder) = self.builder.take() { 96 | builder.build(); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /plank-x86-backend/src/return_fix.rs: -------------------------------------------------------------------------------- 1 | use plank_ir::ir::{self, BlockEnd, Function, Instruction, Layout, Program, Reg, Value}; 2 | use std::collections::HashMap; 3 | 4 | fn fresh_register(regs: &HashMap) -> Reg { 5 | for i in 0..regs.len() { 6 | if !regs.contains_key(&Reg(i as u32)) { 7 | return Reg(i as u32); 8 | } 9 | } 10 | Reg(regs.len() as u32) 11 | } 12 | 13 | fn fix_function(f: &mut Function) { 14 | let output_address = if let Some(layout) = f.output_layout { 15 | if layout.atomic { 16 | None 17 | } else { 18 | let reg = fresh_register(&f.registers); 19 | f.registers.insert( 20 | reg, 21 | Layout { 22 | size: ir::POINTER_SIZE, 23 | align: ir::POINTER_SIZE, 24 | atomic: true, 25 | }, 26 | ); 27 | f.parameters.insert(0, reg); 28 | f.output_layout = None; 29 | Some(reg) 30 | } 31 | } else { 32 | None 33 | }; 34 | for block in f.blocks.values_mut() { 35 | for i in (0..block.ops.len()).rev() { 36 | let initializer = match block.ops[i] { 37 | Instruction::Call(r, _, ref mut params) 38 | | Instruction::CallVirt(r, _, ref mut params) 39 | if !f.registers[&r].atomic => 40 | { 41 | let reg = fresh_register(&f.registers); 42 | f.registers.insert( 43 | reg, 44 | Layout { 45 | size: ir::POINTER_SIZE, 46 | align: ir::POINTER_SIZE, 47 | atomic: true, 48 | }, 49 | ); 50 | params.insert(0, Value::Reg(reg)); 51 | Some((reg, r)) 52 | } 53 | _ => None, 54 | }; 55 | if let Some((param, out)) = initializer { 56 | match block.ops[i].clone() { 57 | Instruction::Call(_, f, params) => { 58 | block.ops[i] = Instruction::CallProc(f, params); 59 | } 60 | Instruction::CallVirt(_, f, params) => { 61 | block.ops[i] = Instruction::CallProcVirt(f, params); 62 | } 63 | _ => panic!("shit"), 64 | } 65 | block.ops.insert(i + 1, Instruction::Drop(param)); 66 | block.ops.insert(i, Instruction::TakeAddress(param, out, 0)); 67 | block.ops.insert(i, Instruction::Init(out)); 68 | } 69 | } 70 | match (block.end.clone(), output_address) { 71 | (BlockEnd::Return(val), Some(reg)) => { 72 | block 73 | .ops 74 | .push(Instruction::DerefStore(Value::Reg(reg), 0, val)); 75 | block.end = BlockEnd::ReturnProc; 76 | } 77 | _ => {} 78 | } 79 | } 80 | } 81 | 82 | pub fn fix_function_returns(program: &mut Program) { 83 | for f in program.functions.values_mut() { 84 | fix_function(f); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tasks/parser/readme.md: -------------------------------------------------------------------------------- 1 | # Parser 2 | 3 | Parser is implemented in [plank-syntax/src/parser.rs](../../plank-syntax/src/parser.rs). The module exports a single function - `parse`. Parsing always produces a `Program` - if errors are encountered while parsing an item or statement, they will be absent from the parsed program. The `Program` and the rest of the AST is defined in [plank-syntax/src/ast.rs](../../plank-syntax/src/ast.rs). All errors are emitted to given `Reporter`. 4 | 5 | 6 | ## Parsing algorithm 7 | 8 | Parser uses two strategies for parsing Plank code: recursive descent parser for item declarations and statements, and [Pratt parser](http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/) for expressions. Reasons for using recursive descent parser for items are these: 9 | 10 | * Very simple to write. 11 | * Grammar is well suited for such parser - for most cases one token of lookahead is sufficient. There are only two cases where two token lookahead is used. Therefore parser does not ever need to backtrack, making it run in linear time. 12 | * Pretty good and simple error recovery. Because almost all statements start with unique token, it is very easy to synchronize after encountering an error. 13 | 14 | And the reasons for using Pratt parser to parse expressions: 15 | 16 | * Pretty simple to write. 17 | * Cleanly handles operator precedence and associativity. 18 | * Easy to expand the language by adding new operators. 19 | 20 | 21 | ## Error recovery 22 | 23 | Error recovery while parsing a block: 24 | 25 | * If failed to parse a statement, skip until the next token is one of: `if`, `loop`, `while` `break`, `continue`, `let`, `return`, `{`, `}`. 26 | * If when skipping we find token `fn`, check the token after that (this is one of the places where 2 token lookahead is neccessary): 27 | * If it is a name, then we probably are at the start of a function definition. So we failed to parse a block. 28 | * If it is not, then it's probably just a function type - continue skipping tokens. 29 | * If when skipping we find `extern` or `struct` then fail parsing a block - these tokens can only appear at the start of item definition. 30 | 31 | Error recovery while parsing items is very similar - cases are basically the same, but now we stop on those which can start an item: 32 | 33 | * `fn`, followed by a name. 34 | * `extern` 35 | * `struct` 36 | 37 | Also, when parsing statements the parser handles one little special case: if one of expected tokens is `;`, and the next token is on the next line, then programmer probably just forgot the semicolon. Parser emits an error with a hint, and continues parsing as if the semicolon was there. 38 | 39 | For example, if we try to parse this program (note the missing semicolon on the second line): 40 | 41 | ```plank 42 | fn main() -> Unit { 43 | let x = 1 44 | let y = 2; 45 | } 46 | ``` 47 | 48 | We get this error: 49 | 50 | ``` 51 | error: expected one of `(`, `.`, `;`, operator, got `let`. 52 | 2 | let x = 1 53 | | ~ maybe you missed a `;`? 54 | 3 | let y = 2; 55 | | ^^^ unexpected `let` 56 | ``` 57 | 58 | There is no error recovery when parsing expressions, mainly because it is difficult to have good error recovery there. However, as the language is not expression oriented, the expressions are usually quite small, and having just one error pointed out is usually enough to fix whole expression. Therefore error recovery only at statement and item level was deemed sufficient. 59 | -------------------------------------------------------------------------------- /plank-ir/src/analysis/liveness.rs: -------------------------------------------------------------------------------- 1 | use super::{initialized_register, Loc}; 2 | use ir::{Block, BlockEnd, BlockId, Function, Instruction, Reg}; 3 | use std::collections::{HashMap, HashSet}; 4 | 5 | struct Liveness<'a> { 6 | function: &'a Function, 7 | reg: Reg, 8 | dead_locations: HashSet, 9 | checked_blocks: HashSet<(BlockId, bool)>, 10 | } 11 | 12 | impl<'a> Liveness<'a> { 13 | fn new(function: &'a Function, reg: Reg) -> Self { 14 | Liveness { 15 | function, 16 | reg, 17 | dead_locations: HashSet::new(), 18 | checked_blocks: HashSet::new(), 19 | } 20 | } 21 | 22 | fn walk_block(&mut self, id: BlockId, live_start: bool) { 23 | if self.checked_blocks.contains(&(id, live_start)) { 24 | return; 25 | } 26 | self.checked_blocks.insert((id, live_start)); 27 | let block = &self.function.blocks[&id]; 28 | let mut is_live = live_start; 29 | for (pos, instr) in block.ops.iter().enumerate() { 30 | if is_dropped(block, pos, self.reg) { 31 | is_live = false; 32 | } 33 | if !is_live { 34 | self.dead_locations.insert(Loc { block: id, pos }); 35 | } 36 | if initialized_register(instr) == Some(self.reg) { 37 | is_live = true; 38 | } 39 | } 40 | if !is_live { 41 | self.dead_locations.insert(Loc { 42 | block: id, 43 | pos: block.ops.len(), 44 | }); 45 | } 46 | match block.end { 47 | BlockEnd::Branch(_, a, b) => { 48 | self.walk_block(a, is_live); 49 | self.walk_block(b, is_live); 50 | } 51 | BlockEnd::Jump(a) => { 52 | self.walk_block(a, is_live); 53 | } 54 | BlockEnd::Return(_) | BlockEnd::ReturnProc | BlockEnd::Unreachable => {} 55 | } 56 | } 57 | } 58 | 59 | fn is_dropped(block: &Block, from: usize, reg: Reg) -> bool { 60 | for op in &block.ops[from..] { 61 | if let Instruction::Drop(r) = *op { 62 | if r == reg { 63 | return true; 64 | } 65 | } else { 66 | return false; 67 | } 68 | } 69 | false 70 | } 71 | 72 | fn get_dead_locations(f: &Function, reg: Reg) -> HashSet { 73 | let mut ctx = Liveness::new(f, reg); 74 | for &id in f.blocks.keys() { 75 | let is_start = f.start_block == Some(id); 76 | ctx.walk_block(id, !is_start || f.parameters.contains(®)); 77 | } 78 | ctx.dead_locations 79 | } 80 | 81 | pub fn register_live_locations(f: &Function, reg: Reg) -> HashSet { 82 | let dead_locations = get_dead_locations(f, reg); 83 | let mut live = HashSet::new(); 84 | for (&id, block) in &f.blocks { 85 | for pos in 0..(block.ops.len() + 1) { 86 | let loc = Loc { block: id, pos }; 87 | if !dead_locations.contains(&loc) { 88 | live.insert(loc); 89 | } 90 | } 91 | } 92 | live 93 | } 94 | 95 | pub fn live_locations(f: &Function) -> HashMap> { 96 | let mut locations = HashMap::new(); 97 | for ® in f.registers.keys() { 98 | locations.insert(reg, register_live_locations(f, reg)); 99 | } 100 | locations 101 | } 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Plank 2 | 3 | This is an implementation of plank - a little programming language I am building for "Compiling methods" course. 4 | 5 | Here is a simple guide to the language: [plank-language.md](./plank-language.md). 6 | 7 | Currently here you can find: 8 | * a compiler frontend that can generate plank IR 9 | * a few optimizations that work on IR 10 | * a simple interpreter that can execute IR 11 | * a simple compiler backend that converts IR to x86 assembly. 12 | 13 | This repository currently consists of 9 crates: 14 | 15 | * `plank-errors` - defines `Position` and `Span` types, handles error reporting and formatting. 16 | * `plank-syntax` - defines plank AST, and contains parser for plank source code. 17 | * `plank-frontend` - validates plank programs and converts AST to intermediate representation. 18 | * `plank-ir` - defines plank intermediate representation and contains optimizations. 19 | * `plank-interpreter` - a simple virtual machine for executing plank intermediate representation. 20 | * `plank-x86-backend` - generates x86 assembly. 21 | * `plank` - driver program that glues everything together. 22 | * `plank-server` - plank language server. 23 | * `tests` - a simple program that builds and runs tests. 24 | 25 | ## Examples 26 | 27 | Here's a hello world program: 28 | 29 | ```rust 30 | fn puts(mut string: *u8) { 31 | while *string != 0 { 32 | putc(*string); 33 | // this doesn't look very nice :( 34 | string = (string as u32 + 1) as *u8; 35 | } 36 | putc('\n'); 37 | } 38 | 39 | fn main() -> i32 { 40 | puts("Hello, world!"); 41 | return 0; 42 | } 43 | ``` 44 | 45 | You can find more in [examples](./examples). 46 | 47 | ## Installing 48 | 49 | Make sure that you have rust and cargo installed. 50 | 51 | ``` 52 | git clone https://github.com/jDomantas/plank.git 53 | cd plank 54 | cargo install --path plank 55 | ``` 56 | 57 | ## Editor support 58 | 59 | If you are using Visual Studio Code, there is an extension that provides syntax highligting and displays diagnostics provided by plank language server. 60 | 61 | You can find the extension [on the VSCode extension marketplace](https://marketplace.visualstudio.com/items?itemName=jDomantas.plank), and you can find its code on [jDomantas/plank-vscode](https://github.com/jDomantas/plank-vscode). 62 | 63 | You can install plank language server from this repo: 64 | 65 | ``` 66 | git clone https://github.com/jDomantas/plank.git 67 | cd plank 68 | cargo install --path plank-server 69 | ``` 70 | 71 | By default (I think) cargo installs binaries to a place that is on your path, so everything should work without further intervention. If for some reason that isn't the case, you can provide path to `plank-server` executable in vscode configuration. 72 | 73 | ## Running tests 74 | 75 | Compiler and interpreter are tested by throwing programs at them and verifying that the outcome matches the expected one. The program that is responsible for that is in `tests` crate. x86 backend is not tested. 76 | 77 | You can run tests by running `cargo run -p tests` in repository root. More precisely, test runner expects to find the following directories: 78 | 79 | * `./examples` - we want to make sure that the examples aren't broken 80 | * `./tests/compile-fail` - programs that should not build 81 | * `./tests/pass` - programs that should produce correct output when ran with given input. 82 | 83 | Currently there are only a couple of test programs, but this will be improved over time. Or maybe not. I probably won't work on this after the semester. 84 | -------------------------------------------------------------------------------- /plank-ir/src/optimization/dead_drop_elimination.rs: -------------------------------------------------------------------------------- 1 | use super::Rewriter; 2 | use analysis::{self, Loc}; 3 | use ir::{BlockEnd, BlockId, Function, Instruction, Program, Reg}; 4 | use std::collections::{HashMap, HashSet}; 5 | 6 | struct Liveness<'a> { 7 | function: &'a Function, 8 | reg: Reg, 9 | live_locations: HashSet, 10 | checked_blocks: HashSet<(BlockId, bool)>, 11 | } 12 | 13 | impl<'a> Liveness<'a> { 14 | fn new(function: &'a Function, reg: Reg) -> Self { 15 | Liveness { 16 | function, 17 | reg, 18 | live_locations: HashSet::new(), 19 | checked_blocks: HashSet::new(), 20 | } 21 | } 22 | 23 | fn walk_block(&mut self, id: BlockId, live_start: bool) { 24 | if self.checked_blocks.contains(&(id, live_start)) { 25 | return; 26 | } 27 | self.checked_blocks.insert((id, live_start)); 28 | let block = &self.function.blocks[&id]; 29 | let mut is_live = live_start; 30 | for (pos, instr) in block.ops.iter().enumerate() { 31 | if is_live { 32 | self.live_locations.insert(Loc { block: id, pos }); 33 | } 34 | if analysis::initialized_register(instr) == Some(self.reg) { 35 | is_live = true; 36 | } 37 | if let Instruction::Drop(reg) = *instr { 38 | if reg == self.reg { 39 | is_live = false; 40 | } 41 | } 42 | } 43 | if is_live { 44 | self.live_locations.insert(Loc { 45 | block: id, 46 | pos: block.ops.len(), 47 | }); 48 | } 49 | match block.end { 50 | BlockEnd::Branch(_, a, b) => { 51 | self.walk_block(a, is_live); 52 | self.walk_block(b, is_live); 53 | } 54 | BlockEnd::Jump(a) => { 55 | self.walk_block(a, is_live); 56 | } 57 | BlockEnd::Return(_) | BlockEnd::ReturnProc | BlockEnd::Unreachable => {} 58 | } 59 | } 60 | } 61 | 62 | fn get_live_locations(f: &Function, reg: Reg) -> HashSet { 63 | let mut ctx = Liveness::new(f, reg); 64 | for &id in f.blocks.keys() { 65 | let is_start = f.start_block == Some(id); 66 | ctx.walk_block(id, is_start && f.parameters.contains(®)); 67 | } 68 | ctx.live_locations 69 | } 70 | 71 | struct Context { 72 | live: HashMap>, 73 | } 74 | 75 | impl Rewriter for Context { 76 | fn rewrite_function(&mut self, f: &mut Function) { 77 | self.live.clear(); 78 | for ® in f.registers.keys() { 79 | self.live.insert(reg, get_live_locations(f, reg)); 80 | } 81 | super::rewrite_function(self, f); 82 | } 83 | 84 | fn rewrite_instruction(&mut self, loc: Loc, instr: &mut Instruction) { 85 | let remove = if let Instruction::Drop(reg) = *instr { 86 | if let Some(locs) = self.live.get(®) { 87 | !locs.contains(&loc) 88 | } else { 89 | true 90 | } 91 | } else { 92 | false 93 | }; 94 | if remove { 95 | *instr = Instruction::Nop; 96 | } 97 | } 98 | } 99 | 100 | pub fn rewrite(program: &mut Program) { 101 | let mut ctx = Context { 102 | live: HashMap::new(), 103 | }; 104 | ctx.rewrite_program(program); 105 | } 106 | -------------------------------------------------------------------------------- /plank-frontend/src/dead_code.rs: -------------------------------------------------------------------------------- 1 | use ast::cfg::{Block, BlockEnd, BlockId, BlockLink, Function, Instruction, Program}; 2 | use plank_syntax::position::Spanned; 3 | use std::collections::{HashSet, VecDeque}; 4 | use CompileCtx; 5 | 6 | fn function_block_chain(f: &Function) -> VecDeque { 7 | let mut blocks = VecDeque::new(); 8 | let mut current = if let Some(block) = f.start_block { 9 | block 10 | } else { 11 | return blocks; 12 | }; 13 | loop { 14 | blocks.push_back(current); 15 | match f.blocks[¤t].link { 16 | BlockLink::Strong(next) | BlockLink::Weak(next) => current = next, 17 | BlockLink::None => break, 18 | } 19 | } 20 | blocks 21 | } 22 | 23 | fn report_unreachable(block: &Block, ctx: &mut CompileCtx) { 24 | let mut span = Spanned::span(&block.ops[0]); 25 | for i in &block.ops { 26 | match **i { 27 | Instruction::StartStatement => { 28 | span = Spanned::span(i); 29 | break; 30 | } 31 | _ => { 32 | span = span.merge(Spanned::span(i)); 33 | } 34 | } 35 | } 36 | ctx.reporter 37 | .warning("dead code detected", span) 38 | .span(span) 39 | .build(); 40 | } 41 | 42 | fn can_be_dead(block: &Block) -> bool { 43 | for op in &block.ops { 44 | match **op { 45 | Instruction::Drop(_) => {} 46 | _ => return true, 47 | } 48 | } 49 | false 50 | } 51 | 52 | fn analyze_function(f: &mut Function, ctx: &mut CompileCtx) { 53 | let mut blocks = function_block_chain(f); 54 | debug_assert_eq!(blocks.len(), f.blocks.len()); 55 | let mut reachable = HashSet::new(); 56 | let mut strong_reachable = None; 57 | let mut queue = VecDeque::new(); 58 | if let Some(block) = f.start_block { 59 | queue.push_back(block); 60 | } 61 | let mut follow_strong = false; 62 | while !queue.is_empty() { 63 | while let Some(block) = queue.pop_front() { 64 | if !reachable.contains(&block) { 65 | reachable.insert(block); 66 | let block = &f.blocks[&block]; 67 | match block.end { 68 | BlockEnd::Branch(_, a, b) => { 69 | queue.push_back(a); 70 | queue.push_back(b); 71 | } 72 | BlockEnd::Jump(next) => { 73 | queue.push_back(next); 74 | } 75 | BlockEnd::Return(_) | BlockEnd::Error => {} 76 | } 77 | if follow_strong { 78 | if let BlockLink::Strong(next) = block.link { 79 | queue.push_back(next); 80 | } 81 | } 82 | } 83 | } 84 | if strong_reachable.is_none() { 85 | strong_reachable = Some(reachable.clone()); 86 | } 87 | follow_strong = true; 88 | while let Some(block) = blocks.pop_front() { 89 | if !reachable.contains(&block) && can_be_dead(&f.blocks[&block]) { 90 | report_unreachable(&f.blocks[&block], ctx); 91 | queue.push_back(block); 92 | break; 93 | } 94 | } 95 | } 96 | let strong_reachable = strong_reachable.unwrap_or_default(); 97 | f.blocks.retain(|k, _| strong_reachable.contains(k)); 98 | } 99 | 100 | pub(crate) fn remove_dead_code(program: &mut Program, ctx: &mut CompileCtx) { 101 | for f in program.functions.values_mut() { 102 | analyze_function(f, ctx); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /plank-x86-backend/src/x86.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | #[derive(PartialEq, Eq, Debug, Hash, Copy, Clone)] 4 | pub enum Register { 5 | Eax, 6 | Ebx, 7 | Ecx, 8 | Edx, 9 | Ebp, 10 | Esp, 11 | Esi, 12 | Edi, 13 | Ax, 14 | Bx, 15 | Cx, 16 | Dx, 17 | Al, 18 | Ah, 19 | Bl, 20 | Bh, 21 | Cl, 22 | Ch, 23 | Dl, 24 | Dh, 25 | } 26 | 27 | #[derive(PartialEq, Eq, Debug, Hash, Copy, Clone)] 28 | pub struct Memory { 29 | pub register: Register, 30 | pub offset: i32, 31 | pub ptr_size: u32, 32 | } 33 | 34 | #[derive(PartialEq, Eq, Debug, Hash, Copy, Clone)] 35 | pub enum Rm { 36 | Register(Register), 37 | Memory(Memory), 38 | } 39 | 40 | #[derive(PartialEq, Eq, Debug, Hash, Clone)] 41 | pub enum TwoArgs { 42 | RmImm(Rm, Immediate), 43 | RegRm(Register, Rm), 44 | RmReg(Rm, Register), 45 | } 46 | 47 | #[derive(PartialEq, Eq, Debug, Hash, Clone)] 48 | pub enum Immediate { 49 | Label(Label), 50 | Constant(u64), 51 | } 52 | 53 | #[derive(PartialEq, Eq, Debug, Hash, Copy, Clone)] 54 | pub enum Condition { 55 | Above, 56 | AboveEqual, 57 | Below, 58 | BelowEqual, 59 | Equal, 60 | NotEqual, 61 | Greater, 62 | GreaterEqual, 63 | Less, 64 | LessEqual, 65 | } 66 | 67 | impl Condition { 68 | pub fn opposite(&self) -> Condition { 69 | match *self { 70 | Condition::Above => Condition::BelowEqual, 71 | Condition::AboveEqual => Condition::Below, 72 | Condition::Below => Condition::AboveEqual, 73 | Condition::BelowEqual => Condition::Above, 74 | Condition::Equal => Condition::NotEqual, 75 | Condition::Greater => Condition::LessEqual, 76 | Condition::GreaterEqual => Condition::Less, 77 | Condition::Less => Condition::GreaterEqual, 78 | Condition::LessEqual => Condition::Greater, 79 | Condition::NotEqual => Condition::Equal, 80 | } 81 | } 82 | 83 | pub fn order_opposite(&self) -> Condition { 84 | match *self { 85 | Condition::Above => Condition::Below, 86 | Condition::AboveEqual => Condition::BelowEqual, 87 | Condition::Below => Condition::Above, 88 | Condition::BelowEqual => Condition::AboveEqual, 89 | Condition::Equal => panic!("no order opposite for Condition::Equal"), 90 | Condition::Greater => Condition::Less, 91 | Condition::GreaterEqual => Condition::LessEqual, 92 | Condition::Less => Condition::Greater, 93 | Condition::LessEqual => Condition::GreaterEqual, 94 | Condition::NotEqual => panic!("no order opposite for Condition::NotEqual"), 95 | } 96 | } 97 | } 98 | 99 | #[derive(PartialEq, Eq, Debug, Hash, Clone)] 100 | pub enum Label { 101 | Unnamed(u32), 102 | Named(Rc), 103 | String(u32), 104 | } 105 | 106 | #[derive(PartialEq, Eq, Debug, Hash, Clone)] 107 | pub enum Instruction { 108 | Invalid, 109 | Mov(TwoArgs), 110 | MovSX(Register, Rm), 111 | MovZX(Register, Rm), 112 | Add(TwoArgs), 113 | Sub(TwoArgs), 114 | Mul(Rm), 115 | Imul(Rm), 116 | ImulReg(Register, Rm), 117 | Div(Rm), 118 | Idiv(Rm), 119 | And(TwoArgs), 120 | Or(TwoArgs), 121 | Xor(TwoArgs), 122 | Cwd, 123 | Cdq, 124 | Setcc(Condition, Rm), 125 | Jmp(Label), 126 | Jcc(Condition, Label), 127 | Neg(Rm), 128 | Lea(Register, Memory), 129 | Push(Rm), 130 | Pop(Rm), 131 | Test(TwoArgs), 132 | Cmp(TwoArgs), 133 | Call(Immediate), 134 | CallVirt(Rm), 135 | Ret, 136 | Label(Label), 137 | } 138 | 139 | #[derive(Debug, Clone)] 140 | pub struct Program { 141 | pub functions: Vec>, 142 | pub strings: Vec>, 143 | } 144 | -------------------------------------------------------------------------------- /plank-ir/src/ir.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::rc::Rc; 3 | 4 | pub const POINTER_SIZE: u32 = 4; 5 | pub const FUNCTION_SIZE: u32 = 4; 6 | 7 | #[derive(PartialEq, Eq, Debug, Hash, Clone)] 8 | pub struct Symbol(pub Rc); 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct Program { 12 | pub functions: HashMap, 13 | } 14 | 15 | #[derive(Debug, Copy, Clone)] 16 | pub struct Layout { 17 | pub size: u32, 18 | pub align: u32, 19 | pub atomic: bool, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub struct Function { 24 | pub parameters: Vec, 25 | pub output_layout: Option, 26 | pub registers: HashMap, 27 | pub blocks: HashMap, 28 | pub start_block: Option, 29 | } 30 | 31 | #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Copy, Clone)] 32 | pub struct Reg(pub u32); 33 | 34 | #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Copy, Clone)] 35 | pub struct BlockId(pub u32); 36 | 37 | #[derive(Debug, Clone)] 38 | pub struct Block { 39 | pub ops: Vec, 40 | pub end: BlockEnd, 41 | } 42 | 43 | #[derive(Debug, Clone)] 44 | pub enum Instruction { 45 | /// `unreachable` 46 | Unreachable, 47 | /// `nop` 48 | Nop, 49 | /// `init reg` 50 | Init(Reg), 51 | /// `drop reg` 52 | Drop(Reg), 53 | /// `reg = op a b` 54 | BinaryOp(Reg, BinaryOp, Value, Value), 55 | /// `reg = op value` 56 | UnaryOp(Reg, UnaryOp, Value), 57 | /// `reg = sym(val1, val2, ...)` 58 | Call(Reg, Symbol, Vec), 59 | /// sym(val1, val2, ...) 60 | CallProc(Symbol, Vec), 61 | /// `reg = sym(val1, val2, ...)` 62 | CallVirt(Reg, Value, Vec), 63 | /// sym(val1, val2, ...) 64 | CallProcVirt(Value, Vec), 65 | /// `*(value + offset) = value` 66 | DerefStore(Value, u32, Value), 67 | /// `reg = *(value + offset)` 68 | DerefLoad(Reg, Value, u32), 69 | /// `*(® + offset) = value` 70 | Store(Reg, u32, Value), 71 | /// `reg = *(® + offset)` 72 | Load(Reg, Reg, u32), 73 | /// `reg = ® + offset` 74 | TakeAddress(Reg, Reg, u32), 75 | /// `reg = value` 76 | Assign(Reg, Value), 77 | /// `reg = cast value` 78 | CastAssign(Reg, Value), 79 | } 80 | 81 | #[derive(PartialEq, Eq, Debug, Clone)] 82 | pub enum Value { 83 | Int(u64, Size), 84 | Reg(Reg), 85 | Symbol(Symbol), 86 | Bytes(Vec), 87 | Undef, 88 | } 89 | 90 | #[derive(Debug, Clone)] 91 | pub enum BlockEnd { 92 | Return(Value), 93 | ReturnProc, 94 | Jump(BlockId), 95 | Branch(Value, BlockId, BlockId), 96 | Unreachable, 97 | } 98 | 99 | #[derive(Debug, Copy, Clone)] 100 | pub enum BinaryOp { 101 | IntOp(IntOp, Signedness, Size), 102 | BitOp(BitOp, Size), 103 | Eq, 104 | Neq, 105 | } 106 | 107 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 108 | pub enum Signedness { 109 | Unsigned, 110 | Signed, 111 | } 112 | 113 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 114 | pub enum Size { 115 | Bit8, 116 | Bit16, 117 | Bit32, 118 | } 119 | 120 | impl Size { 121 | pub fn in_bytes(&self) -> u32 { 122 | match *self { 123 | Size::Bit8 => 1, 124 | Size::Bit16 => 2, 125 | Size::Bit32 => 4, 126 | } 127 | } 128 | 129 | pub fn truncate(&self, value: u64) -> u64 { 130 | match *self { 131 | Size::Bit8 => value & 0xff, 132 | Size::Bit16 => value & 0xffff, 133 | Size::Bit32 => value & 0xffffffff, 134 | } 135 | } 136 | 137 | pub fn to_signed(&self, value: u64) -> i64 { 138 | match *self { 139 | Size::Bit8 => (value as i8) as i64, 140 | Size::Bit16 => (value as i16) as i64, 141 | Size::Bit32 => (value as i32) as i64, 142 | } 143 | } 144 | } 145 | 146 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 147 | pub enum IntOp { 148 | Add, 149 | Sub, 150 | Mul, 151 | Div, 152 | Mod, 153 | Less, 154 | LessEq, 155 | Greater, 156 | GreaterEq, 157 | } 158 | 159 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 160 | pub enum BitOp { 161 | And, 162 | Or, 163 | Xor, 164 | } 165 | 166 | #[derive(Debug, Copy, Clone)] 167 | pub enum UnaryOp { 168 | Negate(Signedness, Size), 169 | } 170 | -------------------------------------------------------------------------------- /plank-frontend/src/cast_check.rs: -------------------------------------------------------------------------------- 1 | use ast::typed::{Expr, Function, Program, Statement, Type, TypedExpr}; 2 | use plank_syntax::position::Spanned; 3 | use struct_layout::{LayoutEngine, LayoutResult}; 4 | use CompileCtx; 5 | 6 | pub(crate) fn check_casts(program: &mut Program, ctx: &mut CompileCtx) { 7 | let layouts = LayoutEngine::new(&program.structs); 8 | let mut ctx = Context::new(ctx, layouts); 9 | for f in &mut program.functions { 10 | ctx.check_function(f); 11 | } 12 | } 13 | 14 | struct Context<'a> { 15 | ctx: &'a mut CompileCtx, 16 | layouts: LayoutEngine<'a>, 17 | } 18 | 19 | impl<'a> Context<'a> { 20 | fn new(ctx: &'a mut CompileCtx, layouts: LayoutEngine<'a>) -> Self { 21 | Context { ctx, layouts } 22 | } 23 | 24 | fn check_function(&mut self, fn_: &mut Function) { 25 | if let Some(ref mut stmt) = fn_.body { 26 | self.check_statement(stmt); 27 | } 28 | } 29 | 30 | fn check_statement(&mut self, stmt: &mut Spanned) { 31 | match **stmt { 32 | Statement::Block(ref mut stmts) => { 33 | for stmt in stmts { 34 | self.check_statement(stmt); 35 | } 36 | } 37 | Statement::Break | Statement::Continue | Statement::Error => {} 38 | Statement::Expr(ref mut expr) | Statement::Return(ref mut expr) => { 39 | self.check_expr(expr) 40 | } 41 | Statement::If(ref mut cond, ref mut then, ref mut else_) => { 42 | self.check_expr(cond); 43 | self.check_statement(then); 44 | if let Some(ref mut else_) = *else_ { 45 | self.check_statement(else_); 46 | } 47 | } 48 | Statement::Let(_, _, _, Some(ref mut value)) => { 49 | self.check_expr(value); 50 | } 51 | Statement::Let(_, _, _, None) => {} 52 | Statement::Loop(ref mut body) => self.check_statement(body), 53 | Statement::While(ref mut cond, ref mut body) => { 54 | self.check_expr(cond); 55 | self.check_statement(body); 56 | } 57 | } 58 | } 59 | 60 | fn check_expr(&mut self, expr: &mut TypedExpr) { 61 | match *expr.expr.as_mut() { 62 | Expr::Binary(ref mut lhs, _, ref mut rhs) => { 63 | self.check_expr(lhs); 64 | self.check_expr(rhs); 65 | return; 66 | } 67 | Expr::Call(ref mut expr, ref mut params) => { 68 | self.check_expr(expr); 69 | for param in params { 70 | self.check_expr(param); 71 | } 72 | return; 73 | } 74 | Expr::Field(ref mut expr, _) | Expr::Unary(_, ref mut expr) => { 75 | self.check_expr(expr); 76 | return; 77 | } 78 | Expr::Error | Expr::Literal(_) | Expr::Name(_, _) => return, 79 | Expr::Cast(ref mut value, ref typ) => { 80 | self.check_expr(value); 81 | let value_layout = self.layouts.size_of(&value.typ); 82 | let typ_layout = self.layouts.size_of(typ); 83 | match (value_layout, typ_layout) { 84 | (LayoutResult::Ok(a), LayoutResult::Ok(b)) => { 85 | if a == b { 86 | return; 87 | } 88 | self.ctx 89 | .reporter 90 | .error("cannot cast between types of different sizes", expr.span) 91 | .span(expr.span) 92 | .build(); 93 | } 94 | (LayoutResult::Error, _) | (_, LayoutResult::Error) => {} 95 | (LayoutResult::HasTypeParam, _) | (_, LayoutResult::HasTypeParam) => { 96 | self.ctx 97 | .reporter 98 | .error("both types must have known fixed sizes", expr.span) 99 | .span(expr.span) 100 | .build(); 101 | } 102 | } 103 | } 104 | } 105 | *expr.expr = Expr::Error; 106 | expr.typ = Type::Error; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /plank-frontend/src/ast/typed.rs: -------------------------------------------------------------------------------- 1 | pub use ast::resolved::{Mutability, Symbol}; 2 | pub use plank_syntax::ast::{BinaryOp, FunctionType, Literal, Number, Signedness, Size, UnaryOp}; 3 | use plank_syntax::position::{Span, Spanned}; 4 | use std::collections::HashMap; 5 | use std::rc::Rc; 6 | 7 | #[derive(Debug, Clone)] 8 | pub enum Expr { 9 | Binary(TypedExpr, Spanned, TypedExpr), 10 | Unary(Spanned, TypedExpr), 11 | Call(TypedExpr, Vec), 12 | Field(TypedExpr, Spanned), 13 | Name(Spanned, Vec>), 14 | Literal(Literal), 15 | Cast(TypedExpr, Spanned), 16 | Error, 17 | } 18 | 19 | #[derive(Debug, Clone)] 20 | pub struct TypedExpr { 21 | pub expr: Box, 22 | pub span: Span, 23 | pub typ: Type, 24 | } 25 | 26 | #[derive(Debug, Clone)] 27 | pub enum Statement { 28 | If( 29 | TypedExpr, 30 | Box>, 31 | Option>>, 32 | ), 33 | Loop(Box>), 34 | While(TypedExpr, Box>), 35 | Break, 36 | Continue, 37 | Return(TypedExpr), 38 | Let( 39 | Mutability, 40 | Spanned, 41 | Spanned, 42 | Option, 43 | ), 44 | Block(Vec>), 45 | Expr(TypedExpr), 46 | Error, 47 | } 48 | 49 | #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Copy, Clone)] 50 | pub struct TypeVar(pub u32); 51 | 52 | #[derive(Debug, Clone)] 53 | pub enum Type { 54 | Var(TypeVar), 55 | Bool, 56 | Unit, 57 | Int(Signedness, Size), 58 | Concrete(Symbol, Rc<[Type]>), 59 | Pointer(Mutability, Rc), 60 | Function(Rc<[Type]>, Rc), 61 | Error, 62 | } 63 | 64 | impl Type { 65 | pub fn replace(&self, mapping: &HashMap) -> Type { 66 | match *self { 67 | Type::Bool | Type::Error | Type::Int(_, _) | Type::Var(_) | Type::Unit => self.clone(), 68 | Type::Concrete(sym, ref params) => { 69 | if let Some(typ) = mapping.get(&sym).cloned() { 70 | typ 71 | } else { 72 | let params = params 73 | .iter() 74 | .map(|ty| ty.replace(mapping)) 75 | .collect::>() 76 | .into(); 77 | Type::Concrete(sym, params) 78 | } 79 | } 80 | Type::Function(ref params, ref out) => { 81 | let params = params 82 | .iter() 83 | .map(|ty| ty.replace(mapping)) 84 | .collect::>() 85 | .into(); 86 | let out = out.replace(mapping); 87 | Type::Function(params, Rc::new(out)) 88 | } 89 | Type::Pointer(mutability, ref to) => { 90 | let to = to.replace(mapping); 91 | Type::Pointer(mutability, Rc::new(to)) 92 | } 93 | } 94 | } 95 | 96 | pub fn is_atomic(&self) -> bool { 97 | match *self { 98 | Type::Bool 99 | | Type::Function(_, _) 100 | | Type::Int(_, _) 101 | | Type::Pointer(_, _) 102 | | Type::Unit => true, 103 | Type::Concrete(_, _) => false, 104 | Type::Error | Type::Var(_) => panic!("cannot say atomicity of {:?}", self), 105 | } 106 | } 107 | } 108 | 109 | #[derive(Debug, Clone)] 110 | pub struct FnParam { 111 | pub mutability: Mutability, 112 | pub name: Symbol, 113 | pub typ: Type, 114 | } 115 | 116 | #[derive(Debug, Clone)] 117 | pub struct Function { 118 | pub complete_span: Span, 119 | pub fn_type: FunctionType, 120 | pub name: Symbol, 121 | pub type_params: Vec, 122 | pub params: Vec, 123 | pub return_type: Type, 124 | pub body: Option>, 125 | } 126 | 127 | #[derive(Debug, Clone)] 128 | pub struct Field { 129 | pub name: Symbol, 130 | pub typ: Type, 131 | } 132 | 133 | #[derive(Debug, Clone)] 134 | pub struct Struct { 135 | pub complete_span: Span, 136 | pub name: Symbol, 137 | pub type_params: Vec, 138 | pub fields: Vec, 139 | } 140 | 141 | #[derive(Debug, Clone)] 142 | pub struct Program { 143 | pub structs: HashMap, 144 | pub functions: Vec, 145 | } 146 | -------------------------------------------------------------------------------- /tests/src/test_parser.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum ParseError { 3 | MalformedBinary(&'static str), 4 | DuplicateBinary(&'static str), 5 | NoOutput, 6 | ErrorsAndIo, 7 | } 8 | 9 | impl ::std::fmt::Display for ParseError { 10 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 11 | use self::ParseError::*; 12 | match *self { 13 | MalformedBinary(name) => write!(f, "annotation `{}` is malformed", name), 14 | DuplicateBinary(name) => write!(f, "annotation `{}` appears multiple times", name), 15 | NoOutput => write!(f, "input is provided but not output"), 16 | ErrorsAndIo => write!(f, "test provides both build errors and io"), 17 | } 18 | } 19 | } 20 | 21 | #[derive(Debug)] 22 | pub struct Error { 23 | pub line: u32, 24 | pub message: String, 25 | } 26 | 27 | #[derive(Debug)] 28 | pub enum Expectation { 29 | /// Build should fail, and all given errors must be present. 30 | BuildErrors(Vec), 31 | /// Build should succeed, and when ran with given 32 | /// input program should produce given output. 33 | Io { input: Vec, output: Vec }, 34 | /// Build should succeed, but program execution is not tested. 35 | BuildSuccess, 36 | } 37 | 38 | fn get_errors(source: &str) -> Vec { 39 | const ANNOTATION: &'static str = "// ERROR: "; 40 | let mut errors = Vec::new(); 41 | for (line_num, line) in source.lines().enumerate() { 42 | if let Some((index, _)) = line.match_indices(ANNOTATION).next() { 43 | let message_from = index + ANNOTATION.len(); 44 | let message = line[message_from..].into(); 45 | errors.push(Error { 46 | line: line_num as u32, 47 | message, 48 | }) 49 | } 50 | } 51 | errors 52 | } 53 | 54 | fn decode_bytes(mut from: &str) -> Result, ()> { 55 | let mut result = Vec::new(); 56 | while let Some(ch) = from.chars().next() { 57 | match ch { 58 | '\\' => { 59 | if from.chars().skip(1).next() != Some('x') { 60 | return Err(()); 61 | } 62 | let b1 = from 63 | .chars() 64 | .skip(2) 65 | .next() 66 | .ok_or(())? 67 | .to_digit(16) 68 | .ok_or(())? as u8; 69 | let b2 = from 70 | .chars() 71 | .skip(3) 72 | .next() 73 | .ok_or(())? 74 | .to_digit(16) 75 | .ok_or(())? as u8; 76 | result.push((b1 << 4) + b2); 77 | from = &from[4..]; 78 | } 79 | _ => { 80 | if ch as u32 >= 127 || (ch as u32) < 32 { 81 | return Err(()); 82 | } 83 | result.push(ch as u8); 84 | from = &from[1..]; 85 | } 86 | } 87 | } 88 | Ok(result) 89 | } 90 | 91 | fn get_io(source: &str, name: &'static str) -> Result>, ParseError> { 92 | let mut annotation = None; 93 | let pattern = format!("// {}: ", name); 94 | for line in source.lines() { 95 | if let Some((index, _)) = line.match_indices(&pattern).next() { 96 | let from = index + pattern.len(); 97 | let bytes = 98 | decode_bytes(&line[from..]).map_err(|()| ParseError::MalformedBinary(name))?; 99 | if annotation.is_some() { 100 | return Err(ParseError::DuplicateBinary(name)); 101 | } 102 | annotation = Some(bytes); 103 | } 104 | } 105 | Ok(annotation) 106 | } 107 | 108 | pub fn parse_test(source: &str) -> Result { 109 | let errors = get_errors(source); 110 | let input = get_io(source, "INPUT")?; 111 | let output = get_io(source, "OUTPUT")?; 112 | if output.is_none() && input.is_some() { 113 | return Err(ParseError::NoOutput); 114 | } 115 | if (output.is_some() || input.is_some()) && !errors.is_empty() { 116 | return Err(ParseError::ErrorsAndIo); 117 | } 118 | Ok(if !errors.is_empty() { 119 | Expectation::BuildErrors(errors) 120 | } else if input.is_some() || output.is_some() { 121 | Expectation::Io { 122 | input: input.unwrap_or_else(Vec::new), 123 | output: output.unwrap_or_else(Vec::new), 124 | } 125 | } else { 126 | Expectation::BuildSuccess 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /plank-syntax/src/ast.rs: -------------------------------------------------------------------------------- 1 | use position::{Span, Spanned}; 2 | 3 | #[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Clone)] 4 | pub struct Ident(pub String); 5 | 6 | #[derive(PartialEq, Eq, Debug, Clone)] 7 | pub enum Literal { 8 | Number(Number), 9 | Bool(bool), 10 | Char(u8), 11 | Str(Vec), 12 | Unit, 13 | } 14 | 15 | #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] 16 | pub struct Number { 17 | pub value: u64, 18 | pub typ: Option<(Signedness, Size)>, 19 | } 20 | 21 | impl ::std::fmt::Display for Number { 22 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 23 | write!(f, "{}", self.value)?; 24 | if let Some((sign, size)) = self.typ { 25 | match sign { 26 | Signedness::Signed => write!(f, "i")?, 27 | Signedness::Unsigned => write!(f, "u")?, 28 | } 29 | match size { 30 | Size::Bit8 => write!(f, "8")?, 31 | Size::Bit16 => write!(f, "16")?, 32 | Size::Bit32 => write!(f, "32")?, 33 | } 34 | } 35 | Ok(()) 36 | } 37 | } 38 | 39 | #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] 40 | pub enum Signedness { 41 | Signed, 42 | Unsigned, 43 | } 44 | 45 | #[derive(PartialEq, Eq, Hash, Debug, Copy, Clone)] 46 | pub enum Size { 47 | Bit8, 48 | Bit16, 49 | Bit32, 50 | } 51 | 52 | #[derive(Debug, Clone)] 53 | pub enum Expr { 54 | Binary(Box>, Spanned, Box>), 55 | Unary(Spanned, Box>), 56 | Call(Box>, Vec), 57 | Field(Box>, Spanned), 58 | Name(Spanned, Vec>), 59 | Literal(Literal), 60 | Cast(Box>, Spanned), 61 | Error, 62 | } 63 | 64 | #[derive(Debug, Clone)] 65 | pub enum CallParam { 66 | Named(Spanned, Spanned), 67 | Unnamed(Spanned), 68 | } 69 | 70 | #[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone)] 71 | pub enum BinaryOp { 72 | Add, 73 | Subtract, 74 | Multiply, 75 | Divide, 76 | Modulo, 77 | Equal, 78 | NotEqual, 79 | Less, 80 | LessEqual, 81 | Greater, 82 | GreaterEqual, 83 | And, 84 | Or, 85 | Assign, 86 | } 87 | 88 | #[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone)] 89 | pub enum UnaryOp { 90 | Plus, 91 | Minus, 92 | Not, 93 | Deref, 94 | AddressOf, 95 | MutAddressOf, 96 | } 97 | 98 | #[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Copy, Clone)] 99 | pub enum Mutability { 100 | Mut, 101 | Const, 102 | } 103 | 104 | #[derive(Debug, Clone)] 105 | pub enum Statement { 106 | If( 107 | Spanned, 108 | Box>, 109 | Option>>, 110 | ), 111 | Loop(Box>), 112 | While(Spanned, Box>), 113 | Break, 114 | Continue, 115 | Return(Spanned), 116 | Let( 117 | Mutability, 118 | Spanned, 119 | Option>, 120 | Option>, 121 | ), 122 | Block(Vec>), 123 | Expr(Spanned), 124 | Error, 125 | } 126 | 127 | #[derive(Debug, Clone)] 128 | pub enum Type { 129 | Wildcard, 130 | I8, 131 | U8, 132 | I16, 133 | U16, 134 | I32, 135 | U32, 136 | Bool, 137 | Unit, 138 | Concrete(Spanned, Vec>), 139 | Pointer(Mutability, Box>), 140 | Function(Vec>, Box>), 141 | Error, 142 | } 143 | 144 | #[derive(Debug, Clone)] 145 | pub struct ItemName { 146 | pub name: Spanned, 147 | pub type_params: Vec>, 148 | } 149 | 150 | #[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone)] 151 | pub enum FunctionType { 152 | Normal, 153 | Extern, 154 | } 155 | 156 | #[derive(Debug, Clone)] 157 | pub struct FnParam { 158 | pub mutability: Mutability, 159 | pub name: Spanned, 160 | pub typ: Spanned, 161 | } 162 | 163 | #[derive(Debug, Clone)] 164 | pub struct Function { 165 | pub complete_span: Span, 166 | pub fn_type: FunctionType, 167 | pub name: ItemName, 168 | pub params: Vec, 169 | pub return_type: Spanned, 170 | pub body: Option>, 171 | } 172 | 173 | #[derive(Debug, Clone)] 174 | pub struct Field { 175 | pub name: Spanned, 176 | pub typ: Spanned, 177 | } 178 | 179 | #[derive(Debug, Clone)] 180 | pub struct Struct { 181 | pub complete_span: Span, 182 | pub name: ItemName, 183 | pub fields: Vec, 184 | } 185 | 186 | #[derive(Debug, Clone)] 187 | pub struct Program { 188 | pub structs: Vec, 189 | pub functions: Vec, 190 | pub possible_structs: Vec, 191 | pub possible_functions: Vec, 192 | } 193 | -------------------------------------------------------------------------------- /tasks/grammar/grammar.bnf: -------------------------------------------------------------------------------- 1 | ::= | "" 2 | ::= | 3 | ::= | "<" ">" 4 | ::= | "," 5 | 6 | ::= "struct" "{" "}" 7 | ::= "" | | "," 8 | ::= ":" 9 | 10 | ::= "extern" | "" 11 | ::= 12 | ::= | ";" 13 | ::= "fn" "(" ")" "->" | "fn" "(" ")" 14 | ::= "" | | "," 15 | ::= ":" 16 | 17 | ::= "_" | "*" | "*" "mut" | | | 18 | ::= "fn" "(" ")" "->" | "fn" "(" ")" 19 | ::= "" | | "," 20 | ::= | "<" ">" 21 | ::= "u8" | "u16" | "u32" | "i8" | "i16" | "i32" | "bool" | "unit" 22 | ::= | "," 23 | 24 | ::= "{" "}" 25 | ::= "" | 26 | ::= | | | | | | | | 27 | ::= "if" 28 | ::= "" | "else" | "else" 29 | ::= "loop" 30 | ::= "while" 31 | ::= "break" ";" 32 | ::= "continue" ";" 33 | ::= "return" ";" | "return" ";" 34 | ::= ";" 35 | 36 | ::= "let" ";" 37 | ::= "mut" | "" 38 | ::= "" | ":" 39 | ::= "" | "=" 40 | 41 | ::= 42 | ::= | "=" 43 | ::= | "||" 44 | ::= | "&&" 45 | ::= | 46 | ::= | 47 | ::= | 48 | ::= | 49 | ::= | "as" 50 | ::= | 51 | ::= | "." | "(" ")" 52 | ::= | | "(" ")" 53 | 54 | ::= "" | | "," 55 | ::= | ":" 56 | 57 | ::= "==" | "!=" 58 | ::= "<" | "<=" | ">" | ">=" 59 | ::= "+" | "-" 60 | ::= "*" | "/" | "%" 61 | ::= "+" | "-" | "*" | "&" | "&" "mut" | "!" 62 | 63 | ::= | "::" "<" ">" 64 | 65 | -- below is lexer stuff 66 | 67 | ::= | "_" 68 | ::= "" | 69 | ::= "_" | | 70 | 71 | ::= | | | 72 | 73 | ::= "true" | "false" 74 | ::= | 75 | ::= | 76 | ::= "i" | "u" | "i8" | "i16" | "i32" | "u8" | "u16" | "u32" 77 | ::= "'" "'" | "'\"'" 78 | ::= "\"" 79 | ::= "\"" | | "'" 80 | 81 | ::= | 82 | ::= "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" 83 | ::= "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | "y" | "z" 84 | ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" 85 | ::= "0" | 86 | 87 | ::= | "a" | "b" | "c" | "d" | "e" | "f" | "A" | "B" | "C" | "D" | "E" | "F" 88 | ::= "\\x" 89 | ::= | "\\n" | "\\\"" | "\\'" | "\\\\" | 90 | ::= | | " " | "!" | "#" | "$" | "%" | "&" | "(" | ")" | "*" | "+" | "," | "-" | "." | "/" | ":" | ";" | "<" | "=" | ">" | "?" | "@" | "[" | "]" | "^" | "_" | "`" | "{" | "|" | "}" | "~" 91 | -------------------------------------------------------------------------------- /plank-frontend/src/literal_size_check.rs: -------------------------------------------------------------------------------- 1 | use ast::typed::{ 2 | Expr, Literal, Number, Program, Signedness, Size, Statement, Type, TypedExpr, UnaryOp, 3 | }; 4 | use plank_errors::position::Span; 5 | use CompileCtx; 6 | 7 | struct Context<'a> { 8 | ctx: &'a mut CompileCtx, 9 | } 10 | 11 | impl<'a> Context<'a> { 12 | fn new(ctx: &'a mut CompileCtx) -> Self { 13 | Context { ctx } 14 | } 15 | 16 | fn check_expr(&mut self, expr: &mut TypedExpr) { 17 | let mut replace_with = None; 18 | match *expr.expr.as_mut() { 19 | Expr::Literal(Literal::Number(ref mut n)) => { 20 | let val = if n.value > ::std::i64::MAX as u64 { 21 | ::std::i64::MAX 22 | } else { 23 | n.value as i64 24 | }; 25 | n.value = check_literal(val, &expr.typ, expr.span, self.ctx) as u64; 26 | } 27 | Expr::Unary(op, ref mut value) => match (*op, value.expr.as_mut()) { 28 | (UnaryOp::Minus, &mut Expr::Literal(Literal::Number(n))) => { 29 | let val = if n.value > ::std::i64::MAX as u64 { 30 | ::std::i64::MIN 31 | } else { 32 | -(n.value as i64) 33 | }; 34 | let value = check_literal(val, &expr.typ, expr.span, self.ctx); 35 | replace_with = Some(Expr::Literal(Literal::Number(Number { 36 | value: value as u64, 37 | ..n 38 | }))); 39 | } 40 | _ => self.check_expr(value), 41 | }, 42 | Expr::Binary(ref mut a, _, ref mut b) => { 43 | self.check_expr(a); 44 | self.check_expr(b); 45 | } 46 | Expr::Call(ref mut f, ref mut params) => { 47 | self.check_expr(f); 48 | for param in params { 49 | self.check_expr(param); 50 | } 51 | } 52 | Expr::Cast(ref mut e, _) | Expr::Field(ref mut e, _) => self.check_expr(e), 53 | Expr::Error | Expr::Name(_, _) | Expr::Literal(_) => {} 54 | } 55 | if let Some(new_expr) = replace_with { 56 | *expr.expr = new_expr 57 | } 58 | } 59 | 60 | fn check_statement(&mut self, stmt: &mut Statement) { 61 | match *stmt { 62 | Statement::Block(ref mut stmts) => { 63 | for stmt in stmts { 64 | self.check_statement(stmt); 65 | } 66 | } 67 | Statement::Break 68 | | Statement::Continue 69 | | Statement::Error 70 | | Statement::Let(_, _, _, None) => {} 71 | Statement::Expr(ref mut expr) 72 | | Statement::Let(_, _, _, Some(ref mut expr)) 73 | | Statement::Return(ref mut expr) => { 74 | self.check_expr(expr); 75 | } 76 | Statement::If(ref mut cond, ref mut then, ref mut else_) => { 77 | self.check_expr(cond); 78 | self.check_statement(then); 79 | if let Some(ref mut stmt) = *else_ { 80 | self.check_statement(stmt); 81 | } 82 | } 83 | Statement::Loop(ref mut stmt) => { 84 | self.check_statement(stmt); 85 | } 86 | Statement::While(ref mut expr, ref mut body) => { 87 | self.check_expr(expr); 88 | self.check_statement(body); 89 | } 90 | } 91 | } 92 | } 93 | 94 | fn check_literal(value: i64, typ: &Type, span: Span, ctx: &mut CompileCtx) -> i64 { 95 | use self::Signedness::*; 96 | use self::Size::*; 97 | use std::{i16, i32, i64, i8, u16, u32, u8}; 98 | let (low, high) = match *typ { 99 | Type::Int(Signed, Bit8) => (i8::MIN as i64, i8::MAX as i64), 100 | Type::Int(Signed, Bit16) => (i16::MIN as i64, i16::MAX as i64), 101 | Type::Int(Signed, Bit32) => (i32::MIN as i64, i32::MAX as i64), 102 | Type::Int(Unsigned, Bit8) => (u8::MIN as i64, u8::MAX as i64), 103 | Type::Int(Unsigned, Bit16) => (u16::MIN as i64, u16::MAX as i64), 104 | Type::Int(Unsigned, Bit32) => (u32::MIN as i64, u32::MAX as i64), 105 | _ => (i64::MIN, i64::MAX), 106 | }; 107 | if value < low { 108 | let msg = format!("should not be below {}", low); 109 | ctx.reporter 110 | .error("int literal is out of bounds", span) 111 | .span_note(msg, span) 112 | .build(); 113 | 0 114 | } else if value > high { 115 | let msg = format!("should not be above {}", high); 116 | ctx.reporter 117 | .error("int literal is out of bounds", span) 118 | .span_note(msg, span) 119 | .build(); 120 | 0 121 | } else { 122 | value 123 | } 124 | } 125 | 126 | pub(crate) fn check_program(program: &mut Program, ctx: &mut CompileCtx) { 127 | let mut ctx = Context::new(ctx); 128 | for f in &mut program.functions { 129 | if let Some(ref mut body) = f.body { 130 | ctx.check_statement(body); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /plank-frontend/src/struct_layout.rs: -------------------------------------------------------------------------------- 1 | use ast::cfg::{Size, Symbol, Type}; 2 | use ast::typed::Struct; 3 | use std::collections::HashMap; 4 | 5 | const POINTER_SIZE: u32 = ::plank_ir::ir::POINTER_SIZE; 6 | const FUNCTION_SIZE: u32 = ::plank_ir::ir::FUNCTION_SIZE; 7 | 8 | #[derive(Debug, Copy, Clone)] 9 | pub enum LayoutResult { 10 | Ok(T), 11 | Error, 12 | HasTypeParam, 13 | } 14 | 15 | impl LayoutResult { 16 | fn map U>(self, f: F) -> LayoutResult { 17 | match self { 18 | LayoutResult::Ok(x) => LayoutResult::Ok(f(x)), 19 | LayoutResult::Error => LayoutResult::Error, 20 | LayoutResult::HasTypeParam => LayoutResult::HasTypeParam, 21 | } 22 | } 23 | 24 | pub fn unwrap(self) -> T { 25 | match self { 26 | LayoutResult::Ok(t) => t, 27 | _ => panic!("unwrapping unsuccessful layout result"), 28 | } 29 | } 30 | } 31 | 32 | pub struct LayoutEngine<'a> { 33 | structs: &'a HashMap, 34 | } 35 | 36 | impl<'a> LayoutEngine<'a> { 37 | pub fn new(structs: &'a HashMap) -> Self { 38 | LayoutEngine { structs } 39 | } 40 | 41 | #[allow(dead_code)] 42 | pub fn size_of(&self, ty: &Type) -> LayoutResult { 43 | self.size_align(ty).map(|(size, _)| size) 44 | } 45 | 46 | #[allow(dead_code)] 47 | pub fn align_of(&self, ty: &Type) -> LayoutResult { 48 | self.size_align(ty).map(|(_, align)| align) 49 | } 50 | 51 | pub fn size_align(&self, ty: &Type) -> LayoutResult<(u32, u32)> { 52 | match *ty { 53 | Type::Bool => LayoutResult::Ok((1, 1)), 54 | Type::Error => LayoutResult::Error, 55 | Type::Pointer(_, _) => LayoutResult::Ok((POINTER_SIZE, POINTER_SIZE)), 56 | Type::Function(_, _) => LayoutResult::Ok((FUNCTION_SIZE, FUNCTION_SIZE)), 57 | Type::Int(_, Size::Bit8) => LayoutResult::Ok((1, 1)), 58 | Type::Int(_, Size::Bit16) => LayoutResult::Ok((2, 2)), 59 | Type::Int(_, Size::Bit32) => LayoutResult::Ok((4, 4)), 60 | Type::Var(_) => LayoutResult::Error, 61 | Type::Unit => LayoutResult::Ok((0, 1)), 62 | Type::Concrete(sym, ref params) => { 63 | let s = match self.structs.get(&sym) { 64 | Some(s) => s, 65 | None => return LayoutResult::HasTypeParam, 66 | }; 67 | debug_assert_eq!(params.len(), s.type_params.len()); 68 | let mapping = s 69 | .type_params 70 | .iter() 71 | .cloned() 72 | .zip(params.iter().cloned()) 73 | .collect(); 74 | s.fields 75 | .iter() 76 | .map(|field| field.typ.replace(&mapping)) 77 | .fold(LayoutResult::Ok((0, 1)), |a, b| { 78 | match (a, self.size_align(&b)) { 79 | (LayoutResult::Ok((s, a)), LayoutResult::Ok((s2, a2))) => { 80 | let s = (s + a2 - 1) / a2 * a2 + s2; 81 | let a = lcm(a, a2); 82 | LayoutResult::Ok((s, a)) 83 | } 84 | (LayoutResult::Error, _) | (_, LayoutResult::Error) => { 85 | LayoutResult::Error 86 | } 87 | (LayoutResult::HasTypeParam, _) | (_, LayoutResult::HasTypeParam) => { 88 | LayoutResult::HasTypeParam 89 | } 90 | } 91 | }) 92 | .map(|(s, a)| { 93 | let s = (s + a - 1) / a * a; 94 | (s, a) 95 | }) 96 | } 97 | } 98 | } 99 | 100 | pub fn field_info(&self, ty: &Type, field: usize) -> (u32, Type) { 101 | match *ty { 102 | Type::Unit 103 | | Type::Bool 104 | | Type::Error 105 | | Type::Pointer(_, _) 106 | | Type::Function(_, _) 107 | | Type::Int(_, _) 108 | | Type::Var(_) => panic!("no fields on type"), 109 | Type::Concrete(sym, ref params) => { 110 | let s = &self.structs[&sym]; 111 | debug_assert_eq!(params.len(), s.type_params.len()); 112 | let mapping = s 113 | .type_params 114 | .iter() 115 | .cloned() 116 | .zip(params.iter().cloned()) 117 | .collect(); 118 | debug_assert!(field < s.fields.len()); 119 | let (size, last_size) = s 120 | .fields 121 | .iter() 122 | .take(field + 1) 123 | .map(|field| field.typ.replace(&mapping)) 124 | .fold((0, 0), |(s, _), ty| { 125 | let (s2, a2) = self.size_align(&ty).unwrap(); 126 | let s = (s + a2 - 1) / a2 * a2 + s2; 127 | (s, s2) 128 | }); 129 | let offset = size - last_size; 130 | let typ = s.fields[field].typ.clone(); 131 | (offset, typ) 132 | } 133 | } 134 | } 135 | } 136 | 137 | fn lcm(a: u32, b: u32) -> u32 { 138 | fn gcd(mut a: u32, mut b: u32) -> u32 { 139 | while b > 0 { 140 | a %= b; 141 | ::std::mem::swap(&mut a, &mut b); 142 | } 143 | a 144 | } 145 | a * b / gcd(a, b) 146 | } 147 | -------------------------------------------------------------------------------- /plank-server/src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate serde_derive; 3 | extern crate languageserver_types; 4 | extern crate serde; 5 | extern crate serde_json; 6 | #[macro_use] 7 | extern crate log; 8 | extern crate plank_errors; 9 | extern crate plank_frontend; 10 | extern crate plank_syntax; 11 | extern crate simple_logging; 12 | extern crate url; 13 | 14 | mod jsonrpc; 15 | mod transport; 16 | 17 | use jsonrpc::{Response, RpcHandler}; 18 | use languageserver_types as lst; 19 | use std::cell::RefCell; 20 | use std::io; 21 | use std::io::{BufRead, Write}; 22 | use std::ops::DerefMut; 23 | use transport::Transport; 24 | use url::Url; 25 | 26 | fn main() { 27 | simple_logging::log_to_stderr(log::LogLevelFilter::Info).expect("failed to initialize logging"); 28 | 29 | let stdin = io::stdin(); 30 | let stdin = stdin.lock(); 31 | let stdout = io::stdout(); 32 | let stdout = stdout.lock(); 33 | 34 | let transport = RefCell::new(Transport::new(stdin, stdout)); 35 | let mut rpc = RpcHandler::new(); 36 | 37 | rpc.add_method("initialize", |_: lst::InitializeParams| { 38 | Response::Success::<_, ()>(lst::InitializeResult { 39 | capabilities: lst::ServerCapabilities { 40 | text_document_sync: Some(lst::TextDocumentSyncKind::Full), 41 | ..Default::default() 42 | }, 43 | }) 44 | }); 45 | 46 | rpc.add_notification("initialized", |_: serde_json::Value| {}); 47 | 48 | rpc.add_notification( 49 | "textDocument/didOpen", 50 | |params: lst::DidOpenTextDocumentParams| { 51 | publish_diagnostics( 52 | ¶ms.text_document.text, 53 | params.text_document.uri, 54 | transport.borrow_mut().deref_mut(), 55 | ); 56 | }, 57 | ); 58 | 59 | rpc.add_notification( 60 | "textDocument/didSave", 61 | |_: lst::DidSaveTextDocumentParams| {}, 62 | ); 63 | 64 | rpc.add_notification( 65 | "textDocument/didClose", 66 | |_: lst::DidCloseTextDocumentParams| {}, 67 | ); 68 | 69 | rpc.add_notification( 70 | "textDocument/didChange", 71 | |params: lst::DidChangeTextDocumentParams| { 72 | if params.content_changes.len() != 1 73 | || params.content_changes[0].range.is_some() 74 | || params.content_changes[0].range_length.is_some() 75 | { 76 | debug!("got bad edit with `didChange` event"); 77 | } else { 78 | publish_diagnostics( 79 | ¶ms.content_changes[0].text, 80 | params.text_document.uri, 81 | transport.borrow_mut().deref_mut(), 82 | ); 83 | } 84 | }, 85 | ); 86 | 87 | loop { 88 | let response = { 89 | let msg = transport 90 | .borrow_mut() 91 | .read_message() 92 | .expect("failed to read message"); 93 | rpc.handle_call(&msg) 94 | }; 95 | if let Some(response) = response { 96 | transport 97 | .borrow_mut() 98 | .send_message(&response) 99 | .expect("failed to send"); 100 | } 101 | } 102 | } 103 | 104 | fn publish_diagnostics(source: &str, doc: Url, transport: &mut Transport) 105 | where 106 | R: BufRead, 107 | W: Write, 108 | { 109 | let diagnostics = make_diagnostics(source); 110 | let params = lst::PublishDiagnosticsParams { 111 | uri: doc, 112 | diagnostics, 113 | }; 114 | #[derive(Serialize)] 115 | struct Notification { 116 | jsonrpc: &'static str, 117 | method: &'static str, 118 | params: lst::PublishDiagnosticsParams, 119 | } 120 | 121 | let notification = Notification { 122 | jsonrpc: "2.0", 123 | method: "textDocument/publishDiagnostics", 124 | params, 125 | }; 126 | let string = serde_json::to_string(¬ification).expect("failed to serialize diagnostics"); 127 | transport 128 | .send_message(&string) 129 | .expect("failed to write message"); 130 | } 131 | 132 | fn make_diagnostics(source: &str) -> Vec { 133 | let reporter = plank_errors::Reporter::new(); 134 | let tokens = plank_syntax::lex(source, reporter.clone()); 135 | let ast = plank_syntax::parse(tokens, reporter.clone()); 136 | let _ = plank_frontend::compile(&ast, reporter.clone()); 137 | reporter 138 | .get_diagnostics() 139 | .into_iter() 140 | .filter_map(convert_diagnostic) 141 | .collect() 142 | } 143 | 144 | fn convert_diagnostic(d: plank_errors::reporter::Diagnostic) -> Option { 145 | fn convert_pos(pos: plank_errors::position::Position) -> lst::Position { 146 | lst::Position { 147 | line: u64::from(pos.line), 148 | character: u64::from(pos.column), 149 | } 150 | } 151 | fn convert_range(range: plank_errors::position::Span) -> lst::Range { 152 | lst::Range { 153 | start: convert_pos(range.start), 154 | end: convert_pos(range.end), 155 | } 156 | } 157 | let primary_span = match d.primary_span { 158 | Some(span) => span, 159 | None => return None, 160 | }; 161 | let severity = match d.severity { 162 | plank_errors::reporter::Severity::Error => lst::DiagnosticSeverity::Error, 163 | plank_errors::reporter::Severity::Warning => lst::DiagnosticSeverity::Warning, 164 | }; 165 | Some(lst::Diagnostic { 166 | range: convert_range(primary_span), 167 | severity: Some(severity), 168 | code: None, 169 | source: Some("plank".into()), 170 | message: d.message, 171 | }) 172 | } 173 | -------------------------------------------------------------------------------- /tasks/lexer/readme.md: -------------------------------------------------------------------------------- 1 | # Lexer 2 | 3 | Lexer is implemented in [plank-syntax/src/lexer.rs](../../plank-syntax/src/lexer.rs). The module exports a single function - `lex`. Lexing always produces some list of tokens - invalid input is represented as token `Token::Error`. All errors are emitted to given `Reporter`. 4 | 5 | ## Grammar 6 | 7 | Second part of [grammar](../grammar/grammar.bnf) is implemented in the lexer: 8 | 9 | ```bnf 10 | ::= | "_" 11 | ::= "" | 12 | ::= "_" | | 13 | 14 | ::= | | | 15 | 16 | ::= "true" | "false" 17 | ::= | 18 | ::= | 19 | ::= "i" | "u" | "i8" | "i16" | "i32" | "u8" | "u16" | "u32" 20 | ::= "'" "'" | "'\"'" 21 | ::= "\"" 22 | ::= "\"" | | "'" 23 | 24 | ::= | 25 | ::= "A" | "B" | "C" | ... | "Z" 26 | ::= "a" | "b" | "c" | ... | "z" 27 | ::= "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" 28 | ::= "0" | 29 | 30 | ::= | "a" | ... | "f" | "A" | ... | "F" 31 | ::= "\\x" 32 | ::= | "\\n" | "\\\"" | "\\'" | "\\\\" | 33 | ::= | | " " | "!" | "#" | "$" | ... | "~" 34 | ``` 35 | 36 | Also, all terminals in first part of grammar (like `fn`, `let`, `+`, and similar) are also recognized by the lexer. 37 | 38 | ## Tokens 39 | 40 | Lexer returns a list of `Spanned`. 41 | * `Token` is defined in [plank-syntax/src/tokens.rs](../../plank-syntax/src/tokens.rs). 42 | * `Spanned` is defined in [plank-syntax/src/position.rs](../../plank-syntax/src/position.rs). 43 | * `Span`, which is used in `Spanned`, is defined in [plank-errors/src/position.rs](../../plank-errors/src/position.rs). 44 | 45 | Here is a table of tokens and their internal representation: 46 | 47 | | Token | Internal representation | Notes | 48 | | ------------ | ------------------------------------------ | --------------- | 49 | | `identifier` | `Token::Ident("identifier")` | | 50 | | `123` | `Token::Number(Number { value: 123, .. })` | | 51 | | `true` | `Token::Bool(true)` | | 52 | | `false` | `Token::Bool(false)` | | 53 | | `'A'` | `Token::Char(65)` | | 54 | | `"ABC"` | `Token::Str(vec![65, 66, 67])` | | 55 | | keyword | `Token::Keyword(_)` | Full list below | 56 | | `<` | `Token::Less` | | 57 | | `<=` | `Token::LessEqual` | | 58 | | `>` | `Token::Greater` | | 59 | | `>=` | `Token::GreaterEqual` | | 60 | | `==` | `Token::Equal` | | 61 | | `!=` | `Token::NotEqual` | | 62 | | `+` | `Token::Plus` | | 63 | | `-` | `Token::Minus` | | 64 | | `*` | `Token::Star` | | 65 | | `/` | `Token::Slash` | | 66 | | `%` | `Token::Percent` | | 67 | | `&` | `Token::Ampersand` | | 68 | | `.` | `Token::Dot` | | 69 | | `(` | `Token::LeftParen` | | 70 | | `)` | `Token::RightParen` | | 71 | | `{` | `Token::LeftBrace` | | 72 | | `}` | `Token::RightBrace` | | 73 | | `->` | `Token::Arrow` | | 74 | | `&&` | `Token::And` | | 75 | | `\|\|` | `Token::Or` | | 76 | | `,` | `Token::Comma` | | 77 | | `_` | `Token::Underscore` | | 78 | | `:` | `Token::Colon` | | 79 | | `::` | `Token::DoubleColon` | | 80 | | `;` | `Token::Semicolon` | | 81 | | `!` | `Token::Not` | | 82 | | `=` | `Token::Assign` | | 83 | 84 | 85 | | Keyword | Internal representation | 86 | | ---------- | ----------------------- | 87 | | `extern` | `Keyword::Extern` | 88 | | `fn` | `Keyword::Fn` | 89 | | `struct` | `Keyword::Struct` | 90 | | `if` | `Keyword::If` | 91 | | `else` | `Keyword::Else` | 92 | | `loop` | `Keyword::Loop` | 93 | | `while` | `Keyword::While` | 94 | | `continue` | `Keyword::Continue` | 95 | | `break` | `Keyword::Break` | 96 | | `let` | `Keyword::Let` | 97 | | `return` | `Keyword::Return` | 98 | | `as` | `Keyword::As` | 99 | | `mut` | `Keyword::Mut` | 100 | | `bool` | `Keyword::Bool` | 101 | | `i8` | `Keyword::I8` | 102 | | `u8` | `Keyword::U8` | 103 | | `i16` | `Keyword::I16` | 104 | | `u16` | `Keyword::U16` | 105 | | `i32` | `Keyword::I32` | 106 | | `u32` | `Keyword::U32` | 107 | | `unit` | `Keyword::Unit` | 108 | -------------------------------------------------------------------------------- /plank-frontend/src/assign_check.rs: -------------------------------------------------------------------------------- 1 | use ast::cfg::{Block, BlockEnd, BlockId, Function, Instruction, Program, Reg, Value}; 2 | use plank_syntax::position::Spanned; 3 | use std::collections::{HashMap, HashSet}; 4 | use CompileCtx; 5 | 6 | struct Context<'a> { 7 | ctx: &'a mut CompileCtx, 8 | function: &'a Function, 9 | reported_regs: HashSet, 10 | assign_position: HashMap<(Reg, BlockId), usize>, 11 | reachable_labels: HashSet<(Reg, BlockId)>, 12 | } 13 | 14 | impl<'a> Context<'a> { 15 | fn new(function: &'a Function, ctx: &'a mut CompileCtx) -> Self { 16 | Context { 17 | ctx, 18 | function, 19 | reported_regs: HashSet::new(), 20 | assign_position: HashMap::new(), 21 | reachable_labels: HashSet::new(), 22 | } 23 | } 24 | 25 | fn store_assigns(&mut self, id: BlockId, block: &Block) { 26 | for (index, op) in block.ops.iter().enumerate() { 27 | match **op { 28 | Instruction::Init(reg) 29 | | Instruction::Assign(reg, _) 30 | | Instruction::BinaryOp(reg, _, _, _) 31 | | Instruction::Call(reg, _, _) 32 | | Instruction::UnaryOp(reg, _, _) 33 | | Instruction::TakeAddress(reg, _, _) 34 | | Instruction::CastAssign(reg, _) => { 35 | self.assign_position.entry((reg, id)).or_insert(index); 36 | } 37 | Instruction::Error 38 | | Instruction::Drop(_) 39 | | Instruction::DerefStore(_, _, _, _) 40 | | Instruction::FieldStore(_, _, _) 41 | | Instruction::StartStatement => {} 42 | } 43 | } 44 | } 45 | 46 | fn check_block(&mut self, id: BlockId, block: &Block) { 47 | for (index, op) in block.ops.iter().enumerate() { 48 | match **op { 49 | Instruction::Assign(_, ref val) 50 | | Instruction::UnaryOp(_, _, ref val) 51 | | Instruction::CastAssign(_, ref val) => { 52 | self.check_value(val, id, index); 53 | } 54 | Instruction::FieldStore(reg, _, ref val) => { 55 | self.check_value(val, id, index); 56 | let reg = Spanned::map(reg, Value::Reg); 57 | self.check_value(®, id, index); 58 | } 59 | Instruction::DerefStore(ref a, _, _, ref b) 60 | | Instruction::BinaryOp(_, _, ref a, ref b) => { 61 | self.check_value(a, id, index); 62 | self.check_value(b, id, index); 63 | } 64 | Instruction::Call(_, ref value, ref params) => { 65 | self.check_value(value, id, index); 66 | for param in params { 67 | self.check_value(param, id, index); 68 | } 69 | } 70 | Instruction::TakeAddress(_, reg, _) => { 71 | let val = Spanned::map(reg, Value::Reg); 72 | self.check_value(&val, id, index); 73 | } 74 | Instruction::Error 75 | | Instruction::StartStatement 76 | | Instruction::Drop(_) 77 | | Instruction::Init(_) => {} 78 | } 79 | } 80 | } 81 | 82 | fn check_value(&mut self, value: &Spanned, block: BlockId, pos: usize) { 83 | match **value { 84 | Value::Int(_, _) 85 | | Value::Symbol(_, _) 86 | | Value::Bytes(_) 87 | | Value::Unit 88 | | Value::Error => {} 89 | Value::Reg(reg) if self.reported_regs.contains(®) => {} 90 | Value::Reg(reg) => { 91 | let assign_pos = self.assign_position.get(&(reg, block)).cloned(); 92 | if assign_pos.map(|p| p < pos) == Some(true) { 93 | return; 94 | } 95 | if !self.reachable_labels.contains(&(reg, block)) { 96 | return; 97 | } 98 | // occasionally temporary registers happen to be uninitialized 99 | // (in cases of constness mismatch when taking references), 100 | // so don't report about that 101 | if let Some(&var_symbol) = self.function.register_symbols.get(®) { 102 | let name = self.ctx.symbols.get_name(var_symbol); 103 | let msg = format!("var `{}` might be uninitialized here", name); 104 | let span = Spanned::span(value); 105 | self.ctx.reporter.error(msg, span).span(span).build(); 106 | self.reported_regs.insert(reg); 107 | } 108 | } 109 | } 110 | } 111 | 112 | fn visit_label(&mut self, reg: Reg, block: BlockId) { 113 | if !self.reachable_labels.contains(&(reg, block)) { 114 | self.reachable_labels.insert((reg, block)); 115 | if self.assign_position.contains_key(&(reg, block)) { 116 | return; 117 | } 118 | match self.function.blocks[&block].end { 119 | BlockEnd::Jump(to) => { 120 | self.visit_label(reg, to); 121 | } 122 | BlockEnd::Branch(_, a, b) => { 123 | self.visit_label(reg, a); 124 | self.visit_label(reg, b); 125 | } 126 | BlockEnd::Return(_) | BlockEnd::Error => {} 127 | } 128 | } 129 | } 130 | 131 | fn check_function(&mut self) { 132 | for (&id, block) in &self.function.blocks { 133 | self.store_assigns(id, block); 134 | } 135 | let parameters = self 136 | .function 137 | .parameters 138 | .iter() 139 | .cloned() 140 | .collect::>(); 141 | if let Some(block) = self.function.start_block { 142 | for ® in self.function.registers.keys() { 143 | if !parameters.contains(®) { 144 | self.visit_label(reg, block); 145 | } 146 | } 147 | } 148 | for (&id, block) in &self.function.blocks { 149 | self.check_block(id, block); 150 | } 151 | } 152 | } 153 | 154 | pub(crate) fn check_program(program: &Program, ctx: &mut CompileCtx) { 155 | for f in program.functions.values() { 156 | let mut ctx = Context::new(f, ctx); 157 | ctx.check_function(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /plank-frontend/src/struct_check.rs: -------------------------------------------------------------------------------- 1 | use ast::resolved::{Program, Symbol, Type}; 2 | use std::collections::{HashMap, HashSet}; 3 | use CompileCtx; 4 | 5 | type Pair = (Symbol, Symbol); 6 | 7 | struct Solver { 8 | node_index: HashMap, 9 | edges: HashMap>, 10 | needed_visits: HashMap, 11 | next_node_index: u32, 12 | visit_queue: Vec, 13 | } 14 | 15 | impl Solver { 16 | fn new(symbols: &HashSet) -> Solver { 17 | let nodes = (symbols.len() * symbols.len()) as u32; 18 | let indices = symbols 19 | .iter() 20 | .flat_map(|&a| symbols.iter().map(move |&b| (a, b))) 21 | .enumerate() 22 | .map(|(index, pair)| (pair, index as u32)) 23 | .collect(); 24 | let needed_visits = (0..nodes).into_iter().map(|i| (i, 1)).collect(); 25 | let mut solver = Solver { 26 | node_index: indices, 27 | edges: HashMap::new(), 28 | needed_visits, 29 | next_node_index: nodes, 30 | visit_queue: Vec::new(), 31 | }; 32 | for &a in symbols { 33 | for &b in symbols { 34 | for &c in symbols { 35 | let first = solver.concrete_node(a, b); 36 | let second = solver.concrete_node(b, c); 37 | let result = solver.concrete_node(a, c); 38 | let node = solver.artificial_node(); 39 | solver.add_edge(first, node); 40 | solver.add_edge(second, node); 41 | solver.add_edge(node, result); 42 | solver.needed_visits.insert(node, 2); 43 | } 44 | } 45 | } 46 | solver 47 | } 48 | 49 | fn concrete_node(&self, a: Symbol, b: Symbol) -> u32 { 50 | self.node_index[&(a, b)] 51 | } 52 | 53 | fn add_edge(&mut self, from: u32, to: u32) { 54 | self.edges 55 | .entry(from) 56 | .or_insert_with(HashSet::new) 57 | .insert(to); 58 | } 59 | 60 | fn artificial_node(&mut self) -> u32 { 61 | self.next_node_index += 1; 62 | self.next_node_index - 1 63 | } 64 | 65 | fn add_fact(&mut self, fact: Pair) { 66 | let node = self.concrete_node(fact.0, fact.1); 67 | if self.needed_visits[&node] != 0 { 68 | self.visit_queue.push(node); 69 | self.needed_visits.insert(node, 0); 70 | } 71 | } 72 | 73 | fn add_rule>(&mut self, bounds: I, result: Pair) { 74 | if bounds.len() == 0 { 75 | self.add_fact(result); 76 | } else { 77 | let node = self.artificial_node(); 78 | self.needed_visits.insert(node, bounds.len() as u32); 79 | let result = self.concrete_node(result.0, result.1); 80 | self.add_edge(node, result); 81 | for (a, b) in bounds { 82 | let condition = self.concrete_node(a, b); 83 | self.add_edge(condition, node); 84 | } 85 | } 86 | } 87 | 88 | fn solve(&mut self) { 89 | while let Some(node) = self.visit_queue.pop() { 90 | if let Some(outgoing) = self.edges.get(&node) { 91 | for &out in outgoing { 92 | match self.needed_visits[&out] { 93 | 0 => {} 94 | 1 => { 95 | self.needed_visits.insert(out, 0); 96 | self.visit_queue.push(out); 97 | } 98 | x => { 99 | self.needed_visits.insert(out, x - 1); 100 | } 101 | } 102 | } 103 | } 104 | } 105 | } 106 | 107 | fn is_recursive(&self, sym: Symbol) -> bool { 108 | let node = self.concrete_node(sym, sym); 109 | self.needed_visits[&node] == 0 110 | } 111 | 112 | fn add_struct(&mut self, program: &Program, root: Symbol, typ: &Type, acc: &mut HashSet) { 113 | match *typ { 114 | Type::Wildcard 115 | | Type::I8 116 | | Type::U8 117 | | Type::I16 118 | | Type::U16 119 | | Type::I32 120 | | Type::U32 121 | | Type::Bool 122 | | Type::Unit 123 | | Type::Pointer(_, _) 124 | | Type::Function(_, _) 125 | | Type::Error => {} 126 | Type::Concrete(sym, ref params) => { 127 | let sym = *sym; 128 | self.add_rule(acc.iter().cloned(), (root, sym)); 129 | if !params.is_empty() { 130 | let symbols = &program.structs[&sym].name.type_params; 131 | debug_assert_eq!(params.len(), symbols.len()); 132 | for (typ, &p) in params.iter().zip(symbols.iter()) { 133 | let new_pair = (sym, *p); 134 | if acc.contains(&new_pair) { 135 | self.add_struct(program, root, typ, acc); 136 | } else { 137 | acc.insert(new_pair); 138 | self.add_struct(program, root, typ, acc); 139 | acc.remove(&new_pair); 140 | } 141 | } 142 | } 143 | } 144 | } 145 | } 146 | } 147 | 148 | pub(crate) fn check_program(program: &mut Program, ctx: &mut CompileCtx) { 149 | let symbols = program 150 | .structs 151 | .values() 152 | .flat_map(|s| s.name.type_params.iter().map(|x| **x)) 153 | .chain(program.structs.keys().cloned()) 154 | .collect::>(); 155 | let symbols_hashset = symbols.iter().cloned().collect::>(); 156 | debug_assert_eq!(symbols.len(), symbols_hashset.len()); 157 | let mut solver = Solver::new(&symbols_hashset); 158 | let mut acc = HashSet::new(); 159 | for (&name, s) in &program.structs { 160 | for var in &s.fields { 161 | solver.add_struct(program, name, &var.typ, &mut acc); 162 | } 163 | } 164 | solver.solve(); 165 | for (&name, s) in &mut program.structs { 166 | if solver.is_recursive(name) { 167 | let msg = format!("struct `{}` is recursive", ctx.symbols.get_name(name),); 168 | ctx.reporter 169 | .error(msg, s.complete_span) 170 | .span(s.complete_span) 171 | .build(); 172 | for var in &mut s.fields { 173 | *var.typ = Type::Error; 174 | } 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /plank-ir/src/analysis/usage.rs: -------------------------------------------------------------------------------- 1 | use analysis::Loc; 2 | use ir::{BlockEnd, Function, Instruction, Reg, Value}; 3 | use std::collections::{HashMap, HashSet}; 4 | 5 | pub struct Context<'a> { 6 | f: &'a Function, 7 | volatile: &'a HashMap>, 8 | } 9 | 10 | impl<'a> Context<'a> { 11 | pub fn new(f: &'a Function, volatile: &'a HashMap>) -> Self { 12 | Context { f, volatile } 13 | } 14 | 15 | pub fn is_value_used(&self, loc: Loc, reg: Reg) -> bool { 16 | let mut visited = HashSet::new(); 17 | let mut frontier = vec![loc]; 18 | while let Some(loc) = frontier.pop() { 19 | if visited.contains(&loc) { 20 | continue; 21 | } 22 | visited.insert(loc); 23 | let block = &self.f.blocks[&loc.block]; 24 | if loc.pos == block.ops.len() { 25 | match block.end { 26 | BlockEnd::Branch(ref val, a, b) => { 27 | if is_used_in_val(reg, val) { 28 | return true; 29 | } 30 | frontier.push(Loc { block: a, pos: 0 }); 31 | frontier.push(Loc { block: b, pos: 0 }); 32 | } 33 | BlockEnd::Jump(a) => { 34 | frontier.push(Loc { block: a, pos: 0 }); 35 | } 36 | BlockEnd::Return(ref val) => { 37 | if is_used_in_val(reg, val) { 38 | return true; 39 | } 40 | } 41 | BlockEnd::ReturnProc | BlockEnd::Unreachable => {} 42 | } 43 | } else { 44 | match block.ops[loc.pos] { 45 | Instruction::Assign(r, ref val) 46 | | Instruction::CastAssign(r, ref val) 47 | | Instruction::UnaryOp(r, _, ref val) => { 48 | if is_used_in_val(reg, val) { 49 | return true; 50 | } 51 | if r == reg { 52 | continue; 53 | } 54 | } 55 | Instruction::BinaryOp(r, _, ref a, ref b) => { 56 | if is_used_in_val(reg, a) { 57 | return true; 58 | } 59 | if is_used_in_val(reg, b) { 60 | return true; 61 | } 62 | if r == reg { 63 | continue; 64 | } 65 | } 66 | Instruction::Call(r, _, ref params) => { 67 | for param in params { 68 | if is_used_in_val(reg, param) { 69 | return true; 70 | } 71 | } 72 | if r == reg { 73 | continue; 74 | } 75 | } 76 | Instruction::CallProc(_, ref params) => { 77 | for param in params { 78 | if is_used_in_val(reg, param) { 79 | return true; 80 | } 81 | } 82 | } 83 | Instruction::CallVirt(r, ref f, ref params) => { 84 | for param in params { 85 | if is_used_in_val(reg, param) { 86 | return true; 87 | } 88 | } 89 | if is_used_in_val(reg, f) { 90 | return true; 91 | } 92 | if r == reg { 93 | continue; 94 | } 95 | } 96 | Instruction::CallProcVirt(ref f, ref params) => { 97 | for param in params { 98 | if is_used_in_val(reg, param) { 99 | return true; 100 | } 101 | } 102 | if is_used_in_val(reg, f) { 103 | return true; 104 | } 105 | } 106 | Instruction::DerefLoad(r, ref val, _) => { 107 | if is_used_in_val(reg, val) { 108 | return true; 109 | } 110 | if self.is_volatile_at(reg, loc) { 111 | return true; 112 | } 113 | if r == reg { 114 | continue; 115 | } 116 | } 117 | Instruction::DerefStore(ref to, _, ref val) => { 118 | if is_used_in_val(reg, to) { 119 | return true; 120 | } 121 | if is_used_in_val(reg, val) { 122 | return true; 123 | } 124 | } 125 | Instruction::Drop(r) 126 | | Instruction::Init(r) 127 | | Instruction::TakeAddress(r, _, _) => { 128 | if r == reg { 129 | continue; 130 | } 131 | } 132 | Instruction::Load(r, from, _) => { 133 | if from == reg { 134 | return true; 135 | } 136 | if r == reg { 137 | continue; 138 | } 139 | } 140 | Instruction::Nop => {} 141 | Instruction::Store(_, _, ref val) => { 142 | if is_used_in_val(reg, val) { 143 | return true; 144 | } 145 | } 146 | Instruction::Unreachable => { 147 | return false; 148 | } 149 | } 150 | frontier.push(Loc { 151 | block: loc.block, 152 | pos: loc.pos + 1, 153 | }); 154 | } 155 | } 156 | false 157 | } 158 | 159 | fn is_volatile_at(&self, reg: Reg, at: Loc) -> bool { 160 | if let Some(locs) = self.volatile.get(®) { 161 | locs.contains(&at) 162 | } else { 163 | false 164 | } 165 | } 166 | } 167 | 168 | fn is_used_in_val(reg: Reg, val: &Value) -> bool { 169 | val == &Value::Reg(reg) 170 | } 171 | -------------------------------------------------------------------------------- /plank-errors/src/reporter.rs: -------------------------------------------------------------------------------- 1 | //! Helpers to build and aggregate diagnostics. 2 | 3 | use position::Span; 4 | use std::cell::RefCell; 5 | use std::rc::Rc; 6 | 7 | /// Reporter aggregates and allows building diagnostics. 8 | /// 9 | /// Note that reporters created by cloning will share diagnostic list with the 10 | /// original reporter. 11 | #[derive(Default, Debug, Clone)] 12 | pub struct Reporter { 13 | diagnostics: Rc>>, 14 | } 15 | 16 | impl Reporter { 17 | /// Create a new reporter with no errors. 18 | pub fn new() -> Reporter { 19 | Default::default() 20 | } 21 | 22 | /// Returns if the reporter has any errors. 23 | /// 24 | /// This function will return false even if reporter has any warnings. 25 | /// 26 | /// # Examples 27 | /// 28 | /// ```rust 29 | /// use plank_errors::reporter::Reporter; 30 | /// 31 | /// let mut reporter = Reporter::new(); 32 | /// // empty reporter should not have any errors 33 | /// assert!(!reporter.has_errors()); 34 | /// ``` 35 | pub fn has_errors(&self) -> bool { 36 | self.diagnostics 37 | .borrow() 38 | .iter() 39 | .any(|d| d.severity == Severity::Error) 40 | } 41 | 42 | /// Return the list of diagnostics collected with this reporter. 43 | /// 44 | /// The diagnosics are returned in arbitrary order. Depending on how they 45 | /// will be displayed, you might want to sort them. 46 | pub fn get_diagnostics(&self) -> Vec { 47 | self.diagnostics.borrow().clone() 48 | } 49 | 50 | /// Create a new error without associated span. 51 | /// 52 | /// # Examples 53 | /// 54 | /// ```rust 55 | /// use plank_errors::reporter::Reporter; 56 | /// 57 | /// let mut reporter = Reporter::new(); 58 | /// reporter.global_error("`main` function is missing"); 59 | /// ``` 60 | pub fn global_error>(&self, msg: T) { 61 | let diagnostic = Diagnostic { 62 | message: msg.into(), 63 | primary_span: None, 64 | notes: Vec::new(), 65 | severity: Severity::Error, 66 | }; 67 | self.diagnostics.borrow_mut().push(diagnostic); 68 | } 69 | 70 | /// Create a builder for a new error. 71 | /// 72 | /// # Examples 73 | /// 74 | /// ```rust 75 | /// use plank_errors::reporter::Reporter; 76 | /// use plank_errors::position::{Position, Span}; 77 | /// 78 | /// let mut reporter = Reporter::new(); 79 | /// # let error_span = Span::new(Position::new(1, 1), Position::new(1, 1)); 80 | /// # let help_span = error_span; 81 | /// reporter 82 | /// .error("error message", error_span) 83 | /// .span_note("shorter message", error_span) 84 | /// .span_note("helper note", help_span) 85 | /// .build(); 86 | /// ``` 87 | pub fn error(&self, msg: T, span: Span) -> Builder 88 | where 89 | T: Into, 90 | { 91 | self.diagnostic(Severity::Error, msg, span) 92 | } 93 | 94 | /// Create a builder for a new warning. 95 | /// 96 | /// # Examples 97 | /// 98 | /// ```rust 99 | /// use plank_errors::reporter::Reporter; 100 | /// use plank_errors::position::{Position, Span}; 101 | /// 102 | /// let mut reporter = Reporter::new(); 103 | /// # let warning_span = Span::new(Position::new(1, 1), Position::new(1, 1)); 104 | /// # let help_span = warning_span; 105 | /// reporter 106 | /// .warning("warning message", warning_span) 107 | /// .span_note("shorter message", warning_span) 108 | /// .span_note("helper note", help_span) 109 | /// .build(); 110 | /// ``` 111 | pub fn warning(&self, msg: T, span: Span) -> Builder 112 | where 113 | T: Into, 114 | { 115 | self.diagnostic(Severity::Warning, msg, span) 116 | } 117 | 118 | /// Create a builder for a new diagnostic. 119 | /// 120 | /// # Examples 121 | /// 122 | /// ```rust 123 | /// use plank_errors::reporter::{Reporter, Severity}; 124 | /// use plank_errors::position::{Position, Span}; 125 | /// let mut reporter = Reporter::new(); 126 | /// # let error_span = Span::new(Position::new(1, 1), Position::new(1, 1)); 127 | /// reporter 128 | /// .diagnostic(Severity::Error, "error message", error_span) 129 | /// .span(error_span) 130 | /// .build(); 131 | /// ``` 132 | pub fn diagnostic(&self, severity: Severity, msg: T, span: Span) -> Builder 133 | where 134 | T: Into, 135 | { 136 | Builder::new(self, severity, msg.into(), span) 137 | } 138 | } 139 | 140 | /// Diagnostics severity. 141 | #[derive(PartialEq, Eq, Debug, Copy, Clone)] 142 | pub enum Severity { 143 | /// Represents a fatal error. 144 | Error, 145 | /// Represents a non-fatal error. 146 | Warning, 147 | } 148 | 149 | #[allow(missing_docs)] 150 | #[derive(Debug, Clone)] 151 | pub struct Diagnostic { 152 | pub message: String, 153 | pub primary_span: Option, 154 | pub severity: Severity, 155 | pub notes: Vec, 156 | } 157 | 158 | #[allow(missing_docs)] 159 | #[derive(Debug, Clone)] 160 | pub struct Note { 161 | pub span: Span, 162 | pub message: Option, 163 | } 164 | 165 | /// A helper for building a diagnostic. 166 | #[must_use] 167 | pub struct Builder { 168 | reporter: Reporter, 169 | diagnostic: Diagnostic, 170 | } 171 | 172 | impl Builder { 173 | fn new(reporter: &Reporter, severity: Severity, msg: String, primary_span: Span) -> Self { 174 | Builder { 175 | reporter: reporter.clone(), 176 | diagnostic: Diagnostic { 177 | message: msg, 178 | severity, 179 | primary_span: Some(primary_span), 180 | notes: Vec::new(), 181 | }, 182 | } 183 | } 184 | 185 | /// Complete current diagnostic. 186 | /// 187 | /// # Panics 188 | /// 189 | /// Panics if current diagnostic has no notes. If you want an error without 190 | /// any notes, use [`Reporter::global_error`] 191 | /// (struct.Reporter.html#method.global_error) instead. 192 | pub fn build(self) { 193 | assert!( 194 | !self.diagnostic.notes.is_empty(), 195 | "built a diagnostic without any notes" 196 | ); 197 | self.reporter.diagnostics.borrow_mut().push(self.diagnostic); 198 | } 199 | 200 | /// Add a new note that has only a span. 201 | pub fn span(self, span: Span) -> Self { 202 | self.note(None, span) 203 | } 204 | 205 | /// Add a new note that has a message and a span. 206 | pub fn span_note(self, msg: T, span: Span) -> Self 207 | where 208 | T: Into, 209 | { 210 | self.note(Some(msg.into()), span) 211 | } 212 | 213 | fn note(mut self, msg: Option, span: Span) -> Self { 214 | self.diagnostic.notes.push(Note { span, message: msg }); 215 | self 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /tasks/semantic-analysis/readme.md: -------------------------------------------------------------------------------- 1 | # Semantic analysis 2 | 3 | Semantic analysis and compilation to IR is implemented in [plank-frontend](../../plank-frontend). The IR itself ir defined in [plank-ir](../../plank-ir). 4 | 5 | ## IR 6 | 7 | Plank program in IR is represented as a collection of functions. Every function has a unique name (symbol). 8 | 9 | A function consists of the following parts: 10 | * Parameter list - a list of registers that hold function parameters. 11 | * Optional output value layout - stores size and alignment of function output value, if it has one. 12 | * Register descriptions - size and alignment for each register that function uses. 13 | 14 | If function has an implementation, then there are also: 15 | * A list of blocks that make up the function body. 16 | * Id of the function entry block. 17 | 18 | When printed to textual format, function blocks will be listed in arbitrary order. Entry block will be denoted using a `start` pseudo-block, which will contain a `goto` to the actual entry block. 19 | 20 | ### Function examples 21 | 22 | ```rust 23 | fn foo(x: bool) -> u32 { 24 | if !x { 25 | return 1; 26 | } else { 27 | return 2; 28 | } 29 | } 30 | ``` 31 | 32 | IR of the previous `foo` function: 33 | 34 | ``` 35 | function foo(%0): { 4, 4 } 36 | register %0: size 1, align 1 37 | register %1: size 1, align 1 38 | start: 39 | goto label_0 40 | label_0: 41 | %1 = xor_8 %0 1_b8 42 | branch %1 label_1 label_2 43 | label_1: 44 | drop %1 45 | return 1_b32 46 | label_2: 47 | drop %1 48 | return 2_b32 49 | ``` 50 | 51 | ## Blocks 52 | 53 | Every function block has its own Id. Ids are unique in a function, but not necessarily unique between functions. Also block has a (possibly empty) list of instructions, and a special block end instruction. 54 | 55 | Possible block end instructions: 56 | * Jump (written as `goto `) - continue execution from given block. 57 | * Branch (written as `branch `) - if `value` is not zero, continue execution from block `then-block`, otherwise from `else-block`. Value must be 8 bits wide. 58 | * Return (written as `return`) - return from current function. Cannot be used in functions that return a value. 59 | * Return value (written as `return `) - return a value from current function. Cannot be used in functions that do not return a value. Size and alignment of value must match those in function declaration. 60 | 61 | ### Block examples 62 | 63 | ``` 64 | label_1: 65 | %0 = 1_b8 66 | %1 = add_i8 %0 2_b8 67 | branch %1 label_2 label_3 68 | ``` 69 | 70 | ``` 71 | label_2: 72 | %0 = 1_b32 73 | return %0 74 | ``` 75 | 76 | ## Instructions 77 | 78 | There are 15 kinds of instructions: 79 | * Init (written as `init `) - initializes register to some value. Makes the register available for usage, but initial value has no guarantees. 80 | * Drop (written as `drop `) - forgets the value stored in the register. Value in the register cannot be used before the next time it is assigned. 81 | * Binary operation (written as ` = `) - performs the operation on two given values, and stores result in the register. Available binary operations are listed below. 82 | * Unary operation (written as ` = `) - performs the operation on given value, and stores result in the register. Available unary operations are listed below. 83 | * Call (written as ` = call ()`) - calls a function associated with given symbol, passing given values as parameters. Parameter sizes and alignments must match those in called function declaration. Register size and alignment must match those of called function return value. Stores return value in given register. 84 | * Call procedure (written as `callproc ()`) - same as call, but for functions that do not return a value. Note only functions that do not return a value cannot be called with `callproc`. 85 | * Virtual call (written as ` = callvirt ()`) - same as call, but calls not a concrete function, but a function that is pointed to by given value. Value must be function-pointer-sized. 86 | * Virtual procedure call (written as `callprocvirt ()`) - same as `callproc`, but virtual call. 87 | * Deref store (written as `store ( + ) `) - store `value-2` at address `value + offset`. `` must be pointer-sized. 88 | * Deref load (written ` = deref ( + )`) - read value from address `value + offset`. `` must be pointer-sized. 89 | * Store (written as `[] = `) - write `value` to register with given offset. Value must fit inside register after the shift. 90 | * Load (written as ` = []`) - read from `register-2` with given offset. `register-2` must be large enough to be possible to fill `register`. 91 | * Take address (written as ` = address []`) - write to `register` the address of `offset`-th byte of `register-2`. `register` must be pointer-sized. 92 | * Assign (written as ` = `) - write value to register. Both register and value must have same size and alignment. 93 | * Cast (written as ` = cast `) - write value to register. Unlike assignment, only same size requirement must be met. 94 | 95 | ## Binary operations 96 | 97 | The list of allowed binary operations: 98 | * Arithmetic: `{add,sub,mul,div,mod}_{i8,u8,i16,u16,i32,u32}` - integer addition, subtraction, multiplication, division, modulo. Operand and output sizes must be same as in instruction name. 99 | * Bit operations `{and,or,xor}_{8,16,32}`. Operand and output sizes must be same as in instruction name. There are no signed/unsigned variants, because that does not matter for bit operations. 100 | * Ordering comparision: `{le,leq,gt,geq}_{i8,u8,i16,u16,i32,u32}` - less than, less or equal, greater, greater or equal. Operand sizes must be same as in instruction name. Output size is one byte. 101 | * Equality comparision: `eq` and `neq`. Operands must have same size. Output size is one byte. 102 | 103 | ## Unary operations 104 | 105 | Currently there is only one unary operation: 106 | * Negation: `neg_{i8,u8,i16,u16,i32,u32}`. Operand and output sizes must be same as in instruction name. 107 | 108 | ## Values 109 | 110 | There are 4 kinds of values: 111 | * Integer constants (examples: `17_b8`, `91_b32`). Constants always have explicitly written size in bits. 112 | * Registers 113 | * Symbols. They are some numeric constants that refer to addresses of corresponding functions. Their size is that of function pointers. 114 | * Byte sequences. They evaluate to pointers to first byte of the sequence, and thus their size matches that of pointers. 115 | 116 | ## Registers 117 | 118 | In IR functions can use arbitrary number of registers. Registers are written as `%0`, `%1`, and so on (the numbers don't necessarily have to be sequential). Size and alignment must be specified for every register used in a function. At the start of function execution, only registers listed as function parameters are live. Register also becomes live when it is assigned a value. `drop`ped registers are not live and cannot be used unless assigned again. Pointers to registers also become invalid when register is `drop`ped. 119 | -------------------------------------------------------------------------------- /plank-syntax/src/tokens.rs: -------------------------------------------------------------------------------- 1 | pub use ast::Number; 2 | use std::fmt; 3 | 4 | #[derive(PartialEq, Eq, Hash, Debug, Clone)] 5 | pub enum Token { 6 | Ident(String), 7 | Number(Number), 8 | Bool(bool), 9 | Char(u8), 10 | Str(Vec), 11 | Keyword(Keyword), 12 | Less, 13 | LessEqual, 14 | Greater, 15 | GreaterEqual, 16 | Equal, 17 | NotEqual, 18 | Plus, 19 | Minus, 20 | Star, 21 | Slash, 22 | Percent, 23 | Ampersand, 24 | Dot, 25 | LeftParen, 26 | RightParen, 27 | LeftBrace, 28 | RightBrace, 29 | Arrow, 30 | And, 31 | Or, 32 | Comma, 33 | Underscore, 34 | Colon, 35 | DoubleColon, 36 | Semicolon, 37 | Not, 38 | Assign, 39 | Error, 40 | } 41 | 42 | impl Token { 43 | pub fn kind(&self) -> TokenKind { 44 | match *self { 45 | Token::Ident(_) => TokenKind::Ident, 46 | Token::Number(_) | Token::Bool(_) | Token::Char(_) | Token::Str(_) => { 47 | TokenKind::Literal 48 | } 49 | ref tok => TokenKind::Token(tok.clone()), 50 | } 51 | } 52 | } 53 | 54 | impl fmt::Display for Token { 55 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 56 | match *self { 57 | Token::Ident(ref s) => write!(f, "{}", s), 58 | Token::Number(n) => write!(f, "{}", n), 59 | Token::Bool(b) => write!(f, "{}", b), 60 | Token::Char(_) => write!(f, "'...'"), 61 | Token::Str(_) => write!(f, "\"...\""), 62 | Token::Keyword(k) => write!(f, "{}", k), 63 | Token::Less => write!(f, "<"), 64 | Token::LessEqual => write!(f, "<="), 65 | Token::Greater => write!(f, ">"), 66 | Token::GreaterEqual => write!(f, ">="), 67 | Token::Equal => write!(f, "=="), 68 | Token::NotEqual => write!(f, "!="), 69 | Token::Plus => write!(f, "+"), 70 | Token::Minus => write!(f, "-"), 71 | Token::Star => write!(f, "*"), 72 | Token::Slash => write!(f, "/"), 73 | Token::Percent => write!(f, "%"), 74 | Token::Ampersand => write!(f, "&"), 75 | Token::Dot => write!(f, "."), 76 | Token::LeftParen => write!(f, "("), 77 | Token::RightParen => write!(f, ")"), 78 | Token::LeftBrace => write!(f, "{{"), 79 | Token::RightBrace => write!(f, "}}"), 80 | Token::Arrow => write!(f, "->"), 81 | Token::And => write!(f, "&&"), 82 | Token::Or => write!(f, "||"), 83 | Token::Comma => write!(f, ","), 84 | Token::Underscore => write!(f, "_"), 85 | Token::Colon => write!(f, ":"), 86 | Token::DoubleColon => write!(f, "::"), 87 | Token::Semicolon => write!(f, ";"), 88 | Token::Assign => write!(f, "="), 89 | Token::Not => write!(f, "!"), 90 | Token::Error => write!(f, "?"), 91 | } 92 | } 93 | } 94 | 95 | #[derive(PartialEq, PartialOrd, Eq, Ord, Hash, Debug, Copy, Clone)] 96 | pub enum Keyword { 97 | Extern, 98 | Fn, 99 | Struct, 100 | If, 101 | Else, 102 | Loop, 103 | While, 104 | Continue, 105 | Break, 106 | Let, 107 | Return, 108 | As, 109 | Mut, 110 | Bool, 111 | I8, 112 | U8, 113 | I16, 114 | U16, 115 | I32, 116 | U32, 117 | Unit, 118 | } 119 | 120 | impl fmt::Display for Keyword { 121 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 122 | match *self { 123 | Keyword::Extern => write!(f, "extern"), 124 | Keyword::Fn => write!(f, "fn"), 125 | Keyword::Struct => write!(f, "struct"), 126 | Keyword::If => write!(f, "if"), 127 | Keyword::Else => write!(f, "else"), 128 | Keyword::Loop => write!(f, "loop"), 129 | Keyword::While => write!(f, "while"), 130 | Keyword::Continue => write!(f, "continue"), 131 | Keyword::Break => write!(f, "break"), 132 | Keyword::Let => write!(f, "let"), 133 | Keyword::Return => write!(f, "return"), 134 | Keyword::As => write!(f, "as"), 135 | Keyword::Mut => write!(f, "mut"), 136 | Keyword::I8 => write!(f, "i8"), 137 | Keyword::I16 => write!(f, "i16"), 138 | Keyword::I32 => write!(f, "i32"), 139 | Keyword::U8 => write!(f, "u8"), 140 | Keyword::U16 => write!(f, "u16"), 141 | Keyword::U32 => write!(f, "u32"), 142 | Keyword::Bool => write!(f, "bool"), 143 | Keyword::Unit => write!(f, "unit"), 144 | } 145 | } 146 | } 147 | 148 | #[derive(PartialEq, Eq, Hash, Debug, Clone)] 149 | pub enum TokenKind { 150 | Token(Token), 151 | Ident, 152 | Literal, 153 | } 154 | 155 | impl TokenKind { 156 | pub fn is_operator(&self) -> bool { 157 | matches!( 158 | *self, 159 | TokenKind::Token(Token::Less) 160 | | TokenKind::Token(Token::LessEqual) 161 | | TokenKind::Token(Token::Greater) 162 | | TokenKind::Token(Token::GreaterEqual) 163 | | TokenKind::Token(Token::Equal) 164 | | TokenKind::Token(Token::NotEqual) 165 | | TokenKind::Token(Token::Plus) 166 | | TokenKind::Token(Token::Minus) 167 | | TokenKind::Token(Token::Star) 168 | | TokenKind::Token(Token::Slash) 169 | | TokenKind::Token(Token::Percent) 170 | | TokenKind::Token(Token::And) 171 | | TokenKind::Token(Token::Or) 172 | | TokenKind::Token(Token::Assign) 173 | ) 174 | } 175 | 176 | pub fn can_start_expression(&self) -> bool { 177 | matches!( 178 | *self, 179 | TokenKind::Ident 180 | | TokenKind::Literal 181 | | TokenKind::Token(Token::Plus) 182 | | TokenKind::Token(Token::Minus) 183 | | TokenKind::Token(Token::Star) 184 | | TokenKind::Token(Token::Ampersand) 185 | | TokenKind::Token(Token::LeftParen) 186 | | TokenKind::Token(Token::Not) 187 | ) 188 | } 189 | 190 | pub fn can_start_type(&self) -> bool { 191 | matches!( 192 | *self, 193 | TokenKind::Token(Token::Keyword(Keyword::Unit)) 194 | | TokenKind::Token(Token::Star) 195 | | TokenKind::Token(Token::Keyword(Keyword::Fn)) 196 | | TokenKind::Token(Token::Underscore) 197 | | TokenKind::Token(Token::Keyword(Keyword::I8)) 198 | | TokenKind::Token(Token::Keyword(Keyword::I16)) 199 | | TokenKind::Token(Token::Keyword(Keyword::I32)) 200 | | TokenKind::Token(Token::Keyword(Keyword::U8)) 201 | | TokenKind::Token(Token::Keyword(Keyword::U16)) 202 | | TokenKind::Token(Token::Keyword(Keyword::U32)) 203 | | TokenKind::Token(Token::Keyword(Keyword::Bool)) 204 | ) 205 | } 206 | } 207 | 208 | impl fmt::Display for TokenKind { 209 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 210 | match *self { 211 | TokenKind::Ident => write!(f, "identifier"), 212 | TokenKind::Literal => write!(f, "literal"), 213 | TokenKind::Token(ref tok) => write!(f, "`{}`", tok), 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /plank-ir/src/optimization/cleanup.rs: -------------------------------------------------------------------------------- 1 | use super::Rewriter; 2 | use analysis::Loc; 3 | use ir::{Block, BlockEnd, BlockId, Function, Instruction, Program, Reg, Value}; 4 | use std::collections::{HashMap, HashSet}; 5 | 6 | struct RemoveNops; 7 | 8 | impl Rewriter for RemoveNops { 9 | fn rewrite_block(&mut self, _id: BlockId, block: &mut Block) { 10 | block.ops.retain(|o| !matches!(*o, Instruction::Nop)); 11 | } 12 | } 13 | 14 | struct ShortenUnreachable; 15 | 16 | impl Rewriter for ShortenUnreachable { 17 | fn rewrite_block(&mut self, _id: BlockId, block: &mut Block) { 18 | let mut to_keep = None; 19 | for (index, op) in block.ops.iter().enumerate() { 20 | if let Instruction::Unreachable = *op { 21 | to_keep = Some(index); 22 | } 23 | } 24 | if let Some(index) = to_keep { 25 | block.ops.truncate(index); 26 | block.end = BlockEnd::Unreachable; 27 | } 28 | } 29 | } 30 | 31 | #[derive(Default)] 32 | struct JoinBlocks { 33 | references: HashMap, 34 | } 35 | 36 | impl Rewriter for JoinBlocks { 37 | fn rewrite_function(&mut self, f: &mut Function) { 38 | self.references.clear(); 39 | for block in f.blocks.values() { 40 | match block.end { 41 | BlockEnd::Branch(_, a, b) => { 42 | *self.references.entry(a).or_insert(0) += 1; 43 | *self.references.entry(b).or_insert(0) += 1; 44 | } 45 | BlockEnd::Jump(a) => { 46 | *self.references.entry(a).or_insert(0) += 1; 47 | } 48 | BlockEnd::Return(_) | BlockEnd::ReturnProc | BlockEnd::Unreachable => {} 49 | } 50 | } 51 | if let Some(block) = f.start_block { 52 | *self.references.entry(block).or_insert(0) += 1; 53 | } 54 | loop { 55 | enum Change { 56 | Remove(BlockId), 57 | Join(BlockId, BlockId), 58 | None, 59 | } 60 | let mut change = Change::None; 61 | for (&id, block) in &f.blocks { 62 | if *self.references.entry(id).or_insert(0) == 0 { 63 | change = Change::Remove(id); 64 | break; 65 | } 66 | if let BlockEnd::Jump(to) = block.end { 67 | if *self.references.entry(to).or_insert(0) == 1 { 68 | change = if id == to { 69 | Change::Remove(id) 70 | } else { 71 | Change::Join(id, to) 72 | }; 73 | break; 74 | } 75 | } 76 | } 77 | match change { 78 | Change::Join(a, b) => { 79 | let removed = f.blocks.remove(&b).unwrap(); 80 | let first = f.blocks.get_mut(&a).unwrap(); 81 | first.ops.extend(removed.ops); 82 | match first.end { 83 | BlockEnd::Branch(_, a, b) => { 84 | *self.references.get_mut(&a).unwrap() -= 1; 85 | *self.references.get_mut(&b).unwrap() -= 1; 86 | } 87 | BlockEnd::Jump(a) => { 88 | *self.references.get_mut(&a).unwrap() -= 1; 89 | } 90 | BlockEnd::Return(_) | BlockEnd::ReturnProc | BlockEnd::Unreachable => {} 91 | } 92 | first.end = removed.end; 93 | } 94 | Change::Remove(block) => { 95 | let block = f.blocks.remove(&block).unwrap(); 96 | match block.end { 97 | BlockEnd::Branch(_, a, b) => { 98 | *self.references.get_mut(&a).unwrap() -= 1; 99 | *self.references.get_mut(&b).unwrap() -= 1; 100 | } 101 | BlockEnd::Jump(a) => { 102 | *self.references.get_mut(&a).unwrap() -= 1; 103 | } 104 | BlockEnd::Return(_) | BlockEnd::ReturnProc | BlockEnd::Unreachable => {} 105 | } 106 | } 107 | Change::None => break, 108 | } 109 | } 110 | } 111 | } 112 | 113 | #[derive(Default)] 114 | struct RemoveUnusedRegs { 115 | used: HashSet, 116 | } 117 | 118 | impl RemoveUnusedRegs { 119 | fn track(&mut self, reg: Reg) { 120 | self.used.insert(reg); 121 | } 122 | 123 | fn track_val(&mut self, val: &Value) { 124 | if let Value::Reg(r) = *val { 125 | self.track(r); 126 | } 127 | } 128 | 129 | fn track_values(&mut self, values: &[Value]) { 130 | for val in values { 131 | self.track_val(val); 132 | } 133 | } 134 | } 135 | 136 | impl Rewriter for RemoveUnusedRegs { 137 | fn rewrite_function(&mut self, f: &mut Function) { 138 | self.used.clear(); 139 | super::rewrite_function(self, f); 140 | for &r in &f.parameters { 141 | self.track(r); 142 | } 143 | f.registers.retain(|&r, _| self.used.contains(&r)); 144 | } 145 | 146 | fn rewrite_instruction(&mut self, _loc: Loc, instr: &mut Instruction) { 147 | match *instr { 148 | Instruction::Assign(r, ref val) 149 | | Instruction::CastAssign(r, ref val) 150 | | Instruction::DerefLoad(r, ref val, _) 151 | | Instruction::Store(r, _, ref val) 152 | | Instruction::UnaryOp(r, _, ref val) => { 153 | self.track(r); 154 | self.track_val(val); 155 | } 156 | Instruction::BinaryOp(r, _, ref a, ref b) => { 157 | self.track(r); 158 | self.track_val(a); 159 | self.track_val(b); 160 | } 161 | Instruction::Call(r, _, ref params) => { 162 | self.track(r); 163 | self.track_values(params); 164 | } 165 | Instruction::CallProc(_, ref params) => { 166 | self.track_values(params); 167 | } 168 | Instruction::CallProcVirt(ref f, ref params) => { 169 | self.track_val(f); 170 | self.track_values(params); 171 | } 172 | Instruction::CallVirt(r, ref f, ref params) => { 173 | self.track(r); 174 | self.track_val(f); 175 | self.track_values(params); 176 | } 177 | Instruction::DerefStore(ref a, _, ref b) => { 178 | self.track_val(a); 179 | self.track_val(b); 180 | } 181 | Instruction::Drop(r) | Instruction::Init(r) => { 182 | self.track(r); 183 | } 184 | Instruction::Load(r1, r2, _) | Instruction::TakeAddress(r1, r2, _) => { 185 | self.track(r1); 186 | self.track(r2); 187 | } 188 | Instruction::Nop | Instruction::Unreachable => {} 189 | } 190 | } 191 | } 192 | 193 | pub fn rewrite(program: &mut Program) { 194 | RemoveNops.rewrite_program(program); 195 | JoinBlocks::default().rewrite_program(program); 196 | ShortenUnreachable.rewrite_program(program); 197 | RemoveUnusedRegs::default().rewrite_program(program); 198 | } 199 | -------------------------------------------------------------------------------- /plank-frontend/src/type_param_check.rs: -------------------------------------------------------------------------------- 1 | use ast::resolved::{Expr, Function, FunctionType, Program, Statement, Struct, Symbol, Type}; 2 | use plank_syntax::position::Spanned; 3 | use std::collections::HashMap; 4 | use CompileCtx; 5 | 6 | pub(crate) fn check_type_params(program: &mut Program, ctx: &mut CompileCtx) { 7 | let mut ctx = Context::new(ctx); 8 | ctx.check_program(program); 9 | } 10 | 11 | struct Context<'a> { 12 | ctx: &'a mut CompileCtx, 13 | param_count: HashMap, 14 | } 15 | 16 | impl<'a> Context<'a> { 17 | fn new(ctx: &'a mut CompileCtx) -> Self { 18 | Context { 19 | ctx, 20 | param_count: HashMap::new(), 21 | } 22 | } 23 | 24 | fn params_taken(&self, symbol: Symbol) -> usize { 25 | self.param_count.get(&symbol).cloned().unwrap_or(0) 26 | } 27 | 28 | fn check_program(&mut self, program: &mut Program) { 29 | for struct_ in program.structs.values() { 30 | self.param_count.insert( 31 | Spanned::into_value(struct_.name.name), 32 | struct_.name.type_params.len(), 33 | ); 34 | } 35 | 36 | for fn_ in &mut program.functions { 37 | self.param_count.insert( 38 | Spanned::into_value(fn_.name.name), 39 | fn_.name.type_params.len(), 40 | ); 41 | 42 | if fn_.fn_type == FunctionType::Extern && !fn_.name.type_params.is_empty() { 43 | let span = Spanned::span(&fn_.name.name); 44 | self.ctx 45 | .reporter 46 | .error("`extern` functions cannot have type parameters", span) 47 | .span(span) 48 | .build(); 49 | } 50 | } 51 | 52 | for struct_ in program.structs.values_mut() { 53 | self.check_struct(struct_); 54 | } 55 | 56 | for fn_ in &mut program.functions { 57 | self.check_function(fn_); 58 | } 59 | } 60 | 61 | fn check_struct(&mut self, struct_: &mut Struct) { 62 | for field in &mut struct_.fields { 63 | self.check_type(&mut field.typ) 64 | } 65 | } 66 | 67 | fn check_function(&mut self, fn_: &mut Function) { 68 | for param in &mut fn_.params { 69 | self.check_type(&mut param.typ); 70 | } 71 | 72 | self.check_type(&mut fn_.return_type); 73 | 74 | if let Some(ref mut stmt) = fn_.body { 75 | self.check_statement(stmt); 76 | } 77 | } 78 | 79 | fn check_type(&mut self, typ: &mut Spanned) { 80 | match **typ { 81 | Type::Unit 82 | | Type::Bool 83 | | Type::Error 84 | | Type::I8 85 | | Type::I16 86 | | Type::I32 87 | | Type::U8 88 | | Type::U16 89 | | Type::U32 90 | | Type::Wildcard => return, 91 | Type::Pointer(_, ref mut typ) => { 92 | self.check_type(typ); 93 | return; 94 | } 95 | Type::Function(ref mut params, ref mut out) => { 96 | for param in params { 97 | self.check_type(param); 98 | } 99 | self.check_type(out); 100 | return; 101 | } 102 | Type::Concrete(name, ref mut params) => { 103 | let name_span = Spanned::span(&name); 104 | let name = Spanned::into_value(name); 105 | for param in params.iter_mut() { 106 | self.check_type(param); 107 | } 108 | let params_expected = self.params_taken(name); 109 | if params.len() != params_expected { 110 | let name = self.ctx.symbols.get_name(name); 111 | let msg = make_error_message("type", name, params_expected, params.len()); 112 | let short_msg = make_short_message(params_expected); 113 | self.ctx 114 | .reporter 115 | .error(msg, name_span) 116 | .span_note(short_msg, name_span) 117 | .build(); 118 | } else { 119 | return; 120 | } 121 | } 122 | } 123 | **typ = Type::Error; 124 | } 125 | 126 | fn check_statement(&mut self, stmt: &mut Spanned) { 127 | match **stmt { 128 | Statement::Block(ref mut stmts) => { 129 | for stmt in stmts { 130 | self.check_statement(stmt); 131 | } 132 | } 133 | Statement::Break | Statement::Continue | Statement::Error => {} 134 | Statement::Expr(ref mut expr) | Statement::Return(ref mut expr) => { 135 | self.check_expr(expr) 136 | } 137 | Statement::If(ref mut cond, ref mut then, ref mut else_) => { 138 | self.check_expr(cond); 139 | self.check_statement(then); 140 | if let Some(ref mut else_) = *else_ { 141 | self.check_statement(else_); 142 | } 143 | } 144 | Statement::Let(_, _, ref mut typ, ref mut value) => { 145 | self.check_type(typ); 146 | if let Some(ref mut value) = *value { 147 | self.check_expr(value); 148 | } 149 | } 150 | Statement::Loop(ref mut body) => self.check_statement(body), 151 | Statement::While(ref mut cond, ref mut body) => { 152 | self.check_expr(cond); 153 | self.check_statement(body); 154 | } 155 | } 156 | } 157 | 158 | fn check_expr(&mut self, expr: &mut Spanned) { 159 | match **expr { 160 | Expr::Binary(ref mut lhs, _, ref mut rhs) => { 161 | self.check_expr(lhs); 162 | self.check_expr(rhs); 163 | return; 164 | } 165 | Expr::Call(ref mut expr, ref mut params) => { 166 | self.check_expr(expr); 167 | for param in params { 168 | self.check_expr(param); 169 | } 170 | return; 171 | } 172 | Expr::Field(ref mut expr, _) | Expr::Unary(_, ref mut expr) => { 173 | self.check_expr(expr); 174 | return; 175 | } 176 | Expr::Error | Expr::Literal(_) => return, 177 | Expr::Name(name, ref mut params) => { 178 | let name_span = Spanned::span(&name); 179 | let name = Spanned::into_value(name); 180 | for param in params.iter_mut() { 181 | self.check_type(param); 182 | } 183 | let params_expected = self.params_taken(name); 184 | if params.is_empty() { 185 | for _ in 0..params_expected { 186 | params.push(Spanned::new(Type::Wildcard, name_span)); 187 | } 188 | return; 189 | } else if params_expected != params.len() { 190 | let name = self.ctx.symbols.get_name(name); 191 | let msg = make_error_message("value", name, params_expected, params.len()); 192 | let short_msg = make_short_message(params_expected); 193 | self.ctx 194 | .reporter 195 | .error(msg, name_span) 196 | .span_note(short_msg, name_span) 197 | .build(); 198 | } else { 199 | return; 200 | } 201 | } 202 | Expr::Cast(ref mut expr, ref mut typ) => { 203 | self.check_expr(expr); 204 | self.check_type(typ); 205 | return; 206 | } 207 | } 208 | **expr = Expr::Error; 209 | } 210 | } 211 | 212 | fn make_error_message(kind: &str, name: &str, expected: usize, got: usize) -> String { 213 | if expected == 0 { 214 | format!("{} `{}` does not take type parameters", kind, name,) 215 | } else if expected % 10 == 1 && expected % 100 != 11 { 216 | format!( 217 | "{} `{}` expects {} type parameter, got {}", 218 | kind, name, expected, got, 219 | ) 220 | } else { 221 | format!( 222 | "{} `{}` expects {} type parameters, got {}", 223 | kind, name, expected, got, 224 | ) 225 | } 226 | } 227 | 228 | fn make_short_message(expected: usize) -> String { 229 | if expected == 0 { 230 | "did not expect type parameters".into() 231 | } else if expected % 10 == 1 && expected % 100 != 11 { 232 | format!("expected {} parameter", expected) 233 | } else { 234 | format!("expected {} parameters", expected) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /plank-frontend/src/type_check/unify.rs: -------------------------------------------------------------------------------- 1 | use super::rollback_map::Map; 2 | use ast::typed::{Mutability, Signedness, Size, Type, TypeVar}; 3 | use std::rc::Rc; 4 | 5 | #[derive(Debug, Clone)] 6 | enum VarTarget { 7 | Type(Type), 8 | Int, 9 | } 10 | 11 | pub struct UnifyTable { 12 | var_target: Map, 13 | next_var: u32, 14 | } 15 | 16 | impl UnifyTable { 17 | pub fn new() -> UnifyTable { 18 | UnifyTable { 19 | var_target: Map::new(), 20 | next_var: 0, 21 | } 22 | } 23 | 24 | pub fn fresh_var(&mut self) -> TypeVar { 25 | let var = TypeVar(self.next_var); 26 | self.next_var += 1; 27 | var 28 | } 29 | 30 | pub fn fresh_int_var(&mut self) -> TypeVar { 31 | let var = TypeVar(self.next_var); 32 | self.var_target.insert(var, VarTarget::Int); 33 | self.var_target.commit(); 34 | self.next_var += 1; 35 | var 36 | } 37 | 38 | pub fn unify(&mut self, a: &Type, b: &Type) -> Result { 39 | match self.unify_raw(a, b, true) { 40 | Ok(()) => { 41 | self.commit(); 42 | // `a` might get coerced to `b`, so we return `b` as result 43 | Ok(b.clone()) 44 | } 45 | Err(()) => { 46 | self.rollback(); 47 | Err(()) 48 | } 49 | } 50 | } 51 | 52 | fn unify_raw(&mut self, a: &Type, b: &Type, allow_coerce: bool) -> Result<(), ()> { 53 | let a = self.shallow_normalize(a); 54 | let b = self.shallow_normalize(b); 55 | match (a, b) { 56 | (Type::Concrete(a, ref ap), Type::Concrete(b, ref bp)) => { 57 | if a == b { 58 | assert_eq!(ap.len(), bp.len()); 59 | for (a, b) in ap.iter().zip(bp.iter()) { 60 | self.unify_raw(a, b, false)?; 61 | } 62 | Ok(()) 63 | } else { 64 | Err(()) 65 | } 66 | } 67 | (Type::Function(ref ap, ref a), Type::Function(ref bp, ref b)) => { 68 | if ap.len() == bp.len() { 69 | for (a, b) in ap.iter().zip(bp.iter()) { 70 | // flip `a` and `b` when unifying because fns are 71 | // contravariant on their arguments - so we can only 72 | // coerce types backwards. 73 | self.unify_raw(b, a, allow_coerce)?; 74 | } 75 | self.unify_raw(a, b, allow_coerce) 76 | } else { 77 | Err(()) 78 | } 79 | } 80 | (Type::Int(sign1, size1), Type::Int(sign2, size2)) => { 81 | if sign1 == sign2 && size1 == size2 { 82 | Ok(()) 83 | } else { 84 | Err(()) 85 | } 86 | } 87 | (Type::Pointer(m1, ref a), Type::Pointer(m2, ref b)) if m1 == m2 => { 88 | self.unify_raw(a, b, false) 89 | } 90 | (Type::Pointer(Mutability::Mut, ref a), Type::Pointer(Mutability::Const, ref b)) 91 | if allow_coerce => 92 | { 93 | self.unify_raw(a, b, false) 94 | } 95 | (Type::Var(a), ty) | (ty, Type::Var(a)) => self.unify_var_type(a, ty), 96 | (Type::Bool, Type::Bool) 97 | | (Type::Unit, Type::Unit) 98 | | (Type::Error, _) 99 | | (_, Type::Error) => Ok(()), 100 | (_, _) => Err(()), 101 | } 102 | } 103 | 104 | fn unify_var_var(&mut self, a: TypeVar, b: TypeVar) -> Result<(), ()> { 105 | let at = self.var_target.get(&a).cloned(); 106 | let bt = self.var_target.get(&b).cloned(); 107 | match (at, bt) { 108 | (Some(VarTarget::Type(_)), _) | (_, Some(VarTarget::Type(_))) => { 109 | panic!("cannot unify non-normalized var") 110 | } 111 | (None, _) | (Some(VarTarget::Int), Some(_)) => { 112 | self.var_target.insert(a, VarTarget::Type(Type::Var(b))); 113 | Ok(()) 114 | } 115 | (_, None) => { 116 | self.var_target.insert(b, VarTarget::Type(Type::Var(a))); 117 | Ok(()) 118 | } 119 | } 120 | } 121 | 122 | fn unify_var_type(&mut self, v: TypeVar, b: Type) -> Result<(), ()> { 123 | match b { 124 | Type::Var(b) if b == v => return Ok(()), 125 | _ => {} 126 | } 127 | if self.occurs(v, &b) { 128 | return Err(()); 129 | } 130 | let vt = self.var_target.get(&v).cloned(); 131 | match (vt, b) { 132 | (_, Type::Error) => { 133 | self.var_target.insert(v, VarTarget::Type(Type::Error)); 134 | Ok(()) 135 | } 136 | (Some(VarTarget::Type(_)), _) => panic!("cannot unify non-normalized var"), 137 | (_, Type::Var(b)) => self.unify_var_var(v, b), 138 | (None, ty) | (Some(VarTarget::Int), ty @ Type::Int(_, _)) => { 139 | self.var_target.insert(v, VarTarget::Type(ty)); 140 | Ok(()) 141 | } 142 | _ => Err(()), 143 | } 144 | } 145 | 146 | fn occurs(&mut self, var: TypeVar, typ: &Type) -> bool { 147 | let typ = self.shallow_normalize(typ); 148 | match typ { 149 | Type::Bool | Type::Error | Type::Int(_, _) | Type::Unit => false, 150 | Type::Concrete(_, ref params) => { 151 | for param in params.iter() { 152 | if self.occurs(var, param) { 153 | return true; 154 | } 155 | } 156 | false 157 | } 158 | Type::Function(ref params, ref out) => { 159 | for param in params.iter() { 160 | if self.occurs(var, param) { 161 | return true; 162 | } 163 | } 164 | self.occurs(var, out) 165 | } 166 | Type::Pointer(_, ref to) => self.occurs(var, to), 167 | Type::Var(v) => var == v, 168 | } 169 | } 170 | 171 | fn get_var_type(&self, var: TypeVar) -> Type { 172 | match self.var_target.get(&var) { 173 | Some(&VarTarget::Type(ref ty)) => self.shallow_normalize(ty), 174 | Some(&VarTarget::Int) | None => Type::Var(var), 175 | } 176 | } 177 | 178 | pub fn shallow_normalize(&self, a: &Type) -> Type { 179 | match a { 180 | &Type::Var(var) => self.get_var_type(var), 181 | ty => ty.clone(), 182 | } 183 | } 184 | 185 | pub fn normalize(&mut self, a: &Type) -> Result { 186 | match self.shallow_normalize(a) { 187 | Type::Bool => Ok(Type::Bool), 188 | Type::Unit => Ok(Type::Unit), 189 | Type::Concrete(sym, ref params) => { 190 | let mut normalized = Vec::new(); 191 | for param in &**params { 192 | normalized.push(self.normalize(param)?); 193 | } 194 | Ok(Type::Concrete(sym, normalized.into())) 195 | } 196 | Type::Error => Ok(Type::Error), 197 | Type::Function(ref params, ref out) => { 198 | let out = self.normalize(out)?; 199 | let mut normalized = Vec::new(); 200 | for param in &**params { 201 | normalized.push(self.normalize(param)?); 202 | } 203 | Ok(Type::Function(normalized.into(), Rc::new(out))) 204 | } 205 | ty @ Type::Int(_, _) => Ok(ty), 206 | Type::Pointer(mutability, ref ty) => { 207 | let ty = self.normalize(ty)?; 208 | Ok(Type::Pointer(mutability, Rc::new(ty))) 209 | } 210 | Type::Var(var) => { 211 | match self.var_target.get(&var) { 212 | Some(&VarTarget::Type(_)) => panic!("var not normalized"), 213 | Some(&VarTarget::Int) => { 214 | return Ok(Type::Int(Signedness::Signed, Size::Bit32)); 215 | } 216 | None => { 217 | // there is no sensible default 218 | // fall though because of borrowck 219 | } 220 | } 221 | // unify this var with an error to prevent further 222 | // error reports about same variable 223 | self.var_target.insert(var, VarTarget::Type(Type::Error)); 224 | Err(()) 225 | } 226 | } 227 | } 228 | 229 | pub fn describe_var(&self, var: TypeVar) -> &'static str { 230 | match self.var_target.get(&var) { 231 | Some(&VarTarget::Int) => "{int}", 232 | _ => "_", 233 | } 234 | } 235 | 236 | fn commit(&mut self) { 237 | self.var_target.commit(); 238 | } 239 | 240 | fn rollback(&mut self) { 241 | self.var_target.rollback(); 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /tests/src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate plank_errors; 2 | extern crate plank_frontend; 3 | extern crate plank_interpreter; 4 | extern crate plank_ir; 5 | extern crate plank_syntax; 6 | 7 | mod test_parser; 8 | 9 | use plank_errors::reporter::Diagnostic; 10 | use std::fs; 11 | use std::io; 12 | use std::io::prelude::*; 13 | 14 | enum BuildError { 15 | Fail(Vec), 16 | BadIr(plank_ir::ir::Symbol, plank_ir::validation::Error), 17 | } 18 | 19 | fn build_code(source: &str) -> Result { 20 | let reporter = plank_errors::Reporter::new(); 21 | let tokens = plank_syntax::lex(source, reporter.clone()); 22 | let program = plank_syntax::parse(tokens, reporter.clone()); 23 | let ir = plank_frontend::compile(&program, reporter.clone()) 24 | .map_err(|()| BuildError::Fail(reporter.get_diagnostics()))?; 25 | if let Err((sym, err)) = plank_ir::validate_ir(&ir) { 26 | return Err(BuildError::BadIr(sym.clone(), err)); 27 | } 28 | Ok(ir) 29 | } 30 | 31 | #[derive(Debug)] 32 | enum TestResult { 33 | BuildFail(Vec), 34 | IrValidationFail(plank_ir::ir::Symbol, plank_ir::validation::Error), 35 | BadBuildPass(Vec), 36 | BuildErrorMismatch(Vec, Vec), 37 | IoMismatch { expected: Vec, got: Vec }, 38 | MalformedTest(test_parser::ParseError), 39 | InterpreterExit(i32), 40 | InterpreterError(plank_interpreter::Error), 41 | Ok, 42 | } 43 | 44 | fn match_build_errors(expected: Vec, got: Vec) -> TestResult { 45 | let mut unmatched = Vec::new(); 46 | for err in expected { 47 | let mut matched = false; 48 | for actual in &got { 49 | if let Some(span) = actual.primary_span { 50 | if span.start.line == err.line && actual.message.contains(&err.message) { 51 | matched = true; 52 | break; 53 | } 54 | } 55 | } 56 | if !matched { 57 | unmatched.push(err); 58 | } 59 | } 60 | if unmatched.is_empty() { 61 | TestResult::Ok 62 | } else { 63 | TestResult::BuildErrorMismatch(got, unmatched) 64 | } 65 | } 66 | 67 | fn interpret_program(program: plank_ir::Program, input: Vec, output: Vec) -> TestResult { 68 | let mut input = ::std::io::Cursor::new(input); 69 | let mut actual_output = Vec::new(); 70 | match plank_interpreter::run_program(&program, &mut input, &mut actual_output) { 71 | Ok(0) if actual_output == output => TestResult::Ok, 72 | Ok(0) => TestResult::IoMismatch { 73 | expected: output, 74 | got: actual_output, 75 | }, 76 | Ok(code) => TestResult::InterpreterExit(code), 77 | Err(e) => TestResult::InterpreterError(e), 78 | } 79 | } 80 | 81 | fn run_test(source: &str) -> TestResult { 82 | let expectation = match test_parser::parse_test(source) { 83 | Ok(e) => e, 84 | Err(e) => return TestResult::MalformedTest(e), 85 | }; 86 | match expectation { 87 | test_parser::Expectation::BuildErrors(errors) => match build_code(source) { 88 | Ok(_) => TestResult::BadBuildPass(errors), 89 | Err(BuildError::Fail(got)) => match_build_errors(errors, got), 90 | Err(BuildError::BadIr(sym, err)) => TestResult::IrValidationFail(sym, err), 91 | }, 92 | test_parser::Expectation::Io { input, output } => match build_code(source) { 93 | Ok(program) => interpret_program(program, input, output), 94 | Err(BuildError::Fail(e)) => TestResult::BuildFail(e), 95 | Err(BuildError::BadIr(sym, err)) => TestResult::IrValidationFail(sym, err), 96 | }, 97 | test_parser::Expectation::BuildSuccess => match build_code(source) { 98 | Ok(_) => TestResult::Ok, 99 | Err(BuildError::Fail(e)) => TestResult::BuildFail(e), 100 | Err(BuildError::BadIr(sym, err)) => TestResult::IrValidationFail(sym, err), 101 | }, 102 | } 103 | } 104 | 105 | fn run_tests() -> io::Result> { 106 | let mut test_results = Vec::new(); 107 | for dir in TEST_DIRS { 108 | for entry in fs::read_dir(dir)? { 109 | let entry = entry?; 110 | if !entry.file_type()?.is_file() { 111 | continue; 112 | } 113 | let mut file = fs::File::open(entry.path())?; 114 | let mut source = String::new(); 115 | file.read_to_string(&mut source)?; 116 | let test_result = run_test(&source); 117 | let test_name = entry.path().display().to_string(); 118 | if let TestResult::Ok = test_result { 119 | println!("test {} ... ok", test_name); 120 | } else { 121 | println!("test {} ... FAIL", test_name); 122 | } 123 | test_results.push((test_name, source, test_result)); 124 | } 125 | } 126 | Ok(test_results) 127 | } 128 | 129 | fn print_expected_errors(errors: &[test_parser::Error]) { 130 | for err in errors { 131 | println!("at line {}: {}", err.line + 1, err.message); 132 | } 133 | } 134 | 135 | fn print_output(out: &[u8]) { 136 | for &byte in out { 137 | if byte < 32 || byte >= 127 || byte == b'\\' { 138 | print!("\\x{:>02X}", byte); 139 | } else { 140 | print!("{}", byte as char); 141 | } 142 | } 143 | println!(); 144 | } 145 | 146 | fn report_results(results: &[(String, String, TestResult)]) { 147 | let mut passed = 0; 148 | for &(ref name, ref source, ref result) in results { 149 | match *result { 150 | TestResult::Ok => passed += 1, 151 | TestResult::BuildFail(ref diagnostics) => { 152 | println!("========================================"); 153 | println!("test {}", name); 154 | println!("unexpected build failure"); 155 | plank_errors::printer::print_diagnostics(source, diagnostics); 156 | println!(); 157 | } 158 | TestResult::BadBuildPass(ref expected) => { 159 | println!("========================================"); 160 | println!("test {}", name); 161 | println!("build passed but expected failure"); 162 | print_expected_errors(expected); 163 | } 164 | TestResult::BuildErrorMismatch(ref got, ref expected) => { 165 | println!("========================================"); 166 | println!("test {}", name); 167 | println!("build error mismatch"); 168 | println!(">> unmatched errors:"); 169 | print_expected_errors(expected); 170 | println!(">> actual errors:"); 171 | plank_errors::printer::print_diagnostics(source, got); 172 | println!(); 173 | } 174 | TestResult::IoMismatch { 175 | ref expected, 176 | ref got, 177 | } => { 178 | println!("========================================"); 179 | println!("test {}", name); 180 | println!("wrong output"); 181 | print!("Expected: "); 182 | print_output(expected); 183 | print!("Got: "); 184 | print_output(got); 185 | println!(); 186 | } 187 | TestResult::IrValidationFail(ref sym, ref err) => { 188 | println!("========================================"); 189 | println!("test {}", name); 190 | println!("ir validation failed"); 191 | println!("error in symbol {:?}: {:?}", sym, err); 192 | println!(); 193 | } 194 | TestResult::MalformedTest(ref err) => { 195 | println!("========================================"); 196 | println!("test {}", name); 197 | println!("malformed test"); 198 | println!("{}", err); 199 | println!(); 200 | } 201 | TestResult::InterpreterExit(code) => { 202 | println!("========================================"); 203 | println!("test {}", name); 204 | println!("interpreted program exited with code {}", code); 205 | println!(); 206 | } 207 | TestResult::InterpreterError(ref err) => { 208 | println!("========================================"); 209 | println!("test {}", name); 210 | println!("interpreted program crashed"); 211 | println!("{}", err); 212 | println!(); 213 | } 214 | } 215 | } 216 | println!("========================================"); 217 | println!("tests: {}", results.len()); 218 | println!("passed: {}", passed); 219 | println!("failed: {}", results.len() - passed); 220 | } 221 | 222 | fn main() { 223 | match run_tests() { 224 | Ok(results) => report_results(&results), 225 | Err(e) => println!("io error: {}", e), 226 | } 227 | } 228 | 229 | const TEST_DIRS: &'static [&'static str] = &[ 230 | // test examples because obviously we want the 231 | // examples people come accross to build successfully 232 | "./examples", 233 | "./tests/compile-fail", 234 | "./tests/pass", 235 | ]; 236 | -------------------------------------------------------------------------------- /plank-server/src/jsonrpc.rs: -------------------------------------------------------------------------------- 1 | use languageserver_types as lst; 2 | use serde::de::DeserializeOwned; 3 | use serde::Serialize; 4 | use serde_json::{self, Value as JsonValue}; 5 | use std::collections::HashMap; 6 | 7 | #[derive(Deserialize)] 8 | struct Request { 9 | jsonrpc: String, 10 | id: Option, 11 | method: String, 12 | params: JsonValue, 13 | } 14 | 15 | #[derive(Serialize)] 16 | pub struct ErrorResponse { 17 | code: i64, 18 | message: String, 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | data: Option, 21 | } 22 | 23 | impl ErrorResponse { 24 | fn parse_error() -> Self { 25 | ErrorResponse { 26 | code: -32_700, 27 | message: "Parse error".into(), 28 | data: None, 29 | } 30 | } 31 | 32 | fn invalid_request() -> Self { 33 | ErrorResponse { 34 | code: -32_600, 35 | message: "Invalid request".into(), 36 | data: None, 37 | } 38 | } 39 | 40 | fn method_not_found() -> Self { 41 | ErrorResponse { 42 | code: -32_601, 43 | message: "Method not found".into(), 44 | data: None, 45 | } 46 | } 47 | 48 | fn invalid_params() -> Self { 49 | ErrorResponse { 50 | code: -32_602, 51 | message: "Invalid params".into(), 52 | data: None, 53 | } 54 | } 55 | 56 | fn internal_error() -> Self { 57 | ErrorResponse { 58 | code: -32_603, 59 | message: "Internal error".into(), 60 | data: None, 61 | } 62 | } 63 | 64 | pub fn new>(code: i64, msg: S, data: Option) -> Self { 65 | ErrorResponse { 66 | code, 67 | message: msg.into(), 68 | data, 69 | } 70 | } 71 | } 72 | 73 | impl ErrorResponse { 74 | fn serialize_error(self) -> ErrorResponse { 75 | let ErrorResponse { 76 | code, 77 | message, 78 | data, 79 | } = self; 80 | let data = if let Some(value) = data { 81 | match serde_json::to_value(&value) { 82 | Ok(value) => Some(value), 83 | Err(_) => { 84 | // failed to serialize error value, 85 | // just respond with internal JSON-RPC error 86 | return ErrorResponse::internal_error(); 87 | } 88 | } 89 | } else { 90 | None 91 | }; 92 | ErrorResponse { 93 | code, 94 | message, 95 | data, 96 | } 97 | } 98 | } 99 | 100 | #[derive(Serialize)] 101 | struct RawResponse { 102 | jsonrpc: &'static str, 103 | #[serde(skip_serializing_if = "Option::is_none")] 104 | result: Option, 105 | #[serde(skip_serializing_if = "Option::is_none")] 106 | error: Option>, 107 | id: Option, 108 | } 109 | 110 | impl RawResponse { 111 | fn ok(result: T, id: Option) -> Self { 112 | RawResponse { 113 | jsonrpc: "2.0", 114 | result: Some(result), 115 | error: None, 116 | id, 117 | } 118 | } 119 | 120 | fn err(error: ErrorResponse, id: Option) -> Self { 121 | RawResponse { 122 | jsonrpc: "2.0", 123 | result: None, 124 | error: Some(error), 125 | id, 126 | } 127 | } 128 | 129 | fn serialize(&self) -> JsonValue { 130 | serde_json::to_value(&self).unwrap_or_else(|_| { 131 | let err = RawResponse::<(), ()>::err(ErrorResponse::internal_error(), None); 132 | serde_json::to_value(&err).expect("failed to serialize basic response") 133 | }) 134 | } 135 | } 136 | 137 | pub enum Response { 138 | Success(T), 139 | Error(ErrorResponse), 140 | } 141 | 142 | impl Response { 143 | pub fn map U>(self, f: F) -> Response { 144 | match self { 145 | Response::Success(value) => Response::Success(f(value)), 146 | Response::Error(err) => Response::Error(err), 147 | } 148 | } 149 | } 150 | 151 | impl Response { 152 | fn into_raw(self, id: Option) -> RawResponse { 153 | match self { 154 | Response::Success(value) => RawResponse::ok(value, id), 155 | Response::Error(err) => RawResponse::err(err, id), 156 | } 157 | } 158 | } 159 | 160 | type CallHandler<'a, T, E> = Box Option> + 'a>; 161 | 162 | #[derive(Default)] 163 | pub struct RpcHandler<'a> { 164 | handlers: HashMap>, 165 | } 166 | 167 | impl<'a> RpcHandler<'a> { 168 | pub fn new() -> Self { 169 | Default::default() 170 | } 171 | 172 | pub fn add_method(&mut self, name: S, handle: F) 173 | where 174 | F: Fn(T) -> Response + 'a, 175 | T: DeserializeOwned, 176 | U: Serialize, 177 | E: Serialize, 178 | S: Into, 179 | { 180 | self.add_handler(name, move |input| handle(input).map(Some)); 181 | } 182 | 183 | pub fn add_notification(&mut self, name: S, handle: F) 184 | where 185 | F: Fn(T) + 'a, 186 | T: DeserializeOwned, 187 | S: Into, 188 | { 189 | self.add_handler::<_, _, _, (), ()>(name, move |input| { 190 | handle(input); 191 | Response::Success(None) 192 | }); 193 | } 194 | 195 | fn add_handler(&mut self, name: S, handle: F) 196 | where 197 | F: Fn(T) -> Response, E> + 'a, 198 | T: DeserializeOwned, 199 | U: Serialize, 200 | E: Serialize, 201 | S: Into, 202 | { 203 | let handle = move |params| { 204 | let params = match serde_json::from_value::(params) { 205 | Ok(params) => params, 206 | Err(_) => { 207 | let response = ErrorResponse::invalid_params(); 208 | return Some(Response::Error(response)); 209 | } 210 | }; 211 | match handle(params) { 212 | Response::Success(Some(response)) => { 213 | let json = serde_json::to_value(&response).unwrap(); 214 | Some(Response::Success(json)) 215 | } 216 | Response::Success(None) => None, 217 | Response::Error(err) => Some(Response::Error(err.serialize_error())), 218 | } 219 | }; 220 | self.handlers.insert(name.into(), Box::new(handle)); 221 | } 222 | 223 | pub fn handle_call(&mut self, input: &str) -> Option { 224 | let value = match serde_json::from_str::(input) { 225 | Ok(value) => value, 226 | Err(_) => return basic_error(&ErrorResponse::parse_error()), 227 | }; 228 | 229 | let response = match value { 230 | JsonValue::Array(values) => { 231 | // batch request 232 | if values.is_empty() { 233 | return basic_error(&ErrorResponse::invalid_request()); 234 | } 235 | let mut responses = Vec::new(); 236 | for request in values { 237 | if let Some(response) = self.handle_request(request) { 238 | responses.push(response.serialize()); 239 | } 240 | } 241 | if responses.is_empty() { 242 | None 243 | } else { 244 | Some(JsonValue::Array(responses)) 245 | } 246 | } 247 | value => { 248 | // single request 249 | self.handle_request(value).map(|r| r.serialize()) 250 | } 251 | }; 252 | response.map(|value| { 253 | serde_json::to_string(&value).expect("failed to serialize json value to string") 254 | }) 255 | } 256 | 257 | fn handle_request(&mut self, request: JsonValue) -> Option> { 258 | let request = match serde_json::from_value::(request) { 259 | Ok(request) => request, 260 | Err(_) => return Some(RawResponse::err(ErrorResponse::invalid_request(), None)), 261 | }; 262 | if request.id.is_some() { 263 | debug!("handling method: '{}'", request.method); 264 | } else { 265 | debug!("handling notification: '{}'", request.method); 266 | } 267 | match self.handlers.get_mut(&request.method) { 268 | Some(handler) => { 269 | let id = request.id; 270 | handler(request.params).map(|resp| resp.into_raw(id)) 271 | } 272 | None => { 273 | error!("handler not found for: '{}'", request.method); 274 | // only reply to requests, not notifications 275 | if request.id.is_some() { 276 | Some(RawResponse::err( 277 | ErrorResponse::method_not_found(), 278 | request.id, 279 | )) 280 | } else { 281 | None 282 | } 283 | } 284 | } 285 | } 286 | } 287 | 288 | fn basic_error(err: &ErrorResponse<()>) -> Option { 289 | Some(serde_json::to_string(err).expect("failed to serialize basic error")) 290 | } 291 | --------------------------------------------------------------------------------