| author | |
| committer | |
| log | c89bf26922f4448e6f0cb6015f4627792841deab |
| tree | de76df5e71a1116de15edc93e1fe53156978c38b |
| parent | 17c6ef0255b85120d2bdcae9f48ecd8c88c3c247 |
| signature | Commit is signed but in an unrecognized format. |
24 files changed, 549 insertions(+), 363 deletions(-)
Cargo.lock+11-11| ... | @@ -126,16 +126,6 @@ version = "1.0.0" | ... | @@ -126,16 +126,6 @@ version = "1.0.0" |
| 126 | source = "registry+https://github.com/rust-lang/crates.io-index" | 126 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 127 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" | 127 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" |
| 128 | 128 | ||
| 129 | [[package]] | ||
| 130 | name = "checker" | ||
| 131 | version = "0.1.0" | ||
| 132 | dependencies = [ | ||
| 133 | "ast", | ||
| 134 | "miette", | ||
| 135 | "num", | ||
| 136 | "thiserror", | ||
| 137 | ] | ||
| 138 | |||
| 139 | [[package]] | 129 | [[package]] |
| 140 | name = "clap" | 130 | name = "clap" |
| 141 | version = "4.4.14" | 131 | version = "4.4.14" |
| ... | @@ -181,12 +171,12 @@ name = "cli" | ... | @@ -181,12 +171,12 @@ name = "cli" |
| 181 | version = "0.1.0" | 171 | version = "0.1.0" |
| 182 | dependencies = [ | 172 | dependencies = [ |
| 183 | "ast", | 173 | "ast", |
| 184 | "checker", | ||
| 185 | "clap", | 174 | "clap", |
| 186 | "js_ast", | 175 | "js_ast", |
| 187 | "js_printer", | 176 | "js_printer", |
| 188 | "miette", | 177 | "miette", |
| 189 | "parser", | 178 | "parser", |
| 179 | "sema", | ||
| 190 | "thiserror", | 180 | "thiserror", |
| 191 | "tokenizer", | 181 | "tokenizer", |
| 192 | ] | 182 | ] |
| ... | @@ -466,6 +456,16 @@ dependencies = [ | ... | @@ -466,6 +456,16 @@ dependencies = [ |
| 466 | "windows-sys", | 456 | "windows-sys", |
| 467 | ] | 457 | ] |
| 468 | 458 | ||
| 459 | [[package]] | ||
| 460 | name = "sema" | ||
| 461 | version = "0.1.0" | ||
| 462 | dependencies = [ | ||
| 463 | "ast", | ||
| 464 | "miette", | ||
| 465 | "num", | ||
| 466 | "thiserror", | ||
| 467 | ] | ||
| 468 | |||
| 469 | [[package]] | 469 | [[package]] |
| 470 | name = "smawk" | 470 | name = "smawk" |
| 471 | version = "0.3.2" | 471 | version = "0.3.2" |
Cargo.toml+1-1| ... | @@ -10,7 +10,7 @@ repository = "https://github.com/lino-levan/blight" | ... | @@ -10,7 +10,7 @@ repository = "https://github.com/lino-levan/blight" |
| 10 | resolver = "2" | 10 | resolver = "2" |
| 11 | members = [ | 11 | members = [ |
| 12 | "crates/ast", | 12 | "crates/ast", |
| 13 | "crates/checker", | 13 | "crates/sema", |
| 14 | "crates/cli", | 14 | "crates/cli", |
| 15 | "crates/js_ast", | 15 | "crates/js_ast", |
| 16 | "crates/js_printer", | 16 | "crates/js_printer", |
crates/ast/ast.rs+38-3| ... | @@ -12,6 +12,7 @@ pub enum BuiltinId { | ... | @@ -12,6 +12,7 @@ pub enum BuiltinId { |
| 12 | 12 | ||
| 13 | #[derive(Debug, Clone)] | 13 | #[derive(Debug, Clone)] |
| 14 | pub struct Module { | 14 | pub struct Module { |
| 15 | pub comment: Option<DocComment>, | ||
| 15 | pub statements: Vec<TopLevelStatement>, | 16 | pub statements: Vec<TopLevelStatement>, |
| 16 | } | 17 | } |
| 17 | 18 | ||
| ... | @@ -31,7 +32,7 @@ pub struct Param { | ... | @@ -31,7 +32,7 @@ pub struct Param { |
| 31 | 32 | ||
| 32 | pub struct Decl { | 33 | pub struct Decl { |
| 33 | pub name: String, | 34 | pub name: String, |
| 34 | pub decl_type: Type, | 35 | pub data_type: Type, |
| 35 | pub init: Expr, | 36 | pub init: Expr, |
| 36 | pub is_constant: bool, | 37 | pub is_constant: bool, |
| 37 | pub is_pub: bool, | 38 | pub is_pub: bool, |
| ... | @@ -43,11 +44,12 @@ pub struct Decl { | ... | @@ -43,11 +44,12 @@ pub struct Decl { |
| 43 | pub struct Expr { | 44 | pub struct Expr { |
| 44 | pub data: ExprData, | 45 | pub data: ExprData, |
| 45 | pub loc: Loc, | 46 | pub loc: Loc, |
| 47 | pub comment: Option<Comment>, | ||
| 46 | } | 48 | } |
| 47 | 49 | ||
| 48 | impl Expr { | 50 | impl Expr { |
| 49 | pub fn new(data: ExprData, loc: Loc) -> Self { | 51 | pub fn new(data: ExprData, loc: Loc, comment: Option<Comment>) -> Self { |
| 50 | Self { data, loc } | 52 | Self { data, loc, comment } |
| 51 | } | 53 | } |
| 52 | } | 54 | } |
| 53 | 55 | ||
| ... | @@ -57,6 +59,35 @@ pub struct DocComment { | ... | @@ -57,6 +59,35 @@ pub struct DocComment { |
| 57 | pub loc: Loc, | 59 | pub loc: Loc, |
| 58 | } | 60 | } |
| 59 | 61 | ||
| 62 | #[derive(Debug, Clone)] | ||
| 63 | pub struct Comment { | ||
| 64 | pub data: String, | ||
| 65 | } | ||
| 66 | |||
| 67 | impl Comment { | ||
| 68 | pub fn format(&self) -> String { | ||
| 69 | let mut lines = self.data.lines(); | ||
| 70 | let first_line = lines.next().unwrap(); | ||
| 71 | let mut result = format!("// {}", first_line); | ||
| 72 | for line in lines { | ||
| 73 | result.push_str(&format!("\n// {}", line)); | ||
| 74 | } | ||
| 75 | result | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | impl DocComment { | ||
| 80 | pub fn format(&self) -> String { | ||
| 81 | let mut lines = self.data.lines(); | ||
| 82 | let first_line = lines.next().unwrap(); | ||
| 83 | let mut result = format!("/// {}", first_line); | ||
| 84 | for line in lines { | ||
| 85 | result.push_str(&format!("\n/// {}", line)); | ||
| 86 | } | ||
| 87 | result | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 60 | #[derive(Debug, Clone)] | 91 | #[derive(Debug, Clone)] |
| 61 | pub struct BlockWithLoc { | 92 | pub struct BlockWithLoc { |
| 62 | pub block: Block, | 93 | pub block: Block, |
| ... | @@ -110,12 +141,14 @@ pub enum ExprData { | ... | @@ -110,12 +141,14 @@ pub enum ExprData { |
| 110 | pub enum Statement { | 141 | pub enum Statement { |
| 111 | Expr(Expr), | 142 | Expr(Expr), |
| 112 | Decl(Decl), | 143 | Decl(Decl), |
| 144 | Comment(Comment), | ||
| 113 | } | 145 | } |
| 114 | 146 | ||
| 115 | #[derive(Debug, Clone)] | 147 | #[derive(Debug, Clone)] |
| 116 | pub enum TopLevelStatement { | 148 | pub enum TopLevelStatement { |
| 117 | Expr(Expr), | 149 | Expr(Expr), |
| 118 | Decl(Decl), | 150 | Decl(Decl), |
| 151 | Comment(Comment), | ||
| 119 | } | 152 | } |
| 120 | 153 | ||
| 121 | impl BuiltinId { | 154 | impl BuiltinId { |
| ... | @@ -149,6 +182,7 @@ impl Statement { | ... | @@ -149,6 +182,7 @@ impl Statement { |
| 149 | match self { | 182 | match self { |
| 150 | Statement::Expr(expr) => TopLevelStatement::Expr(expr), | 183 | Statement::Expr(expr) => TopLevelStatement::Expr(expr), |
| 151 | Statement::Decl(decl) => TopLevelStatement::Decl(decl), | 184 | Statement::Decl(decl) => TopLevelStatement::Decl(decl), |
| 185 | Statement::Comment(comment) => TopLevelStatement::Comment(comment), | ||
| 152 | } | 186 | } |
| 153 | } | 187 | } |
| 154 | } | 188 | } |
| ... | @@ -169,6 +203,7 @@ impl TopLevelStatement { | ... | @@ -169,6 +203,7 @@ impl TopLevelStatement { |
| 169 | match self { | 203 | match self { |
| 170 | TopLevelStatement::Expr(expr) => Statement::Expr(expr), | 204 | TopLevelStatement::Expr(expr) => Statement::Expr(expr), |
| 171 | TopLevelStatement::Decl(decl) => Statement::Decl(decl), | 205 | TopLevelStatement::Decl(decl) => Statement::Decl(decl), |
| 206 | TopLevelStatement::Comment(comment) => Statement::Comment(comment), | ||
| 172 | } | 207 | } |
| 173 | } | 208 | } |
| 174 | } | 209 | } |
crates/ast/operators.rs+34| ... | @@ -169,3 +169,37 @@ impl UnaryOperator { | ... | @@ -169,3 +169,37 @@ impl UnaryOperator { |
| 169 | } | 169 | } |
| 170 | } | 170 | } |
| 171 | } | 171 | } |
| 172 | |||
| 173 | /// Taken from from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table | ||
| 174 | #[derive(PartialEq, PartialOrd)] | ||
| 175 | pub enum BinaryOperatorLevel { | ||
| 176 | Exponentiation, // ** | ||
| 177 | Multiplicative, // *, /, % | ||
| 178 | Additive, // +, - | ||
| 179 | BitwiseShift, // <<, >>, >>> | ||
| 180 | Relational, // <, <=, >, >= | ||
| 181 | Equality, // ==, != | ||
| 182 | BitwiseAnd, // & | ||
| 183 | BitwiseXor, // ^ | ||
| 184 | BitwiseOr, // | | ||
| 185 | LogicalAnd, // && | ||
| 186 | LogicalOr, // ||, ?? | ||
| 187 | } | ||
| 188 | |||
| 189 | pub fn get_binary_op_level(token: &Token) -> Option<BinaryOperatorLevel> { | ||
| 190 | use BinaryOperatorLevel::*; | ||
| 191 | match token { | ||
| 192 | Token::StarStar => Some(Exponentiation), | ||
| 193 | Token::Star | Token::Slash | Token::Percent => Some(Multiplicative), | ||
| 194 | Token::Plus | Token::Minus => Some(Additive), | ||
| 195 | Token::LtLt | Token::GtGt | Token::GtGtGt => Some(BitwiseShift), | ||
| 196 | Token::Gt | Token::GtEqual | Token::Lt | Token::LtEqual => Some(Relational), | ||
| 197 | Token::EqualEqual | Token::BangEqual => Some(Equality), | ||
| 198 | Token::And => Some(BitwiseAnd), | ||
| 199 | Token::Caret => Some(BitwiseXor), | ||
| 200 | Token::Or => Some(BitwiseOr), | ||
| 201 | Token::AndAnd => Some(LogicalAnd), | ||
| 202 | Token::OrOr => Some(LogicalOr), | ||
| 203 | _ => None, | ||
| 204 | } | ||
| 205 | } |
crates/checker/Cargo.toml deleted-13| ... | @@ -1,13 +0,0 @@ | ||
| 1 | [package] | ||
| 2 | name = "checker" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | |||
| 6 | [lib] | ||
| 7 | |||
| 8 | [dependencies] | ||
| 9 | miette.workspace = true | ||
| 10 | thiserror.workspace = true | ||
| 11 | num.workspace = true | ||
| 12 | |||
| 13 | ast = { path = "../ast" } | ||
| \ No newline at end of file | |||
crates/checker/src/ast_type_coersion.rs deleted-54| ... | @@ -1,54 +0,0 @@ | ||
| 1 | // use ast::{BinaryOperator, ExprData, Type}; | ||
| 2 | |||
| 3 | // impl TypeCoerceable for ExprData { | ||
| 4 | // fn to_type(&self) -> Option<Type> { | ||
| 5 | // match self { | ||
| 6 | // ExprData::Block(block) => { | ||
| 7 | // if (block.has_implicit_return) { | ||
| 8 | // block.statements.last().unwrap().to_type() | ||
| 9 | // } else { | ||
| 10 | // Some(Type::Void) | ||
| 11 | // } | ||
| 12 | // } | ||
| 13 | // ExprData::Binary { op, left, right } => { | ||
| 14 | // // TODO: this likely has to do further type checking | ||
| 15 | // let left_type = left.to_type(); | ||
| 16 | // match op { | ||
| 17 | // BinaryOperator::Add => left_type, | ||
| 18 | // BinaryOperator::Sub => left_type, | ||
| 19 | // BinaryOperator::Mul => left_type, | ||
| 20 | // BinaryOperator::Div => left_type, | ||
| 21 | // BinaryOperator::Pow => left_type, | ||
| 22 | // BinaryOperator::Eq => Some(Type::Bool), | ||
| 23 | // BinaryOperator::Gt => Some(Type::Bool), | ||
| 24 | // BinaryOperator::Lt => Some(Type::Bool), | ||
| 25 | // BinaryOperator::Gte => Some(Type::Bool), | ||
| 26 | // BinaryOperator::Lte => Some(Type::Bool), | ||
| 27 | // BinaryOperator::Lshift => Some(Type::Int), | ||
| 28 | // BinaryOperator::Rshift => Some(Type::Int), | ||
| 29 | // BinaryOperator::BitAnd => Some(Type::Int), | ||
| 30 | // BinaryOperator::BitOr => Some(Type::Int), | ||
| 31 | // BinaryOperator::BitXor => Some(Type::Int), | ||
| 32 | // BinaryOperator::Mod => Some(Type::Int), | ||
| 33 | // BinaryOperator::NotEq => Some(Type::Int), | ||
| 34 | |||
| 35 | // BinaryOperator::And => Some(Type::Bool), | ||
| 36 | // BinaryOperator::Or => Some(Type::Bool), | ||
| 37 | // } | ||
| 38 | // } | ||
| 39 | // ExprData::Unary { op, value } => todo!(), | ||
| 40 | // ExprData::Function { | ||
| 41 | // name, | ||
| 42 | // params, | ||
| 43 | // return_type, | ||
| 44 | // block, | ||
| 45 | // } => Some(return_type), | ||
| 46 | // ExprData::Call { base, arguments } => None, // oh shit. yeah this trait isn't implementable | ||
| 47 | // ExprData::Builtin { id, arguments } => todo!(), | ||
| 48 | // ExprData::If(_) => todo!(), | ||
| 49 | // ExprData::Return(_) => todo!(), | ||
| 50 | // ExprData::Literal(_) => todo!(), | ||
| 51 | // ExprData::Identifier(_) => todo!(), | ||
| 52 | // } | ||
| 53 | // } | ||
| 54 | // } | ||
crates/checker/src/lib.rs deleted-96| ... | @@ -1,96 +0,0 @@ | ||
| 1 | // use ast::{ Module, Type}; | ||
| 2 | // use miette::Result; | ||
| 3 | |||
| 4 | // mod ast_type_coersion; | ||
| 5 | |||
| 6 | // pub fn check_module(block: &Module) -> Result<()> { | ||
| 7 | // for statement in &block.statements { | ||
| 8 | // check_expression(statement)?; | ||
| 9 | // } | ||
| 10 | |||
| 11 | // Ok(()) | ||
| 12 | // } | ||
| 13 | |||
| 14 | // pub fn resolve_type(expr: &Ast) -> Result<Type> { | ||
| 15 | // match expr { | ||
| 16 | // Ast::Number(value) => Ok(Type::NumberLiteral(value.clone())), | ||
| 17 | // Ast::Declaration { | ||
| 18 | // name, | ||
| 19 | // decl_type, | ||
| 20 | // value, | ||
| 21 | // is_constant, | ||
| 22 | // } => Ok(Type::NoReturn), | ||
| 23 | // Ast::Binary { | ||
| 24 | // operator, | ||
| 25 | // left, | ||
| 26 | // right, | ||
| 27 | // } => merge_types(resolve_type(left)?, resolve_type(right)?), | ||
| 28 | // Ast::Function { | ||
| 29 | // name, | ||
| 30 | // arguments, | ||
| 31 | // return_type, | ||
| 32 | // block, | ||
| 33 | // } => todo!(), | ||
| 34 | // Ast::FunctionCall { name, arguments } => todo!(), | ||
| 35 | // Ast::If { | ||
| 36 | // condition, | ||
| 37 | // value, | ||
| 38 | // else_value, | ||
| 39 | // } => todo!(), | ||
| 40 | // Ast::Return(_) => Ok(Type::NoReturn), | ||
| 41 | // Ast::Public(_) => todo!(), // TODO: remove Public | ||
| 42 | // Ast::Block(_) => todo!(), | ||
| 43 | // Ast::Bool(_) => Ok(Type::Bool), | ||
| 44 | // Ast::String(_) => Ok(Type::String), | ||
| 45 | // Ast::Identifier(_) => todo!(), | ||
| 46 | // Ast::BuiltIn(_) => todo!(), | ||
| 47 | |||
| 48 | // Ast::Comment(_) => unreachable!(), | ||
| 49 | // Ast::DocComment(_) => unreachable!(), | ||
| 50 | // } | ||
| 51 | // } | ||
| 52 | |||
| 53 | // pub fn check_expression(expr: &Ast) -> Result<()> { | ||
| 54 | // panic!() | ||
| 55 | // match expr { | ||
| 56 | // Ast::Declaration { | ||
| 57 | // name, | ||
| 58 | // decl_type: return_type, | ||
| 59 | // value, | ||
| 60 | // is_constant, | ||
| 61 | // } => { | ||
| 62 | // check_expression(value)?; | ||
| 63 | // // coerce_type(return_type.clone(), resolve_type(value)?)?; | ||
| 64 | // } | ||
| 65 | // _ => {} | ||
| 66 | // // Ast::Operation { | ||
| 67 | // // operator, | ||
| 68 | // // left, | ||
| 69 | // // right, | ||
| 70 | // // } => Ok(()), | ||
| 71 | // // Ast::Function { | ||
| 72 | // // name, | ||
| 73 | // // arguments, | ||
| 74 | // // return_type, | ||
| 75 | // // block, | ||
| 76 | // // } => Ok(()), | ||
| 77 | // // Ast::FunctionCall { name, arguments } => Ok(()), | ||
| 78 | // // Ast::If { | ||
| 79 | // // condition, | ||
| 80 | // // value, | ||
| 81 | // // else_value, | ||
| 82 | // // } => Ok(()), | ||
| 83 | // // Ast::Return(_) => Ok(()), | ||
| 84 | // // Ast::Public(_) => Ok(()), | ||
| 85 | // // Ast::Block(_) => Ok(()), | ||
| 86 | // // Ast::Number(_) => Ok(()), | ||
| 87 | // // Ast::Bool(_) => Ok(()), | ||
| 88 | // // Ast::String(_) => Ok(()), | ||
| 89 | // // Ast::Identifier(_) => Ok(()), | ||
| 90 | // // Ast::BuiltIn(_) => Ok(()), | ||
| 91 | // // Ast::Comment(_) => Ok(()), | ||
| 92 | // // Ast::DocComment(_) => Ok(()), | ||
| 93 | // } | ||
| 94 | |||
| 95 | // Ok(()) | ||
| 96 | // } | ||
crates/cli/Cargo.toml+1-1| ... | @@ -17,4 +17,4 @@ ast = { path = "../ast" } | ... | @@ -17,4 +17,4 @@ ast = { path = "../ast" } |
| 17 | parser = { path = "../parser" } | 17 | parser = { path = "../parser" } |
| 18 | js_ast = { path = "../js_ast" } | 18 | js_ast = { path = "../js_ast" } |
| 19 | js_printer = { path = "../js_printer" } | 19 | js_printer = { path = "../js_printer" } |
| 20 | checker = { path = "../checker" } | 20 | sema = { path = "../sema" } |
crates/cli/src/main.rs+1-1| ... | @@ -56,7 +56,7 @@ pub fn transpile(content: String) -> Result<String> { | ... | @@ -56,7 +56,7 @@ pub fn transpile(content: String) -> Result<String> { |
| 56 | // 2. Parse the tokens into an AST | 56 | // 2. Parse the tokens into an AST |
| 57 | let blight_ast = parser::parse(tokens)?; | 57 | let blight_ast = parser::parse(tokens)?; |
| 58 | // 3. Type check the AST | 58 | // 3. Type check the AST |
| 59 | // checker::check_module(&blight_ast)?; | 59 | // sema::check_module(&blight_ast)?; |
| 60 | // 4. Optimize the AST | 60 | // 4. Optimize the AST |
| 61 | // optimize(tree.clone())?; | 61 | // optimize(tree.clone())?; |
| 62 | // 5. Translate the AST into javascript | 62 | // 5. Translate the AST into javascript |
crates/js_ast/src/lib.rs+1| ... | @@ -180,6 +180,7 @@ fn translate_statement(ast: &Statement, ctx: &mut TranslateContext) { | ... | @@ -180,6 +180,7 @@ fn translate_statement(ast: &Statement, ctx: &mut TranslateContext) { |
| 180 | }); | 180 | }); |
| 181 | } | 181 | } |
| 182 | Statement::Expr(_) => todo!(), | 182 | Statement::Expr(_) => todo!(), |
| 183 | Statement::Comment(_) => todo!(), | ||
| 183 | // Ast::Function { | 184 | // Ast::Function { |
| 184 | // name, | 185 | // name, |
| 185 | // arguments, | 186 | // arguments, |
crates/parser/parse_declaration.rs+8-4| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | use ast::{Decl, Type}; | 1 | use ast::{Decl, DocComment, Type}; |
| 2 | use miette::Result; | 2 | use miette::Result; |
| 3 | use tokenizer::Token; | 3 | use tokenizer::Token; |
| 4 | 4 | ||
| ... | @@ -7,7 +7,11 @@ use crate::parse_type::parse_type; | ... | @@ -7,7 +7,11 @@ use crate::parse_type::parse_type; |
| 7 | use crate::Parser; | 7 | use crate::Parser; |
| 8 | 8 | ||
| 9 | /// Precondition: The first token exists and is either 'var' or 'let' | 9 | /// Precondition: The first token exists and is either 'var' or 'let' |
| 10 | pub fn parse_declaration(p: &mut Parser, is_pub: bool) -> Result<Decl> { | 10 | pub fn parse_declaration( |
| 11 | p: &mut Parser, | ||
| 12 | is_pub: bool, | ||
| 13 | comment: Option<DocComment>, | ||
| 14 | ) -> Result<Decl> { | ||
| 11 | let loc = p.loc(); | 15 | let loc = p.loc(); |
| 12 | let is_constant = match p.next() { | 16 | let is_constant = match p.next() { |
| 13 | Some(Token::Var) => false, | 17 | Some(Token::Var) => false, |
| ... | @@ -36,11 +40,11 @@ pub fn parse_declaration(p: &mut Parser, is_pub: bool) -> Result<Decl> { | ... | @@ -36,11 +40,11 @@ pub fn parse_declaration(p: &mut Parser, is_pub: bool) -> Result<Decl> { |
| 36 | }; | 40 | }; |
| 37 | Ok(Decl { | 41 | Ok(Decl { |
| 38 | name: name.clone(), | 42 | name: name.clone(), |
| 39 | decl_type: return_type, | 43 | data_type: return_type, |
| 40 | init, | 44 | init, |
| 41 | is_constant, | 45 | is_constant, |
| 42 | is_pub, | 46 | is_pub, |
| 43 | comment: None, | 47 | comment, |
| 44 | loc, | 48 | loc, |
| 45 | }) | 49 | }) |
| 46 | } | 50 | } |
crates/parser/parse_doc_comment.rs created+15| ... | @@ -0,0 +1,15 @@ | ||
| 1 | use ast::DocComment; | ||
| 2 | use tokenizer::Token; | ||
| 3 | |||
| 4 | use crate::Parser; | ||
| 5 | |||
| 6 | pub fn maybe_read_doc_comment(p: &mut Parser) -> Option<DocComment> { | ||
| 7 | match p.peek() { | ||
| 8 | Some(Token::DocComment(data)) => { | ||
| 9 | let loc = p.loc(); | ||
| 10 | p.next(); | ||
| 11 | Some(DocComment { data, loc }) | ||
| 12 | } | ||
| 13 | _ => None, | ||
| 14 | } | ||
| 15 | } | ||
crates/parser/parse_expr.rs+12-101| ... | @@ -1,45 +1,8 @@ | ... | @@ -1,45 +1,8 @@ |
| 1 | use ast::{BinaryOperator, Expr, ExprData, Literal}; | 1 | use ast::{get_binary_op_level, BinaryOperator, BinaryOperatorLevel, Expr, ExprData}; |
| 2 | use miette::Result; | 2 | use miette::Result; |
| 3 | use tokenizer::Token; | ||
| 4 | 3 | ||
| 5 | use crate::{ | 4 | use crate::parse_expr_basic::parse_expr_basic; |
| 6 | parse_call_arguments::parse_call_arguments, parse_expr_basic::parse_expr_basic, Parser, | 5 | use crate::Parser; |
| 7 | }; | ||
| 8 | |||
| 9 | /// Derived from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Operator_precedence#table | ||
| 10 | |||
| 11 | #[derive(PartialEq, PartialOrd)] | ||
| 12 | enum Level { | ||
| 13 | Prefix, // +, -, !, ~ | ||
| 14 | Exponentiation, // ** | ||
| 15 | Multiplicative, // *, /, % | ||
| 16 | Additive, // +, - | ||
| 17 | BitwiseShift, // <<, >>, >>> | ||
| 18 | Relational, // <, <=, >, >= | ||
| 19 | Equality, // ==, != | ||
| 20 | BitwiseAnd, // & | ||
| 21 | BitwiseXor, // ^ | ||
| 22 | BitwiseOr, // | | ||
| 23 | LogicalAnd, // && | ||
| 24 | LogicalOr, // ||, ?? | ||
| 25 | } | ||
| 26 | |||
| 27 | pub fn get_postfix_op_level(token: &Token) -> Option<Level> { | ||
| 28 | match token { | ||
| 29 | Token::StarStar => Some(Level::Exponentiation), | ||
| 30 | Token::Star | Token::Slash | Token::Percent => Some(Level::Multiplicative), | ||
| 31 | Token::Plus | Token::Minus => Some(Level::Additive), | ||
| 32 | Token::LtLt | Token::GtGt | Token::GtGtGt => Some(Level::BitwiseShift), | ||
| 33 | Token::Gt | Token::GtEqual | Token::Lt | Token::LtEqual => Some(Level::Relational), | ||
| 34 | Token::EqualEqual | Token::BangEqual => Some(Level::Equality), | ||
| 35 | Token::And => Some(Level::BitwiseAnd), | ||
| 36 | Token::Caret => Some(Level::BitwiseXor), | ||
| 37 | Token::Or => Some(Level::BitwiseOr), | ||
| 38 | Token::AndAnd => Some(Level::LogicalAnd), | ||
| 39 | Token::OrOr => Some(Level::LogicalOr), | ||
| 40 | _ => None, | ||
| 41 | } | ||
| 42 | } | ||
| 43 | 6 | ||
| 44 | pub fn parse_expr(p: &mut Parser) -> Result<Expr> { | 7 | pub fn parse_expr(p: &mut Parser) -> Result<Expr> { |
| 45 | let mut expr = parse_expr_basic(p)?; | 8 | let mut expr = parse_expr_basic(p)?; |
| ... | @@ -49,68 +12,10 @@ pub fn parse_expr(p: &mut Parser) -> Result<Expr> { | ... | @@ -49,68 +12,10 @@ pub fn parse_expr(p: &mut Parser) -> Result<Expr> { |
| 49 | return Ok(expr); | 12 | return Ok(expr); |
| 50 | } | 13 | } |
| 51 | 14 | ||
| 52 | // Parse function calls and field accesses | ||
| 53 | loop { | ||
| 54 | match p.peek() { | ||
| 55 | Some(Token::Dot) => { | ||
| 56 | let loc = p.loc(); | ||
| 57 | _ = p.next(); | ||
| 58 | let field = match p.peek() { | ||
| 59 | Some(Token::Identifier(name)) => { | ||
| 60 | let loc = p.loc(); | ||
| 61 | p.next(); | ||
| 62 | Expr::new(ExprData::Literal(Literal::String(name)), loc) | ||
| 63 | } | ||
| 64 | _ => { | ||
| 65 | return Err(p.error("Expected identifier after dot".to_string()).into()); | ||
| 66 | } | ||
| 67 | }; | ||
| 68 | expr = Expr::new( | ||
| 69 | ExprData::Field { | ||
| 70 | base: Box::new(expr), | ||
| 71 | field: Box::new(field), | ||
| 72 | }, | ||
| 73 | loc, | ||
| 74 | ); | ||
| 75 | } | ||
| 76 | Some(Token::OpenParen) => { | ||
| 77 | let loc = p.loc(); | ||
| 78 | _ = p.next(); | ||
| 79 | let arguments = parse_call_arguments(p)?; | ||
| 80 | expr = Expr::new( | ||
| 81 | ExprData::Call { | ||
| 82 | base: Box::new(expr), | ||
| 83 | arguments, | ||
| 84 | }, | ||
| 85 | loc, | ||
| 86 | ); | ||
| 87 | } | ||
| 88 | Some(Token::OpenBracket) => { | ||
| 89 | let loc = p.loc(); | ||
| 90 | _ = p.next(); | ||
| 91 | let index = parse_expr(p)?; | ||
| 92 | if let Some(Token::ClosedBracket) = p.next() { | ||
| 93 | expr = Expr::new( | ||
| 94 | ExprData::Field { | ||
| 95 | base: Box::new(expr), | ||
| 96 | field: Box::new(index), | ||
| 97 | }, | ||
| 98 | loc, | ||
| 99 | ); | ||
| 100 | } else { | ||
| 101 | return Err(p | ||
| 102 | .error("Expected closing bracket after index".to_string()) | ||
| 103 | .into()); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | _ => break, | ||
| 107 | } | ||
| 108 | } | ||
| 109 | |||
| 110 | // Parse operators | 15 | // Parse operators |
| 111 | loop { | 16 | loop { |
| 112 | if let Some(token) = p.peek() { | 17 | if let Some(token) = p.peek() { |
| 113 | if let Some(level) = get_postfix_op_level(&token) { | 18 | if let Some(level) = get_binary_op_level(&token) { |
| 114 | _ = p.next(); | 19 | _ = p.next(); |
| 115 | expr = parse_expr_binary( | 20 | expr = parse_expr_binary( |
| 116 | p, | 21 | p, |
| ... | @@ -128,13 +33,18 @@ pub fn parse_expr(p: &mut Parser) -> Result<Expr> { | ... | @@ -128,13 +33,18 @@ pub fn parse_expr(p: &mut Parser) -> Result<Expr> { |
| 128 | } | 33 | } |
| 129 | 34 | ||
| 130 | /// Prerequisite: `p.peek()` is an operator | 35 | /// Prerequisite: `p.peek()` is an operator |
| 131 | fn parse_expr_binary(p: &mut Parser, op: BinaryOperator, left: Expr, level: Level) -> Result<Expr> { | 36 | fn parse_expr_binary( |
| 37 | p: &mut Parser, | ||
| 38 | op: BinaryOperator, | ||
| 39 | left: Expr, | ||
| 40 | level: BinaryOperatorLevel, | ||
| 41 | ) -> Result<Expr> { | ||
| 132 | let loc = p.loc(); | 42 | let loc = p.loc(); |
| 133 | let mut right = parse_expr_basic(p)?; | 43 | let mut right = parse_expr_basic(p)?; |
| 134 | 44 | ||
| 135 | loop { | 45 | loop { |
| 136 | if let Some(token) = p.peek() { | 46 | if let Some(token) = p.peek() { |
| 137 | if let Some(next_level) = get_postfix_op_level(&token) { | 47 | if let Some(next_level) = get_binary_op_level(&token) { |
| 138 | if next_level <= level { | 48 | if next_level <= level { |
| 139 | _ = p.next(); | 49 | _ = p.next(); |
| 140 | right = parse_expr_binary( | 50 | right = parse_expr_binary( |
| ... | @@ -157,5 +67,6 @@ fn parse_expr_binary(p: &mut Parser, op: BinaryOperator, left: Expr, level: Leve | ... | @@ -157,5 +67,6 @@ fn parse_expr_binary(p: &mut Parser, op: BinaryOperator, left: Expr, level: Leve |
| 157 | right: Box::new(right), | 67 | right: Box::new(right), |
| 158 | }, | 68 | }, |
| 159 | loc, | 69 | loc, |
| 70 | None, | ||
| 160 | )) | 71 | )) |
| 161 | } | 72 | } |
crates/parser/parse_expr_basic.rs+90-20| ... | @@ -9,32 +9,35 @@ use crate::{ | ... | @@ -9,32 +9,35 @@ use crate::{ |
| 9 | 9 | ||
| 10 | pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { | 10 | pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { |
| 11 | let loc = p.loc(); | 11 | let loc = p.loc(); |
| 12 | match p.peek() { | 12 | let comments = p.take_comments(); |
| 13 | let mut expr = match p.peek() { | ||
| 13 | Some(Token::Number(number)) => { | 14 | Some(Token::Number(number)) => { |
| 14 | _ = p.next(); | 15 | _ = p.next(); |
| 15 | Ok(Expr::new( | 16 | Expr::new( |
| 16 | ExprData::Literal(Literal::Number(number.clone())), | 17 | ExprData::Literal(Literal::Number(number.clone())), |
| 17 | loc, | 18 | loc, |
| 18 | )) | 19 | comments, |
| 20 | ) | ||
| 19 | } | 21 | } |
| 20 | Some(Token::String(string)) => { | 22 | Some(Token::String(string)) => { |
| 21 | _ = p.next(); | 23 | _ = p.next(); |
| 22 | Ok(Expr::new( | 24 | Expr::new( |
| 23 | ExprData::Literal(Literal::String(string.clone())), | 25 | ExprData::Literal(Literal::String(string.clone())), |
| 24 | loc, | 26 | loc, |
| 25 | )) | 27 | comments, |
| 28 | ) | ||
| 26 | } | 29 | } |
| 27 | Some(Token::True) => { | 30 | Some(Token::True) => { |
| 28 | _ = p.next(); | 31 | _ = p.next(); |
| 29 | Ok(Expr::new(ExprData::Literal(Literal::Bool(true)), loc)) | 32 | Expr::new(ExprData::Literal(Literal::Bool(true)), loc, comments) |
| 30 | } | 33 | } |
| 31 | Some(Token::False) => { | 34 | Some(Token::False) => { |
| 32 | _ = p.next(); | 35 | _ = p.next(); |
| 33 | Ok(Expr::new(ExprData::Literal(Literal::Bool(false)), loc)) | 36 | Expr::new(ExprData::Literal(Literal::Bool(false)), loc, comments) |
| 34 | } | 37 | } |
| 35 | Some(Token::Void) => { | 38 | Some(Token::Void) => { |
| 36 | _ = p.next(); | 39 | _ = p.next(); |
| 37 | Ok(Expr::new(ExprData::Literal(Literal::Void), loc)) | 40 | Expr::new(ExprData::Literal(Literal::Void), loc, comments) |
| 38 | } | 41 | } |
| 39 | Some(Token::Identifier(name)) => { | 42 | Some(Token::Identifier(name)) => { |
| 40 | let loc = p.loc(); | 43 | let loc = p.loc(); |
| ... | @@ -43,7 +46,7 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { | ... | @@ -43,7 +46,7 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { |
| 43 | if let Some(id) = BuiltinId::from_str(name.as_str()) { | 46 | if let Some(id) = BuiltinId::from_str(name.as_str()) { |
| 44 | if let Some(Token::OpenParen) = p.next() { | 47 | if let Some(Token::OpenParen) = p.next() { |
| 45 | let arguments = parse_call_arguments(p)?; | 48 | let arguments = parse_call_arguments(p)?; |
| 46 | return Ok(Expr::new(ExprData::Builtin { id, arguments }, loc)); | 49 | Expr::new(ExprData::Builtin { id, arguments }, loc, comments) |
| 47 | } else { | 50 | } else { |
| 48 | return Err(p | 51 | return Err(p |
| 49 | .error_at(format!("Built-in function '{}' must be called", name), loc) | 52 | .error_at(format!("Built-in function '{}' must be called", name), loc) |
| ... | @@ -55,7 +58,7 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { | ... | @@ -55,7 +58,7 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { |
| 55 | .into()); | 58 | .into()); |
| 56 | } | 59 | } |
| 57 | } else { | 60 | } else { |
| 58 | Ok(Expr::new(ExprData::Identifier(name), loc)) | 61 | Expr::new(ExprData::Identifier(name), loc, comments) |
| 59 | } | 62 | } |
| 60 | } | 63 | } |
| 61 | 64 | ||
| ... | @@ -66,18 +69,21 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { | ... | @@ -66,18 +69,21 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { |
| 66 | .into()); | 69 | .into()); |
| 67 | } else { | 70 | } else { |
| 68 | _ = p.next(); | 71 | _ = p.next(); |
| 69 | 72 | return Ok(Expr::new( | |
| 70 | return Ok(Expr::new(ExprData::Return(Box::new(parse_expr(p)?)), loc)); | 73 | ExprData::Return(Box::new(parse_expr(p)?)), |
| 74 | loc, | ||
| 75 | comments, | ||
| 76 | )); | ||
| 71 | } | 77 | } |
| 72 | } | 78 | } |
| 73 | 79 | ||
| 74 | Some(Token::If) => Ok(Expr::new(ExprData::If(parse_expr_if(p)?), loc)), | 80 | Some(Token::If) => Expr::new(ExprData::If(parse_expr_if(p)?), loc, comments), |
| 75 | 81 | ||
| 76 | Some(Token::OpenBrace) => Ok(Expr::new(ExprData::Block(parse_block(p)?), loc)), | 82 | Some(Token::OpenBrace) => Expr::new(ExprData::Block(parse_block(p)?), loc, comments), |
| 77 | 83 | ||
| 78 | Some(Token::OpenParen) => { | 84 | Some(Token::OpenParen) => { |
| 79 | _ = p.next(); | 85 | _ = p.next(); |
| 80 | let expr = parse_expr_basic(p)?; | 86 | let expr = parse_expr(p)?; |
| 81 | match p.next() { | 87 | match p.next() { |
| 82 | Some(Token::ClosedParen) => (), | 88 | Some(Token::ClosedParen) => (), |
| 83 | Some(token) => { | 89 | Some(token) => { |
| ... | @@ -85,12 +91,12 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { | ... | @@ -85,12 +91,12 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { |
| 85 | } | 91 | } |
| 86 | None => return Err(p.error("Unexpected end of file".to_string()).into()), | 92 | None => return Err(p.error("Unexpected end of file".to_string()).into()), |
| 87 | } | 93 | } |
| 88 | Ok(expr) | 94 | expr |
| 89 | } | 95 | } |
| 90 | 96 | ||
| 91 | Some(t @ Token::Bang) | Some(t @ Token::Minus) | Some(t @ Token::Tilde) => { | 97 | Some(t @ Token::Bang) | Some(t @ Token::Minus) | Some(t @ Token::Tilde) => { |
| 92 | _ = p.next(); | 98 | _ = p.next(); |
| 93 | Ok(Expr::new( | 99 | Expr::new( |
| 94 | ExprData::Unary { | 100 | ExprData::Unary { |
| 95 | op: match t { | 101 | op: match t { |
| 96 | Token::Bang => UnaryOperator::Not, | 102 | Token::Bang => UnaryOperator::Not, |
| ... | @@ -101,10 +107,74 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { | ... | @@ -101,10 +107,74 @@ pub fn parse_expr_basic(p: &mut Parser) -> Result<Expr> { |
| 101 | value: Box::new(parse_expr_basic(p)?), | 107 | value: Box::new(parse_expr_basic(p)?), |
| 102 | }, | 108 | }, |
| 103 | loc, | 109 | loc, |
| 104 | )) | 110 | comments, |
| 111 | ) | ||
| 105 | } | 112 | } |
| 106 | 113 | ||
| 107 | Some(t) => Err(p.error(format!("Unexpected '{}'", t)).into()), | 114 | Some(t) => return Err(p.error(format!("Unexpected '{}'", t)).into()), |
| 108 | None => Err(p.error("Unexpected end of file".to_string()).into()), | 115 | None => return Err(p.error("Unexpected end of file".to_string()).into()), |
| 116 | }; | ||
| 117 | |||
| 118 | // Parse function calls and field accesses | ||
| 119 | loop { | ||
| 120 | match p.peek() { | ||
| 121 | Some(Token::Dot) => { | ||
| 122 | let loc = p.loc(); | ||
| 123 | _ = p.next(); | ||
| 124 | let field = match p.peek() { | ||
| 125 | Some(Token::Identifier(name)) => { | ||
| 126 | let loc = p.loc(); | ||
| 127 | p.next(); | ||
| 128 | Expr::new(ExprData::Literal(Literal::String(name)), loc, None) | ||
| 129 | } | ||
| 130 | _ => { | ||
| 131 | return Err(p.error("Expected identifier after dot".to_string()).into()); | ||
| 132 | } | ||
| 133 | }; | ||
| 134 | expr = Expr::new( | ||
| 135 | ExprData::Field { | ||
| 136 | base: Box::new(expr), | ||
| 137 | field: Box::new(field), | ||
| 138 | }, | ||
| 139 | loc, | ||
| 140 | None, | ||
| 141 | ); | ||
| 142 | } | ||
| 143 | Some(Token::OpenParen) => { | ||
| 144 | let loc = p.loc(); | ||
| 145 | _ = p.next(); | ||
| 146 | let arguments = parse_call_arguments(p)?; | ||
| 147 | expr = Expr::new( | ||
| 148 | ExprData::Call { | ||
| 149 | base: Box::new(expr), | ||
| 150 | arguments, | ||
| 151 | }, | ||
| 152 | loc, | ||
| 153 | None, | ||
| 154 | ); | ||
| 155 | } | ||
| 156 | Some(Token::OpenBracket) => { | ||
| 157 | let loc = p.loc(); | ||
| 158 | _ = p.next(); | ||
| 159 | let index = parse_expr(p)?; | ||
| 160 | if let Some(Token::ClosedBracket) = p.next() { | ||
| 161 | expr = Expr::new( | ||
| 162 | ExprData::Field { | ||
| 163 | base: Box::new(expr), | ||
| 164 | field: Box::new(index), | ||
| 165 | }, | ||
| 166 | loc, | ||
| 167 | None, | ||
| 168 | ); | ||
| 169 | } else { | ||
| 170 | return Err(p | ||
| 171 | .error("Expected closing bracket after index".to_string()) | ||
| 172 | .into()); | ||
| 173 | } | ||
| 174 | } | ||
| 175 | _ => break, | ||
| 176 | } | ||
| 109 | } | 177 | } |
| 178 | |||
| 179 | Ok(expr) | ||
| 110 | } | 180 | } |
crates/parser/parse_expr_if.rs+3-1| ... | @@ -2,7 +2,9 @@ use ast::{BlockWithLoc, If}; | ... | @@ -2,7 +2,9 @@ use ast::{BlockWithLoc, If}; |
| 2 | use miette::Result; | 2 | use miette::Result; |
| 3 | use tokenizer::Token; | 3 | use tokenizer::Token; |
| 4 | 4 | ||
| 5 | use crate::{parse_block::parse_block, parse_expr::parse_expr, Parser}; | 5 | use crate::parse_block::parse_block; |
| 6 | use crate::parse_expr::parse_expr; | ||
| 7 | use crate::Parser; | ||
| 6 | 8 | ||
| 7 | /// Precondition: the first token exists and is 'if' | 9 | /// Precondition: the first token exists and is 'if' |
| 8 | pub fn parse_expr_if(p: &mut Parser) -> Result<If> { | 10 | pub fn parse_expr_if(p: &mut Parser) -> Result<If> { |
crates/parser/parse_fn_declaration.rs+9-5| ... | @@ -1,4 +1,4 @@ | ... | @@ -1,4 +1,4 @@ |
| 1 | use ast::{Decl, Expr, ExprData, Type}; | 1 | use ast::{Decl, DocComment, Expr, ExprData, Type}; |
| 2 | use miette::Result; | 2 | use miette::Result; |
| 3 | use tokenizer::Token; | 3 | use tokenizer::Token; |
| 4 | 4 | ||
| ... | @@ -8,7 +8,11 @@ use crate::parse_type::parse_type; | ... | @@ -8,7 +8,11 @@ use crate::parse_type::parse_type; |
| 8 | use crate::Parser; | 8 | use crate::Parser; |
| 9 | 9 | ||
| 10 | /// Precondition: | 10 | /// Precondition: |
| 11 | pub fn parse_fn_declaration(p: &mut Parser, is_pub: bool) -> Result<Decl> { | 11 | pub fn parse_fn_declaration( |
| 12 | p: &mut Parser, | ||
| 13 | is_pub: bool, | ||
| 14 | comment: Option<DocComment>, | ||
| 15 | ) -> Result<Decl> { | ||
| 12 | let loc = p.loc(); | 16 | let loc = p.loc(); |
| 13 | match p.next() { | 17 | match p.next() { |
| 14 | Some(Token::Fn) => (), | 18 | Some(Token::Fn) => (), |
| ... | @@ -96,11 +100,11 @@ pub fn parse_fn_declaration(p: &mut Parser, is_pub: bool) -> Result<Decl> { | ... | @@ -96,11 +100,11 @@ pub fn parse_fn_declaration(p: &mut Parser, is_pub: bool) -> Result<Decl> { |
| 96 | }; | 100 | }; |
| 97 | Ok(Decl { | 101 | Ok(Decl { |
| 98 | name, | 102 | name, |
| 99 | decl_type: Type::Any, | 103 | data_type: Type::Any, |
| 100 | init: Expr::new(function_body, block_loc), | 104 | init: Expr::new(function_body, block_loc, None), |
| 101 | is_constant: true, | 105 | is_constant: true, |
| 102 | is_pub, | 106 | is_pub, |
| 103 | comment: None, | 107 | comment, |
| 104 | loc: loc, | 108 | loc: loc, |
| 105 | }) | 109 | }) |
| 106 | } else { | 110 | } else { |
crates/parser/parse_module.rs+16-9| ... | @@ -1,23 +1,27 @@ | ... | @@ -1,23 +1,27 @@ |
| 1 | use crate::{parse_top_level_statement::parse_top_level_statement, Parser}; | 1 | use crate::parse_doc_comment::maybe_read_doc_comment; |
| 2 | use crate::parse_top_level_statement::parse_top_level_statement; | ||
| 3 | use crate::Parser; | ||
| 2 | use ast::Module; | 4 | use ast::Module; |
| 3 | use miette::Result; | 5 | use miette::Result; |
| 4 | use tokenizer::Token; | 6 | use tokenizer::Token; |
| 5 | 7 | ||
| 6 | pub fn parse_module(tokens: &mut Parser) -> Result<Module> { | 8 | pub fn parse_module(p: &mut Parser) -> Result<Module> { |
| 7 | let mut statements = Vec::new(); | 9 | let mut statements = Vec::new(); |
| 8 | 10 | ||
| 11 | let file_doc_comment = maybe_read_doc_comment(p); | ||
| 12 | |||
| 9 | loop { | 13 | loop { |
| 10 | let statement = parse_top_level_statement(tokens)?; | 14 | let statement = parse_top_level_statement(p)?; |
| 11 | 15 | ||
| 12 | match tokens.peek() { | 16 | match p.peek() { |
| 13 | Some(Token::Semicolon) => { | 17 | Some(Token::Semicolon) => { |
| 14 | while let Some(Token::Semicolon) = tokens.peek() { | 18 | while let Some(Token::Semicolon) = p.peek() { |
| 15 | tokens.next(); | 19 | p.next(); |
| 16 | } | 20 | } |
| 17 | } | 21 | } |
| 18 | _ => { | 22 | _ => { |
| 19 | if statement.requires_semicolon() { | 23 | if statement.requires_semicolon() { |
| 20 | return Err(tokens | 24 | return Err(p |
| 21 | .error("Expected semicolon after statement".to_string()) | 25 | .error("Expected semicolon after statement".to_string()) |
| 22 | .into()); | 26 | .into()); |
| 23 | } | 27 | } |
| ... | @@ -26,8 +30,11 @@ pub fn parse_module(tokens: &mut Parser) -> Result<Module> { | ... | @@ -26,8 +30,11 @@ pub fn parse_module(tokens: &mut Parser) -> Result<Module> { |
| 26 | 30 | ||
| 27 | statements.push(statement); | 31 | statements.push(statement); |
| 28 | 32 | ||
| 29 | if tokens.empty() { | 33 | if p.empty() { |
| 30 | return Ok(Module { statements }); | 34 | return Ok(Module { |
| 35 | statements, | ||
| 36 | comment: file_doc_comment, | ||
| 37 | }); | ||
| 31 | } | 38 | } |
| 32 | } | 39 | } |
| 33 | } | 40 | } |
crates/parser/parse_statement.rs+39-5| ... | @@ -3,26 +3,60 @@ use miette::Result; | ... | @@ -3,26 +3,60 @@ use miette::Result; |
| 3 | use tokenizer::Token; | 3 | use tokenizer::Token; |
| 4 | 4 | ||
| 5 | use crate::parse_declaration::parse_declaration; | 5 | use crate::parse_declaration::parse_declaration; |
| 6 | use crate::parse_doc_comment::maybe_read_doc_comment; | ||
| 6 | use crate::parse_expr::parse_expr; | 7 | use crate::parse_expr::parse_expr; |
| 7 | use crate::parse_fn_declaration::parse_fn_declaration; | 8 | use crate::parse_fn_declaration::parse_fn_declaration; |
| 8 | use crate::Parser; | 9 | use crate::Parser; |
| 9 | 10 | ||
| 10 | pub fn parse_statement(p: &mut Parser) -> Result<Statement> { | 11 | pub fn parse_statement(p: &mut Parser) -> Result<Statement> { |
| 12 | if let Some(comments) = p.take_comments() { | ||
| 13 | return Ok(Statement::Comment(comments)); | ||
| 14 | } | ||
| 15 | |||
| 16 | let m_doc_comment = maybe_read_doc_comment(p); | ||
| 17 | if let Some(doc_comment) = m_doc_comment.clone() { | ||
| 18 | if p.has_comments() { | ||
| 19 | // this implies that comments are immediately after the doc | ||
| 20 | return Err(p | ||
| 21 | .error_at(format!("Unattached doc comment"), doc_comment.loc) | ||
| 22 | .into()); | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 11 | match p.peek() { | 26 | match p.peek() { |
| 27 | Some(Token::DocComment(_)) => unreachable!(), | ||
| 12 | Some(Token::Pub) => { | 28 | Some(Token::Pub) => { |
| 13 | _ = p.next(); | 29 | _ = p.next(); |
| 14 | match p.peek() { | 30 | match p.peek() { |
| 15 | Some(Token::Var) | Some(Token::Let) => { | 31 | Some(Token::Var) | Some(Token::Let) => { |
| 16 | Ok(Statement::Decl(parse_declaration(p, true)?)) | 32 | Ok(Statement::Decl(parse_declaration(p, true, m_doc_comment)?)) |
| 17 | } | 33 | } |
| 18 | Some(Token::Fn) => Ok(Statement::Decl(parse_fn_declaration(p, true)?)), | 34 | Some(Token::Fn) => Ok(Statement::Decl(parse_fn_declaration( |
| 35 | p, | ||
| 36 | true, | ||
| 37 | m_doc_comment, | ||
| 38 | )?)), | ||
| 19 | Some(_) => Err(p.error("Expected a declaration".to_string()).into()), | 39 | Some(_) => Err(p.error("Expected a declaration".to_string()).into()), |
| 20 | None => Err(p.error("Unexpected end of file".to_string()).into()), | 40 | None => Err(p.error("Unexpected end of file".to_string()).into()), |
| 21 | } | 41 | } |
| 22 | } | 42 | } |
| 23 | Some(Token::Var) | Some(Token::Let) => Ok(Statement::Decl(parse_declaration(p, false)?)), | 43 | Some(Token::Var) | Some(Token::Let) => { |
| 24 | Some(Token::Fn) => Ok(Statement::Decl(parse_fn_declaration(p, false)?)), | 44 | Ok(Statement::Decl(parse_declaration(p, false, m_doc_comment)?)) |
| 25 | Some(_) => Ok(Statement::Expr(parse_expr(p)?)), | 45 | } |
| 46 | Some(Token::Fn) => Ok(Statement::Decl(parse_fn_declaration( | ||
| 47 | p, | ||
| 48 | false, | ||
| 49 | m_doc_comment, | ||
| 50 | )?)), | ||
| 51 | Some(_) => { | ||
| 52 | if let Some(doc_comment) = m_doc_comment { | ||
| 53 | Err(p | ||
| 54 | .error_at(format!("Unattached doc comment"), doc_comment.loc) | ||
| 55 | .into()) | ||
| 56 | } else { | ||
| 57 | Ok(Statement::Expr(parse_expr(p)?)) | ||
| 58 | } | ||
| 59 | } | ||
| 26 | None => Err(p.error("Unexpected end of file".to_string()).into()), | 60 | None => Err(p.error("Unexpected end of file".to_string()).into()), |
| 27 | } | 61 | } |
| 28 | } | 62 | } |
crates/parser/parser.rs+56-16| ... | @@ -1,6 +1,7 @@ | ... | @@ -1,6 +1,7 @@ |
| 1 | mod parse_block; | 1 | mod parse_block; |
| 2 | mod parse_call_arguments; | 2 | mod parse_call_arguments; |
| 3 | mod parse_declaration; | 3 | mod parse_declaration; |
| 4 | mod parse_doc_comment; | ||
| 4 | mod parse_expr; | 5 | mod parse_expr; |
| 5 | mod parse_expr_basic; | 6 | mod parse_expr_basic; |
| 6 | mod parse_expr_if; | 7 | mod parse_expr_if; |
| ... | @@ -11,15 +12,14 @@ mod parse_statement; | ... | @@ -11,15 +12,14 @@ mod parse_statement; |
| 11 | mod parse_top_level_statement; | 12 | mod parse_top_level_statement; |
| 12 | mod parse_type; | 13 | mod parse_type; |
| 13 | 14 | ||
| 14 | use tokenizer::{Loc, Token}; | 15 | use ast::Comment; |
| 15 | |||
| 16 | use miette::Diagnostic; | ||
| 17 | use thiserror::Error; | ||
| 18 | |||
| 19 | use ast::Module; | 16 | use ast::Module; |
| 17 | use miette::Diagnostic; | ||
| 20 | use miette::Result; | 18 | use miette::Result; |
| 21 | use parse_module::parse_module; | 19 | use parse_module::parse_module; |
| 20 | use thiserror::Error; | ||
| 22 | use tokenizer::tokenize; | 21 | use tokenizer::tokenize; |
| 22 | use tokenizer::{Loc, Token}; | ||
| 23 | 23 | ||
| 24 | pub fn parse(tokens: Vec<Token>) -> Result<Module> { | 24 | pub fn parse(tokens: Vec<Token>) -> Result<Module> { |
| 25 | let mut p = Parser::new(tokens); | 25 | let mut p = Parser::new(tokens); |
| ... | @@ -47,11 +47,15 @@ impl ParseError { | ... | @@ -47,11 +47,15 @@ impl ParseError { |
| 47 | 47 | ||
| 48 | #[derive(Debug)] | 48 | #[derive(Debug)] |
| 49 | pub struct Parser { | 49 | pub struct Parser { |
| 50 | pub tokens: Vec<Token>, | 50 | /// TODO(@paperdave): switch to streaming tokenizer + one that can |
| 51 | pub cursor: usize, | 51 | /// report line and column info |
| 52 | tokens: Vec<Token>, | ||
| 53 | cursor: usize, | ||
| 52 | 54 | ||
| 53 | /// Whether or not the parser is at the top level | 55 | /// Whether or not the parser is at the top level |
| 54 | pub is_top_level: bool, | 56 | pub is_top_level: bool, |
| 57 | |||
| 58 | comments: Vec<String>, | ||
| 55 | } | 59 | } |
| 56 | 60 | ||
| 57 | impl Parser { | 61 | impl Parser { |
| ... | @@ -60,6 +64,7 @@ impl Parser { | ... | @@ -60,6 +64,7 @@ impl Parser { |
| 60 | tokens, | 64 | tokens, |
| 61 | cursor: 0, | 65 | cursor: 0, |
| 62 | is_top_level: true, | 66 | is_top_level: true, |
| 67 | comments: vec![], | ||
| 63 | } | 68 | } |
| 64 | } | 69 | } |
| 65 | 70 | ||
| ... | @@ -67,26 +72,61 @@ impl Parser { | ... | @@ -67,26 +72,61 @@ impl Parser { |
| 67 | self.cursor == self.tokens.len() | 72 | self.cursor == self.tokens.len() |
| 68 | } | 73 | } |
| 69 | 74 | ||
| 70 | pub fn back(&mut self) { | 75 | // TODO(@paperdave): fix parser token stuff. its very messy rn |
| 71 | self.cursor -= 1; | ||
| 72 | } | ||
| 73 | 76 | ||
| 74 | pub fn next(&mut self) -> Option<Token> { | 77 | pub fn next(&mut self) -> Option<Token> { |
| 75 | if self.cursor == self.tokens.len() { | 78 | if self.cursor == self.tokens.len() { |
| 76 | return None; | 79 | return None; |
| 77 | } | 80 | } |
| 78 | 81 | ||
| 79 | let token = self.tokens[self.cursor].clone(); | 82 | loop { |
| 80 | self.cursor += 1; | 83 | match self.tokens[self.cursor].clone() { |
| 81 | Some(token) | 84 | Token::Comment(comment) => { |
| 85 | self.comments.push(comment); | ||
| 86 | self.cursor += 1; | ||
| 87 | continue; | ||
| 88 | } | ||
| 89 | token => { | ||
| 90 | self.cursor += 1; | ||
| 91 | return Some(token); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 82 | } | 95 | } |
| 83 | 96 | ||
| 84 | pub fn peek(&self) -> Option<Token> { | 97 | pub fn take_comments(&mut self) -> Option<Comment> { |
| 85 | if self.cursor == self.tokens.len() { | 98 | if self.comments.len() == 0 { |
| 86 | return None; | 99 | return None; |
| 87 | } | 100 | } |
| 101 | let comments = self.comments.clone(); | ||
| 102 | self.comments = vec![]; | ||
| 103 | Some(Comment { | ||
| 104 | data: comments.join("\n"), | ||
| 105 | }) | ||
| 106 | } | ||
| 107 | |||
| 108 | pub fn has_comments(&mut self) -> bool { | ||
| 109 | self.peek(); | ||
| 110 | self.comments.len() > 0 | ||
| 111 | } | ||
| 88 | 112 | ||
| 89 | Some(self.tokens[self.cursor].clone()) | 113 | pub fn peek(&mut self) -> Option<Token> { |
| 114 | loop { | ||
| 115 | if self.cursor == self.tokens.len() { | ||
| 116 | return None; | ||
| 117 | } | ||
| 118 | |||
| 119 | match self.tokens[self.cursor].clone() { | ||
| 120 | Token::Comment(comment) => { | ||
| 121 | self.comments.push(comment); | ||
| 122 | self.cursor += 1; | ||
| 123 | continue; | ||
| 124 | } | ||
| 125 | token => { | ||
| 126 | return Some(token); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 90 | } | 130 | } |
| 91 | 131 | ||
| 92 | pub fn error(&self, error: String) -> ParseError { | 132 | pub fn error(&self, error: String) -> ParseError { |
crates/sema/Cargo.toml created+13| ... | @@ -0,0 +1,13 @@ | ||
| 1 | [package] | ||
| 2 | name = "sema" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | |||
| 6 | [lib] | ||
| 7 | |||
| 8 | [dependencies] | ||
| 9 | miette.workspace = true | ||
| 10 | thiserror.workspace = true | ||
| 11 | num.workspace = true | ||
| 12 | |||
| 13 | ast = { path = "../ast" } | ||
| \ No newline at end of file | |||
crates/sema/src/ast_type_coersion.rs created+54| ... | @@ -0,0 +1,54 @@ | ||
| 1 | // use ast::{BinaryOperator, ExprData, Type}; | ||
| 2 | |||
| 3 | // impl TypeCoerceable for ExprData { | ||
| 4 | // fn to_type(&self) -> Option<Type> { | ||
| 5 | // match self { | ||
| 6 | // ExprData::Block(block) => { | ||
| 7 | // if (block.has_implicit_return) { | ||
| 8 | // block.statements.last().unwrap().to_type() | ||
| 9 | // } else { | ||
| 10 | // Some(Type::Void) | ||
| 11 | // } | ||
| 12 | // } | ||
| 13 | // ExprData::Binary { op, left, right } => { | ||
| 14 | // // TODO: this likely has to do further type checking | ||
| 15 | // let left_type = left.to_type(); | ||
| 16 | // match op { | ||
| 17 | // BinaryOperator::Add => left_type, | ||
| 18 | // BinaryOperator::Sub => left_type, | ||
| 19 | // BinaryOperator::Mul => left_type, | ||
| 20 | // BinaryOperator::Div => left_type, | ||
| 21 | // BinaryOperator::Pow => left_type, | ||
| 22 | // BinaryOperator::Eq => Some(Type::Bool), | ||
| 23 | // BinaryOperator::Gt => Some(Type::Bool), | ||
| 24 | // BinaryOperator::Lt => Some(Type::Bool), | ||
| 25 | // BinaryOperator::Gte => Some(Type::Bool), | ||
| 26 | // BinaryOperator::Lte => Some(Type::Bool), | ||
| 27 | // BinaryOperator::Lshift => Some(Type::Int), | ||
| 28 | // BinaryOperator::Rshift => Some(Type::Int), | ||
| 29 | // BinaryOperator::BitAnd => Some(Type::Int), | ||
| 30 | // BinaryOperator::BitOr => Some(Type::Int), | ||
| 31 | // BinaryOperator::BitXor => Some(Type::Int), | ||
| 32 | // BinaryOperator::Mod => Some(Type::Int), | ||
| 33 | // BinaryOperator::NotEq => Some(Type::Int), | ||
| 34 | |||
| 35 | // BinaryOperator::And => Some(Type::Bool), | ||
| 36 | // BinaryOperator::Or => Some(Type::Bool), | ||
| 37 | // } | ||
| 38 | // } | ||
| 39 | // ExprData::Unary { op, value } => todo!(), | ||
| 40 | // ExprData::Function { | ||
| 41 | // name, | ||
| 42 | // params, | ||
| 43 | // return_type, | ||
| 44 | // block, | ||
| 45 | // } => Some(return_type), | ||
| 46 | // ExprData::Call { base, arguments } => None, // oh shit. yeah this trait isn't implementable | ||
| 47 | // ExprData::Builtin { id, arguments } => todo!(), | ||
| 48 | // ExprData::If(_) => todo!(), | ||
| 49 | // ExprData::Return(_) => todo!(), | ||
| 50 | // ExprData::Literal(_) => todo!(), | ||
| 51 | // ExprData::Identifier(_) => todo!(), | ||
| 52 | // } | ||
| 53 | // } | ||
| 54 | // } | ||
crates/sema/src/lib.rs created+96| ... | @@ -0,0 +1,96 @@ | ||
| 1 | // use ast::{ Module, Type}; | ||
| 2 | // use miette::Result; | ||
| 3 | |||
| 4 | // mod ast_type_coersion; | ||
| 5 | |||
| 6 | // pub fn check_module(block: &Module) -> Result<()> { | ||
| 7 | // for statement in &block.statements { | ||
| 8 | // check_expression(statement)?; | ||
| 9 | // } | ||
| 10 | |||
| 11 | // Ok(()) | ||
| 12 | // } | ||
| 13 | |||
| 14 | // pub fn resolve_type(expr: &Ast) -> Result<Type> { | ||
| 15 | // match expr { | ||
| 16 | // Ast::Number(value) => Ok(Type::NumberLiteral(value.clone())), | ||
| 17 | // Ast::Declaration { | ||
| 18 | // name, | ||
| 19 | // decl_type, | ||
| 20 | // value, | ||
| 21 | // is_constant, | ||
| 22 | // } => Ok(Type::NoReturn), | ||
| 23 | // Ast::Binary { | ||
| 24 | // operator, | ||
| 25 | // left, | ||
| 26 | // right, | ||
| 27 | // } => merge_types(resolve_type(left)?, resolve_type(right)?), | ||
| 28 | // Ast::Function { | ||
| 29 | // name, | ||
| 30 | // arguments, | ||
| 31 | // return_type, | ||
| 32 | // block, | ||
| 33 | // } => todo!(), | ||
| 34 | // Ast::FunctionCall { name, arguments } => todo!(), | ||
| 35 | // Ast::If { | ||
| 36 | // condition, | ||
| 37 | // value, | ||
| 38 | // else_value, | ||
| 39 | // } => todo!(), | ||
| 40 | // Ast::Return(_) => Ok(Type::NoReturn), | ||
| 41 | // Ast::Public(_) => todo!(), // TODO: remove Public | ||
| 42 | // Ast::Block(_) => todo!(), | ||
| 43 | // Ast::Bool(_) => Ok(Type::Bool), | ||
| 44 | // Ast::String(_) => Ok(Type::String), | ||
| 45 | // Ast::Identifier(_) => todo!(), | ||
| 46 | // Ast::BuiltIn(_) => todo!(), | ||
| 47 | |||
| 48 | // Ast::Comment(_) => unreachable!(), | ||
| 49 | // Ast::DocComment(_) => unreachable!(), | ||
| 50 | // } | ||
| 51 | // } | ||
| 52 | |||
| 53 | // pub fn check_expression(expr: &Ast) -> Result<()> { | ||
| 54 | // panic!() | ||
| 55 | // match expr { | ||
| 56 | // Ast::Declaration { | ||
| 57 | // name, | ||
| 58 | // decl_type: return_type, | ||
| 59 | // value, | ||
| 60 | // is_constant, | ||
| 61 | // } => { | ||
| 62 | // check_expression(value)?; | ||
| 63 | // // coerce_type(return_type.clone(), resolve_type(value)?)?; | ||
| 64 | // } | ||
| 65 | // _ => {} | ||
| 66 | // // Ast::Operation { | ||
| 67 | // // operator, | ||
| 68 | // // left, | ||
| 69 | // // right, | ||
| 70 | // // } => Ok(()), | ||
| 71 | // // Ast::Function { | ||
| 72 | // // name, | ||
| 73 | // // arguments, | ||
| 74 | // // return_type, | ||
| 75 | // // block, | ||
| 76 | // // } => Ok(()), | ||
| 77 | // // Ast::FunctionCall { name, arguments } => Ok(()), | ||
| 78 | // // Ast::If { | ||
| 79 | // // condition, | ||
| 80 | // // value, | ||
| 81 | // // else_value, | ||
| 82 | // // } => Ok(()), | ||
| 83 | // // Ast::Return(_) => Ok(()), | ||
| 84 | // // Ast::Public(_) => Ok(()), | ||
| 85 | // // Ast::Block(_) => Ok(()), | ||
| 86 | // // Ast::Number(_) => Ok(()), | ||
| 87 | // // Ast::Bool(_) => Ok(()), | ||
| 88 | // // Ast::String(_) => Ok(()), | ||
| 89 | // // Ast::Identifier(_) => Ok(()), | ||
| 90 | // // Ast::BuiltIn(_) => Ok(()), | ||
| 91 | // // Ast::Comment(_) => Ok(()), | ||
| 92 | // // Ast::DocComment(_) => Ok(()), | ||
| 93 | // } | ||
| 94 | |||
| 95 | // Ok(()) | ||
| 96 | // } | ||
crates/tokenizer/src/lib.rs+49-13| ... | @@ -71,12 +71,12 @@ pub enum Token { | ... | @@ -71,12 +71,12 @@ pub enum Token { |
| 71 | impl std::fmt::Display for Token { | 71 | impl std::fmt::Display for Token { |
| 72 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | 72 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 73 | let token = match self { | 73 | let token = match self { |
| 74 | Token::Identifier(_) => "an identifier".to_string(), | 74 | Token::Identifier(_) => "identifier".to_string(), |
| 75 | 75 | ||
| 76 | Token::Number(n) => n.to_string(), | 76 | Token::Number(n) => n.to_string(), |
| 77 | Token::String(s) => format!("\"{}\"", s), | 77 | Token::String(s) => format!("\"{}\"", s), |
| 78 | Token::Comment(_) => "a comment".to_string(), | 78 | Token::Comment(_) => "comment".to_string(), |
| 79 | Token::DocComment(_) => "a documentation comment".to_string(), | 79 | Token::DocComment(_) => "documentation comment".to_string(), |
| 80 | 80 | ||
| 81 | Token::Else => "else".to_string(), | 81 | Token::Else => "else".to_string(), |
| 82 | Token::False => "false".to_string(), | 82 | Token::False => "false".to_string(), |
| ... | @@ -129,6 +129,7 @@ impl std::fmt::Display for Token { | ... | @@ -129,6 +129,7 @@ impl std::fmt::Display for Token { |
| 129 | } | 129 | } |
| 130 | } | 130 | } |
| 131 | 131 | ||
| 132 | // TODO(@paperdave): rewrite this to not use a Vec<Token>. we probably dont need it. also store Loc | ||
| 132 | pub fn tokenize(content: String) -> Result<Vec<Token>> { | 133 | pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 133 | let mut tokens: Vec<Token> = Vec::new(); | 134 | let mut tokens: Vec<Token> = Vec::new(); |
| 134 | let mut chars = content.chars().enumerate().peekable(); | 135 | let mut chars = content.chars().enumerate().peekable(); |
| ... | @@ -277,20 +278,55 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { | ... | @@ -277,20 +278,55 @@ pub fn tokenize(content: String) -> Result<Vec<Token>> { |
| 277 | 278 | ||
| 278 | let mut comment = String::new(); | 279 | let mut comment = String::new(); |
| 279 | loop { | 280 | loop { |
| 280 | let c = chars.next(); | 281 | // skip leading whitespace |
| 281 | if c.is_none() { | 282 | while let Some((_, c)) = chars.peek() { |
| 282 | break; | 283 | if !c.is_whitespace() { |
| 284 | break; | ||
| 285 | } | ||
| 286 | chars.next(); | ||
| 283 | } | 287 | } |
| 284 | let (_, c) = c.unwrap(); | 288 | // read the comment |
| 285 | match c { | 289 | while let Some((_, c)) = chars.next() { |
| 286 | '\n' => break, | 290 | if c == '\n' || c == '\r' { |
| 287 | _ => { | 291 | break; |
| 288 | comment.push(c.clone()); | ||
| 289 | } | 292 | } |
| 293 | comment.push(c); | ||
| 290 | } | 294 | } |
| 291 | } | 295 | // trim trailing whitespace on line |
| 296 | comment = comment.trim_end().to_string(); | ||
| 297 | // skip to next non-whitespace character | ||
| 298 | while let Some((_, c)) = chars.peek() { | ||
| 299 | if !c.is_whitespace() { | ||
| 300 | break; | ||
| 301 | } | ||
| 302 | chars.next(); | ||
| 303 | } | ||
| 304 | // check if we've hit another comment | ||
| 305 | let mut tmp = chars.clone(); | ||
| 292 | 306 | ||
| 293 | comment = comment.trim().to_string(); | 307 | let is_another_comment = match tmp.peek() { |
| 308 | Some((_, '/')) => { | ||
| 309 | tmp.next(); // Consume the second slash | ||
| 310 | match tmp.peek() { | ||
| 311 | Some((_, '/')) => { | ||
| 312 | tmp.next(); // Consume the third slash | ||
| 313 | is_doc_comment | ||
| 314 | } | ||
| 315 | _ => !is_doc_comment, | ||
| 316 | } | ||
| 317 | } | ||
| 318 | _ => false, | ||
| 319 | }; | ||
| 320 | |||
| 321 | if is_another_comment { | ||
| 322 | // apply our temporary iterator to the main iterator | ||
| 323 | comment.push('\n'); | ||
| 324 | chars = tmp; | ||
| 325 | chars.next(); | ||
| 326 | } else { | ||
| 327 | break; | ||
| 328 | } | ||
| 329 | } | ||
| 294 | 330 | ||
| 295 | if is_doc_comment { | 331 | if is_doc_comment { |
| 296 | tokens.push(Token::DocComment(comment)); | 332 | tokens.push(Token::DocComment(comment)); |
examples/basics/comments.blight+2-9| ... | @@ -1,9 +1,2 @@ | ... | @@ -1,9 +1,2 @@ |
| 1 | // this is a regular comment | 1 | 2 // yapping |
| 2 | let x: int = 0; | 2 | + 2; |
| 3 | |||
| 4 | /// this is a doc comment | ||
| 5 | let y: int = 0; | ||
| 6 | |||
| 7 | /// Doc comments can be multiple lines. | ||
| 8 | /// This can be done by simply adding another triple-slash to the next line. | ||
| 9 | let z: int = 0; | ||
| \ No newline at end of file | |||