| author | |
| committer | |
| log | 6c14072860c381a177f886b7c3f00001fafd9abc |
| tree | ce847a8d38b134e2a3e349ddc141dc98eb7c2618 |
| parent | de824b0e32bfb81f03869727ac3ff2006a3492e9 |
9 files changed, 177 insertions(+), 135 deletions(-)
examples/easy.blight deleted-7| ... | ... | @@ -1,7 +0,0 @@ |
| 1 | var x: int = 0; | |
| 2 | var y: float = 0; | |
| 3 | var z: bigint = 0; | |
| 4 | ||
| 5 | let a: int = 0; | |
| 6 | let b: float = 0; | |
| 7 | let c: bigint = 0; |
examples/operations.blight created+1| ... | ... | @@ -0,0 +1 @@ |
| 1 | let a: int = 10 - 2 / 4 * 15; |
examples/variables.blight created+7| ... | ... | @@ -0,0 +1,7 @@ |
| 1 | var x: int = 0; | |
| 2 | var y: float = 0; | |
| 3 | var z: bigint = 0; | |
| 4 | ||
| 5 | let a: int = 0; | |
| 6 | let b: float = 0; | |
| 7 | let c: bigint = 0; |
src/main.rs+2-2| ... | ... | @@ -1,7 +1,7 @@ |
| 1 | 1 | use std::path::PathBuf; |
| 2 | 2 | |
| 3 | use miette::{Result, NamedSource}; | |
| 4 | 3 | use clap::{Parser, Subcommand}; |
| 4 | use miette::Result; | |
| 5 | 5 | |
| 6 | 6 | mod transpile; |
| 7 | 7 | |
| ... | ... | @@ -21,7 +21,7 @@ enum Commands { |
| 21 | 21 | /// Compiles a file into javascript |
| 22 | 22 | Compile { |
| 23 | 23 | /// lists test values |
| 24 | file: PathBuf | |
| 24 | file: PathBuf, | |
| 25 | 25 | }, |
| 26 | 26 | } |
| 27 | 27 |
src/transpile/ast.rs+71-50| ... | ... | @@ -1,19 +1,21 @@ |
| 1 | use miette::{Result, Diagnostic}; | |
| 1 | use miette::{Diagnostic, Result}; | |
| 2 | 2 | use thiserror::Error; |
| 3 | 3 | |
| 4 | 4 | use super::tokenizer::Token; |
| 5 | use super::util::PeekableIterator; | |
| 5 | use super::util::{Operator, PeekableIterator}; | |
| 6 | 6 | |
| 7 | 7 | #[derive(Error, Diagnostic, Debug)] |
| 8 | 8 | #[error("AstError")] |
| 9 | 9 | struct AstError { |
| 10 | 10 | #[help] |
| 11 | error: String | |
| 11 | error: String, | |
| 12 | 12 | } |
| 13 | 13 | |
| 14 | 14 | impl AstError { |
| 15 | 15 | fn new(error: &'static str) -> AstError { |
| 16 | AstError { error: String::from(error) } | |
| 16 | AstError { | |
| 17 | error: String::from(error), | |
| 18 | } | |
| 17 | 19 | } |
| 18 | 20 | } |
| 19 | 21 | |
| ... | ... | @@ -21,7 +23,7 @@ impl AstError { |
| 21 | 23 | pub enum Type { |
| 22 | 24 | Int, |
| 23 | 25 | Float, |
| 24 | Bigint | |
| 26 | Bigint, | |
| 25 | 27 | } |
| 26 | 28 | |
| 27 | 29 | #[derive(Debug)] |
| ... | ... | @@ -29,107 +31,126 @@ pub enum Ast { |
| 29 | 31 | ConstantDeclaration { |
| 30 | 32 | name: String, |
| 31 | 33 | typ: Type, |
| 32 | value: Box<Ast> | |
| 34 | value: Box<Ast>, | |
| 33 | 35 | }, |
| 34 | 36 | VariableDeclaration { |
| 35 | 37 | name: String, |
| 36 | 38 | typ: Type, |
| 37 | value: Box<Ast> | |
| 39 | value: Box<Ast>, | |
| 40 | }, | |
| 41 | Operation { | |
| 42 | operator: Operator, | |
| 43 | left: Box<Ast>, | |
| 44 | right: Box<Ast>, | |
| 38 | 45 | }, |
| 39 | 46 | Number(String), |
| 40 | 47 | } |
| 41 | 48 | |
| 42 | fn parse_expression(tokens: &mut dyn PeekableIterator<Item=&Token>) -> Result<Ast> { | |
| 43 | match tokens.next() { | |
| 49 | fn parse_expression(tokens: &mut dyn PeekableIterator<Item = &Token>) -> Result<Ast> { | |
| 50 | let left = match tokens.next() { | |
| 44 | 51 | Some(token) => match token { |
| 45 | Token::Number(number) => Token::Number(number.clone()), | |
| 46 | _ => return Err(AstError::new("Expected expression, but did not get it").into()) | |
| 52 | Token::Number(number) => Ast::Number(number.clone()), | |
| 53 | _ => return Err(AstError::new("Expected expression, but did not get it").into()), | |
| 47 | 54 | }, |
| 48 | None => return Err(AstError::new("Expected expression, but got nothing instead").into()) | |
| 49 | ||
| 55 | None => return Err(AstError::new("Expected expression, but got nothing instead").into()), | |
| 50 | 56 | }; |
| 57 | ||
| 58 | match tokens.peek() { | |
| 59 | Some(token) => match token { | |
| 60 | Token::Operator(operator) => { | |
| 61 | tokens.next(); | |
| 62 | return Ok(Ast::Operation { | |
| 63 | operator: operator.clone(), | |
| 64 | left: Box::new(left), | |
| 65 | right: Box::new(parse_expression(tokens)?), | |
| 66 | }); | |
| 67 | } | |
| 68 | _ => (), | |
| 69 | }, | |
| 70 | _ => (), | |
| 71 | } | |
| 72 | ||
| 51 | 73 | Ok(Ast::Number(String::from("10"))) |
| 52 | 74 | } |
| 53 | 75 | |
| 54 | 76 | /// Precondition: There is at least one token in the iterator |
| 55 | fn parse_statement(tokens: &mut dyn PeekableIterator<Item=&Token>) -> Result<Ast> { | |
| 77 | fn parse_statement(tokens: &mut dyn PeekableIterator<Item = &Token>) -> Result<Ast> { | |
| 56 | 78 | match tokens.next().unwrap() { |
| 57 | 79 | Token::Let => { |
| 58 | 80 | let name = match tokens.next() { |
| 59 | 81 | Some(Token::Identifier(name)) => name, |
| 60 | _ => return Err(AstError::new("Expected identifier after var").into()) | |
| 82 | _ => return Err(AstError::new("Expected identifier after var").into()), | |
| 61 | 83 | }; |
| 62 | 84 | let typ = match tokens.next() { |
| 63 | Some(Token::Colon) => { | |
| 64 | match tokens.next() { | |
| 65 | Some(Token::Identifier(typ)) => { | |
| 66 | match typ.as_str() { | |
| 67 | "int" => Type::Int, | |
| 68 | "float" => Type::Float, | |
| 69 | "bigint" => Type::Bigint, | |
| 70 | _ => return Err(AstError::new("Expected type after colon").into()) | |
| 71 | } | |
| 72 | }, | |
| 73 | _ => return Err(AstError::new("Expected type after colon").into()) | |
| 74 | } | |
| 85 | Some(Token::Colon) => match tokens.next() { | |
| 86 | Some(Token::Identifier(typ)) => match typ.as_str() { | |
| 87 | "int" => Type::Int, | |
| 88 | "float" => Type::Float, | |
| 89 | "bigint" => Type::Bigint, | |
| 90 | _ => return Err(AstError::new("Expected type after colon").into()), | |
| 91 | }, | |
| 92 | _ => return Err(AstError::new("Expected type after colon").into()), | |
| 75 | 93 | }, |
| 76 | _ => return Err(AstError::new("Expected colon after identifier").into()) | |
| 94 | _ => return Err(AstError::new("Expected colon after identifier").into()), | |
| 77 | 95 | }; |
| 78 | 96 | let value = match tokens.next() { |
| 79 | 97 | Some(Token::Equals) => parse_expression(tokens)?, |
| 80 | _ => return Err(AstError::new("Expected equals after identifier").into()) | |
| 98 | _ => return Err(AstError::new("Expected equals after identifier").into()), | |
| 81 | 99 | }; |
| 82 | 100 | match tokens.next() { |
| 83 | 101 | Some(Token::Semicolon) => (), |
| 84 | _ => return Err(AstError::new("Expected semicolon after variable declaration").into()) | |
| 102 | _ => { | |
| 103 | return Err( | |
| 104 | AstError::new("Expected semicolon after variable declaration").into(), | |
| 105 | ) | |
| 106 | } | |
| 85 | 107 | }; |
| 86 | 108 | Ok(Ast::ConstantDeclaration { |
| 87 | 109 | name: name.clone(), |
| 88 | 110 | typ, |
| 89 | 111 | value: Box::new(value), |
| 90 | 112 | }) |
| 91 | }, | |
| 113 | } | |
| 92 | 114 | Token::Var => { |
| 93 | 115 | let name = match tokens.next() { |
| 94 | 116 | Some(Token::Identifier(name)) => name, |
| 95 | _ => return Err(AstError::new("Expected identifier after var").into()) | |
| 117 | _ => return Err(AstError::new("Expected identifier after var").into()), | |
| 96 | 118 | }; |
| 97 | 119 | let typ = match tokens.next() { |
| 98 | Some(Token::Colon) => { | |
| 99 | match tokens.next() { | |
| 100 | Some(Token::Identifier(typ)) => { | |
| 101 | match typ.as_str() { | |
| 102 | "int" => Type::Int, | |
| 103 | "float" => Type::Float, | |
| 104 | "bigint" => Type::Bigint, | |
| 105 | _ => return Err(AstError::new("Expected type after colon").into()) | |
| 106 | } | |
| 107 | }, | |
| 108 | _ => return Err(AstError::new("Expected type after colon").into()) | |
| 109 | } | |
| 120 | Some(Token::Colon) => match tokens.next() { | |
| 121 | Some(Token::Identifier(typ)) => match typ.as_str() { | |
| 122 | "int" => Type::Int, | |
| 123 | "float" => Type::Float, | |
| 124 | "bigint" => Type::Bigint, | |
| 125 | _ => return Err(AstError::new("Expected type after colon").into()), | |
| 126 | }, | |
| 127 | _ => return Err(AstError::new("Expected type after colon").into()), | |
| 110 | 128 | }, |
| 111 | _ => return Err(AstError::new("Expected colon after identifier").into()) | |
| 129 | _ => return Err(AstError::new("Expected colon after identifier").into()), | |
| 112 | 130 | }; |
| 113 | 131 | let value = match tokens.next() { |
| 114 | 132 | Some(Token::Equals) => parse_expression(tokens)?, |
| 115 | _ => return Err(AstError::new("Expected equals after identifier").into()) | |
| 133 | _ => return Err(AstError::new("Expected equals after identifier").into()), | |
| 116 | 134 | }; |
| 117 | 135 | match tokens.next() { |
| 118 | 136 | Some(Token::Semicolon) => (), |
| 119 | _ => return Err(AstError::new("Expected semicolon after variable declaration").into()) | |
| 137 | _ => { | |
| 138 | return Err( | |
| 139 | AstError::new("Expected semicolon after variable declaration").into(), | |
| 140 | ) | |
| 141 | } | |
| 120 | 142 | }; |
| 121 | 143 | Ok(Ast::VariableDeclaration { |
| 122 | 144 | name: name.clone(), |
| 123 | 145 | typ, |
| 124 | 146 | value: Box::new(value), |
| 125 | 147 | }) |
| 126 | }, | |
| 148 | } | |
| 127 | 149 | Token::Number(number) => Ok(Ast::Number(number.clone())), |
| 128 | _ => return Err(AstError::new("Unexpected token").into()) | |
| 150 | _ => return Err(AstError::new("Unexpected token").into()), | |
| 129 | 151 | } |
| 130 | 152 | } |
| 131 | 153 | |
| 132 | ||
| 133 | 154 | pub fn parse(tokens: Vec<Token>) -> Result<Vec<Ast>> { |
| 134 | 155 | let mut iter = tokens.iter().peekable(); |
| 135 | 156 | let mut ast: Vec<Ast> = Vec::new(); |
src/transpile/mod.rs+3-3| ... | ... | @@ -1,12 +1,12 @@ |
| 1 | use miette::{Result}; | |
| 1 | use miette::Result; | |
| 2 | 2 | |
| 3 | mod tokenizer; | |
| 4 | 3 | mod ast; |
| 4 | mod tokenizer; | |
| 5 | 5 | mod walker; |
| 6 | 6 | |
| 7 | 7 | mod util; |
| 8 | 8 | |
| 9 | pub fn transpile(content: String) -> Result<String> { | |
| 9 | pub fn transpile(content: String) -> Result<String> { | |
| 10 | 10 | // 1. Tokenize the input |
| 11 | 11 | let tokens = tokenizer::tokenize(content)?; |
| 12 | 12 | // 2. Parse the tokens into an AST |
src/transpile/tokenizer.rs+56-61| ... | ... | @@ -1,19 +1,13 @@ |
| 1 | use miette::{Result, Diagnostic}; | |
| 1 | use miette::{Diagnostic, Result}; | |
| 2 | 2 | use thiserror::Error; |
| 3 | 3 | |
| 4 | use super::util::Operator; | |
| 5 | ||
| 4 | 6 | #[derive(Error, Diagnostic, Debug)] |
| 5 | 7 | #[error("UnknownCharacter")] |
| 6 | 8 | struct UnknownCharacter { |
| 7 | 9 | #[help] |
| 8 | error: String | |
| 9 | } | |
| 10 | ||
| 11 | #[derive(Debug, Clone)] | |
| 12 | pub enum Operator { | |
| 13 | Add, | |
| 14 | Substract, | |
| 15 | Multiply, | |
| 16 | Divide, | |
| 10 | error: String, | |
| 17 | 11 | } |
| 18 | 12 | |
| 19 | 13 | #[derive(Debug, Clone)] |
| ... | ... | @@ -28,7 +22,7 @@ pub enum Token { |
| 28 | 22 | Equals, |
| 29 | 23 | Semicolon, |
| 30 | 24 | // Operators |
| 31 | Operator(Operator) | |
| 25 | Operator(Operator), | |
| 32 | 26 | } |
| 33 | 27 | |
| 34 | 28 | pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| ... | ... | @@ -37,64 +31,65 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 37 | 31 | |
| 38 | 32 | loop { |
| 39 | 33 | match chars.next() { |
| 40 | Some((i, c)) => { | |
| 41 | match c { | |
| 42 | 'a'..='z' | 'A'..='Z' | '_' => { | |
| 43 | let mut current_token = String::new(); | |
| 44 | current_token.push(c); | |
| 45 | loop { | |
| 46 | let c = chars.peek(); | |
| 47 | if c.is_none() { | |
| 48 | break; | |
| 49 | } | |
| 50 | let (_, c) = c.unwrap(); | |
| 51 | match c { | |
| 52 | 'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => { | |
| 53 | current_token.push(*c); | |
| 54 | chars.next(); | |
| 55 | }, | |
| 56 | _ => break, | |
| 34 | Some((_, c)) => match c { | |
| 35 | 'a'..='z' | 'A'..='Z' | '_' => { | |
| 36 | let mut current_token = String::new(); | |
| 37 | current_token.push(c); | |
| 38 | loop { | |
| 39 | let c = chars.peek(); | |
| 40 | if c.is_none() { | |
| 41 | break; | |
| 42 | } | |
| 43 | let (_, c) = c.unwrap(); | |
| 44 | match c { | |
| 45 | 'a'..='z' | 'A'..='Z' | '_' | '0'..='9' => { | |
| 46 | current_token.push(*c); | |
| 47 | chars.next(); | |
| 57 | 48 | } |
| 49 | _ => break, | |
| 58 | 50 | } |
| 51 | } | |
| 59 | 52 | |
| 60 | match current_token.as_str() { | |
| 61 | "var" => tokens.push(Token::Var), | |
| 62 | "let" => tokens.push(Token::Let), | |
| 63 | _ => tokens.push(Token::Identifier(current_token)), | |
| 53 | match current_token.as_str() { | |
| 54 | "var" => tokens.push(Token::Var), | |
| 55 | "let" => tokens.push(Token::Let), | |
| 56 | _ => tokens.push(Token::Identifier(current_token)), | |
| 57 | } | |
| 58 | } | |
| 59 | '0'..='9' => { | |
| 60 | let mut current_token = String::new(); | |
| 61 | current_token.push(c); | |
| 62 | loop { | |
| 63 | let c = chars.peek(); | |
| 64 | if c.is_none() { | |
| 65 | break; | |
| 64 | 66 | } |
| 65 | }, | |
| 66 | '0'..='9' => { | |
| 67 | let mut current_token = String::new(); | |
| 68 | current_token.push(c); | |
| 69 | loop { | |
| 70 | let c = chars.peek(); | |
| 71 | if c.is_none() { | |
| 72 | break; | |
| 73 | } | |
| 74 | let (_, c) = c.unwrap(); | |
| 75 | match c { | |
| 76 | '0'..='9' => { | |
| 77 | current_token.push(*c); | |
| 78 | chars.next(); | |
| 79 | }, | |
| 80 | _ => break, | |
| 67 | let (_, c) = c.unwrap(); | |
| 68 | match c { | |
| 69 | '0'..='9' => { | |
| 70 | current_token.push(*c); | |
| 71 | chars.next(); | |
| 81 | 72 | } |
| 73 | _ => break, | |
| 82 | 74 | } |
| 83 | tokens.push(Token::Number(current_token)); | |
| 84 | }, | |
| 85 | ':' => tokens.push(Token::Colon), | |
| 86 | '=' => tokens.push(Token::Equals), | |
| 87 | ';' => tokens.push(Token::Semicolon), | |
| 88 | '+' => tokens.push(Token::Operator(Operator::Add)), | |
| 89 | '-' => tokens.push(Token::Operator(Operator::Substract)), | |
| 90 | '*' => tokens.push(Token::Operator(Operator::Multiply)), | |
| 91 | '/' => tokens.push(Token::Operator(Operator::Divide)), | |
| 92 | s if s.is_whitespace() => (), | |
| 93 | _ => { | |
| 94 | return Err(UnknownCharacter { error: format!("Unsupported character: {}", c)}.into()) | |
| 95 | 75 | } |
| 76 | tokens.push(Token::Number(current_token)); | |
| 77 | } | |
| 78 | ':' => tokens.push(Token::Colon), | |
| 79 | '=' => tokens.push(Token::Equals), | |
| 80 | ';' => tokens.push(Token::Semicolon), | |
| 81 | '+' => tokens.push(Token::Operator(Operator::Add)), | |
| 82 | '-' => tokens.push(Token::Operator(Operator::Substract)), | |
| 83 | '*' => tokens.push(Token::Operator(Operator::Multiply)), | |
| 84 | '/' => tokens.push(Token::Operator(Operator::Divide)), | |
| 85 | s if s.is_whitespace() => (), | |
| 86 | _ => { | |
| 87 | return Err(UnknownCharacter { | |
| 88 | error: format!("Unsupported character: {}", c), | |
| 89 | } | |
| 90 | .into()) | |
| 96 | 91 | } |
| 97 | } | |
| 92 | }, | |
| 98 | 93 | None => break, |
| 99 | 94 | } |
| 100 | 95 | } |
src/transpile/util.rs+20-1| ... | ... | @@ -1,6 +1,6 @@ |
| 1 | 1 | // Rust why do you not have a peekable iterator in the standard library? |
| 2 | 2 | // Stolen from https://users.rust-lang.org/t/peekable-argument-there-has-to-be-a-simpler-way/6959 |
| 3 | pub trait PeekableIterator : std::iter::Iterator { | |
| 3 | pub trait PeekableIterator: std::iter::Iterator { | |
| 4 | 4 | fn peek(&mut self) -> Option<&Self::Item>; |
| 5 | 5 | } |
| 6 | 6 | |
| ... | ... | @@ -9,3 +9,22 @@ impl<I: std::iter::Iterator> PeekableIterator for std::iter::Peekable<I> { |
| 9 | 9 | std::iter::Peekable::peek(self) |
| 10 | 10 | } |
| 11 | 11 | } |
| 12 | ||
| 13 | #[derive(Debug, Clone)] | |
| 14 | pub enum Operator { | |
| 15 | Add, | |
| 16 | Substract, | |
| 17 | Multiply, | |
| 18 | Divide, | |
| 19 | } | |
| 20 | ||
| 21 | impl Operator { | |
| 22 | pub fn to_string(self) -> String { | |
| 23 | String::from(match self { | |
| 24 | Self::Add => "+", | |
| 25 | Self::Substract => "-", | |
| 26 | Self::Divide => "/", | |
| 27 | Self::Multiply => "*", | |
| 28 | }) | |
| 29 | } | |
| 30 | } |
src/transpile/walker.rs+17-11| ... | ... | @@ -3,18 +3,24 @@ use super::ast::{Ast, Type}; |
| 3 | 3 | fn transpile_helper(tree: Ast) -> String { |
| 4 | 4 | match tree { |
| 5 | 5 | Ast::Number(number) => format!("{}", number), |
| 6 | Ast::VariableDeclaration { name, typ, value } => { | |
| 7 | match typ { | |
| 8 | Type::Bigint => format!("let {} = BigInt({});\n", name, transpile_helper(*value)), | |
| 9 | _ => format!("let {} = {};\n", name, transpile_helper(*value)) | |
| 10 | } | |
| 6 | Ast::Operation { | |
| 7 | operator, | |
| 8 | left, | |
| 9 | right, | |
| 10 | } => format!( | |
| 11 | "{} {} {}", | |
| 12 | transpile_helper(*left), | |
| 13 | operator.to_string(), | |
| 14 | transpile_helper(*right) | |
| 15 | ), | |
| 16 | Ast::VariableDeclaration { name, typ, value } => match typ { | |
| 17 | Type::Bigint => format!("let {} = BigInt({});\n", name, transpile_helper(*value)), | |
| 18 | _ => format!("let {} = {};\n", name, transpile_helper(*value)), | |
| 19 | }, | |
| 20 | Ast::ConstantDeclaration { name, typ, value } => match typ { | |
| 21 | Type::Bigint => format!("const {} = BigInt({});\n", name, transpile_helper(*value)), | |
| 22 | _ => format!("const {} = {};\n", name, transpile_helper(*value)), | |
| 11 | 23 | }, |
| 12 | Ast::ConstantDeclaration { name, typ, value } => { | |
| 13 | match typ { | |
| 14 | Type::Bigint => format!("const {} = BigInt({});\n", name, transpile_helper(*value)), | |
| 15 | _ => format!("const {} = {};\n", name, transpile_helper(*value)) | |
| 16 | } | |
| 17 | } | |
| 18 | 24 | } |
| 19 | 25 | } |
| 20 | 26 |