├── .editorconfig ├── .gitignore ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── repl.png ├── repl2.png └── src ├── bin └── moon.rs └── moon ├── ast.rs ├── lexer.rs ├── lib.rs ├── parser.rs ├── result.rs ├── span.rs └── vm.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | #end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.rs] 8 | ident_style=space 9 | ident_size=4 10 | 11 | [Makefile] 12 | ident_style = tab 13 | ident_size = 4 14 | 15 | [*.py] 16 | ident_style = space 17 | ident_size = 4 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Object files 2 | *.o 3 | *.ko 4 | 5 | # Libraries 6 | *.lib 7 | *.a 8 | 9 | # Shared objects (inc. Windows DLLs) 10 | *.dll 11 | *.so 12 | *.so.* 13 | *.dylib 14 | 15 | # Executables 16 | *.exe 17 | *.out 18 | *.app 19 | 20 | ./Makefile 21 | testgc 22 | gc 23 | Testing 24 | CTestTestfile.cmake 25 | target/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | 3 | name = "moon" 4 | version = "0.1.0" 5 | authors = [ "dnfagnan@gmail.com" ] 6 | 7 | [[lib]] 8 | 9 | name = "moon" 10 | path = "src/moon/lib.rs" 11 | 12 | [[bin]] 13 | 14 | name = "moon" 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Daniel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | RUSTC ?= rustc 2 | RUSTC_FLAGS ?= 3 | 4 | BINS = moon 5 | 6 | SRC = $(shell find src -name '*.rs') 7 | 8 | all: libmoon moon test 9 | 10 | moon: libmoon 11 | mkdir -p target 12 | $(RUSTC) $(RUST_FLAGS) -Ltarget --out-dir target src/bin/moon.rs 13 | 14 | libmoon: $(SRC) 15 | mkdir -p target 16 | $(RUSTC) $(RUSTC_FLAGS) --out-dir target src/moon/lib.rs 17 | 18 | test: $(SRC) 19 | mkdir -p target 20 | $(RUSTC) $(RUSTC_FLAGS) --test --out-dir target src/moon/lib.rs 21 | ./target/moon 22 | 23 | clean: 24 | rm -rf target 25 | 26 | .PHONY: clean all libmoon 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Moon [![status](http://img.shields.io/badge/stage-alpha-orange.svg)]() [![version](http://img.shields.io/badge/cargo-0.1.0-blue.svg)]() 2 | 3 | Moon is a small Lisp-dialect written purely in Rust. This should allow Rust programs (including games) to have access to a high-quality scripting language that's also safe. Moon is made to work extremely well with Rust or even C. 4 | 5 | === 6 | 7 | ```lisp 8 | (defn shoot 9 | [player bullet] 10 | ...) 11 | 12 | ;; The core render loop for the scripting engine. 13 | ;; On every tick, draw the ship. 14 | (defn render 15 | [inter] 16 | (draw-ship inter)) 17 | ``` 18 | 19 | ![Repl](../master/repl.png?raw=true) 20 | 21 | ![Repl 2](../master/repl2.png?raw=true) 22 | 23 | ## Installing 24 | 25 | Clone the repo: 26 | 27 | ``` 28 | git clone git@github.com:TheHydroImpulse/moon.git 29 | cd moon/ 30 | ``` 31 | 32 | Build the project: 33 | 34 | ``` 35 | make moon 36 | ``` 37 | 38 | Access the Repl: 39 | 40 | ``` 41 | ./target/moon 42 | ``` 43 | 44 | ## Testing 45 | 46 | ``` 47 | make test 48 | ``` 49 | 50 | ## License 51 | 52 | The MIT License (MIT) 53 | 54 | Copyright (c) 2014 Daniel Fagnan 55 | 56 | Permission is hereby granted, free of charge, to any person obtaining a copy 57 | of this software and associated documentation files (the "Software"), to deal 58 | in the Software without restriction, including without limitation the rights 59 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 60 | copies of the Software, and to permit persons to whom the Software is 61 | furnished to do so, subject to the following conditions: 62 | 63 | The above copyright notice and this permission notice shall be included in 64 | all copies or substantial portions of the Software. 65 | 66 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 67 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 68 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 69 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 70 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 71 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 72 | THE SOFTWARE. 73 | 74 | -------------------------------------------------------------------------------- /repl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thehydroimpulse/moon/b7a528b921b2e4b1e16139f21277e35671ebca7e/repl.png -------------------------------------------------------------------------------- /repl2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thehydroimpulse/moon/b7a528b921b2e4b1e16139f21277e35671ebca7e/repl2.png -------------------------------------------------------------------------------- /src/bin/moon.rs: -------------------------------------------------------------------------------- 1 | #![feature(globs)] 2 | #![allow(dead_code)] 3 | 4 | extern crate moon; 5 | use std::io; 6 | 7 | use moon::lexer::Lexer; 8 | 9 | static color_normal: &'static str = "\x1B[0m"; 10 | static color_red: &'static str = "\x1B[31m"; 11 | static color_cyan: &'static str = "\x1B[36m"; 12 | static color_magenta: &'static str = "\x1B[35m"; 13 | static color_bold: &'static str = "\x1B[1m"; 14 | 15 | fn main() { 16 | let mut running = true; 17 | 18 | println!("{:s}Moon{:s} alpha. v0.1.0 \n", color_cyan, color_normal); 19 | 20 | while running { 21 | print!("{:s}$moon{:s}{:s}>{:s} ", color_magenta, color_normal, 22 | color_bold, color_normal); 23 | for line in io::stdin().lines() { 24 | match line { 25 | Ok(line) => { 26 | if line == "exit\n".to_string() { 27 | running = false; 28 | break; 29 | } 30 | 31 | let mut lexer = Lexer::new(line.as_slice()); 32 | print!("{}\n", lexer.collect()); 33 | }, 34 | _ => {} 35 | } 36 | 37 | break; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/moon/ast.rs: -------------------------------------------------------------------------------- 1 | use lexer; 2 | 3 | /// A generic trait that's implemented for each Ast node. We can't use 4 | /// enums for the Ast because they are a union type, thus, they 5 | /// are as large as the largest variant. This isn't a good fit for 6 | /// a primitive that's used a **lot** throughout the compiler. 7 | pub trait Ast { 8 | fn compile(&self); 9 | } 10 | 11 | pub struct AstRoot { 12 | nodes: Vec> 13 | } 14 | 15 | impl AstRoot { 16 | pub fn new() -> AstRoot { 17 | AstRoot { 18 | nodes: Vec::new() 19 | } 20 | } 21 | } 22 | 23 | impl Ast for AstRoot { 24 | fn compile(&self) {} 25 | } 26 | 27 | #[cfg(test)] 28 | mod test { 29 | use super::*; 30 | } 31 | -------------------------------------------------------------------------------- /src/moon/lexer.rs: -------------------------------------------------------------------------------- 1 | use std::str::Chars; 2 | use std::io::Reader; 3 | use std::str::SendStr; 4 | use span::Span; 5 | use std::str::Owned; 6 | use std::from_str::from_str; 7 | 8 | fn is_num_type(n: char) -> bool { 9 | if n != '6' && n != '4' && n != '8' && n != '3' && n != '2' { 10 | false 11 | } else { 12 | true 13 | } 14 | } 15 | 16 | /// A `Token` represents a special symbol within 17 | /// the language. These do not contain any extra metadata 18 | /// with it, that's stored elsewhere. 19 | /// 20 | /// Unlike most languages, characters like `+`, `-`, `*`, `/` 21 | /// are **not** considered tokens, but, in fact regular identifiers. 22 | /// This allows us to implement these without the compiler 23 | /// to have any sort of knowledge of them. 24 | #[deriving(PartialEq, Show, Clone)] 25 | pub enum Token { 26 | Ident(String), 27 | Str(String), 28 | Integer(i64), 29 | LBracket, 30 | RBracket, 31 | Comma, 32 | Keyword(SendStr), 33 | Colon, 34 | Caret, 35 | LParen, 36 | RParen, 37 | Noop, 38 | Done 39 | } 40 | 41 | #[deriving(PartialEq, Show, Clone)] 42 | pub enum IntegerParserState { 43 | NumInit, 44 | /// We have found a type (such as `6u`, `9i`, etc..), 45 | /// however, we might have more information coming up, such as 46 | /// the specialization of the type. 47 | NumTyped(String), 48 | 49 | /// This means we have seen a typed integer, but not a fully 50 | /// specialized one. 51 | NumTypedPartial(String), 52 | 53 | /// The integer type has been more specific. This allows us 54 | /// to know when we have successfully specialized an integer. 55 | /// However, we still need an intermediate step to where 56 | /// the integer has been typed, but not fully specialized. 57 | NumTypedSpecialized(String) 58 | } 59 | 60 | /// The lexer is the system that has any notion of an input stream. The 61 | /// stream can come from many sources, thus, we use the Reader trait 62 | /// that is implemented for strings, files, tcp, etc... 63 | /// 64 | /// The lexer should also not consume the whole stream at once, as 65 | /// that will be a bottleneck for larger input sources. 66 | pub struct Lexer<'a> { 67 | source: &'a str, 68 | iter: Chars<'a>, 69 | token: Token, 70 | done: bool, 71 | span: Span 72 | } 73 | 74 | impl<'a> Lexer<'a> { 75 | /// Return a brand new lexer containing an iterator 76 | /// of chars and a bunch of state to keep track of (such 77 | /// as the current Span). 78 | pub fn new(source: &'a str) -> Lexer<'a> { 79 | Lexer { 80 | source: source, 81 | iter: source.chars(), 82 | token: Noop, 83 | done: false, 84 | span: Span::new(0, 0) 85 | } 86 | } 87 | 88 | pub fn collect(&mut self) -> Vec { 89 | let mut vec = Vec::new(); 90 | 91 | self.bump().while_some(|tok| { 92 | if tok != Done && tok != Noop { 93 | vec.push(tok); 94 | } 95 | 96 | if !self.done { 97 | self.bump() 98 | } else { 99 | None 100 | } 101 | }); 102 | 103 | vec 104 | } 105 | 106 | /// Parse and fetch the next token in the text stream. 107 | /// This doesn't return the token, it stores it 108 | /// in the lexer. 109 | pub fn bump(&mut self) -> Option { 110 | // Fetch the next token so that we can start matching 111 | // against it. If we don't return anything, we'll set 112 | // the token to `Done`, resulting in a completed lexer. 113 | // 114 | // FIXME: I don't think I'm checking for a `None` case 115 | // anywhere else. 116 | let c = match self.iter.next() { 117 | Some(c) => c, 118 | None => { 119 | self.done = true; 120 | self.token = Done; 121 | return None; 122 | } 123 | }; 124 | 125 | match c { 126 | ' ' => { return self.bump() }, 127 | '\n' => { return self.bump() }, 128 | '\t' => { return self.bump() }, 129 | ',' => { self.token = Comma }, 130 | // Parse some simple tokens. 131 | '(' => { self.token = LParen }, 132 | ')' => { self.token = RParen }, 133 | '^' => { self.token = Caret }, 134 | '[' => { self.token = LBracket }, 135 | ']' => { self.token = RBracket }, 136 | num @ '0'..'9' | num @ '-' => { 137 | // Hold a collection of numbered chars so that 138 | // we can convert to a proper number format later. 139 | let mut combined = String::new(); 140 | 141 | // We need to keep track of the current state 142 | // for the integer. This let's us know whether 143 | // we're parsing a specialization, or the user 144 | // has supplied an invalid form. 145 | let mut state = NumInit; 146 | 147 | // We need to push the one char we grabbed before 148 | // all the while_some stuff. 149 | combined.push_char(num); 150 | 151 | // Iterate over the next char until we return 152 | // `None` or the iterator returns `None`. 153 | self.iter.next().while_some(|n| { 154 | 155 | match n { 156 | // Ok, so we have a simple number, that's 157 | // pretty easy. Simply push the char 158 | // to the string. 159 | // 160 | // However, we need to first check 161 | // what state we're in, currently. 162 | '0'..'9' => { 163 | match state.clone() { 164 | // We haven't seen any further information 165 | // about the integer, so we can just 166 | // push the char onto the string normally. 167 | NumInit => combined.push_char(n), 168 | NumTyped(ref kind) => { 169 | if !is_num_type(n) { 170 | fail!("Unexpected specializer {}", n); 171 | } 172 | 173 | let mut s = kind.clone(); 174 | s.push_char(n); 175 | state = NumTypedPartial(s); 176 | }, 177 | NumTypedPartial(ref kind) => { 178 | if !is_num_type(n) { 179 | fail!("Unexpected specializer {}", n); 180 | } 181 | 182 | if kind.as_slice() == "u8" { 183 | fail!("Unexpected character {}, expected nothing.", n); 184 | } 185 | 186 | let mut s = kind.clone(); 187 | s.push_char(n); 188 | let rev = kind.as_slice().chars().last(); 189 | state = NumTypedSpecialized(s); 190 | }, 191 | NumTypedSpecialized(ref kind) => { 192 | println!("x {}", n); 193 | } 194 | } 195 | }, 196 | 'u' => state = NumTyped("u".to_string()), 197 | 'i' => state = NumTyped("i".to_string()), 198 | '\n' => return None, 199 | n if n.is_whitespace() => return None, 200 | _ => fail!("A number must be isolated from other tokens.") 201 | } 202 | 203 | self.iter.next() 204 | }); 205 | 206 | let num = match from_str::(combined.as_slice()) { 207 | Some(num) => num, 208 | None => fail!("Not a number.") 209 | }; 210 | 211 | self.token = Integer(num); 212 | }, 213 | // Parse a simple keyword. Keywords are somewhat like 214 | // strings, but, they're more limited in the format, but 215 | // a lot nicer to use. 216 | ':' => { 217 | let mut keyword = String::new(); 218 | self.iter.next().while_some(|a| { 219 | if self.is_keyword(a) { 220 | keyword.push_char(a); 221 | self.iter.next() 222 | } else { 223 | None 224 | } 225 | }); 226 | 227 | self.token = Keyword(Owned(keyword)); 228 | }, 229 | // Parse the beginning of a string until we find the ending 230 | // quote. Anything can be placed within the double quotes. 231 | // 232 | // This doesn't support any escaping yet, so that'll break. 233 | // To support it, we'll need to keep track of the previous 234 | // token. Finding a `\` character will result in it 235 | // being saved until we look at the next character; which 236 | // we'll look back at the previous character to figure 237 | // out our current state. Escaping will only be set if 238 | // it matches a good escape pattern. 239 | '"' => { 240 | let mut concat = String::new(); 241 | self.iter.next().while_some(|a| { 242 | if a == '"' { 243 | None 244 | } else { 245 | concat.push_char(a); 246 | self.iter.next() 247 | } 248 | }); 249 | 250 | self.token = Str(concat); 251 | }, 252 | // An identifier is composed of any 253 | // characters that contain no whitespace 254 | // and doesn't start with the designated 255 | // characters above. 256 | ch => { 257 | let mut ident = String::new(); 258 | 259 | if self.is_ident(ch) { 260 | ident.push_char(ch); 261 | } else { 262 | // Push an `Unknown` token here. 263 | } 264 | 265 | self.iter.next().while_some(|a| { 266 | if self.is_ident(a) { 267 | ident.push_char(a); 268 | self.iter.next() 269 | } else { 270 | None 271 | } 272 | }); 273 | 274 | if ident.len() > 0 { 275 | self.token = Ident(ident); 276 | } 277 | } 278 | } 279 | 280 | Some(self.token.clone()) 281 | } 282 | 283 | /// Determine if a char is allowed within an identifier. 284 | /// An identifier can be used for: 285 | /// 286 | /// * Function names 287 | /// * Binding names 288 | /// * Other 289 | /// 290 | /// Because many of the built-in forms have identifiers 291 | /// such as `+`, `-`, `*`, `/`, we can't make these tokens, 292 | /// nor can we exclude them from the identifier format. 293 | pub fn is_ident(&self, ch: char) -> bool { 294 | if ch.is_whitespace() || ch == ':' || ch == '(' || ch == ')' || ch == '"' 295 | || ch == '@' || ch == '!' || ch == '\n' || ch == '\t' || ch == ',' { 296 | false 297 | } else { 298 | true 299 | } 300 | } 301 | 302 | /// Verify if the char is within the allowed character set for keywords. This 303 | /// only checks a single character at a time. 304 | #[inline] 305 | pub fn is_keyword(&self, a: char) -> bool { 306 | if a.is_whitespace() || a == '(' || a == ')' || a == '^' || a == ':' || a == ',' { 307 | false 308 | } else { 309 | true 310 | } 311 | } 312 | 313 | /// Expect a given token to come next, otherwise 314 | /// fail. Failing will result in a formatted 315 | /// error message that uses the Span and lets 316 | /// the user know what went wrong. 317 | pub fn expect(&mut self, tok: &Token) { 318 | if self.token == *tok { 319 | self.bump(); 320 | } else { 321 | fail!("Expected {} but found {}", *tok, self.token); 322 | } 323 | } 324 | 325 | /// Optionally eat a given token. If the token is not 326 | /// found, this will simply return false and 327 | /// won't fail. 328 | pub fn eat(&mut self, tok: &Token) -> bool { 329 | if self.token == *tok { 330 | self.bump(); 331 | true 332 | } else { 333 | false 334 | } 335 | } 336 | } 337 | 338 | 339 | #[cfg(test)] 340 | mod test { 341 | use super::*; 342 | use span::Span; 343 | use std::str::{Slice, Owned}; 344 | 345 | #[test] 346 | fn noop() { 347 | let lexer = Lexer::new("foo"); 348 | assert_eq!(lexer.token, Noop); 349 | } 350 | 351 | #[test] 352 | fn zeroed_span() { 353 | let lexer = Lexer::new("foo"); 354 | assert_eq!(lexer.span, Span::new(0, 0)); 355 | } 356 | 357 | #[test] 358 | fn bump_empty() { 359 | let mut lexer = Lexer::new(""); 360 | lexer.bump(); 361 | assert_eq!(lexer.token, Done); 362 | } 363 | 364 | #[test] 365 | fn bump_lparen() { 366 | let mut lexer = Lexer::new("("); 367 | lexer.bump(); 368 | assert_eq!(lexer.token, LParen); 369 | } 370 | 371 | #[test] 372 | fn bump_rparen() { 373 | let mut lexer = Lexer::new(")"); 374 | lexer.bump(); 375 | assert_eq!(lexer.token, RParen); 376 | } 377 | 378 | #[test] 379 | fn bump_simple_keyword() { 380 | let mut lexer = Lexer::new(":foo"); 381 | lexer.bump(); 382 | assert_eq!(lexer.token, Keyword(Slice("foo"))); 383 | } 384 | 385 | #[test] 386 | fn bump_keyword_dash() { 387 | let mut lexer = Lexer::new(":foo-"); 388 | lexer.bump(); 389 | assert_eq!(lexer.token, Keyword(Slice("foo-"))); 390 | } 391 | 392 | #[test] 393 | fn bump_keyword_dash_middle() { 394 | let mut lexer = Lexer::new(":foo-bar"); 395 | lexer.bump(); 396 | assert_eq!(lexer.token, Keyword(Slice("foo-bar"))); 397 | } 398 | 399 | #[test] 400 | fn bump_string() { 401 | let mut lexer = Lexer::new("\"hah-w091:--:\""); 402 | lexer.bump(); 403 | assert_eq!(lexer.token, Str("hah-w091:--:".to_string())); 404 | } 405 | 406 | #[test] 407 | fn bump_ident() { 408 | let mut lexer = Lexer::new("foo-bar-&#^"); 409 | lexer.bump(); 410 | assert_eq!(lexer.token, Ident("foo-bar-&#^".to_string())); 411 | } 412 | 413 | #[test] 414 | fn should_eat_lparen() { 415 | let mut lexer = Lexer::new("("); 416 | lexer.bump(); 417 | assert_eq!(lexer.eat(&LParen), true); 418 | } 419 | 420 | #[test] 421 | #[should_fail] 422 | fn should_expect_lparen() { 423 | let mut lexer = Lexer::new(")"); 424 | lexer.bump(); 425 | lexer.expect(&LParen); 426 | } 427 | 428 | #[test] 429 | fn should_expect() { 430 | let mut lexer = Lexer::new("("); 431 | lexer.bump(); 432 | lexer.expect(&LParen); 433 | } 434 | } 435 | -------------------------------------------------------------------------------- /src/moon/lib.rs: -------------------------------------------------------------------------------- 1 | #![crate_id = "moon"] 2 | #![crate_type = "lib"] 3 | #![feature(globs, macro_rules)] 4 | #![allow(experimental)] 5 | 6 | pub mod vm; 7 | pub mod ast; 8 | pub mod lexer; 9 | pub mod span; 10 | pub mod result; 11 | -------------------------------------------------------------------------------- /src/moon/parser.rs: -------------------------------------------------------------------------------- 1 | use lexer::Lexer; 2 | use lexer::Token; 3 | use ast::Ast; 4 | use span::Span; 5 | use result::MoonResult; 6 | 7 | /// The parser is responsible for taking a stream of generic tokens 8 | /// that come from the lexer and turn them into a correct Abstract Synax 9 | /// Tree (AST). The AST will be responsible for having the domain-logic 10 | /// to compile the specialized nodes into something we can execute 11 | /// (either JIT or interpreted.). 12 | #[deriving(PartialEq, Show)] 13 | pub struct Parser<'a> { 14 | lexer: Lexer<'a>, 15 | ast: AstRoot 16 | } 17 | 18 | 19 | impl<'a> Parser<'a> { 20 | pub fn new(input: &'a str) -> Parser<'a> { 21 | Parser { 22 | lexer: Lexer::new(input), 23 | ast: AstRoot::new() 24 | } 25 | } 26 | 27 | pub fn execute() -> MoonResult<()> { 28 | Ok(()) 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod test { 34 | use super::*; 35 | 36 | #[test] 37 | fn should_parse_empty_program() { 38 | let mut parser = Parser::new(""); 39 | parser.execute(); 40 | assert_eq!(parser.ast.nodes.len(), 0); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/moon/result.rs: -------------------------------------------------------------------------------- 1 | use std::str::SendStr; 2 | use std::io; 3 | use span::Span; 4 | 5 | pub type MoonResult = Result; 6 | 7 | #[deriving(PartialEq, Show)] 8 | pub struct MoonError { 9 | span: Span, 10 | kind: MoonErrorKind, 11 | message: SendStr 12 | } 13 | 14 | #[deriving(PartialEq, Show)] 15 | pub enum MoonErrorKind { 16 | Noop 17 | } 18 | 19 | impl MoonError { 20 | pub fn new>(span: Span, 21 | kind: MoonErrorKind, 22 | msg: T) -> MoonError { 23 | MoonError { 24 | span: span, 25 | kind: kind, 26 | message: msg.into_maybe_owned() 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/moon/span.rs: -------------------------------------------------------------------------------- 1 | /// A `Span` keeps track of a position in the source 2 | /// stream. This allows us to later accurately look 3 | /// back to the original location and display errors or 4 | /// do something else with the source. 5 | /// 6 | /// Spans keeps track for a column and line number. 7 | #[deriving(PartialEq, Show)] 8 | pub struct Span { 9 | line: uint, 10 | col: uint 11 | } 12 | 13 | 14 | impl Span { 15 | 16 | /// Create a new Span given a line number and column. 17 | pub fn new(line: uint, col: uint) -> Span { 18 | Span { 19 | line: line, 20 | col: col 21 | } 22 | } 23 | 24 | #[inline] 25 | pub fn line(&mut self) { 26 | self.line += 1; 27 | } 28 | 29 | #[inline] 30 | pub fn col(&mut self) { 31 | self.col += 1; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/moon/vm.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thehydroimpulse/moon/b7a528b921b2e4b1e16139f21277e35671ebca7e/src/moon/vm.rs --------------------------------------------------------------------------------