├── .gitignore ├── README.md ├── lia-experimental ├── Cargo.toml ├── build.rs └── src │ ├── ast.rs │ ├── elaborate.rs │ ├── grammar.lalrpop │ ├── interpreter.rs │ ├── lexer.in.rs │ ├── lib.rs │ ├── mark.rs │ ├── pprint.rs │ ├── token.rs │ └── typecheck.rs ├── lia-jit ├── Cargo.toml └── src │ ├── bin │ └── repl.rs │ └── lib.rs ├── lia-plugin ├── Cargo.toml └── src │ ├── lib.rs │ └── plugin.rs ├── lia-runtime ├── Cargo.toml └── src │ └── lib.rs ├── lia-tests ├── Cargo.lock ├── Cargo.toml └── src │ ├── lib.rs │ ├── lists.rs │ └── matrix.rs └── lia ├── Cargo.toml ├── build.rs └── src ├── ast.rs ├── codegen.rs ├── elaborate.rs ├── grammar.lalrpop ├── lib.rs ├── runtime.rs └── token.rs /.gitignore: -------------------------------------------------------------------------------- 1 | lia/Cargo.lock 2 | lia/target 3 | lia/src/grammar.rs 4 | 5 | lia-plugin/Cargo.lock 6 | lia-plugin/target 7 | 8 | lia-tests/Cargo.lock 9 | lia-tests/target 10 | 11 | lia-jit/Cargo.lock 12 | lia-jit/target 13 | 14 | lia-sandbox 15 | 16 | lia-runtime/Cargo.lock 17 | lia-runtime/target 18 | 19 | lia-experimental/Cargo.lock 20 | lia-experimental/target 21 | lia-experimental/src/grammar.rs 22 | lia-experimental/src/lexer.rs 23 | 24 | **/*.o -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lia: A High-Level Language for Rust 2 | 3 | > 俩 (liǎ) - 1. two, 2. a pair 4 | 5 | Lia is a dynamically typed and garbage collected programming language that seamlessly interoperates with Rust by using Rust as a compile target. This enables Lia users to drop down into efficient Rust code when necessary, but work with a high-level Javascript-esque language for the majority of their application. For example, binding to a matrix library (à la numpy) is simple: 6 | 7 | ```rust 8 | // lia! declares a set of Lia functions. It is a procedural macro that compiles Lia into Rust. 9 | lia! { 10 | function multiply_matrices() { 11 | console.log("Multiplying matrices"); 12 | var x = @Matrix::from_list([[4, 3], [2, 1]]); // The @ means a foreign (Rust) function 13 | var y = @Matrix::from_list([[1, 2], [3, 4]]); 14 | var z = @Matrix::multiply(x, y); 15 | return @Matrix::get(z, 0, 0); 16 | } 17 | } 18 | 19 | #[derive(Clone)] 20 | struct Matrix { 21 | data: Vec, 22 | rows: i32, 23 | cols: i32, 24 | } 25 | 26 | // Putting #[lia_impl_glue] on an impl block will automatically generate functions that 27 | // do the appropriate type-casting from Lia's dynamic types into Rust's static types. 28 | #[lia_impl_glue] 29 | impl Matrix { 30 | // some functions omitted... 31 | 32 | pub fn multiply(&self, other: &Matrix) -> Matrix { 33 | assert!(self.cols == other.rows); 34 | 35 | let mut new_mat = Matrix::new(self.rows, other.cols); 36 | for i in 0..self.rows { 37 | for j in 0..other.cols { 38 | let mut dot = 0; 39 | for k in 0..self.cols { 40 | dot += self.get(i, k) * other.get(k, j); 41 | } 42 | new_mat.set(i, j, dot); 43 | } 44 | } 45 | 46 | return new_mat; 47 | } 48 | } 49 | 50 | fn main() { 51 | // Lia includes macros that simplify handling Lia functions and values in Rust. 52 | let result: LiaAny = call!(multiply_matrices()); 53 | cast!(let num: i32 = result); 54 | assert!(num == 13); 55 | } 56 | ``` 57 | 58 | ## Using Lia 59 | 60 | Look at `lia-tests/src/lib.rs` for example usage. Right now Lia is still in its early stages and requires nightly to build so it's not ready for prime time, but I encourage you to try it out and bring up any suggestions in an issue/PR or email me at [wcrichto@stanford.edu](mailto:wcrichto@stanford.edu). 61 | 62 | To use Lia in your own code, first switch to Rust nightly with `multirust override nightly`. Then add to your `Cargo.toml`: 63 | 64 | ```toml 65 | [dependencies] 66 | lia = "0.1.0" 67 | lia-plugin = "0.1.0" 68 | ``` 69 | 70 | Then in the file you want to use Lia: 71 | 72 | ```rust 73 | #![feature(plugin, box_syntax)] 74 | #![plugin(lia_plugin)] 75 | 76 | #[macro_use] 77 | extern crate lia; 78 | 79 | use lia::runtime::*; 80 | 81 | lia! { ... } 82 | ``` 83 | 84 | ## Motivation 85 | 86 | One of the biggest challenges facing programming languages today is interoperability. As PLs both proliferate in ever increasing numbers and specialize to application domains, many programmers need the ability to work effectively across languages. For example: 87 | 88 | * Python has a plethora of bindings to C, CUDA, etc. for efficient operations on numeric or image data. This is implemented in libraries like [numpy](http://www.numpy.org/) and [scipy](http://www.scipy.org/). 89 | * Javascript recently saw the rise of [React](http://reactjs.org/) which enabled seamless integration of HTML and Javascript in their new JSX format (this is very similar to the procedural HTML generation that popularized PHP many years earlier). 90 | * A number of domain-specific languages, e.g. [Halide](http://halide-lang.org/), have popped up in recent years with varying levels of interoperability to existing general-purpose programming languages. 91 | 92 | Many of the problems with interoperability between two languages can be solved by sharing a common type system. Even if two languages have a different runtime and different syntax, if they merge at the type-level then it's considerably easier for them to work together. For example, [Terra](http://terralang.org) addressed this problem by modifying Lua to have code generation facilities for a C-like language and then using LuaJIT's FFI as the type system glue between the two languages. However, this approach necessitates that the two languages (Lua and Terra) have separate runtimes and relies on a fragile layer of type glue between Lua's types and what is effectively C's types. 93 | 94 | Lia takes a different approach: instead of separating the high level interpreted runtime and the low level compiled runtime, we compile Lia code into Rust code. That way it shares the same type system and runtime and enables seamless interoperability between the two languages. Lia raises the level of abstraction over Rust by eliminating lifetimes/memory management as well as static typing. 95 | 96 | ## System description 97 | 98 | ```rust 99 | type LiaAny = Rc>>>>; 100 | ``` 101 | 102 | All values in Lia (integers, closures, etc.) have the type `LiaAny`. We'll walk through the components of the above type to understand Lia's layers of abstraction. 103 | 104 | ### Eliminating memory management 105 | 106 | The burden of managing memory/lifetimes is shifted from the programmer at compile time to the program at runtime via reference counting (type `Rc`). Ideally this would be a garbage collected pointer (like [this one](https://github.com/Manishearth/rust-gc)) that won't leak memory when cycles are induced, but that can come later. The `Rc` wraps a `RefCell` to allow the enclosed value to be mutated. 107 | 108 | A simple implementation of a runtime-managed type could be just `Rc>`. However, we need a second layer of indirection because variables can be reassigned. Consider the following example: 109 | 110 | ```javascript 111 | var x = 1; 112 | x = 2; 113 | ``` 114 | 115 | With the naive implementation, this could compile as: 116 | 117 | ```rust 118 | let mut x = alloc(1); 119 | x = alloc(2); 120 | ``` 121 | 122 | However, we do not want to rely on the mutability of Rust variables (specifically the names, e.g. `x`, not the values) to enable the mutability of Lia. This arises when we consider the interaction between closures and mutability: 123 | 124 | ```javascript 125 | var x = 1; 126 | (function(){ x = 2; })(); 127 | x = 3; 128 | ``` 129 | 130 | Our above scheme would compile into: 131 | 132 | ```rust 133 | let mut x = alloc(1); 134 | (move || { x = alloc(2); })(); 135 | x = alloc(3); 136 | ``` 137 | 138 | And the compiler will complain on the third line that `x` is already moved into the closure. Instead, we have each Rust name correspond to a slot that contains a value. The above example actually compiles into roughly: 139 | 140 | ```rust 141 | let x = alloc(alloc(1)); 142 | let x_copy = x.clone(); 143 | (move || { *x_copy.borrow_mut() = alloc(2); })(); 144 | *x.borrow_mut() = alloc(3); 145 | ``` 146 | 147 | The compiler creates the copies by finding variables closed by the Lia closures and creating copies. In sum: to have both runtime-managed memories and closures, each value is wrapped in two layers of `Rc`. 148 | 149 | ### Eliminating static typing 150 | 151 | To make the language dynamically typed, each underlying value in the slots discussed above has type `Box`. The [`Any`](http://doc.rust-lang.org/stable/std/any/index.html) type represents a value of any possible type, and can be checked/casted at runtime. The `Box<...>` ensures that the type is a fixed size (similar to the OCaml runtime, where each value is the size of a pointer). 152 | 153 | Casting in to the generic type is done with `lia::runtime::alloc` that does all the proper allocations. Casting out uses the `cast!` macro. Cast semantics depend on whether the type being casted into is owned or borrowed. The `Any` type from the standard library provides downcast methods that return a reference to the enclosed value when casted correctly. So when casting a `LiaAny` into a reference type, the `cast!` macro just uses the given reference. However, if you want to cast into an owned type, then the referenced value gets cloned. This is because it would be unsafe to take ownership from the Lia runtime, as Lia makes no guarantees on the number of references to a value at any point in time. 154 | 155 | ## TODO list 156 | - [ ] Add JIT for dynamic execution 157 | - [ ] Make errors clear on both the Lia level (Spans) and Rust level (code maps?) 158 | - [ ] Import remaining language constructs 159 | - [ ] Loops 160 | - [ ] Rest of the binops 161 | - [ ] Modules 162 | -------------------------------------------------------------------------------- /lia-experimental/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lia-experimental" 3 | version = "0.1.0" 4 | authors = ["Will Crichton "] 5 | build = "build.rs" 6 | 7 | [dependencies] 8 | lalrpop-util = "0.12.0" 9 | rabbot = { path = "../../rabbot/rabbot", version = "0.1" } 10 | rabbot-plugin = { path = "../../rabbot/rabbot-plugin" } 11 | lia-jit = { path = "../lia-jit" } 12 | llvm-alt = "0.5.0" 13 | rustlex_codegen = { version = "0.3.3", features = ["with-syntex"] } 14 | 15 | [build-dependencies] 16 | rustlex_codegen = { version = "0.3.3", features = ["with-syntex"] } 17 | syntex = { version = "0.32.0" } 18 | lalrpop = "0.12.0" 19 | -------------------------------------------------------------------------------- /lia-experimental/build.rs: -------------------------------------------------------------------------------- 1 | extern crate lalrpop; 2 | extern crate syntex; 3 | extern crate rustlex_codegen; 4 | 5 | use std::env; 6 | use std::path::Path; 7 | 8 | fn main () { 9 | lalrpop::process_root().unwrap(); 10 | 11 | let mut registry = syntex::Registry::new(); 12 | rustlex_codegen::plugin_registrar(&mut registry); 13 | let src = Path::new("src/lexer.in.rs"); 14 | let dst = Path::new("src/lexer.rs"); 15 | println!("{:?}, {:?}", src, dst); 16 | registry.expand("", &src, &dst).unwrap(); 17 | } 18 | -------------------------------------------------------------------------------- /lia-experimental/src/ast.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | #[derive(Clone, Eq, Debug, PartialEq)] 4 | pub enum TypPrimitive { 5 | Int32, 6 | String 7 | } 8 | 9 | impl TryFrom for TypPrimitive { 10 | type Err = (); 11 | fn try_from(t: String) -> Result { 12 | use ast::TypPrimitive::*; 13 | match t.as_str() { 14 | "i32" => Ok(Int32), 15 | "String" => Ok(String), 16 | _ => Err(()) 17 | } 18 | } 19 | } 20 | 21 | rabbot! { 22 | use mark::Mark; 23 | use ast::TypPrimitive; 24 | 25 | enum Typ {mark: Mark} { 26 | Hole, 27 | Primitive(TypPrimitive), 28 | Arrow((Typ, Typ)), 29 | Product(Vec<(String, Typ)>), 30 | ForAll(Binding . Typ), 31 | Exists(Binding . Typ) 32 | } 33 | 34 | enum Term {mark: Mark} { 35 | Dummy, 36 | Number(i32), 37 | String(String), 38 | Quote(Vec), 39 | Plus((Term, Term)), 40 | Lam(Binding . Term), 41 | Let((Term, Binding . Term)), 42 | TLet((Typ, Binding. Term)), 43 | Product(Vec<(String, Term)>), 44 | Dot((Term, String)), 45 | Annot((Term, Typ)), 46 | App((Term, Term)) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lia-experimental/src/elaborate.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use super::ast::*; 3 | 4 | type BindMap = HashMap>; 5 | 6 | pub fn elaborate(expr: Expr) -> Expr { 7 | elab_expr(expr, &mut HashMap::new(), 0) 8 | } 9 | 10 | pub fn elab_expr(expr: Expr, binds: &mut BindMap, depth: i32) -> Expr { 11 | match expr { 12 | Expr::Let(id, box bind, box body) => { 13 | let bind_new = elab_expr(bind, binds, depth); 14 | { 15 | let depths = binds.entry(id.string()).or_insert(Vec::new()); 16 | depths.push(depth); 17 | } 18 | let body_new = elab_expr(body, binds, depth + 1); 19 | { 20 | let depths = binds.entry(id.string()).or_insert(Vec::new()); 21 | depths.pop(); 22 | } 23 | Expr::Let(id, box bind_new, box body_new) 24 | }, 25 | Expr::Plus(box l, box r) => 26 | Expr::Plus(box elab_expr(l, binds, depth), box elab_expr(r, binds, depth)), 27 | Expr::Seq(box l, box r) => 28 | Expr::Seq(box elab_expr(l, binds, depth), box elab_expr(r, binds, depth)), 29 | Expr::Id(id) => 30 | Expr::Id(Id::Index(*(binds.get(&id.string()).unwrap().last().unwrap()))), 31 | _ => expr 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lia-experimental/src/grammar.lalrpop: -------------------------------------------------------------------------------- 1 | use std::default::Default; 2 | use super::token::Token; 3 | use super::mark::Mark; 4 | use super::ast::{term, typ}; 5 | use super::ast::term::{Term, View as TermV}; 6 | use super::ast::typ::{Typ, View as TypV}; 7 | use rabbot::var::Var; 8 | 9 | grammar; 10 | 11 | extern { 12 | type Location = u32; 13 | 14 | enum Token { 15 | Let => Token::Let, 16 | Eq => Token::Eq, 17 | Semi => Token::Semi, 18 | Plus => Token::Plus, 19 | Int => Token::Int(), 20 | Lparen => Token::Lparen, 21 | Rparen => Token::Rparen, 22 | Lbrace => Token::Lbrace, 23 | Rbrace => Token::Rbrace, 24 | QuoteMarker => Token::QuoteMarker, 25 | Fun => Token::Fun, 26 | FatArrow => Token::FatArrow, 27 | ThinArrow => Token::ThinArrow, 28 | Colon => Token::Colon, 29 | Comma => Token::Comma, 30 | Dot => Token::Dot, 31 | Type => Token::Type, 32 | QuoteChar => Token::QuoteChar(), 33 | Splice => Token::Splice(), 34 | IdT => Token::Id(), 35 | String => Token::String(), 36 | } 37 | } 38 | 39 | TeMeta: term::Meta = { 40 | => { 41 | term::Meta { 42 | val: s, 43 | mark: Mark { lo: lo, hi: hi }, 44 | } 45 | } 46 | }; 47 | 48 | TeM: Term = { 49 | TeMeta => term::into(<>) 50 | }; 51 | 52 | TyMeta: typ::Meta = { 53 | => { 54 | typ::Meta { 55 | val: s, 56 | mark: Mark { lo: lo, hi: hi }, 57 | } 58 | } 59 | }; 60 | 61 | TyM: Typ = { 62 | TyMeta => typ::into(<>) 63 | }; 64 | 65 | // Dummy definitions to work with the LALRPOP type system 66 | Term: Term = {}; 67 | Var: Var = {}; 68 | TermV: TermV = {}; 69 | TypV: TypV = {}; 70 | 71 | TypP0: TypV = { 72 | > ThinArrow > => TypV::Arrow((t1, t2)), 73 | TypP1 74 | }; 75 | 76 | 77 | Sep: Vec = { 78 | S)*> => { 79 | let mut ts = ts; 80 | ts.push(t); 81 | ts 82 | } 83 | }; 84 | 85 | TypP1: TypV = { 86 | TyMeta => typ::var(<>), 87 | Lbrace Colon >), Comma>> Rbrace => TypV::Product(tys), 88 | }; 89 | 90 | TyAnnot: (T, Option) = { 91 | >)?> => (t, ty) 92 | }; 93 | 94 | pub Toplevel: Term = { TeM }; 95 | 96 | Block: TermV = { 97 | Let > Eq > Semi > => { 98 | let (id, ty) = id; 99 | let bind = match ty { 100 | Some(ty) => term::into(term::Meta { 101 | val: TermV::Annot((bind, ty)), 102 | ..Default::default() 103 | }), 104 | None => bind 105 | }; 106 | TermV::Let((bind, (id, body))) 107 | }, 108 | > Semi > => 109 | TermV::Let((l, (Var::new(), r))), 110 | Type Eq > Semi > => 111 | TermV::TLet((l, (id, r))), 112 | TermP0 113 | }; 114 | 115 | TermP0: TermV = { 116 | > > => TermV::App((l, r)), 117 | TermP1 118 | }; 119 | 120 | TermP1: TermV = { 121 | Fun FatArrow > => TermV::Lam((id, e)), 122 | TermP2 123 | }; 124 | 125 | TermP2: TermV = { 126 | > Plus > => TermV::Plus((l, r)), 127 | TermP3 128 | }; 129 | 130 | TermP3: TermV = { 131 | > Dot => TermV::Dot((l, r)), 132 | TermP4 133 | }; 134 | 135 | TermP4: TermV = { 136 | Int => TermV::Number(<>), 137 | TeMeta => term::var(<>), 138 | String => TermV::String(<>), 139 | Lparen Rparen => e, 140 | Lbrace Rbrace => e, 141 | Lbrace Colon >), Comma>> Rbrace => TermV::Product(fields), 142 | QuoteMarker Lbrace *> Rbrace => TermV::Quote(q) 143 | }; 144 | 145 | StringToVar: Var = { 146 | T => Var::from_string(<>) 147 | }; 148 | 149 | QuotePart: TermV = { 150 | QuoteChar => TermV::String(<>), 151 | TeMeta, Var> => term::var(<>) 152 | }; 153 | 154 | Id: Var = { StringToVar }; 155 | -------------------------------------------------------------------------------- /lia-experimental/src/interpreter.rs: -------------------------------------------------------------------------------- 1 | use super::ast::term::{Term, View, out, into_view, subst}; 2 | use syntax::ext::base::ExtCtxt; 3 | use llvm::JitEngine; 4 | use lia_jit::{Jit, JitFun}; 5 | 6 | type Value = i32; 7 | 8 | pub struct EvalState<'a, 'b> { 9 | pub cx: ExtCtxt<'a>, 10 | pub jit: Jit<'b, JitEngine> 11 | } 12 | 13 | pub fn eval<'a, 'b>(st: &mut EvalState<'a, 'b>, expr: Term) -> Term { 14 | //println!("{:?}", expr); 15 | match out(expr).val { 16 | e @ View::Number(_) | e @ View::Lam(_) | e @ View::String(_) => into_view(e), 17 | View::Plus((l, r)) => { 18 | bind!(View::Number{x} = out(eval(st, l)).val); 19 | bind!(View::Number{y} = out(eval(st, r)).val); 20 | into_view(View::Number(x + y)) 21 | }, 22 | View::Let((binding, (var, body))) => { 23 | let e = eval(st, binding); 24 | eval(st, subst(e, var, body)) 25 | }, 26 | View::TLet((_, (_, body))) => eval(st, body), 27 | View::Annot((t, _)) => eval(st, t), 28 | View::App((fun, arg)) => { 29 | match out(eval(st, fun)).val { 30 | View::Lam((var, body)) => { 31 | let arg = eval(st, arg); 32 | eval(st, subst(arg, var, body)) 33 | }, 34 | View::Quote(parts) => { 35 | let s = parts.into_iter().map(|part| match out(part).val { 36 | View::String(s) => s, 37 | View::Number(n) => n.to_string(), 38 | View::Lam(_) => panic!("Lambdas in quotes not implemented"), 39 | View::Quote(_) => panic!("Quotes in quotes not implemented"), 40 | _ => unreachable!() 41 | }).collect::>().join(""); 42 | let f: JitFun = st.jit.gen_fun(s).unwrap(); 43 | bind!(View::Number{x} = out(arg).val); 44 | into_view(View::Number(f(x))) 45 | }, 46 | _ => unreachable!() 47 | } 48 | }, 49 | View::Quote(parts) => { 50 | into_view(View::Quote(parts.into_iter().map(|part| eval(st, part)).collect())) 51 | }, 52 | View::Product(fields) => { 53 | into_view(View::Product( 54 | fields.into_iter().map(|(key, val)| (key, eval(st, val))).collect())) 55 | }, 56 | View::Dot((prod, key)) => { 57 | bind!(View::Product{fields} = out(eval(st, prod)).val); 58 | let (_, val) = 59 | fields.into_iter().find(|&(ref fkey, _)| *fkey == key).unwrap(); 60 | val 61 | }, 62 | View::Var(_) | View::Var_(_) | View::Dummy => unreachable!(), 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lia-experimental/src/lexer.in.rs: -------------------------------------------------------------------------------- 1 | use token::{Token as Tok}; 2 | use std::str::FromStr; 3 | use mark::{Mark, Marked}; 4 | 5 | type Token = Marked; 6 | 7 | macro_rules! mark { 8 | ($lexer:ident, $e:expr) => { 9 | Some(Marked { 10 | node: $e, 11 | mark: Mark { 12 | lo: $lexer._input.tok.off as u32, 13 | hi: $lexer._input.pos.off as u32 14 | } 15 | }) 16 | } 17 | } 18 | 19 | macro_rules! some { ($x:expr) => { |lexer: &mut Lexer| -> Option { 20 | mark!(lexer, $x) 21 | } } } 22 | macro_rules! none { () => { |_: &mut Lexer| -> Option { None } } } 23 | 24 | rustlex! Lexer { 25 | property depth: u32 = 0; 26 | 27 | let WHITESPACE = [' ' '\n' '\t']; 28 | let ID = ['a'-'z''A'-'Z''_']['a'-'z''A'-'Z''_''0'-'9']*; 29 | let NUM = '0' | ['1'-'9']['0'-'9']*; 30 | let STRING = '"' .* '"'; 31 | 32 | INITIAL { 33 | . => |lexer: &mut Lexer| -> Option { 34 | panic!("unexpected token {:?}", lexer.yystr()) 35 | }, 36 | 37 | WHITESPACE => none!(), 38 | 39 | ID => |lexer: &mut Lexer| { 40 | mark!(lexer, Tok::Id(lexer.yystr().clone())) 41 | } 42 | NUM => |lexer: &mut Lexer| { 43 | mark!(lexer, Tok::Int(i32::from_str(&lexer.yystr()).unwrap())) 44 | } 45 | STRING => |lexer: &mut Lexer| { 46 | let s = lexer.yystr(); 47 | mark!(lexer, Tok::String((&s[1..(s.len()-1)]).to_string())) 48 | } 49 | 50 | '(' => some!(Tok::Lparen), 51 | ')' => some!(Tok::Rparen), 52 | 53 | '{' => |lexer: &mut Lexer| -> Option { 54 | mark!(lexer, Tok::Lbrace) 55 | }, 56 | 57 | '}' => |lexer: &mut Lexer| -> Option { 58 | mark!(lexer, Tok::Rbrace) 59 | }, 60 | 61 | '.' => some!(Tok::Dot), 62 | ',' => some!(Tok::Comma), 63 | ':' => some!(Tok::Colon), 64 | ';' => some!(Tok::Semi), 65 | '=' => some!(Tok::Eq), 66 | '+' => some!(Tok::Plus), 67 | "=>" => some!(Tok::FatArrow), 68 | "->" => some!(Tok::ThinArrow), 69 | 70 | "fn" => some!(Tok::Fun), 71 | "let" => some!(Tok::Let), 72 | "type" => some!(Tok::Type), 73 | 74 | "$rs" WHITESPACE* => |lexer: &mut Lexer| -> Option { 75 | lexer.QUOTE(); 76 | mark!(lexer, Tok::QuoteMarker) 77 | }, 78 | } 79 | 80 | QUOTE { 81 | [^'{''}''$']+ => |lexer: &mut Lexer| { 82 | mark!(lexer, Tok::QuoteChar(lexer.yystr())) 83 | } 84 | 85 | '}' => |lexer: &mut Lexer| -> Option { 86 | lexer.depth -= 1; 87 | mark!(lexer, if lexer.depth == 0 { 88 | lexer.INITIAL(); 89 | Tok::Rbrace 90 | } else { 91 | Tok::QuoteChar("}".to_string()) 92 | }) 93 | }, 94 | 95 | '{' => |lexer: &mut Lexer| -> Option { 96 | let tok = if lexer.depth == 0 { 97 | Tok::Lbrace 98 | } else { 99 | Tok::QuoteChar("{".to_string()) 100 | }; 101 | lexer.depth += 1; 102 | mark!(lexer, tok) 103 | }, 104 | 105 | '$' . => |_: &mut Lexer| -> Option { panic!("bad splice") } 106 | 107 | '$' ID => |lexer: &mut Lexer| -> Option { 108 | let s = lexer.yystr(); 109 | mark!(lexer, Tok::Splice((&s[1..s.len()]).to_string())) 110 | }, 111 | 112 | // "${" => |lexer: &mut Lexer| -> Option { 113 | // { 114 | // let mut depth = &mut lexer.depth[lexer.depth.len()-1]; 115 | // *depth += 1; 116 | // } 117 | // lexer.INITIAL(); 118 | // Some(Tok::SpliceStart) 119 | // } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /lia-experimental/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(box_syntax, box_patterns, slice_patterns, plugin, rustc_private, 2 | quote, question_mark, try_from)] 3 | #![plugin(rabbot_plugin)] 4 | 5 | #[macro_use] extern crate rabbot; 6 | #[macro_use] extern crate lia_jit; 7 | extern crate rustlex_codegen as rustlex; 8 | extern crate syntax; 9 | extern crate llvm; 10 | 11 | use std::io::BufReader; 12 | use lia_jit::{JitOptions, get_sysroot}; 13 | 14 | use mark::Marked; 15 | use token::Token; 16 | use lexer::Lexer; 17 | use ast::term::Term; 18 | use grammar::{parse_Toplevel as parse_toplevel}; 19 | use interpreter::EvalState; 20 | 21 | mod token; 22 | mod mark; 23 | mod lexer; 24 | mod ast; 25 | mod grammar; 26 | mod pprint; 27 | mod typecheck; 28 | mod interpreter; 29 | 30 | macro_rules! make_state { 31 | ($state:ident) => { 32 | let sess = syntax::parse::ParseSess::new(); 33 | let cfg = vec![]; 34 | let ecfg = syntax::ext::expand::ExpansionConfig::default("_".to_string()); 35 | let mut loader = syntax::ext::base::DummyMacroLoader; 36 | let cx = syntax::ext::base::ExtCtxt::new(&sess, cfg, ecfg, &mut loader); 37 | make_jit!(jit, JitOptions { sysroot: get_sysroot() }); 38 | let mut $state = EvalState { cx: cx, jit: jit }; 39 | } 40 | } 41 | 42 | pub fn compile(input: String) -> Term { 43 | let input = BufReader::new(input.as_bytes()); 44 | let lexer = Lexer::new(input); 45 | let tokens = lexer.collect::>>(); 46 | // println!("{:?}", tokens); 47 | let abt = parse_toplevel(tokens).unwrap(); 48 | //println!("{:?}", abt); 49 | if let Err(err) = typecheck::infer(abt.clone()) { 50 | panic!("{}", err) 51 | } 52 | 53 | make_state!(state); 54 | 55 | // Compiler complains if we use the canonical return form. ¯\_(ツ)_/¯ 56 | interpreter::eval(&mut state, abt) 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::compile; 62 | 63 | use ast::term::{View, out}; 64 | 65 | macro_rules! test_pass { 66 | ($name:ident, $src:expr) => { 67 | #[test] 68 | fn $name () { 69 | compile($src.to_string()); 70 | } 71 | }; 72 | ($name:ident, $src:expr, $expected:expr) => { 73 | #[test] 74 | fn $name () { 75 | bind!(View::Number{n} = out(compile($src.to_string())).val); 76 | assert_eq!(n, $expected); 77 | } 78 | }; 79 | } 80 | 81 | macro_rules! test_fail { 82 | ($name:ident, $src:expr) => { 83 | #[test] 84 | #[should_panic] 85 | fn $name () { 86 | compile($src.to_string()); 87 | } 88 | } 89 | } 90 | 91 | test_pass!( 92 | call_function, 93 | "let x = fn y => { y + 1 }; (x 0)", 94 | 1); 95 | 96 | test_pass!( 97 | quote, 98 | " 99 | let n = 0; 100 | let incr: i32 -> i32 = $rs { 101 | fn incr_n(x: i32) -> i32 { x + $n } 102 | }; 103 | (incr 1)", 104 | 1); 105 | 106 | test_pass!( 107 | preserve_polymorphism, 108 | r#"let x = fn y => { 1 }; (x 1); (x "foo"); x"#); 109 | 110 | test_pass!( 111 | type_alias, 112 | "type X = i32; let n: X = 3; n"); 113 | 114 | test_pass!( 115 | product_type, 116 | "type Foo = { y: i32 }; let x = { y: 3 }; x.y", 117 | 3); 118 | 119 | test_fail!( 120 | product_no_type_decl, 121 | "let x = { y: 3 }; x.y"); 122 | 123 | } 124 | -------------------------------------------------------------------------------- /lia-experimental/src/mark.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::default::Default; 3 | use token::Token; 4 | use grammar; 5 | 6 | #[derive(Clone, Debug)] 7 | pub struct Mark { 8 | pub lo: u32, 9 | pub hi: u32 10 | } 11 | 12 | pub static DUMMY: Mark = Mark { 13 | lo: 0, 14 | hi: 0, 15 | }; 16 | 17 | impl Default for Mark { 18 | fn default() -> Mark { DUMMY.clone() } 19 | } 20 | 21 | #[derive(Clone)] 22 | pub struct Marked { 23 | pub node: T, 24 | pub mark: Mark 25 | } 26 | 27 | impl fmt::Debug for Marked { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | self.node.fmt(f) 30 | } 31 | } 32 | 33 | impl grammar::__ToTriple for Marked { 34 | type Error = (); 35 | fn to_triple(value: Self) -> Result<(u32,Token,u32), ()> { 36 | Ok((value.mark.lo, value.node, value.mark.hi)) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lia-experimental/src/pprint.rs: -------------------------------------------------------------------------------- 1 | use ast::typ; 2 | use ast::typ::{Typ, View as TypV}; 3 | 4 | pub fn typ_to_string(typ: Typ) -> String { 5 | match typ::out(typ).val { 6 | TypV::Hole => "hole".to_string(), 7 | TypV::Primitive(prim) => format!("{:?}", prim), 8 | TypV::Arrow((t1, t2)) => 9 | format!("{} -> {}", typ_to_string(t1), typ_to_string(t2)), 10 | TypV::Product(typs) => 11 | format!("{{{}}}", 12 | typs.into_iter() 13 | .map(|(s, ty)| format!("{}: {}", s, typ_to_string(ty))) 14 | .collect::>() 15 | .join(", ")), 16 | TypV::ForAll((var, body)) => 17 | format!("∀{}. ({})", var, typ_to_string(body)), 18 | TypV::Exists((var, body)) => 19 | format!("∃{}. ({})", var, typ_to_string(body)), 20 | TypV::Var(var) => { 21 | let var = typ::extract_var(var); 22 | format!("{}", var) 23 | } 24 | TypV::Var_(_) => unreachable!() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lia-experimental/src/token.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug)] 2 | pub enum Token { 3 | Let, 4 | Eq, 5 | Semi, 6 | Plus, 7 | Int(i32), 8 | Lparen, 9 | Rparen, 10 | Lbrace, 11 | Rbrace, 12 | Fun, 13 | FatArrow, 14 | ThinArrow, 15 | Colon, 16 | Comma, 17 | Dot, 18 | Type, 19 | String(String), 20 | Id(String), 21 | QuoteChar(String), 22 | QuoteMarker, 23 | Splice(String), 24 | } 25 | -------------------------------------------------------------------------------- /lia-experimental/src/typecheck.rs: -------------------------------------------------------------------------------- 1 | //! Hindley-Milner type inference. 2 | /// Largely copied from here: 3 | 4 | use rabbot::var::Var; 5 | use mark::DUMMY; 6 | use ast::{term, typ, TypPrimitive as TyPrim}; 7 | use ast::term::{Term, View as TermV}; 8 | use ast::typ::{Typ, View as TypV}; 9 | use pprint::typ_to_string; 10 | use std::collections::HashMap; 11 | 12 | type Constraint = (Typ, Typ); 13 | type TypContext = HashMap; // term variables -> types 14 | type Solution = HashMap; // type variables -> types 15 | 16 | macro_rules! err { 17 | ($($x:expr),*) => { 18 | return Err(format!($($x),*)) 19 | } 20 | } 21 | 22 | fn fresh() -> Typ { 23 | typ::into_view(typ::var(typ::Meta { 24 | val: Var::new(), 25 | mark: Default::default() 26 | })) 27 | } 28 | 29 | fn apply_sol(sol: &Solution, ty: Typ) -> Typ { 30 | sol.iter().fold(ty, |ty, (var, sub)| { 31 | typ::subst(sub.clone(), var.clone(), ty) 32 | }) 33 | } 34 | 35 | fn apply_sol_ctx(sol: &Solution, ctx: TypContext) -> TypContext { 36 | ctx.into_iter() 37 | .map(|(var, ty)| { (var, apply_sol(sol, ty)) }) 38 | .collect::() 39 | } 40 | 41 | fn combine_sol(sol1: Solution, sol2: Solution) -> Solution { 42 | let sol1_it = sol1.clone().into_iter() 43 | .filter(|&(ref var, _)| !sol2.contains_key(&var)); 44 | let mut sol2 = sol2.clone().into_iter() 45 | .map(|(var, ty)| (var, apply_sol(&sol1, ty))) 46 | .collect::(); 47 | sol2.extend(sol1_it); 48 | sol2 49 | } 50 | 51 | fn add_sol(v: Var, ty: Typ, mut sol: Solution) -> Solution { 52 | let ty = apply_sol(&sol, ty); 53 | sol.insert(v, ty); 54 | sol 55 | } 56 | 57 | fn subst_constrs(ty: Typ, var: Var, cs: Vec) -> Vec { 58 | cs.into_iter().map(|(l, r)| { 59 | (typ::subst(ty.clone(), var.clone(), l), 60 | typ::subst(ty.clone(), var.clone(), r)) 61 | }).collect::>() 62 | } 63 | 64 | fn generalize_monotype(ctx: TypContext, ty: Typ) -> Typ { 65 | let free = typ::free_vars(ty.clone()); 66 | free.into_iter().fold(ty, |ty, var| { 67 | if !ctx.contains_key(&var) { 68 | typ::into(typ::Meta { 69 | val: TypV::ForAll((var, ty.clone())), 70 | mark: typ::out(ty).mark 71 | }) 72 | } else { 73 | ty 74 | } 75 | }) 76 | } 77 | 78 | // a <: b 79 | fn is_subtype(a: Typ, b: Typ) -> bool { 80 | if typ::aequiv(a.clone(), b.clone()) { 81 | return true; 82 | } 83 | 84 | match (typ::out(a).val, typ::out(b).val) { 85 | (_, TypV::Var(_)) => true, 86 | (TypV::Arrow((l1, r1)), TypV::Arrow((l2, r2))) => { 87 | // contravariant covariant 88 | is_subtype(l1, l2) && is_subtype(r2, r1) 89 | }, 90 | (TypV::ForAll((_, t1)), TypV::ForAll((_, t2))) => { 91 | is_subtype(t1, t2) 92 | }, 93 | _ => false 94 | } 95 | } 96 | 97 | fn unify(mut constraints: Vec) -> Solution { 98 | match constraints.pop() { 99 | Some((l, r)) => { 100 | let (lnode, rnode) = (typ::out(l.clone()), typ::out(r.clone())); 101 | match (lnode.val, rnode.val) { 102 | (TypV::Primitive(ref t1), TypV::Primitive(ref t2)) if t1 == t2 => 103 | unify(constraints), 104 | (TypV::Var(i), TypV::Var(j)) => { 105 | let (i, j) = (typ::extract_var(i), typ::extract_var(j)); 106 | if i == j { 107 | unify(constraints) 108 | } else { 109 | add_sol( 110 | i.clone(), r.clone(), 111 | unify(subst_constrs(r.clone(), i, constraints))) 112 | } 113 | }, 114 | (TypV::Var(i), ty) | (ty, TypV::Var(i)) => { 115 | let i = typ::extract_var(i); 116 | let ty = typ::into(typ::Meta { 117 | val: ty.clone(), ..Default::default() 118 | }); 119 | // TODO: need to check occurs in 120 | add_sol( 121 | i.clone(), 122 | ty.clone(), 123 | unify(subst_constrs(ty, i, constraints))) 124 | }, 125 | (TypV::Arrow((l1, r1)), TypV::Arrow((l2, r2))) => { 126 | constraints.push((l1, l2)); 127 | constraints.push((r1, r2)); 128 | unify(constraints) 129 | }, 130 | _ => panic!("Unification error: {} != {}", typ_to_string(l), typ_to_string(r)) 131 | } 132 | }, 133 | None => HashMap::new() 134 | } 135 | } 136 | 137 | fn constrain(mut ctx: TypContext, t: Term, base_sol: &Solution) 138 | -> Result<(Typ, Solution), String> 139 | { 140 | //println!("{:?}", t); 141 | let node = term::out(t); 142 | let (typ, sol) = match node.val.clone() { 143 | TermV::Number(_) => (typ::into_view(TypV::Primitive(TyPrim::Int32)), HashMap::new()), 144 | TermV::String(_) => (typ::into_view(TypV::Primitive(TyPrim::String)), HashMap::new()), 145 | TermV::Var(var) => { 146 | let var = term::extract_var(var); 147 | match ctx.get(&var) { 148 | Some(ty) => (match typ::out(ty.clone()).val { 149 | TypV::ForAll((_, ty)) => ty, 150 | _ => ty.clone() 151 | }, HashMap::new()), 152 | None => panic!("Unbound var {:?}", var) 153 | } 154 | }, 155 | TermV::Lam((bind, body)) => { 156 | let arg_var = Var::new(); 157 | let arg_ty = typ::into(typ::Meta { 158 | val: typ::var(typ::Meta { 159 | val: arg_var.clone(), 160 | mark: DUMMY.clone() 161 | }), 162 | mark: DUMMY.clone() 163 | }); 164 | ctx.insert(bind.clone(), arg_ty.clone()); 165 | let (ret_ty, sol) = constrain(ctx.clone(), body, base_sol)?; 166 | let arg_ty = apply_sol(&sol, arg_ty.clone()); 167 | (typ::into_view(TypV::Arrow((arg_ty, ret_ty))), sol) 168 | }, 169 | TermV::App((l, r)) => { 170 | let (domain_ty, range_ty) = (fresh(), fresh()); 171 | let (fun_ty, sol1) = constrain(ctx.clone(), l, base_sol)?; 172 | let (arg_ty, sol2) = constrain(apply_sol_ctx(&sol1, ctx.clone()), r, base_sol)?; 173 | let sol = combine_sol(sol1, sol2); 174 | let sol = combine_sol(sol.clone(), unify(vec![ 175 | (apply_sol(&sol, fun_ty), 176 | apply_sol(&sol, 177 | typ::into_view(TypV::Arrow((domain_ty.clone(), range_ty.clone()))))), 178 | (apply_sol(&sol, arg_ty), 179 | apply_sol(&sol, domain_ty))])); 180 | (range_ty, sol) 181 | }, 182 | TermV::Let((t, (var, body))) => { 183 | let (t_ty, sol1) = constrain(ctx.clone(), t, base_sol)?; 184 | let mut ctx = apply_sol_ctx(&sol1, ctx.clone()); 185 | let t_ty = generalize_monotype(ctx.clone(), apply_sol(&sol1, t_ty)); 186 | ctx.insert(var, t_ty.clone()); 187 | let (r_ty, sol2) = constrain(ctx, body, base_sol)?; 188 | (r_ty, combine_sol(sol1, sol2)) 189 | }, 190 | TermV::TLet((t, (var, body))) => { 191 | let mut sol = base_sol.clone(); 192 | let t = apply_sol(&sol, t); 193 | sol.insert(var, t); 194 | let (r_ty, rsol) = constrain(ctx.clone(), body, &sol)?; 195 | (r_ty, combine_sol(sol, rsol)) 196 | }, 197 | TermV::Plus((l, r)) => { 198 | let (l_ty, l_sol) = constrain(ctx.clone(), l, base_sol)?; 199 | let (r_ty, r_sol) = constrain(ctx.clone(), r, base_sol)?; 200 | let num_ty = typ::into_view(TypV::Primitive(TyPrim::Int32)); 201 | let sol = combine_sol(l_sol, r_sol); 202 | let sol = combine_sol(sol.clone(), unify(vec![ 203 | (apply_sol(&sol, l_ty), num_ty.clone()), 204 | (apply_sol(&sol, r_ty), num_ty.clone())])); 205 | (num_ty, sol) 206 | }, 207 | TermV::Quote(_) => { 208 | let (a, b) = (fresh(), fresh()); 209 | let ty = typ::into_view(TypV::Arrow((a, b))); 210 | (ty, HashMap::new()) 211 | }, 212 | TermV::Annot((t, annotated)) => { 213 | let (inferred, sol) = constrain(ctx.clone(), t, base_sol)?; 214 | if !is_subtype( 215 | apply_sol(&sol, annotated.clone()), 216 | apply_sol(&sol, inferred.clone())) { 217 | return Err(format!("Annotation error: {} != {}", 218 | typ_to_string(annotated), 219 | typ_to_string(inferred))); 220 | } 221 | (annotated, sol) 222 | }, 223 | TermV::Product(fields) => { 224 | let mut expected = None; 225 | for (ty_name, ty) in base_sol.iter() { 226 | if let TypV::Product(ty_fields) = typ::out(ty.clone()).val { 227 | expected = Some((ty.clone(), ty_name.clone(), ty_fields)); 228 | } 229 | } 230 | 231 | match expected { 232 | Some((ty, ty_name, ty_fields)) => { 233 | let mut sol = base_sol.clone(); 234 | for (key, val) in fields.into_iter() { 235 | let mut found = false; 236 | for &(ref ty_key, ref ty_val) in ty_fields.iter() { 237 | if key == *ty_key { 238 | found = true; 239 | let (ty_term_val, new_sol) = 240 | constrain(ctx.clone(), val, &sol)?; 241 | sol = combine_sol(sol, new_sol); 242 | 243 | if !typ::aequiv(ty_term_val.clone(), ty_val.clone()) { 244 | return Err(format!( 245 | "{:?}: {} != {}", 246 | key, 247 | typ_to_string(ty_val.clone()), 248 | typ_to_string(ty_term_val))); 249 | } 250 | 251 | break; 252 | } 253 | } 254 | 255 | if !found { 256 | err!("Unexpected field {:?}", key); 257 | } 258 | } 259 | 260 | (ty, sol) 261 | } 262 | None => err!("No matching product type") 263 | } 264 | }, 265 | TermV::Dot((prod, key)) => { 266 | let (prod_ty, sol) = constrain(ctx.clone(), prod, base_sol)?; 267 | match typ::out(prod_ty).val { 268 | TypV::Product(fields) => { 269 | match fields.iter().find(|&&(ref ty_key, _)| key == *ty_key) { 270 | Some(&(_, ref ty_val)) => (ty_val.clone(), sol), 271 | None => err!("Key `{}` does not exist", key) 272 | } 273 | }, 274 | _ => err!("Key access `{}` on non-product type", key) 275 | } 276 | }, 277 | TermV::Var_(_) | TermV::Dummy => unreachable!(), 278 | }; 279 | 280 | let sol = combine_sol(base_sol.clone(), sol); 281 | 282 | //let typ = resolve_alias(&sol, typ); 283 | //println!("{:?} --> {:?}", node.val, typ); 284 | 285 | Ok((typ, sol)) 286 | } 287 | 288 | pub fn infer(t: Term) -> Result { 289 | let mut base_sol = HashMap::new(); 290 | base_sol.insert( 291 | Var::from_string("i32".to_string()), 292 | typ::into_view(TypV::Primitive(TyPrim::Int32))); 293 | 294 | let (ty, sol) = constrain(HashMap::new(), t, &base_sol)?; 295 | Ok(generalize_monotype(HashMap::new(), apply_sol(&sol, ty))) 296 | } 297 | -------------------------------------------------------------------------------- /lia-jit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lia-jit" 3 | version = "0.1.0" 4 | authors = ["Will Crichton "] 5 | 6 | [dependencies] 7 | llvm-alt = "0.5.0" 8 | llvm-sys = "0.4.0" 9 | -------------------------------------------------------------------------------- /lia-jit/src/bin/repl.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private, question_mark)] 2 | 3 | extern crate syntax; 4 | extern crate getopts; 5 | extern crate llvm; 6 | #[macro_use] extern crate lia_jit; 7 | 8 | use llvm::JitEngine; 9 | use lia_jit::Jit; 10 | 11 | static EVAL_FN: &'static str = "_jit_eval"; 12 | 13 | struct State<'a> { 14 | jit: Jit<'a, JitEngine>, 15 | anon_count: i32 16 | } 17 | 18 | fn run<'a>(st: &mut State<'a>, input: String) -> Result<(), String> { 19 | use syntax::parse; 20 | 21 | let sess = parse::ParseSess::new(); 22 | let crate_name = "jit".to_string(); 23 | 24 | let input = match parse::parse_item_from_source_str( 25 | crate_name.clone(), input.clone(), vec![], &sess) 26 | { 27 | Ok(Some(_)) => input, 28 | err => { 29 | if let Err(mut err) = err { 30 | err.cancel(); 31 | } 32 | 33 | match parse::parse_expr_from_source_str( 34 | crate_name.clone(), input.clone(), vec![], &sess) 35 | { 36 | Ok(_) => { 37 | let anon_fn = format!("{}_{}", EVAL_FN, st.anon_count); 38 | st.anon_count += 1; 39 | format!(r#"fn {} () {{ println!("{{:?}}", {{ {} }}) }}"#, anon_fn, input) 40 | }, 41 | Err(mut err) => { 42 | err.cancel(); 43 | return Err("Input was neither expression nor item".to_string()) 44 | } 45 | } 46 | } 47 | }; 48 | 49 | let fun = st.jit.gen_fun::<(),()>(input)?; 50 | fun(()); 51 | 52 | Ok(()) 53 | } 54 | 55 | fn repl(matches: getopts::Matches) { 56 | use std::io::{stdin, stdout, Write}; 57 | use lia_jit::{JitOptions, get_sysroot}; 58 | 59 | if !matches.free.is_empty() { 60 | panic!("Need to handle input files"); 61 | } 62 | 63 | make_jit!(jit, JitOptions { sysroot: get_sysroot() }); 64 | let mut st = State { jit: jit, anon_count: 0 }; 65 | 66 | loop { 67 | let mut line = String::new(); 68 | print!(">>> "); stdout().flush().unwrap(); 69 | stdin().read_line(&mut line).expect("Failed to read line"); 70 | match run(&mut st, line) { 71 | Ok(_) => (), 72 | Err(s) => println!("{}", s) 73 | } 74 | } 75 | } 76 | 77 | fn print_usage(program: &str, opts: &[getopts::OptGroup]) { 78 | let brief = format!("Usage: {} [FILE] [OPTIONS]", program); 79 | print!("{}", getopts::usage(&brief, opts)); 80 | } 81 | 82 | fn main() { 83 | use getopts::{optflag, getopts}; 84 | use std::env; 85 | 86 | let args: Vec = env::args().collect(); 87 | let program = args[0].clone(); 88 | let opts = &[ 89 | optflag("h", "help", "print this help menu") 90 | ]; 91 | let matches = match getopts(&args[1..], opts) { 92 | Ok(m) => { m } 93 | Err(f) => { panic!(f.to_string()) } 94 | }; 95 | if matches.opt_present("h") { 96 | print_usage(&program, opts); 97 | return; 98 | } 99 | 100 | repl(matches); 101 | } 102 | -------------------------------------------------------------------------------- /lia-jit/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin, rustc_private, box_syntax)] 2 | 3 | extern crate rustc; 4 | extern crate rustc_driver; 5 | extern crate rustc_lint; 6 | extern crate rustc_metadata; 7 | extern crate rustc_llvm; 8 | extern crate rustc_resolve; 9 | extern crate rustc_trans; 10 | #[macro_use] extern crate syntax; 11 | extern crate getopts; 12 | 13 | extern crate llvm; 14 | extern crate llvm_sys; 15 | 16 | use rustc_trans::ModuleSource; 17 | use rustc_driver::{CompilerCalls, Compilation}; 18 | use rustc_driver::driver::CompileController; 19 | use rustc::session::Session; 20 | use rustc::middle::cstore::LinkagePreference; 21 | use syntax::codemap::FileLoader; 22 | use syntax::print::pprust; 23 | use syntax::ast::ItemKind; 24 | use syntax::parse::token::str_to_ident; 25 | use std::ffi::CString; 26 | use std::io; 27 | use std::path::{PathBuf, Path}; 28 | use std::rc::Rc; 29 | use std::cell::RefCell; 30 | use std::convert::From; 31 | use std::mem; 32 | use std::ptr; 33 | use llvm::{ExecutionEngine, JitEngine, Compile}; 34 | use llvm_sys::execution_engine::{LLVMExecutionEngineRef as LLVMEngine}; 35 | 36 | pub type JitFun = Box R>; 37 | 38 | #[derive(Clone)] 39 | struct JitInput { 40 | input: String 41 | } 42 | 43 | impl JitInput { 44 | pub fn new(input: String) -> JitInput { 45 | JitInput { 46 | input: input 47 | } 48 | } 49 | } 50 | 51 | impl FileLoader for JitInput { 52 | fn file_exists(&self, _: &Path) -> bool { true } 53 | fn abs_path(&self, _: &Path) -> Option { None } 54 | fn read_file(&self, _: &Path) -> io::Result { Ok(self.input.clone()) } 55 | } 56 | 57 | #[allow(dead_code)] 58 | struct JitState<'a, Eng> 59 | where Eng: ExecutionEngine<'a>, LLVMEngine: From<&'a Eng> 60 | { 61 | engine: llvm::CSemiBox<'a, Eng>, 62 | other_modules: Vec>, 63 | name: String, 64 | anon_count: u32, 65 | return_slot: *const i32, 66 | funs: String, 67 | } 68 | 69 | impl<'a> JitState<'a, JitEngine> 70 | { 71 | fn llvm_to_fun(&mut self, llmod: &llvm::CSemiBox<'a, llvm::Module>) { 72 | self.engine.add_module(llmod); 73 | let fun = self.engine.find_function(self.name.as_str()).expect("Function not found"); 74 | self.return_slot = unsafe { 75 | let fun: extern fn(()) -> () = self.engine.get_function(fun); 76 | mem::transmute(fun) 77 | }; 78 | } 79 | } 80 | 81 | pub struct JitOptions { 82 | pub sysroot: String 83 | } 84 | 85 | pub struct Jit<'a, Eng> 86 | where Eng: ExecutionEngine<'a>, LLVMEngine: From<&'a Eng> 87 | { 88 | state: Rc>>, 89 | opts: JitOptions, 90 | } 91 | 92 | impl<'a> Jit<'a, JitEngine> { 93 | pub fn new(engine: llvm::CSemiBox<'a, JitEngine>, 94 | opts: JitOptions) 95 | -> Jit<'a, JitEngine> 96 | { 97 | Jit { 98 | opts: opts, 99 | state: Rc::new(RefCell::new(JitState { 100 | engine: engine, 101 | other_modules: vec![], 102 | name: "".to_string(), 103 | funs: "".to_string(), 104 | anon_count: 0u32, 105 | return_slot: ptr::null(), 106 | })) 107 | } 108 | } 109 | 110 | pub fn gen_fun(&mut self, input: String) -> Result, String> 111 | where A: Compile<'a> + 'static, R: Compile<'a> + 'static 112 | { 113 | use rustc_driver; 114 | use syntax::parse; 115 | 116 | let crate_name = "jit".to_string(); 117 | let sess = parse::ParseSess::new(); 118 | 119 | let (input, name, decl) = match parse::parse_item_from_source_str( 120 | crate_name.clone(), input.clone(), vec![], &sess) 121 | { 122 | Ok(Some(item)) => { 123 | let item = item.unwrap(); 124 | let name = item.ident; 125 | let (input, decl) = match item.node { 126 | ItemKind::Fn(decl, unsafety, constness, _, generics, body) => { 127 | let name_u = str_to_ident(format!("_{}", name.name.as_str()).as_str()); 128 | let extern_s = pprust::fun_to_string( 129 | &decl.clone().unwrap(), unsafety, constness, 130 | name_u.clone(), &generics); 131 | let decl_s = pprust::fun_to_string( 132 | &decl.clone().unwrap(), unsafety, constness, 133 | name.clone(), &generics); 134 | let args = decl.inputs.iter() 135 | .map(|arg| pprust::pat_to_string(&arg.pat.clone().unwrap())) 136 | .collect::>() 137 | .join(","); 138 | let block = pprust::block_to_string(&body.unwrap()); 139 | (format!("#[no_mangle] {} {{ {} }} \ 140 | #[no_mangle] {} {{ {}({}) }}", 141 | extern_s, block, decl_s, name_u, args), 142 | format!("extern {{ {}; }} \ 143 | #[no_mangle] {} {{ unsafe {{ {}({}) }} }}", 144 | extern_s, decl_s, name_u, args)) 145 | } 146 | _ => return Err("Not a function".to_string()) 147 | }; 148 | (input, name, decl) 149 | } 150 | Err(mut err) => { 151 | err.cancel(); 152 | return Err(err.message.clone()); 153 | }, 154 | Ok(None) => { return Err("Bad parse".to_string()); } 155 | }; 156 | 157 | let input = { 158 | let mut state = self.state.borrow_mut(); 159 | let input = format!("{}\n#[no_mangle] {}", state.funs, input); 160 | state.name = name.name.as_str().to_string(); 161 | input 162 | }; 163 | 164 | let jit_input = JitInput::new(input.clone()); 165 | let args: Vec = 166 | format!( 167 | "_ {} --sysroot {} --crate-type dylib --cap-lints allow", 168 | crate_name, 169 | self.opts.sysroot) 170 | .split(' ').map(|s| s.to_string()).collect(); 171 | 172 | if let (Err(n), _) = 173 | rustc_driver::run_compiler_with_file_loader(&args, self, box jit_input) 174 | { 175 | return Err(format!("Compilation error {}", n)); 176 | }; 177 | rustc_driver::driver::reset_thread_local_state(); 178 | 179 | let mut state = self.state.borrow_mut(); 180 | state.funs = format!("{}\n{}", state.funs, decl); 181 | Ok(box unsafe { 182 | mem::transmute(state.return_slot) 183 | }) 184 | } 185 | } 186 | 187 | impl<'a> CompilerCalls<'a> for Jit<'a, JitEngine> { 188 | fn build_controller(&mut self, 189 | _: &Session, 190 | _: &getopts::Matches) 191 | -> CompileController<'a> { 192 | let mut cc: CompileController<'a> = CompileController::basic(); 193 | cc.after_llvm.stop = Compilation::Stop; 194 | cc.after_llvm.run_callback_on_error = true; 195 | let jit_state = self.state.clone(); 196 | cc.after_llvm.callback = Box::new(move |state| { 197 | state.session.abort_if_errors(); 198 | let trans = state.trans.unwrap(); 199 | assert_eq!(trans.modules.len(), 1); 200 | 201 | let rs_llmod = match trans.modules[0].source { 202 | ModuleSource::Translated(llmod) => llmod.llmod, 203 | ModuleSource::Preexisting(_) => unreachable!() 204 | }; 205 | assert!(!rs_llmod.is_null()); 206 | 207 | //unsafe { rustc_llvm::LLVMDumpModule(rs_llmod) }; 208 | 209 | let crates = state.session.cstore.used_crates(LinkagePreference::RequireDynamic); 210 | 211 | // Collect crates used in the session. Reverse order finds dependencies first. 212 | let deps: Vec = 213 | crates.into_iter().rev().filter_map(|(_, p)| p).collect(); 214 | 215 | for path in deps { 216 | let s = match path.as_os_str().to_str() { 217 | Some(s) => s, 218 | None => panic!( 219 | "Could not convert crate path to UTF-8 string: {:?}", path) 220 | }; 221 | let cs = CString::new(s).unwrap(); 222 | let res = unsafe { llvm_sys::support::LLVMLoadLibraryPermanently(cs.as_ptr()) }; 223 | if res != 0 { 224 | panic!("Failed to load crate {:?}", path.display()); 225 | } 226 | } 227 | 228 | let llmod: &'a llvm::Module = 229 | (rs_llmod as llvm_sys::prelude::LLVMModuleRef).into(); 230 | let llmod = llmod.clone(); 231 | llmod.verify().expect("Module invalid"); 232 | 233 | let mut state = jit_state.borrow_mut(); 234 | state.llvm_to_fun(&llmod); 235 | 236 | state.other_modules.push(llmod); 237 | 238 | }); 239 | cc 240 | } 241 | } 242 | 243 | pub fn get_sysroot() -> String { 244 | use std::env; 245 | match env::var("SYSROOT") { 246 | Ok(sysroot) => sysroot, 247 | Err(_) => panic!("SYSROOT env var not set") 248 | } 249 | } 250 | 251 | #[macro_export] 252 | macro_rules! make_jit { 253 | ($jit:ident, $opts:expr) => { 254 | let _jit_ctx = ::llvm::Context::new(); 255 | let _jit_ctx = _jit_ctx.as_semi(); 256 | let module = ::llvm::Module::new("_jit_main", &_jit_ctx); 257 | let engine = { 258 | use llvm::ExecutionEngine; 259 | ::llvm::JitEngine::new(&module, ::llvm::JitOptions {opt_level: 0}) 260 | .expect("Jit not initialized") 261 | }; 262 | let $jit = ::lia_jit::Jit::new(engine, $opts); 263 | } 264 | } 265 | 266 | #[cfg(test)] 267 | mod test { 268 | use super::*; 269 | 270 | macro_rules! make_test { 271 | ($fun:ident, $code:expr, $e:expr) => { 272 | #[test] 273 | fn $fun() { 274 | let _jit_ctx = ::llvm::Context::new(); 275 | let _jit_ctx = _jit_ctx.as_semi(); 276 | let module = ::llvm::Module::new("_jit_main", &_jit_ctx); 277 | let engine = { 278 | use llvm::ExecutionEngine; 279 | ::llvm::JitEngine::new(&module, ::llvm::JitOptions {opt_level: 0}) 280 | .expect("Jit not initialized") 281 | }; 282 | let mut jit = Jit::new(engine, JitOptions { sysroot: get_sysroot() }); 283 | let input = $code.to_string(); 284 | let fun: JitFun<(), i32> = jit.gen_fun(input).expect("Invalid fun"); 285 | assert_eq!(fun(()), $e); 286 | } 287 | } 288 | } 289 | 290 | //make_test!(compile_test, r#"#[no_mangle] pub fn test_add(a: i32, b: i32) -> i32 { a + b }"#); 291 | make_test!(expr_test, "fn foo() -> i32 { 1 + 2 }", 3); 292 | // make_test!(print_test, "{println!(\"hello world\");}"); 293 | } 294 | -------------------------------------------------------------------------------- /lia-plugin/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lia-plugin" 3 | version = "0.1.0" 4 | description = "Compiler plugin for writing Lia code as a macro" 5 | repository = "https://github.com/willcrichton/lia" 6 | license = "Apache-2.0/MIT" 7 | authors = ["Will Crichton "] 8 | 9 | [lib] 10 | plugin = true 11 | crate-type = ["dylib"] 12 | 13 | [dependencies] 14 | lia = { path = "../lia", version = "0.1.0" } 15 | -------------------------------------------------------------------------------- /lia-plugin/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private, plugin_registrar, quote, box_syntax)] 2 | //#![allow(unused_imports, unused_variables, dead_code)] 3 | 4 | extern crate rustc; 5 | extern crate rustc_plugin; 6 | extern crate syntax; 7 | extern crate lia; 8 | 9 | mod plugin; 10 | 11 | use rustc_plugin::Registry; 12 | use syntax::parse::token::intern; 13 | use syntax::ext::base::SyntaxExtension; 14 | 15 | #[plugin_registrar] 16 | pub fn plugin_registrar(reg: &mut Registry) { 17 | reg.register_macro("lia", plugin::expand_lia); 18 | reg.register_macro("_borrow_type", plugin::expand_borrow_type); 19 | reg.register_macro("_alloc", plugin::expand_alloc); 20 | reg.register_syntax_extension( 21 | intern("lia_impl_glue"), 22 | SyntaxExtension::MultiModifier(box plugin::impl_glue)); 23 | } 24 | -------------------------------------------------------------------------------- /lia-plugin/src/plugin.rs: -------------------------------------------------------------------------------- 1 | use syntax::codemap::Span; 2 | use syntax::parse::token::{Token, str_to_ident}; 3 | use syntax::parse::token; 4 | use syntax::ast::*; 5 | use syntax::tokenstream::TokenTree; 6 | use syntax::ext::base::{ExtCtxt, MacResult, MacEager, Annotatable}; 7 | use syntax::ext::build::AstBuilder; 8 | use syntax::fold; 9 | use syntax::fold::Folder; 10 | use syntax::util::small_vector::SmallVector as Svec; 11 | use syntax::ptr::P; 12 | 13 | use lia::token::LiaToken; 14 | use lia::ast::prefix_ident; 15 | use lia::codegen::gen_fn; 16 | use lia; 17 | 18 | fn tt_flatten(tt: &TokenTree) -> Vec { 19 | match tt { 20 | &TokenTree::Token(_, ref t) => vec![LiaToken::from_rust_token(t.clone())], 21 | &TokenTree::Delimited(_, ref delim) => { 22 | let ref tokens = delim.tts; 23 | let mut quoted = Vec::new(); 24 | let rs_id = str_to_ident("rust"); 25 | let mut in_quote = false; 26 | let mut quote_start = 0; 27 | let mut i = 0; 28 | 29 | quoted.push(LiaToken::from_rust_token(Token::OpenDelim(delim.delim))); 30 | if tokens.len() > 0 { 31 | while i < tokens.len() - 1 { 32 | let is_rust = |j| { 33 | if let &TokenTree::Token(_, Token::Ident(rs_id2)) = &tokens[j] { 34 | rs_id2.name.as_str() == rs_id.name.as_str() 35 | } else { 36 | false 37 | } 38 | }; 39 | match (is_rust(i), is_rust(i+1), &tokens[i], &tokens[i+1]) { 40 | (true, _, _, &TokenTree::Token(_, Token::Pound)) => { 41 | in_quote = true; 42 | quote_start = i + 2; 43 | i += 1; 44 | }, 45 | (_, true, &TokenTree::Token(_, Token::Pound), _) => { 46 | in_quote = false; 47 | quoted.push(LiaToken::Quote(tokens[quote_start..i].to_vec())); 48 | i += 1; 49 | }, 50 | _ => { 51 | if !in_quote { 52 | quoted.append(&mut tt_flatten(&tokens[i])); 53 | } 54 | } 55 | }; 56 | i += 1; 57 | } 58 | 59 | quoted.append(&mut tt_flatten(&tokens[i])); 60 | } 61 | quoted.push(LiaToken::from_rust_token(Token::CloseDelim(delim.delim))); 62 | 63 | quoted 64 | }, 65 | _ => panic!("TokenTree has Sequence??: {:?}", tt) 66 | } 67 | } 68 | 69 | #[allow(unused_variables)] 70 | pub fn expand_lia(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> 71 | Box 72 | { 73 | let tokens: Vec = 74 | args 75 | .into_iter() 76 | .flat_map(tt_flatten) 77 | .collect(); 78 | 79 | 80 | println!("tokens: {:?}", tokens); 81 | 82 | let ast = 83 | lia::grammar::parse_funs(tokens) 84 | .unwrap_or_else(|err| panic!("Parse error {:?}", err)); 85 | 86 | let ast = lia::elaborate::elaborate(ast); 87 | 88 | // All instances of the identifier "this" in the codegen'd AST must 89 | // have the same token or the compiler will complain. I'm not sure how 90 | // else to ensure this besides folding over the AST as below. 91 | let this = token::str_to_ident("this"); 92 | let mut renamer = Renamer { id: this, from: "this".to_string() }; 93 | 94 | let fs: Vec> = 95 | ast.into_iter() 96 | .map(|fun| renamer.fold_item(gen_fn(cx, fun)).get(0).clone()) 97 | .collect(); 98 | 99 | MacEager::items(Svec::many(fs)) 100 | } 101 | 102 | #[allow(unused_variables)] 103 | pub fn expand_alloc(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> 104 | Box 105 | { 106 | use syntax::parse::tts_to_parser; 107 | 108 | let mut parser = tts_to_parser(&cx.parse_sess, args.to_vec(), Vec::new()); 109 | let ty = match parser.parse_ty() { 110 | Ok(ty) => ty, 111 | Err(_) => panic!("Invalid type"), 112 | }; 113 | 114 | let expr = match parser.parse_expr() { 115 | Ok(expr) => expr, 116 | Err(_) => panic!("Invalid expr"), 117 | }; 118 | 119 | MacEager::expr(match ty.node.clone() { 120 | TyKind::Path(_, path) => { 121 | let path_s = format!("{}", path); 122 | if path_s == "i32" || path_s == "LiaNumber" { 123 | quote_expr!(cx, alloc_number($expr)) 124 | } else if path_s == "String" || path_s == "LiaString" { 125 | quote_expr!(cx, alloc_string($expr)) 126 | } else if path_s == "bool" || path_s == "LiaBool" { 127 | quote_expr!(cx, alloc_bool($expr)) 128 | } else if path_s == "LiaObject" { 129 | quote_expr!(cx, alloc_object($expr)) 130 | } else if path_s == "LiaClosure" { 131 | quote_expr!(cx, alloc_closure($expr)) 132 | } else { 133 | quote_expr!(cx, alloc_other($expr)) 134 | } 135 | }, 136 | TyKind::Tup(vec) => { 137 | assert!(vec.len() == 0); 138 | quote_expr!(cx, alloc_null($expr)) 139 | }, 140 | _ => panic!("Ty must be a path") 141 | }) 142 | } 143 | 144 | #[allow(unused_variables)] 145 | pub fn expand_borrow_type(cx: &mut ExtCtxt, sp: Span, args: &[TokenTree]) -> 146 | Box 147 | { 148 | use syntax::parse::tts_to_parser; 149 | 150 | let mut parser = tts_to_parser(&cx.parse_sess, args.to_vec(), Vec::new()); 151 | 152 | let id = match parser.parse_ident() { 153 | Ok(id) => id, 154 | Err(_) => panic!("Invalid ident"), 155 | }; 156 | 157 | let ty = match parser.parse_ty() { 158 | Ok(ty) => ty, 159 | Err(_) => panic!("Invalid ty"), 160 | }; 161 | 162 | let (ty, is_ref) = match &ty.node { 163 | &TyKind::Rptr(_, ref mutty) => { 164 | let sub_ty = mutty.ty.clone(); 165 | (sub_ty, true) 166 | }, 167 | &TyKind::Path(_, ref path) => { 168 | (ty.clone(), false) 169 | }, 170 | _ => panic!("Type isn't yet supported for casting with Lia"), 171 | }; 172 | 173 | let is_mut_borrow = match parser.parse_expr() { 174 | Ok(expr) => if let &ExprKind::Lit(ref lit) = &expr.node { 175 | if let &LitKind::Bool(ref b) = &lit.node { 176 | *b 177 | } else { 178 | panic!("Bad bool") 179 | } 180 | } else { 181 | panic!("Bad bool") 182 | }, 183 | Err(_) => { 184 | panic!("Bad bool") 185 | } 186 | }; 187 | 188 | let make_caster = |path: Path, path_s: String| -> P { 189 | let err_str = format!("Invalid cast: expected {}, found {{:?}}", path_s); 190 | if is_mut_borrow { 191 | quote_expr!(cx, { 192 | match *$id { 193 | $path(ref mut x) => x, 194 | ref other => panic!($err_str, other) 195 | } 196 | }) 197 | } else { 198 | quote_expr!(cx, { 199 | match *$id { 200 | $path(ref x) => x, 201 | ref other => panic!($err_str, other) 202 | } 203 | }) 204 | } 205 | }; 206 | 207 | let ty_str = format!("Invalid cast: expected {:?}, found {{:?}}", ty); 208 | 209 | let default = 210 | make_caster(quote_path!(cx, LiaValue::Unknown), format!("{:?}", ty)); 211 | let default = 212 | quote_expr!(cx, { $default.downcast_ref::<$ty>().expect($ty_str) }); 213 | 214 | let expr = match ty.node.clone() { 215 | TyKind::Path(_, path) => { 216 | let path_s = format!("{}", path); 217 | if path_s == "i32" || path_s == "LiaNumber" { 218 | make_caster(quote_path!(cx, LiaValue::Number), path_s) 219 | } else if path_s == "String" || path_s == "LiaString" { 220 | make_caster(quote_path!(cx, LiaValue::String), path_s) 221 | } else if path_s == "bool" || path_s == "LiaBool" { 222 | make_caster(quote_path!(cx, LiaValue::Bool), path_s) 223 | } else if path_s == "LiaObject" { 224 | make_caster(quote_path!(cx, LiaValue::Object), path_s) 225 | } else if path_s == "LiaClosure" { 226 | make_caster(quote_path!(cx, LiaValue::Closure), path_s) 227 | } else { 228 | default 229 | } 230 | }, 231 | _ => default 232 | }; 233 | 234 | let expr = if !is_ref { quote_expr!(cx, { ($expr).clone() }) } 235 | else { expr }; 236 | 237 | MacEager::expr(expr) 238 | } 239 | 240 | 241 | 242 | struct Renamer { 243 | from: String, 244 | id: Ident 245 | } 246 | 247 | impl Folder for Renamer { 248 | fn fold_ident(&mut self, id: Ident) -> Ident { 249 | if id.name.as_str() == self.from.as_str() { 250 | self.id 251 | } else { 252 | id 253 | } 254 | } 255 | 256 | fn fold_mac(&mut self, mac: Mac) -> Mac { 257 | fold::noop_fold_mac(mac, self) 258 | } 259 | } 260 | 261 | #[allow(unused_variables)] 262 | pub fn impl_glue(cx: &mut ExtCtxt, sp: Span, mitem: &MetaItem, item: Annotatable) 263 | -> Annotatable 264 | { 265 | match item { 266 | Annotatable::Item(ref it) => { 267 | if let ItemKind::Impl(unsafety, polarity, ref generics, 268 | ref traitref, ref impl_ty, ref items) 269 | = it.node 270 | { 271 | let new_items: Vec = items.iter().flat_map(|impl_item| { 272 | let mut items = vec![impl_item.clone()]; 273 | if let ImplItemKind::Method(ref sig, ref body) = impl_item.node { 274 | let ref inputs = sig.decl.inputs; 275 | let mut binds = vec![]; 276 | let mut new_body = body.clone(); 277 | 278 | for i in 0..inputs.len() { 279 | let s = format!("Arg {}", i); 280 | let bind = match &inputs[i].pat.node { 281 | &PatKind::Ident(_, ref ident, _) => { 282 | let mut id = ident.node; 283 | let is_self = id.name.as_str() == "self"; 284 | let ty = if is_self { 285 | let new_id = token::str_to_ident("_lia_self"); 286 | let mut renamer = Renamer {id: new_id, from: "self".to_string()}; 287 | new_body = renamer.fold_block(new_body); 288 | 289 | id = new_id; 290 | let ty = impl_ty.clone(); 291 | match sig.decl.get_self().unwrap().node.clone() { 292 | SelfKind::Region(life, muty) => P(Ty { 293 | id: ty.id, 294 | span: ty.span, 295 | node: TyKind::Rptr(life, MutTy { 296 | ty: impl_ty.clone(), 297 | mutbl: muty, 298 | }) 299 | }), 300 | _ => ty 301 | } 302 | } else { 303 | inputs[i].ty.clone() 304 | }; 305 | 306 | let i = i + 1; // shift right for first "this" arg 307 | quote_block!(cx, { 308 | cast!(let mut $id: $ty = args.get($i).expect($s)); 309 | }).unwrap().stmts 310 | }, 311 | _ => panic!("#[lia_impl_glue] only supports methods with no pattern matching in the arguments") 312 | }; 313 | binds.push(bind); 314 | } 315 | 316 | let (ret_ty, new_body) = match sig.decl.output { 317 | FunctionRetTy::Ty(ref ty) => (ty.clone(), new_body), 318 | _ => (quote_ty!(cx, ()), quote_block!(cx, { 319 | {$new_body}; 320 | return (); 321 | })) 322 | }; 323 | 324 | let binds: Vec = binds.into_iter().flat_map(|e| e).collect(); 325 | // TODO: attrs for ignoring warnings on fn? 326 | let fun = 327 | quote_item!( 328 | cx, 329 | fn _ignore_this_name (args: Vec) -> LiaPtr { 330 | $binds; 331 | alloc!($ret_ty, (move ||$new_body)()) 332 | }).unwrap(); 333 | 334 | if let ItemKind::Fn(decl, unsafety, constness, abi, 335 | generics, block) 336 | = fun.node.clone() 337 | { 338 | let new_decl = decl.clone().unwrap(); 339 | let mut new_item = impl_item.clone(); 340 | new_item.ident = prefix_ident(&new_item.ident, "_lia_"); 341 | new_item.node = 342 | ImplItemKind::Method(MethodSig { 343 | unsafety: unsafety, 344 | constness: constness, 345 | abi: abi, 346 | decl: P(new_decl), 347 | generics: generics, 348 | }, block); 349 | items.push(new_item); 350 | } else { 351 | unreachable!() 352 | } 353 | } 354 | items 355 | }).collect(); 356 | let newimpl = ItemKind::Impl( 357 | unsafety, polarity, generics.clone(), traitref.clone(), 358 | impl_ty.clone(), new_items); 359 | Annotatable::Item(cx.item(sp, it.ident, it.attrs.clone(), newimpl)) 360 | } else { 361 | cx.span_err(sp, "#[lia_impl_glue] must annotate an impl block"); 362 | item.clone() 363 | } 364 | }, 365 | _ => { 366 | cx.span_err(sp, "#[lia_impl_glue] must annotate an item"); 367 | item 368 | } 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /lia-runtime/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lia-runtime" 3 | version = "0.1.0" 4 | authors = ["Will Crichton "] 5 | 6 | [dependencies] 7 | lia-plugin = { path = "../lia-plugin" } 8 | -------------------------------------------------------------------------------- /lia-runtime/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin)] 2 | #![plugin(lia_plugin)] 3 | 4 | use std::any::Any; 5 | use std::cell::RefCell; 6 | use std::rc::Rc; 7 | use std::collections::HashMap; 8 | use std::fmt; 9 | 10 | pub type LiaNumber = i32; 11 | pub type LiaBool = bool; 12 | pub type LiaString = String; 13 | pub type LiaClosure = Box) -> LiaPtr>; 14 | pub type LiaObject = HashMap; 15 | 16 | pub enum LiaValue { 17 | Number(LiaNumber), 18 | String(LiaString), 19 | Closure(LiaClosure), 20 | Bool(LiaBool), 21 | Object(LiaObject), 22 | Null, 23 | Unknown(Box) 24 | } 25 | 26 | pub type LiaPtr = Rc>>>; 27 | 28 | fn wrap(t: LiaValue) -> LiaPtr { 29 | Rc::new(RefCell::new(Rc::new(RefCell::new(t)))) 30 | } 31 | 32 | macro_rules! make_allocator { 33 | ($fun:ident, $ty:ty, $path:path) => { 34 | pub fn $fun (t: $ty) -> LiaPtr { 35 | wrap($path(t)) 36 | } 37 | } 38 | } 39 | 40 | make_allocator!(alloc_number, LiaNumber, LiaValue::Number); 41 | make_allocator!(alloc_string, LiaString, LiaValue::String); 42 | make_allocator!(alloc_closure, LiaClosure, LiaValue::Closure); 43 | make_allocator!(alloc_bool, LiaBool, LiaValue::Bool); 44 | make_allocator!(alloc_object, LiaObject, LiaValue::Object); 45 | 46 | pub fn alloc_null(_: ()) -> LiaPtr { 47 | wrap(LiaValue::Null) 48 | } 49 | 50 | pub fn alloc_other(t: T) -> LiaPtr { 51 | wrap(LiaValue::Unknown(Box::new(t))) 52 | } 53 | 54 | pub fn new_obj() -> LiaObject { 55 | let obj: LiaObject = HashMap::new(); 56 | obj 57 | } 58 | 59 | #[macro_export] 60 | macro_rules! alloc { 61 | ($t:ty, $e:expr) => { 62 | _alloc!($t {$e}) 63 | } 64 | } 65 | 66 | #[macro_export] 67 | macro_rules! cast { 68 | (let mut $id:ident : $t:ty = $e:expr) => { 69 | let _tmp = $e; 70 | let mut _tmp = _tmp.borrow_mut(); 71 | let mut _tmp = _tmp.borrow_mut(); 72 | let mut _tmp = _borrow_type!(_tmp $t true); 73 | let mut $id = _tmp; 74 | }; 75 | (let $id:ident : $t:ty = $e:expr) => { 76 | let _tmp = $e; 77 | let _tmp = _tmp.borrow(); 78 | let _tmp = _tmp.borrow(); 79 | let _tmp = _borrow_type!(_tmp $t false); 80 | let $id = _tmp; 81 | }; 82 | } 83 | 84 | #[macro_export] 85 | macro_rules! call { 86 | ($id:ident ( $( $x:expr ),* )) => {{ 87 | concat_idents!(_lia_, $id)(vec![alloc_object(new_obj()), $( $x ),*]) 88 | }} 89 | } 90 | 91 | impl fmt::Debug for LiaValue { 92 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 93 | match self { 94 | &LiaValue::Number(ref n) => n.fmt(f), 95 | &LiaValue::String(ref s) => s.fmt(f), 96 | &LiaValue::Closure(_) => f.pad(""), 97 | &LiaValue::Bool(b) => b.fmt(f), 98 | &LiaValue::Object(ref o) => o.fmt(f), 99 | &LiaValue::Null => f.pad("null"), 100 | &LiaValue::Unknown(_) => f.pad("") 101 | } 102 | } 103 | } 104 | 105 | pub fn _lia_print(args: Vec) -> LiaPtr { 106 | for arg in args[1..].into_iter() { 107 | let _tmp = arg.borrow(); 108 | let arg = _tmp.borrow(); 109 | println!("{:?}", arg); 110 | } 111 | alloc_null(()) 112 | } 113 | 114 | pub fn val_to_key(val: LiaPtr) -> String { 115 | let _tmp = val.borrow(); 116 | let val = _tmp.borrow(); 117 | match *val { 118 | LiaValue::Number(ref n) => format!("{}", n), 119 | LiaValue::String(ref s) => s.clone(), 120 | _ => panic!("Object key must be string or number"), 121 | } 122 | } 123 | 124 | pub fn _lia_call(args: Vec) -> LiaPtr { 125 | let fun = args.get(1).expect("Arg 1").clone(); 126 | let ctx = args.get(2).expect("Arg 2").clone(); 127 | 128 | cast!(let fun: &LiaClosure = fun); 129 | fun(vec![ctx]) 130 | } 131 | -------------------------------------------------------------------------------- /lia-tests/Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "lia-tests" 3 | version = "0.1.0" 4 | dependencies = [ 5 | "lia-plugin 0.1.0", 6 | "lia-runtime 0.1.0", 7 | ] 8 | 9 | [[package]] 10 | name = "aho-corasick" 11 | version = "0.5.2" 12 | source = "registry+https://github.com/rust-lang/crates.io-index" 13 | dependencies = [ 14 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 15 | ] 16 | 17 | [[package]] 18 | name = "atty" 19 | version = "0.1.2" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | dependencies = [ 22 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 24 | "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 25 | ] 26 | 27 | [[package]] 28 | name = "bit-set" 29 | version = "0.3.0" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | dependencies = [ 32 | "bit-vec 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", 33 | ] 34 | 35 | [[package]] 36 | name = "bit-vec" 37 | version = "0.4.3" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "0.4.0" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | 45 | [[package]] 46 | name = "diff" 47 | version = "0.1.9" 48 | source = "registry+https://github.com/rust-lang/crates.io-index" 49 | 50 | [[package]] 51 | name = "docopt" 52 | version = "0.6.80" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | dependencies = [ 55 | "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", 56 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "strsim 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 58 | ] 59 | 60 | [[package]] 61 | name = "fixedbitset" 62 | version = "0.1.1" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | 65 | [[package]] 66 | name = "itertools" 67 | version = "0.3.25" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | 70 | [[package]] 71 | name = "kernel32-sys" 72 | version = "0.2.2" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | dependencies = [ 75 | "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 77 | ] 78 | 79 | [[package]] 80 | name = "lalrpop" 81 | version = "0.11.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | dependencies = [ 84 | "atty 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 85 | "bit-set 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", 86 | "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 87 | "diff 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 88 | "docopt 0.6.80 (registry+https://github.com/rust-lang/crates.io-index)", 89 | "itertools 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)", 90 | "lalrpop-intern 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 91 | "lalrpop-snap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 92 | "lalrpop-util 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 93 | "petgraph 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 94 | "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", 95 | "regex-syntax 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", 96 | "rustc-serialize 0.3.19 (registry+https://github.com/rust-lang/crates.io-index)", 97 | "term 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "unicode-xid 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 100 | ] 101 | 102 | [[package]] 103 | name = "lalrpop-intern" 104 | version = "0.11.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | 107 | [[package]] 108 | name = "lalrpop-snap" 109 | version = "0.11.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | dependencies = [ 112 | "diff 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", 113 | "itertools 0.3.25 (registry+https://github.com/rust-lang/crates.io-index)", 114 | "lalrpop-intern 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 115 | "lalrpop-util 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 116 | "petgraph 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)", 117 | "regex 0.1.71 (registry+https://github.com/rust-lang/crates.io-index)", 118 | "unicode-xid 0.0.2 (registry+https://github.com/rust-lang/crates.io-index)", 119 | ] 120 | 121 | [[package]] 122 | name = "lalrpop-util" 123 | version = "0.11.0" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | 126 | [[package]] 127 | name = "lia" 128 | version = "0.1.0" 129 | dependencies = [ 130 | "lalrpop 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "lalrpop-util 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 132 | ] 133 | 134 | [[package]] 135 | name = "lia-plugin" 136 | version = "0.1.0" 137 | dependencies = [ 138 | "lia 0.1.0", 139 | ] 140 | 141 | [[package]] 142 | name = "lia-runtime" 143 | version = "0.1.0" 144 | dependencies = [ 145 | "lia-plugin 0.1.0", 146 | ] 147 | 148 | [[package]] 149 | name = "libc" 150 | version = "0.2.11" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | 153 | [[package]] 154 | name = "memchr" 155 | version = "0.1.11" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | dependencies = [ 158 | "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 159 | ] 160 | 161 | [[package]] 162 | name = "petgraph" 163 | version = "0.1.18" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | dependencies = [ 166 | "fixedbitset 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 167 | ] 168 | 169 | [[package]] 170 | name = "regex" 171 | version = "0.1.71" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | dependencies = [ 174 | "aho-corasick 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", 175 | "memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", 176 | "regex-syntax 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", 177 | "thread_local 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", 178 | "utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", 179 | ] 180 | 181 | [[package]] 182 | name = "regex-syntax" 183 | version = "0.2.6" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | 186 | [[package]] 187 | name = "regex-syntax" 188 | version = "0.3.2" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | 191 | [[package]] 192 | name = "rustc-serialize" 193 | version = "0.3.19" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | 196 | [[package]] 197 | name = "strsim" 198 | version = "0.3.0" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | 201 | [[package]] 202 | name = "term" 203 | version = "0.4.4" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | dependencies = [ 206 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 207 | "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 208 | ] 209 | 210 | [[package]] 211 | name = "thread-id" 212 | version = "2.0.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | dependencies = [ 215 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 216 | "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 217 | ] 218 | 219 | [[package]] 220 | name = "thread_local" 221 | version = "0.2.5" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | dependencies = [ 224 | "thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 225 | ] 226 | 227 | [[package]] 228 | name = "time" 229 | version = "0.1.35" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | dependencies = [ 232 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 233 | "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", 234 | "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", 235 | ] 236 | 237 | [[package]] 238 | name = "unicode-xid" 239 | version = "0.0.2" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | 242 | [[package]] 243 | name = "utf8-ranges" 244 | version = "0.1.3" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | 247 | [[package]] 248 | name = "winapi" 249 | version = "0.2.7" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | 252 | [[package]] 253 | name = "winapi-build" 254 | version = "0.1.1" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | 257 | -------------------------------------------------------------------------------- /lia-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lia-tests" 3 | version = "0.1.0" 4 | authors = ["Will Crichton "] 5 | 6 | [dependencies] 7 | lia-plugin = { path = "../lia-plugin" } 8 | lia-runtime = { path = "../lia-runtime" } 9 | -------------------------------------------------------------------------------- /lia-tests/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(plugin, box_syntax, test, concat_idents)] 2 | #![plugin(lia_plugin)] 3 | 4 | #[macro_use] extern crate lia_runtime; 5 | extern crate test; 6 | 7 | // mod matrix; 8 | mod lists; 9 | 10 | use lia_runtime::*; 11 | 12 | lia! { 13 | function add_test() { 14 | return 1 + 2; 15 | } 16 | 17 | function string_test() { 18 | return "Hello world!"; 19 | } 20 | 21 | function extern_test() { 22 | return @external_fun(3); 23 | } 24 | 25 | function by_ref_test() { 26 | var x = {"foo": 0}; 27 | var y = x; 28 | y["foo"] = 1; 29 | return x["foo"]; 30 | } 31 | 32 | function by_val_test() { 33 | var x = 3; 34 | var y = x; 35 | y = 2; 36 | return x; 37 | } 38 | 39 | function closure_test() { 40 | var x = 0; 41 | (function() { x += 1; })(); 42 | return x; 43 | } 44 | 45 | function fib_test(n) { 46 | if (n <= 1) { return n; } 47 | return @fib_test(n - 1) + @fib_test(n - 2); 48 | } 49 | 50 | function nested_object_test() { 51 | var x = {foo: {bar: 2}}; 52 | x.foo.bar = 3; 53 | return x.foo.bar; 54 | } 55 | 56 | function while_test() { 57 | var x = 0; 58 | while (x < 10) { 59 | x += 1; 60 | } 61 | return x; 62 | } 63 | 64 | function for_test() { 65 | for (var x = 0; x < 10; x += 1) {} 66 | return x; 67 | } 68 | 69 | function foreach_test() { 70 | var x = {foo: 1, bar: 2}; 71 | var z = 0; 72 | for (var y : x) { 73 | z += x[y]; 74 | } 75 | return z; 76 | } 77 | 78 | function self_ref_test() { 79 | var x = { 80 | y: function() { 81 | this.z = 5; 82 | }, 83 | z: 0 84 | }; 85 | x.y(); 86 | return x.z; 87 | } 88 | 89 | function if_test() { 90 | var x = 0; 91 | var y = {}; 92 | if (0) { x += 1; } 93 | if (true) { x += 1; } 94 | if (y.foo) { x += 1; } 95 | return x; 96 | } 97 | 98 | function if_else_test() { 99 | var x = 0; 100 | if (0) { x = 1; } 101 | else { x = 2; } 102 | return x; 103 | } 104 | 105 | function double_add_test() { 106 | var x = 1; 107 | x += x; 108 | return x; 109 | } 110 | 111 | function concat_test() { 112 | var x = "foo"; 113 | x += "bar"; 114 | return x; 115 | } 116 | 117 | function str_eq_test() { 118 | var x = 0; 119 | if ("foo" == "foo") { x += 1; } 120 | if ("bar" == "foo") { x += 1; } 121 | return x; 122 | } 123 | } 124 | 125 | fn _lia_external_fun(args: Vec) -> LiaPtr { 126 | cast!(let num: i32 = args[1].clone()); 127 | return alloc_number(num + 1); 128 | } 129 | 130 | // TODO: auto-generate function name somehow? 131 | macro_rules! gen_test { 132 | ($test:ident, $fun:ident, $ty:ty, $val:expr) => { 133 | #[test] 134 | fn $test () { 135 | cast!(let result: $ty = call!($fun())); 136 | assert_eq!(result, $val); 137 | } 138 | } 139 | } 140 | 141 | gen_test!(lia_add_test, add_test, i32, 3); 142 | gen_test!(lia_string_test, string_test, String, "Hello world!"); 143 | gen_test!(lia_extern_test, extern_test, i32, 4); 144 | gen_test!(lia_by_ref_test, by_ref_test, i32, 1); 145 | gen_test!(lia_by_val_test, by_val_test, i32, 3); 146 | gen_test!(lia_closure_test, closure_test, i32, 1); 147 | gen_test!(lia_nested_object_test, nested_object_test, i32, 3); 148 | gen_test!(lia_while_test, while_test, i32, 10); 149 | gen_test!(lia_for_test, for_test, i32, 10); 150 | gen_test!(lia_foreach_test, foreach_test, i32, 3); 151 | gen_test!(lia_self_ref_test, self_ref_test, i32, 5); 152 | gen_test!(lia_if_test, if_test, i32, 1); 153 | gen_test!(lia_if_else_test, if_else_test, i32, 2); 154 | gen_test!(lia_double_add_test, double_add_test, i32, 2); 155 | gen_test!(lia_concat_test, concat_test, String, "foobar"); 156 | gen_test!(lia_str_eq_test, str_eq_test, i32, 1); 157 | 158 | #[test] 159 | fn lia_fib_test() { 160 | cast!(let num: i32 = call!(fib_test(alloc_number(10)))); 161 | assert!(num == 55); 162 | } 163 | 164 | // TODO: only run this when user does cargo bench 165 | // use test::Bencher; 166 | // #[bench] 167 | // fn lia_fib_bench(b: &mut Bencher) { 168 | // b.iter(|| call!(fib_test(alloc_number(30)))); 169 | // } 170 | -------------------------------------------------------------------------------- /lia-tests/src/lists.rs: -------------------------------------------------------------------------------- 1 | use lia_runtime::*; 2 | 3 | lia! { 4 | function new(obj) { 5 | var x = {}; 6 | for (var method : obj.prototype) { 7 | x[method] = obj.prototype[method]; 8 | } 9 | @call(obj.prototype.constructor, x); 10 | return x; 11 | } 12 | 13 | function list_test() { 14 | var list = {}; 15 | list.prototype = { 16 | constructor: function() { 17 | this.length = 0; 18 | }, 19 | append: function(x) { 20 | this[this.length] = x; 21 | this.length = this.length + 1; 22 | } 23 | }; 24 | 25 | var x = @new(list); 26 | x.append(3); 27 | return x[0]; 28 | } 29 | } 30 | 31 | #[test] 32 | fn lia_list_test() { 33 | let result = call!(list_test()); 34 | cast!(let num: i32 = result); 35 | assert!(num == 3); 36 | } 37 | -------------------------------------------------------------------------------- /lia-tests/src/matrix.rs: -------------------------------------------------------------------------------- 1 | use lia_runtime::*; 2 | 3 | lia! { 4 | function multiply_matrices() { 5 | var x = @Matrix::new(1, 1); 6 | var y = @Matrix::new(1, 1); 7 | @Matrix::set(x, 0, 0, 5); 8 | @Matrix::set(y, 0, 0, 10); 9 | var z = @Matrix::multiply(x, y); 10 | return @Matrix::get(z, 0, 0); 11 | } 12 | } 13 | 14 | #[derive(Clone)] 15 | struct Matrix { 16 | data: Vec, 17 | rows: i32, 18 | cols: i32, 19 | } 20 | 21 | #[allow(unused_mut, dead_code)] 22 | #[lia_impl_glue] 23 | impl Matrix { 24 | pub fn new(rows: i32, cols: i32) -> Matrix { 25 | let mut data = Vec::with_capacity((rows * cols) as usize); 26 | for _ in 0..(rows * cols) { 27 | data.push(0); 28 | } 29 | 30 | Matrix { 31 | rows: rows, 32 | cols: cols, 33 | data: data, 34 | } 35 | } 36 | 37 | pub fn get(&self, row: i32, col: i32) -> i32 { 38 | self.data[(row * self.cols + col) as usize] 39 | } 40 | 41 | pub fn set(&mut self, row: i32, col: i32, val: i32) { 42 | self.data[(row * self.cols + col) as usize] = val; 43 | } 44 | 45 | pub fn multiply(&self, other: &Matrix) -> Matrix { 46 | assert!(self.cols == other.rows); 47 | 48 | let mut new_mat = Matrix::new(self.rows, other.cols); 49 | for i in 0..self.rows { 50 | for j in 0..other.cols { 51 | let mut dot = 0; 52 | for k in 0..self.cols { 53 | dot += self.get(i, k) * other.get(k, j); 54 | } 55 | new_mat.set(i, j, dot); 56 | } 57 | } 58 | 59 | return new_mat; 60 | } 61 | } 62 | 63 | #[test] 64 | fn matrix_test() { 65 | let result = call!(multiply_matrices()); 66 | cast!(let num: i32 = result); 67 | assert!(num == 50); 68 | } 69 | -------------------------------------------------------------------------------- /lia/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lia" 3 | version = "0.1.0" 4 | description = "A high-level language for Rust" 5 | repository = "https://github.com/willcrichton/lia" 6 | readme = "../README.md" 7 | keywords = ["language", "compiler", "high-level", "dynamic"] 8 | license = "Apache-2.0/MIT" 9 | authors = ["Will Crichton "] 10 | build = "build.rs" 11 | 12 | [dependencies] 13 | lalrpop-util = "0.11.0" 14 | 15 | [build-dependencies] 16 | lalrpop = "0.11.0" 17 | -------------------------------------------------------------------------------- /lia/build.rs: -------------------------------------------------------------------------------- 1 | extern crate lalrpop; 2 | 3 | fn main () { 4 | lalrpop::process_root().unwrap(); 5 | } 6 | -------------------------------------------------------------------------------- /lia/src/ast.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use token::LiaToken; 3 | use syntax::parse::token::{Token as RsToken, BinOpToken, str_to_ident}; 4 | use syntax::tokenstream::TokenTree; 5 | use syntax::ast::Ident; 6 | 7 | #[derive(Debug, Clone)] 8 | pub enum LiaExpr { 9 | BinOp(RsToken, Box, Box), 10 | Integer(i32), 11 | String(String), 12 | Bool(bool), 13 | Var(Ident), 14 | RsVar(Vec), 15 | Call(Box, Vec), 16 | Closure(Vec, Vec), 17 | Object(Vec<(LiaExpr, LiaExpr)>), 18 | Index(Box, Box), 19 | Array(Vec), 20 | Quote(Vec), 21 | } 22 | 23 | #[derive(Debug, Clone)] 24 | pub enum LiaStmt { 25 | Declare(Ident), 26 | Assign(LiaExpr, LiaExpr), 27 | Return(LiaExpr), 28 | Expr(LiaExpr), 29 | If(LiaExpr, Vec, Option>), 30 | While(LiaExpr, Vec), 31 | ForObj(Ident, LiaExpr, Vec), 32 | } 33 | 34 | #[derive(Debug, Clone)] 35 | pub struct LiaFn { 36 | pub name: Ident, 37 | pub args: Vec, 38 | pub body: Vec, 39 | } 40 | 41 | pub fn prefix_ident(id: &Ident, prefix: &str) -> Ident { 42 | str_to_ident(format!("{}{}", prefix, id.name.as_str()).as_str()) 43 | } 44 | 45 | fn get_mapping(mapping: &mut HashMap, id: &Ident) -> Ident { 46 | if !mapping.contains_key(id) { 47 | mapping.insert(id.clone(), prefix_ident(id, "_copy")); 48 | }; 49 | mapping.get(id).expect("Free mapping was invalid").clone() 50 | } 51 | 52 | 53 | impl LiaExpr { 54 | pub fn remap_free_vars( 55 | &mut self, 56 | bound: &mut HashSet, 57 | mapping: &mut HashMap) 58 | { 59 | use self::LiaExpr::*; 60 | match self { 61 | &mut Var(ref mut id) => { 62 | if !bound.contains(id) { 63 | *id = get_mapping(mapping, id); 64 | } 65 | }, 66 | &mut Closure(ref args, ref mut stmts) => { 67 | for id in args { 68 | bound.insert(id.clone()); 69 | } 70 | 71 | for mut s in stmts.iter_mut() { 72 | s.remap_free_vars_aux(bound, mapping); 73 | } 74 | }, 75 | &mut BinOp(_, ref mut left, ref mut right) => { 76 | left.remap_free_vars(bound, mapping); 77 | right.remap_free_vars(bound, mapping); 78 | }, 79 | &mut Call(ref mut fun, ref mut args) => { 80 | fun.remap_free_vars(bound, mapping); 81 | for arg in args.iter_mut() { 82 | arg.remap_free_vars(bound, mapping); 83 | } 84 | }, 85 | _ => () 86 | } 87 | } 88 | } 89 | 90 | impl LiaStmt { 91 | pub fn remap_free_vars(&mut self) -> HashMap { 92 | let mut bound = HashSet::new(); 93 | let mut mapping = HashMap::new(); 94 | self.remap_free_vars_aux(&mut bound, &mut mapping); 95 | mapping 96 | } 97 | 98 | pub fn remap_free_vars_aux( 99 | &mut self, 100 | bound: &mut HashSet, 101 | mapping: &mut HashMap) 102 | { 103 | use self::LiaStmt::*; 104 | match self { 105 | &mut Declare(id) => { 106 | bound.insert(id); 107 | }, 108 | &mut Assign(ref mut lhs, ref mut rhs) => { 109 | lhs.remap_free_vars(bound, mapping); 110 | rhs.remap_free_vars(bound, mapping); 111 | } 112 | &mut Return(ref mut expr) => { 113 | expr.remap_free_vars(bound, mapping); 114 | }, 115 | &mut Expr(ref mut expr) => { 116 | expr.remap_free_vars(bound, mapping); 117 | }, 118 | &mut If(ref mut expr, ref mut if_, ref mut else_) => { 119 | expr.remap_free_vars(bound, mapping); 120 | for s in if_.iter_mut() { 121 | s.remap_free_vars_aux(bound, mapping); 122 | } 123 | if let &mut Some(ref mut else_) = else_ { 124 | for s in else_.iter_mut() { 125 | s.remap_free_vars_aux(bound, mapping); 126 | } 127 | } 128 | } 129 | &mut While(ref mut guard, ref mut body) => { 130 | guard.remap_free_vars(bound, mapping); 131 | for s in body.iter_mut() { 132 | s.remap_free_vars_aux(bound, mapping); 133 | } 134 | } 135 | &mut ForObj(ref mut id, ref mut expr, ref mut body) => { 136 | bound.insert(id.clone()); 137 | expr.remap_free_vars(bound, mapping); 138 | for s in body.iter_mut() { 139 | s.remap_free_vars_aux(bound, mapping); 140 | } 141 | 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /lia/src/codegen.rs: -------------------------------------------------------------------------------- 1 | use syntax::codemap::{Span, ExpnId, BytePos, Pos}; 2 | use syntax::ext::base::ExtCtxt; 3 | use syntax::ast::{Expr, Stmt, Item, Path, Ident, PathSegment, PathParameters}; 4 | use syntax::parse::token::{Token as RsToken, BinOpToken, str_to_ident}; 5 | use syntax::ptr::P; 6 | 7 | use ast::{LiaExpr, LiaStmt, LiaFn, prefix_ident}; 8 | 9 | fn rs_ident_to_path(mut segs: Vec) -> Path { 10 | { 11 | let len = segs.len(); 12 | let last = &mut segs[len - 1]; 13 | *last = prefix_ident(last, "_lia_"); 14 | } 15 | Path { 16 | span: Span { 17 | lo: BytePos::from_usize(0), 18 | hi: BytePos::from_usize(0), 19 | expn_id: ExpnId::from_u32(0), 20 | }, 21 | global: false, 22 | segments: segs.into_iter().map(|seg| PathSegment { 23 | identifier: seg, 24 | parameters: PathParameters::none() 25 | }).collect() 26 | } 27 | } 28 | 29 | fn gen_expr(cx: &mut ExtCtxt, expr: LiaExpr) -> P { 30 | match expr { 31 | LiaExpr::BinOp(op, box e1, box e2) => { 32 | let s1 = gen_expr(cx, e1); 33 | let s2 = gen_expr(cx, e2); 34 | let (e, fun) = match op { 35 | RsToken::BinOp(BinOpToken::Plus) => 36 | (quote_expr!(cx, s1v + s2v), str_to_ident("alloc_number")), 37 | RsToken::BinOp(BinOpToken::Minus) => 38 | (quote_expr!(cx, s1v - s2v), str_to_ident("alloc_number")), 39 | RsToken::EqEq => 40 | (quote_expr!(cx, s1v == s2v), str_to_ident("alloc_bool")), 41 | RsToken::Le => 42 | (quote_expr!(cx, s1v <= s2v), str_to_ident("alloc_bool")), 43 | RsToken::Lt => 44 | (quote_expr!(cx, s1v < s2v), str_to_ident("alloc_bool")), 45 | _ => { 46 | let s = format!("Binop `{:?}` not yet implemented for numbers", op); 47 | (quote_expr!(cx, panic!($s)), str_to_ident("alloc_number")) 48 | } 49 | }; 50 | 51 | let (se, sfun) = match op { 52 | RsToken::BinOp(BinOpToken::Plus) => 53 | (quote_expr!(cx, s1v.clone() + s2v.as_str()), str_to_ident("alloc_string")), 54 | RsToken::EqEq => 55 | (quote_expr!(cx, s1v == s2v.as_str()), str_to_ident("alloc_bool")), 56 | _ => { 57 | let s = format!("Binop `{:?}` not yet implemented for strings", op); 58 | (quote_expr!(cx, panic!($s)), str_to_ident("alloc_string")) 59 | } 60 | }; 61 | 62 | // Have to clone s1v and s2v because if s1 is the same variable 63 | // as s2, then it becomes a double borrow 64 | quote_expr!(cx, { 65 | let s1 = $s1; 66 | let s1 = s1.borrow(); 67 | let s1 = s1.borrow(); 68 | let s2 = $s2; 69 | let s2 = s2.borrow(); 70 | let s2 = s2.borrow(); 71 | match (&*s1, &*s2) { 72 | (&LiaValue::Number(ref s1v), &LiaValue::Number(ref s2v)) => { 73 | let (s1v, s2v) = (*s1v, *s2v); 74 | $fun($e) 75 | }, 76 | (&LiaValue::String(ref s1v), &LiaValue::String(ref s2v)) => { 77 | let (s1v, s2v) = (s1v, s2v); 78 | $sfun($se) 79 | }, 80 | _ => panic!("Invalid expr") 81 | } 82 | }) 83 | }, 84 | LiaExpr::Integer(n) => { 85 | quote_expr!(cx, alloc_number($n)) 86 | }, 87 | LiaExpr::String(s) => { 88 | quote_expr!(cx, alloc_string(String::from($s))) 89 | }, 90 | LiaExpr::Bool(b) => { 91 | quote_expr!(cx, alloc_bool($b)) 92 | }, 93 | LiaExpr::Var(id) => { 94 | quote_expr!(cx, $id.clone()) 95 | }, 96 | LiaExpr::RsVar(id) => { 97 | let new_id = rs_ident_to_path(id); 98 | quote_expr!(cx, { 99 | let fun: LiaClosure = Box::new(move |args: Vec| $new_id(args)); 100 | alloc_closure(fun) 101 | }) 102 | }, 103 | LiaExpr::Object(kvs) => { 104 | let kvs: Vec> = kvs.into_iter().map(|(key, value)| { 105 | let ke = gen_expr(cx, key); 106 | let ve = gen_expr(cx, value); 107 | quote_expr!(cx, { 108 | let key = $ke; 109 | cast!(let key: String = key); 110 | let _val = $ve; 111 | let val = _val.borrow(); 112 | let slot = alloc_null(()); 113 | { 114 | let mut _tmp = slot.borrow_mut(); 115 | *_tmp = val.clone(); 116 | } 117 | ht.insert(key, slot); 118 | }) 119 | }).collect(); 120 | quote_expr!(cx, { 121 | let mut ht = new_obj(); 122 | $kvs; 123 | alloc_object(ht) 124 | }) 125 | }, 126 | LiaExpr::Index(box obj, box key) => { 127 | let obj = gen_expr(cx, obj); 128 | let key = gen_expr(cx, key); 129 | // Must get key before object to avoid conflicting borrows, i.e. x[x.y] 130 | quote_expr!(cx, { 131 | let key = $key; 132 | let s = val_to_key(key); 133 | let obj = $obj; 134 | cast!(let mut ht: &LiaObject = obj); 135 | fn make_null() -> LiaPtr { alloc_null(()) } 136 | ht.entry(s).or_insert_with(make_null).clone() 137 | }) 138 | }, 139 | // TODO: make this a macro? 140 | LiaExpr::Array(exprs) => { 141 | let mut kvs = Vec::new(); 142 | for i in 0..exprs.len() { 143 | kvs.push((LiaExpr::String(format!("{}", i)), exprs[i].clone())); 144 | } 145 | gen_expr(cx, LiaExpr::Object(kvs)) 146 | }, 147 | LiaExpr::Call(box fun, exprs) => { 148 | let mut exps: Vec> = 149 | exprs.into_iter().map(|expr| { 150 | let expr = gen_expr(cx, expr); 151 | quote_expr!(cx, {args.push($expr)}) 152 | }).collect(); 153 | 154 | let call = match fun.clone() { 155 | LiaExpr::RsVar(id) => { 156 | let new_id = rs_ident_to_path(id); 157 | quote_expr!(cx, $new_id(args)) 158 | }, 159 | _ => { 160 | let f = gen_expr(cx, fun.clone()); 161 | // Can't borrow_mut as this breaks recursive functions 162 | quote_expr!(cx, { 163 | let e = $f; 164 | cast!(let e: LiaClosure = e); 165 | e(args) 166 | }) 167 | } 168 | }; 169 | 170 | match fun.clone() { 171 | LiaExpr::Index(box context, _) => { 172 | let expr = gen_expr(cx, context); 173 | exps.insert(0, quote_expr!(cx, {args.push($expr)})); 174 | }, 175 | _ => { 176 | exps.insert(0, quote_expr!(cx, {args.push({ 177 | alloc_object(new_obj()) 178 | })})); 179 | } 180 | }; 181 | 182 | quote_expr!(cx, { 183 | let mut args = Vec::new(); 184 | $exps 185 | $call 186 | }) 187 | }, 188 | LiaExpr::Closure(mut args, stmts) => { 189 | use std::collections::{HashMap, HashSet}; 190 | args.insert(0, str_to_ident("this")); 191 | let mut copies = Vec::new(); 192 | let stmts = { 193 | let mut bound = HashSet::new(); 194 | let mut mapping = HashMap::new(); 195 | let mut e = LiaExpr::Closure(args.clone(), stmts); 196 | e.remap_free_vars(&mut bound, &mut mapping); 197 | 198 | for (src, dst) in &mapping { 199 | copies.push(quote_stmt!(cx, let $dst = $src.clone();) 200 | .expect("Invalid stmt")); 201 | } 202 | 203 | match e { 204 | LiaExpr::Closure(_, stmts) => stmts, 205 | _ => unreachable!() 206 | } 207 | }; 208 | 209 | let st: Vec = stmts.into_iter().flat_map(|stmt| gen_stmt(cx, stmt)).collect(); 210 | let mut binds = vec![]; 211 | for i in 0..args.len() { 212 | let arg_id = args[i]; 213 | let s = format!("Arg {} missing", i); 214 | binds.push(quote_stmt!(cx, let $arg_id = args.get($i).expect($s).clone()) 215 | .expect("Invalid stmt")); 216 | } 217 | 218 | // Not clear what the type of the closure is by default. Have to explicitly cast it. 219 | quote_expr!(cx, { 220 | $copies; 221 | let fun: LiaClosure = Box::new(move |args: Vec| { 222 | $binds; 223 | $st; 224 | return alloc_null(()); 225 | }); 226 | alloc_closure(fun) 227 | }) 228 | } 229 | LiaExpr::Quote(toks) => quote_expr!(cx, { 230 | quote_expr!(cx, { 231 | $toks 232 | }) 233 | }) 234 | } 235 | } 236 | 237 | 238 | fn gen_stmt(cx: &mut ExtCtxt, stmt: LiaStmt) -> Vec { 239 | match stmt { 240 | LiaStmt::Declare(id) => { 241 | vec![quote_stmt!(cx, let $id = alloc_null(());).expect("Invalid stmt")] 242 | }, 243 | LiaStmt::Assign(lhs, rhs) => { 244 | let lhs = gen_expr(cx, lhs); 245 | let rhs = gen_expr(cx, rhs); 246 | vec![quote_stmt!(cx, { 247 | let lhs = $lhs; 248 | let rhs = $rhs; 249 | let made_it = { 250 | let _tmp = rhs.borrow(); 251 | let src = _tmp.borrow(); 252 | let _tmp = lhs.borrow_mut(); 253 | let mut dst = _tmp.borrow_mut(); 254 | match &*src { 255 | &LiaValue::Number(ref n) => { 256 | *dst = LiaValue::Number(n.clone()); 257 | true 258 | }, 259 | &LiaValue::Bool(ref b) => { 260 | *dst = LiaValue::Bool(b.clone()); 261 | true 262 | }, 263 | _ => false 264 | } 265 | }; 266 | if !made_it { 267 | let mut dst = lhs.borrow_mut(); 268 | let src = rhs.borrow_mut(); 269 | *dst = src.clone(); 270 | } 271 | }).unwrap()] 272 | }, 273 | LiaStmt::Return(expr) => { 274 | let e = gen_expr(cx, expr); 275 | vec![quote_stmt!(cx, return $e;).expect("Invalid return stmt")] 276 | }, 277 | LiaStmt::Expr(expr) => { 278 | let e = gen_expr(cx, expr); 279 | vec![quote_stmt!(cx, let _ = $e;).expect("Invalid expr stmt")] 280 | }, 281 | LiaStmt::If(cond, if_, else_) => { 282 | let e = gen_expr(cx, cond); 283 | let if_: Vec = if_.into_iter().flat_map(|stmt| gen_stmt(cx, stmt)).collect(); 284 | let else_: Vec = match else_ { 285 | Some(else_) => else_.into_iter().flat_map(|stmt| gen_stmt(cx, stmt)).collect(), 286 | None => vec![] 287 | }; 288 | vec![quote_stmt!(cx, { 289 | let e = $e; 290 | let _tmp = e.borrow_mut(); 291 | let cond = _tmp.borrow_mut(); 292 | let b = match &*cond { 293 | &LiaValue::Bool(ref b) => b.clone(), 294 | &LiaValue::Number(ref n) => *n != 0, 295 | &LiaValue::Null => false, 296 | _ => true, 297 | }; 298 | if b { $if_; } 299 | else { $else_; } 300 | }).expect("Invalid if stmt")] 301 | }, 302 | LiaStmt::While(guard, body) => { 303 | let guard = gen_expr(cx, guard); 304 | let body: Vec = 305 | body.into_iter().flat_map(|stmt| gen_stmt(cx, stmt)).collect(); 306 | vec![quote_stmt!(cx, { 307 | while { 308 | let e = $guard; 309 | cast!(let b: LiaBool = e); 310 | b 311 | } { $body; } 312 | }).expect("Invalid while stmt")] 313 | }, 314 | LiaStmt::ForObj(id, expr, body) => { 315 | let expr = gen_expr(cx, expr); 316 | let body: Vec = 317 | body.into_iter().flat_map(|stmt| gen_stmt(cx, stmt)).collect(); 318 | vec![quote_stmt!(cx, { 319 | { 320 | let e = $expr; 321 | let keys = { 322 | cast!(let obj: LiaObject = e); 323 | let keys: Vec = obj.keys().map(|s| s.clone()).collect(); 324 | keys 325 | }; 326 | for $id in keys { 327 | let $id = alloc_string($id); 328 | $body; 329 | } 330 | } 331 | }).expect("Invalid for stmt")] 332 | } 333 | } 334 | } 335 | 336 | pub fn gen_fn(cx: &mut ExtCtxt, mut fun: LiaFn) -> P { 337 | let st: Vec = fun.body.into_iter().flat_map(|stmt| gen_stmt(cx, stmt)).collect(); 338 | let id = prefix_ident(&fun.name, "_lia_"); 339 | let mut binds = vec![]; 340 | fun.args.insert(0, str_to_ident("this")); 341 | for i in 0..fun.args.len() { 342 | let arg_id = fun.args[i]; 343 | let s = format!("Arg {}", i); 344 | binds.push(quote_stmt!(cx, let $arg_id = args.get($i).expect($s).clone()).unwrap()); 345 | } 346 | 347 | quote_item!( 348 | cx, 349 | #[allow(unreachable_code, dead_code, unused_mut, unused_assignments, unused_parens, unused_variables)] 350 | fn $id (args: Vec) -> LiaPtr { 351 | $binds; 352 | $st; 353 | return alloc_null(()); 354 | } 355 | ).unwrap() 356 | } 357 | -------------------------------------------------------------------------------- /lia/src/elaborate.rs: -------------------------------------------------------------------------------- 1 | use super::ast::*; 2 | 3 | // This doesn't do any meaningful elaboration right now. It just "typechecks" the 4 | // LHS of an assign. 5 | 6 | pub fn elaborate(funs: Vec) -> Vec { 7 | funs.into_iter().map(elaborate_fun).collect() 8 | } 9 | 10 | fn elaborate_fun(fun: LiaFn) -> LiaFn { 11 | LiaFn { 12 | name: fun.name, 13 | args: fun.args, 14 | body: fun.body.into_iter().map(elaborate_stmt).collect() 15 | } 16 | } 17 | 18 | fn elaborate_stmt(stmt: LiaStmt) -> LiaStmt { 19 | match stmt.clone() { 20 | LiaStmt::Assign(lhs, _) => { 21 | match lhs { 22 | LiaExpr::Var(_) | LiaExpr::Index(_, _) => stmt, 23 | _ => panic!("Invalid LHS of assign") 24 | } 25 | }, 26 | _ => stmt 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lia/src/grammar.lalrpop: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | use syntax::ast::{Name, Ident}; 3 | use syntax::parse::token::{Token as RsToken, BinOpToken, Lit, DelimToken, intern}; 4 | use super::ast::{LiaExpr, LiaStmt, LiaFn}; 5 | use super::token::LiaToken; 6 | use syntax::tokenstream::TokenTree; 7 | 8 | grammar; 9 | 10 | extern { 11 | enum LiaToken { 12 | Num => LiaToken::RustToken(RsToken::Literal(Lit::Integer(), _)), 13 | String => LiaToken::RustToken(RsToken::Literal(Lit::Str_(), _)), 14 | Ident => LiaToken::RustToken(RsToken::Ident()), 15 | Op => LiaToken::RustToken(RsToken::BinOp()), 16 | OpEq => LiaToken::RustToken(RsToken::BinOpEq()), 17 | Eq => LiaToken::RustToken(RsToken::Eq), 18 | EqEq => LiaToken::RustToken(RsToken::EqEq), 19 | Ge => LiaToken::RustToken(RsToken::Ge), 20 | Le => LiaToken::RustToken(RsToken::Le), 21 | Lt => LiaToken::RustToken(RsToken::Lt), 22 | Semi => LiaToken::RustToken(RsToken::Semi), 23 | Comma => LiaToken::RustToken(RsToken::Comma), 24 | Colon => LiaToken::RustToken(RsToken::Colon), 25 | Dot => LiaToken::RustToken(RsToken::Dot), 26 | At => LiaToken::RustToken(RsToken::At), 27 | ModSep => LiaToken::RustToken(RsToken::ModSep), 28 | Lparen => LiaToken::RustToken(RsToken::OpenDelim(DelimToken::Paren)), 29 | Rparen => LiaToken::RustToken(RsToken::CloseDelim(DelimToken::Paren)), 30 | Lbrace => LiaToken::RustToken(RsToken::OpenDelim(DelimToken::Brace)), 31 | Rbrace => LiaToken::RustToken(RsToken::CloseDelim(DelimToken::Brace)), 32 | Lbracket => LiaToken::RustToken(RsToken::OpenDelim(DelimToken::Bracket)), 33 | Rbracket => LiaToken::RustToken(RsToken::CloseDelim(DelimToken::Bracket)), 34 | Var => LiaToken::Var, 35 | Function => LiaToken::Function, 36 | Return => LiaToken::Return, 37 | For => LiaToken::For, 38 | While => LiaToken::While, 39 | If => LiaToken::If, 40 | Else => LiaToken::Else, 41 | True => LiaToken::True, 42 | False => LiaToken::False, 43 | Quote => LiaToken::Quote(>), 44 | } 45 | } 46 | 47 | pub funs: Vec = { 48 | => fns 49 | }; 50 | 51 | fun: LiaFn = { 52 | Function Lparen > Rparen Lbrace Rbrace => LiaFn { 53 | name: id, 54 | args: args, 55 | body: s, 56 | } 57 | }; 58 | 59 | stmt_list: Vec = { 60 | => s.into_iter().flat_map(|s| s).collect::>() 61 | }; 62 | 63 | block: Vec = { 64 | Lbrace Rbrace => s 65 | }; 66 | 67 | assign: Vec = { 68 | Var Eq => 69 | vec![LiaStmt::Declare(id), LiaStmt::Assign(LiaExpr::Var(id), e)], 70 | Eq => vec![LiaStmt::Assign(lhs, rhs)], 71 | => vec![LiaStmt::Assign(lhs.clone(), 72 | LiaExpr::BinOp(RsToken::BinOp(op), Box::new(lhs), Box::new(rhs)))] 73 | }; 74 | 75 | stmt: Vec = { 76 | Semi => vec![LiaStmt::Expr(e)], 77 | Semi => a, 78 | Return Semi => vec![LiaStmt::Return(e)], 79 | If Lparen Rparen )?> => 80 | vec![LiaStmt::If(e, if_, else_)], 81 | While Lparen Rparen => vec![LiaStmt::While(guard, body)], 82 | For Lparen Var Colon Rparen => 83 | vec![LiaStmt::ForObj(id, iterable, body)], 84 | For Lparen Semi Semi Rparen => { 85 | let mut init = init; 86 | let mut body = body; 87 | let mut incr = incr; 88 | body.append(&mut incr); 89 | init.push(LiaStmt::While(guard, body)); 90 | init 91 | } 92 | }; 93 | 94 | Binop: LiaExpr = { 95 | => match t { 96 | LiaToken::RustToken(t) => LiaExpr::BinOp(t, Box::new(e1), Box::new(e2)), 97 | _ => unreachable!(), 98 | } 99 | }; 100 | 101 | expr: LiaExpr = { 102 | => LiaExpr::BinOp(RsToken::BinOp(op), Box::new(e1), Box::new(e2)), 103 | Binop, 104 | Binop, 105 | Binop, 106 | atom 107 | }; 108 | 109 | string: LiaExpr = { 110 | => LiaExpr::String(String::from_str(&s.as_str()).unwrap()) 111 | }; 112 | 113 | id_string: LiaExpr = { 114 | => LiaExpr::String(String::from_str(&id.name.as_str()).unwrap()) 115 | }; 116 | 117 | key_ident: LiaExpr = { 118 | )+> => { 119 | let mut ex = e; 120 | for id in ids { 121 | ex = LiaExpr::Index(Box::new(ex), Box::new(id)); 122 | } 123 | ex 124 | } 125 | }; 126 | 127 | atom: LiaExpr = { 128 | => LiaExpr::Integer(i32::from_str(&n.as_str()).unwrap()), 129 | string, 130 | ident, 131 | key_ident, 132 | True => LiaExpr::Bool(true), 133 | False => LiaExpr::Bool(false), 134 | Lparen > Rparen => LiaExpr::Call(Box::new(f), e), 135 | Lbrace > Rbrace => LiaExpr::Object(kvs), 136 | Lbracket > Rbracket => LiaExpr::Array(vals), 137 | Lbracket Rbracket => LiaExpr::Index(Box::new(obj), Box::new(e)), 138 | Function Lparen > Rparen Lbrace Rbrace => LiaExpr::Closure(args, s), 139 | => LiaExpr::Quote(q), 140 | }; 141 | 142 | ident: LiaExpr = { 143 | => LiaExpr::Var(id), 144 | At > => LiaExpr::RsVar(id), 145 | }; 146 | 147 | ident_or_expr: LiaExpr = { 148 | ident, 149 | key_ident, 150 | Lparen Rparen => e 151 | }; 152 | 153 | key_value: (LiaExpr, LiaExpr) = { 154 | Colon => (s, e), 155 | Colon => (s, e), 156 | }; 157 | 158 | Sep: Vec = { 159 | S)*> => match e { 160 | None => v, 161 | Some(e) => { 162 | let mut v = v; 163 | v.push(e); 164 | v 165 | } 166 | } 167 | }; 168 | 169 | 170 | SepPlus: Vec = { 171 | S)*> => { 172 | let mut v = v; 173 | v.push(e); 174 | v 175 | } 176 | }; -------------------------------------------------------------------------------- /lia/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(rustc_private, quote, box_patterns)] 2 | #![allow(unused_imports, unused_variables, dead_code)] 3 | 4 | extern crate syntax; 5 | 6 | pub mod token; 7 | pub mod ast; 8 | pub mod grammar; 9 | pub mod elaborate; 10 | pub mod codegen; 11 | -------------------------------------------------------------------------------- /lia/src/runtime.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::cell::RefCell; 3 | use std::rc::Rc; 4 | use std::collections::HashMap; 5 | use std::fmt; 6 | 7 | pub type LiaNumber = i32; 8 | pub type LiaBool = bool; 9 | pub type LiaString = String; 10 | pub type LiaClosure = Box) -> LiaPtr>; 11 | pub type LiaObject = HashMap; 12 | 13 | pub enum LiaValue { 14 | Number(LiaNumber), 15 | String(LiaString), 16 | Closure(LiaClosure), 17 | Bool(LiaBool), 18 | Object(LiaObject), 19 | Null, 20 | Unknown(Box) 21 | } 22 | 23 | pub type LiaPtr = Rc>>>; 24 | 25 | fn wrap(t: LiaValue) -> LiaPtr { 26 | Rc::new(RefCell::new(Rc::new(RefCell::new(t)))) 27 | } 28 | 29 | macro_rules! make_allocator { 30 | ($fun:ident, $ty:ty, $path:path) => { 31 | pub fn $fun (t: $ty) -> LiaPtr { 32 | wrap($path(t)) 33 | } 34 | } 35 | } 36 | 37 | make_allocator!(alloc_number, LiaNumber, LiaValue::Number); 38 | make_allocator!(alloc_string, LiaString, LiaValue::String); 39 | make_allocator!(alloc_closure, LiaClosure, LiaValue::Closure); 40 | make_allocator!(alloc_bool, LiaBool, LiaValue::Bool); 41 | make_allocator!(alloc_object, LiaObject, LiaValue::Object); 42 | 43 | pub fn alloc_null() -> LiaPtr { 44 | wrap(LiaValue::Null) 45 | } 46 | 47 | pub fn alloc_other(t: T) -> LiaPtr { 48 | wrap(LiaValue::Unknown(Box::new(t))) 49 | } 50 | 51 | pub fn new_obj() -> LiaObject { 52 | let obj: LiaObject = HashMap::new(); 53 | obj 54 | } 55 | 56 | #[macro_export] 57 | macro_rules! cast { 58 | (let mut $id:ident : $t:ty = $e:expr) => { 59 | let _tmp = $e; 60 | let mut _tmp = _tmp.borrow_mut(); 61 | let mut _tmp = _tmp.borrow_mut(); 62 | let _tmp = _borrow_type!(_tmp $t); 63 | let mut $id = _tmp; 64 | }; 65 | (let $id:ident : $t:ty = $e:expr) => { 66 | let _tmp = $e; 67 | let _tmp = _tmp.borrow(); 68 | let _tmp = _tmp.borrow(); 69 | let _tmp = _borrow_type!(_tmp $t); 70 | let $id = _tmp; 71 | }; 72 | } 73 | 74 | #[macro_export] 75 | macro_rules! call { 76 | ($id:ident ( $( $x:expr ),* )) => {{ 77 | concat_idents!(_lia_, $id)(vec![alloc_object(new_obj()), $( $x ),*]) 78 | }} 79 | } 80 | 81 | impl fmt::Debug for LiaValue { 82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 83 | match self { 84 | &LiaValue::Number(ref n) => n.fmt(f), 85 | &LiaValue::String(ref s) => s.fmt(f), 86 | &LiaValue::Closure(_) => f.pad(""), 87 | &LiaValue::Bool(b) => b.fmt(f), 88 | &LiaValue::Object(ref o) => o.fmt(f), 89 | &LiaValue::Null => f.pad("null"), 90 | &LiaValue::Unknown(_) => f.pad("") 91 | } 92 | } 93 | } 94 | 95 | pub fn _lia_print(args: Vec) -> LiaPtr { 96 | for arg in args[1..].into_iter() { 97 | let _tmp = arg.borrow(); 98 | let arg = _tmp.borrow(); 99 | println!("{:?}", arg); 100 | } 101 | alloc_null() 102 | } 103 | 104 | pub fn val_to_key(val: LiaPtr) -> String { 105 | let _tmp = val.borrow(); 106 | let val = _tmp.borrow(); 107 | match *val { 108 | LiaValue::Number(ref n) => format!("{}", n), 109 | LiaValue::String(ref s) => s.clone(), 110 | _ => panic!("Object key must be string or number"), 111 | } 112 | } 113 | 114 | pub fn _lia_call(args: Vec) -> LiaPtr { 115 | let fun = args.get(1).expect("Arg 1").clone(); 116 | let ctx = args.get(2).expect("Arg 2").clone(); 117 | 118 | cast!(let fun: LiaClosure = fun); 119 | fun(vec![ctx]); 120 | } 121 | -------------------------------------------------------------------------------- /lia/src/token.rs: -------------------------------------------------------------------------------- 1 | use syntax::parse::token::{Token as RsToken}; 2 | use syntax::tokenstream::TokenTree; 3 | 4 | #[derive(Debug, Clone)] 5 | pub enum LiaToken { 6 | RustToken(RsToken), 7 | Var, 8 | Function, 9 | Return, 10 | If, 11 | Else, 12 | While, 13 | For, 14 | True, 15 | False, 16 | Quote(Vec), 17 | Rust, 18 | } 19 | 20 | impl LiaToken { 21 | pub fn from_rust_token(t: RsToken) -> LiaToken { 22 | if let RsToken::Ident(ident) = t { 23 | let s = ident.name.as_str(); 24 | 25 | // No better way to go from InternedString -> &str? 26 | match unsafe { s.slice_unchecked(0, s.len()) } { 27 | "function" => LiaToken::Function, 28 | "var" => LiaToken::Var, 29 | "return" => LiaToken::Return, 30 | "if" => LiaToken::If, 31 | "else" => LiaToken::Else, 32 | "while" => LiaToken::While, 33 | "for" => LiaToken::For, 34 | "true" => LiaToken::True, 35 | "false" => LiaToken::False, 36 | "rust" => LiaToken::Rust, 37 | _ => LiaToken::RustToken(t) 38 | } 39 | } else { 40 | LiaToken::RustToken(t) 41 | } 42 | } 43 | } 44 | --------------------------------------------------------------------------------