diff --git a/Cargo.lock b/Cargo.lock index 3fcf7f88..05e63be9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ version = "0.1.0" dependencies = [ "clap", "logos", + "rand", ] [[package]] @@ -42,6 +43,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "clap" version = "2.33.3" @@ -63,6 +70,17 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "hermit-abi" version = "0.1.18" @@ -102,6 +120,12 @@ dependencies = [ "utf8-ranges", ] +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + [[package]] name = "proc-macro2" version = "1.0.26" @@ -120,6 +144,46 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + [[package]] name = "regex-syntax" version = "0.6.23" @@ -176,6 +240,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index a792b2ec..9f8f743d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,4 +8,5 @@ edition = "2018" [dependencies] clap="*" -logos = "0.12" \ No newline at end of file +logos = "0.12" +rand = "*" \ No newline at end of file diff --git a/able-script-test/parse_test.able b/able-script-test/parse_test.able new file mode 100644 index 00000000..72786dce --- /dev/null +++ b/able-script-test/parse_test.able @@ -0,0 +1,10 @@ +functio test() { + functio nested() { + var c = false; + } + var a = true; +} + +functio another() { + var b = false; +} \ No newline at end of file diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..510aa334 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,19 @@ +use std::ops::Range; + +#[derive(Debug, Clone)] +pub struct Error { + pub kind: ErrorKind, + pub position: Range, +} + +#[derive(Debug, Clone)] +pub enum ErrorKind { + SyntaxError, +} + +impl Error { + pub fn panic(&self, span: &str) { + println!("{:?} occured at {:?}", self.kind, self.position); + println!(" {}", &span); + } +} diff --git a/src/main.rs b/src/main.rs index 73bcfea7..bf9230a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,15 @@ +#![forbid(unsafe_code)] + mod base_55; +mod error; mod parser; -mod scanner; mod tokens; mod variables; use clap::{App, Arg}; -use scanner::Scanner; - +use parser::Parser; fn main() { - variables::test(); + // variables::test(); let matches = App::new("AbleScript") .version(env!("CARGO_PKG_VERSION")) @@ -29,9 +30,10 @@ fn main() { // Read file let source = std::fs::read_to_string(file_path).unwrap(); - // Print token type: `value` - let mut scanner = Scanner::new(&source); - scanner.scan(); + // Parse + let mut parser = Parser::new(&source); + let ast = parser.parse(); + println!("{:#?}", ast); } None => { println!("hi"); diff --git a/src/parser.rs b/src/parser.rs deleted file mode 100644 index 1c4be808..00000000 --- a/src/parser.rs +++ /dev/null @@ -1,17 +0,0 @@ -use crate::tokens::Abool; - -pub fn abool2num(abool: Abool) -> i32 { - match abool { - Abool::Never => -1, - Abool::Sometimes => 0, - Abool::Always => 1, - } -} -pub fn num2abool(number: i32) -> Abool { - match number { - -1 => Abool::Never, - 0 => Abool::Sometimes, - 1 => Abool::Always, - _ => Abool::Sometimes, - } -} diff --git a/src/parser/item.rs b/src/parser/item.rs new file mode 100644 index 00000000..af6af748 --- /dev/null +++ b/src/parser/item.rs @@ -0,0 +1,6 @@ +#[derive(Debug, Clone)] +pub enum Expr { + VariableDeclaration { iden: String, init: Option }, + FunctionDeclaration { iden: String, body: Vec }, + BfFDeclaration { iden: String, code: String }, +} diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 00000000..33a84097 --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1,98 @@ +mod item; +mod utils; + +use item::Expr; + +use crate::error::{Error, ErrorKind}; +use crate::tokens::Token; + +use logos::Logos; + +/// Parser structure / state machine +pub struct Parser<'a> { + lexer: logos::Lexer<'a, Token>, +} + +impl<'a> Parser<'a> { + /// Create a new parser object + pub fn new(source: &'a str) -> Self { + Self { + lexer: Token::lexer(source), + } + } + + /// Start parsing Token Vector into Abstract Syntax Tree + pub fn parse(&mut self) -> Vec { + let mut ast = vec![]; + while let Some(token) = self.lexer.next() { + let expr = match token { + Token::Variable => self.variable_declaration(), + Token::Function => self.function_declaration(), + Token::BfFunction => self.bff_declaration(), + Token::RightBrace => return ast, + _ => Err(Error { + kind: ErrorKind::SyntaxError, + position: 0..0, + }), + }; + match expr { + Ok(o) => ast.push(o), + Err(e) => { + e.panic(self.lexer.slice()); + break; + } + } + } + + ast + } + + /// Parse variable declaration + /// + /// `var [iden] = [literal];` + fn variable_declaration(&mut self) -> Result { + let iden = self.require(Token::Identifier)?; + + let init = match self.lexer.next() { + Some(Token::Semicolon) => None, + Some(Token::Assignment) => { + let value = self.require(Token::Boolean)?; // TODO: Shouldn't be limited to boolean (pattern match?) + self.require(Token::Semicolon)?; + Some(value) + } + _ => { + return Err(Error { + kind: ErrorKind::SyntaxError, + position: self.lexer.span(), + }) + } + }; + + Ok(Expr::VariableDeclaration { iden, init }) + } + + /// Declare function + /// + /// `functio [iden] ([expr], [expr]) { ... } + fn function_declaration(&mut self) -> Result { + let iden = self.require(Token::Identifier)?; + self.require(Token::LeftParenthesis)?; + // TODO: Arguments + self.require(Token::RightParenthesis)?; + self.require(Token::LeftBrace)?; + let body = self.parse(); + + Ok(Expr::FunctionDeclaration { iden, body }) + } + + /// Declare BF FFI Function + /// + /// `bff [iden] { ... }` + fn bff_declaration(&mut self) -> Result { + let iden = self.require(Token::Identifier)?; + self.require(Token::LeftBrace)?; + let code = self.require(Token::String)?; // <-- Nasty hack, but works + self.require(Token::RightBrace)?; + Ok(Expr::BfFDeclaration { iden, code }) + } +} diff --git a/src/parser/utils.rs b/src/parser/utils.rs new file mode 100644 index 00000000..bae92a37 --- /dev/null +++ b/src/parser/utils.rs @@ -0,0 +1,35 @@ +use crate::error::{Error, ErrorKind}; +use crate::tokens::Token; +use crate::variables::Abool; + +use super::Parser; + +pub fn abool2num(abool: Abool) -> i32 { + match abool { + Abool::Never => -1, + Abool::Sometimes => 0, + Abool::Always => 1, + } +} +pub fn num2abool(number: i32) -> Abool { + match number { + -1 => Abool::Never, + 0 => Abool::Sometimes, + 1 => Abool::Always, + _ => Abool::Sometimes, + } +} + +impl<'a> Parser<'a> { + /// Require type of token as next and return it's value (sometimes irrelevant) + pub(super) fn require(&mut self, with: Token) -> Result { + if self.lexer.next() == Some(with) { + Ok(self.lexer.slice().to_owned()) + } else { + Err(Error { + kind: ErrorKind::SyntaxError, + position: self.lexer.span(), + }) + } + } +} diff --git a/src/scanner.rs b/src/scanner.rs deleted file mode 100644 index 1e968262..00000000 --- a/src/scanner.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::ops::Range; - -use logos::Logos; - -use crate::tokens::{self, Token}; -pub struct Scanner<'a> { - source: &'a str, - lexer: logos::Lexer<'a, Token>, -} - -impl<'a> Scanner<'a> { - pub fn new(source: &'a str) -> Self { - Self { - source, - lexer: tokens::Token::lexer(source), - } - } - - pub fn scan(&mut self) { - while let Some(tok) = self.lexer.next() { - if matches!(tok, Token::Error) { - self.throw_err(&self.lexer.span()); - } else { - println!("Token: {:?}", tok); - } - } - } - - fn throw_err(&self, location: &Range) { - let part = &self.source[location.clone()]; - println!("Unknown keyword `{}` found on {:?}", part, location); - } -} diff --git a/src/tokens.rs b/src/tokens.rs index bf1e315d..ddfe26eb 100644 --- a/src/tokens.rs +++ b/src/tokens.rs @@ -2,6 +2,23 @@ use logos::Logos; #[derive(Logos, Debug, PartialEq)] pub enum Token { + // Literals + /// True, False + #[regex("true|false")] + Boolean, + + /// Always, Sometimes, Never + #[regex("always|sometimes|never")] + Aboolean, + + /// String + #[regex("\"(\\.|[^\"])*\"")] + String, + + /// Integer + #[regex(r"[0-9]+")] + Integer, + /// A C-complaint identifier #[regex(r"[a-zA-Z_][a-zA-Z_0-9]*")] Identifier, @@ -30,6 +47,7 @@ pub enum Token { #[regex(r"#.*")] Comment, + // Operators #[token("-")] Subtract, @@ -60,22 +78,6 @@ pub enum Token { #[token("var")] Variable, - /// True, False - #[regex("true|false")] - Boolean, - - /// Always, Sometimes, Never - #[regex("always|sometimes|never")] - Aboolean, - - /// String - #[regex("\"(\\.|[^\"])*\"")] - String, - - /// Integer - #[regex(r"[0-9]+")] - Integer, - /// Prints the preceding things #[token("print")] Print, @@ -87,14 +89,17 @@ pub enum Token { #[token("T-Dark")] TDark, + // Expressions + #[token("if")] + If, + + #[token("else")] + Else, + + #[token("loop")] + Loop, + #[regex(r"[ \t\n\f]+", logos::skip)] #[error] Error, } - -#[derive(Debug, PartialEq)] -pub enum Abool { - Never = -1, - Sometimes = 0, - Always = 1, -} diff --git a/src/variables.rs b/src/variables.rs index df9cd067..b3020a15 100644 --- a/src/variables.rs +++ b/src/variables.rs @@ -1,11 +1,29 @@ +use rand::Rng; use std::collections::HashMap; -#[derive(Debug)] -enum Value { +#[derive(Debug, Clone, PartialEq)] +pub enum Abool { + Never = -1, + Sometimes = 0, + Always = 1, +} + +impl Into for Abool { + fn into(self) -> bool { + match self { + Abool::Never => false, + Abool::Always => true, + Abool::Sometimes => rand::thread_rng().gen(), + } + } +} + +#[derive(Debug, Clone)] +pub enum Value { Str(String), Int(i32), Bool(bool), - //TODO(Able): Add abool and other variable types + Abool(Abool), } #[derive(Debug)]