Compare commits

...

10 Commits

Author SHA1 Message Date
Araozu 10eaa66a6d vscode 2023-10-14 06:14:13 -05:00
Araozu ed0e163283 [Syntax] Refactor binding parsing 2023-10-05 20:26:47 -05:00
Araozu f769a2ec1d tag 0.0.7 2023-10-05 06:56:21 -05:00
Araozu 18cbe2a8ab [Syntax] Parse var/val binding as statemente 2023-10-05 06:51:48 -05:00
Araozu 0630287e34 [Syntax] Parse function call as a statement 2023-10-05 06:31:24 -05:00
Araozu 9af450eaa0 Refactor 2023-10-01 18:41:00 -05:00
Araozu 971b9d9516 Parse minimal function call 2023-10-01 17:43:59 -05:00
Araozu c4d13e76bc Parse empty arguments list 2023-10-01 17:37:19 -05:00
Araozu 03b5a1b6de Start function call parsing 2023-10-01 17:18:28 -05:00
Araozu 2e9776de01 [syntax] refactor bindings 2023-09-24 18:51:08 -05:00
22 changed files with 494 additions and 198 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
target target
examples

3
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"rust-analyzer.inlayHints.typeHints.enable": false
}

View File

@ -2,6 +2,8 @@
## TODO ## TODO
- Parse multiple statements
- Parse binary operators
- Parse more complex bindings - Parse more complex bindings
- Parse block of code - Parse block of code
- Watch mode - Watch mode
@ -14,6 +16,17 @@
- Document code - Document code
## v0.0.8
- Parse multiple statements inside a block
## v0.0.7
- Parse minimal function declarations following a grammar
- Parse function call, binding as statement
- Parse a statement as body of a function declaration
## v0.0.6 ## v0.0.6
- Parse function declarations - Parse function declarations

2
Cargo.lock generated
View File

@ -254,7 +254,7 @@ dependencies = [
[[package]] [[package]]
name = "thp" name = "thp"
version = "0.0.6" version = "0.0.7"
dependencies = [ dependencies = [
"clap", "clap",
"colored", "colored",

View File

@ -1,6 +1,6 @@
[package] [package]
name = "thp" name = "thp"
version = "0.0.6" version = "0.0.7"
edition = "2021" edition = "2021"

View File

@ -1,5 +1,5 @@
use super::Transpilable; use super::Transpilable;
use crate::syntax::ast::Binding; use crate::syntax::ast::var_binding::Binding;
impl Transpilable for Binding { impl Transpilable for Binding {
/// Transpiles val and var bindings into PHP. /// Transpiles val and var bindings into PHP.
@ -22,7 +22,10 @@ impl Transpilable for Binding {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::syntax::ast::{Binding, Expression, ValBinding}; use crate::syntax::ast::{
var_binding::{Binding, ValBinding},
Expression,
};
#[test] #[test]
fn binding_should_transpile() { fn binding_should_transpile() {

View File

@ -18,7 +18,10 @@ impl Transpilable for ModuleAST {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::syntax::ast::{Binding, Expression, TopLevelDeclaration, ValBinding}; use crate::syntax::ast::{
var_binding::{Binding, ValBinding},
Expression, TopLevelDeclaration,
};
#[test] #[test]
fn module_ast_should_transpile() { fn module_ast_should_transpile() {

View File

@ -32,12 +32,11 @@ enum Commands {
R {}, R {},
} }
const VERSION: &str = "0.0.6";
fn get_copyright() -> String { fn get_copyright() -> String {
let crate_version = env!("CARGO_PKG_VERSION");
format!( format!(
"THP {}\nCopyright (c) 2023 Fernando Enrique Araoz Morales\n", "THP {}\nCopyright (c) 2023 Fernando Enrique Araoz Morales\n",
VERSION, crate_version,
) )
} }

View File

@ -0,0 +1,12 @@
#[derive(Debug)]
pub struct FunctionCall {
pub identifier: Box<String>,
}
#[derive(Debug)]
pub struct ArgumentsList {
pub arguments: Vec<Box<Argument>>,
}
#[derive(Debug)]
pub enum Argument {}

View File

@ -1,10 +1,14 @@
pub mod functions;
pub mod statement;
pub mod var_binding;
pub struct ModuleAST { pub struct ModuleAST {
pub declarations: Vec<TopLevelDeclaration>, pub declarations: Vec<TopLevelDeclaration>,
} }
#[derive(Debug)] #[derive(Debug)]
pub enum TopLevelDeclaration { pub enum TopLevelDeclaration {
Binding(Binding), Binding(var_binding::Binding),
FunctionDeclaration(FunctionDeclaration), FunctionDeclaration(FunctionDeclaration),
} }
@ -15,31 +19,13 @@ pub struct FunctionDeclaration {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct Block {} pub struct Block {
pub statements: Vec<statement::Statement>,
}
#[derive(Debug)] #[derive(Debug)]
pub struct ParamsList {} pub struct ParamsList {}
#[derive(Debug)]
pub enum Binding {
Val(ValBinding),
Var(VarBinding),
}
#[derive(Debug)]
pub struct ValBinding {
pub datatype: Option<String>,
pub identifier: Box<String>,
pub expression: Expression,
}
#[derive(Debug)]
pub struct VarBinding {
pub datatype: Option<String>,
pub identifier: Box<String>,
pub expression: Expression,
}
#[derive(Debug)] #[derive(Debug)]
pub enum Expression { pub enum Expression {
Number(Box<String>), Number(Box<String>),

View File

@ -0,0 +1,7 @@
use super::{functions::FunctionCall, var_binding::Binding};
#[derive(Debug)]
pub enum Statement {
FunctionCall(FunctionCall),
Binding(Binding),
}

View File

@ -0,0 +1,21 @@
use super::Expression;
#[derive(Debug)]
pub enum Binding {
Val(ValBinding),
Var(VarBinding),
}
#[derive(Debug)]
pub struct ValBinding {
pub datatype: Option<String>,
pub identifier: Box<String>,
pub expression: Expression,
}
#[derive(Debug)]
pub struct VarBinding {
pub datatype: Option<String>,
pub identifier: Box<String>,
pub expression: Expression,
}

View File

@ -1,147 +1,118 @@
use super::ast::{Binding, ValBinding, VarBinding}; use super::ast::var_binding::{Binding, ValBinding, VarBinding};
use super::utils::{try_operator, try_token_type}; use super::utils::{try_operator, try_token_type, parse_token_type};
use super::{expression, SyntaxResult}; use super::{expression, ParseResult};
use crate::error_handling::SyntaxError; use crate::error_handling::SyntaxError;
use crate::lexic::token::{Token, TokenType}; use crate::lexic::token::{Token, TokenType};
use crate::utils::Result3; use crate::utils::Result3;
// TODO: Should return a 3 state value: pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<Binding, ()> {
// - Success: binding parsed successfully
// - NotFound: the first token (var | val) was not found, so the parser should try other options
// - Error: token (var | val) was found, but then other expected tokens were not found
pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> Option<SyntaxResult> {
let mut current_pos = pos; let mut current_pos = pos;
// Optional datatype annotation
let datatype_annotation = {
match try_token_type(tokens, current_pos, TokenType::Datatype) {
Result3::Ok(t) => {
current_pos += 1;
Some(String::from(&t.value))
}
Result3::Err(_) => None,
Result3::None => panic!(
"Internal compiler error: Illegal token stream at src/syntax/binding.rs#try_parse"
),
}
};
/* /*
* val/var keyword * val/var keyword
*/ */
let (is_val, binding_token) = { let (is_val, binding_token, next_pos) = {
let res1 = try_token_type(tokens, current_pos, TokenType::VAL); let res1 = parse_token_type(tokens, current_pos, TokenType::VAL);
match res1 { match res1 {
Result3::Ok(val_token) => (true, val_token), ParseResult::Ok(val_token, next) => (true, val_token, next),
_ => { _ => {
let res2 = try_token_type(tokens, current_pos, TokenType::VAR); let res2 = parse_token_type(tokens, current_pos, TokenType::VAR);
match res2 { match res2 {
Result3::Ok(var_token) => (false, var_token), ParseResult::Ok(var_token, next) => (false, var_token, next),
// Neither VAL nor VAR were matched, the parser should try // Neither VAL nor VAR were matched, the caller should try
// other constructs // other constructs
_ => return None, _ => return ParseResult::Unmatched,
} }
} }
} }
}; };
current_pos = next_pos;
/* /*
* identifier * identifier
*/ */
let identifier = match try_token_type(tokens, current_pos + 1, TokenType::Identifier) { let (identifier, next_pos) = match parse_token_type(tokens, current_pos, TokenType::Identifier) {
Result3::Ok(t) => t, ParseResult::Ok(t, n) => (t, n),
Result3::Err(t) => { ParseResult::Err(error) => {
// The parser found a token, but it's not an identifier // The parser found a token, but it's not an identifier
return Some(SyntaxResult::Err(SyntaxError { return ParseResult::Err(error);
reason: format!(
"There should be an identifier after a `{}` token",
if is_val { "val" } else { "var" }
),
error_start: t.position,
error_end: t.get_end_position(),
}));
} }
Result3::None => { _ => {
// The parser didn't find an Identifier after VAL/VAR // The parser didn't find an Identifier after VAL/VAR
return Some(SyntaxResult::Err(SyntaxError { return ParseResult::Err(SyntaxError {
reason: format!( reason: format!(
"There should be an identifier after a `{}` token", "There should be an identifier after a `{}` token",
if is_val { "val" } else { "var" } if is_val { "val" } else { "var" }
), ),
error_start: binding_token.position, error_start: binding_token.position,
error_end: binding_token.get_end_position(), error_end: binding_token.get_end_position(),
})); });
} }
}; };
current_pos = next_pos;
/* /*
* Equal (=) operator * Equal (=) operator
*/ */
let equal_operator: &Token = match try_operator(tokens, current_pos + 2, String::from("=")) { let equal_operator: &Token = match try_operator(tokens, current_pos, String::from("=")) {
Result3::Ok(t) => t, Result3::Ok(t) => t,
Result3::Err(t) => { Result3::Err(t) => {
// The parser found a token, but it's not the `=` operator // The parser found a token, but it's not the `=` operator
return Some(SyntaxResult::Err(SyntaxError { return ParseResult::Err(SyntaxError {
reason: format!("There should be an equal sign `=` after the identifier"), reason: format!("There should be an equal sign `=` after the identifier"),
error_start: t.position, error_start: t.position,
error_end: t.get_end_position(), error_end: t.get_end_position(),
})); });
} }
Result3::None => { Result3::None => {
// The parser didn't find the `=` operator after the identifier // The parser didn't find the `=` operator after the identifier
return Some(SyntaxResult::Err(SyntaxError { return ParseResult::Err(SyntaxError {
reason: format!("There should be an equal sign `=` after the identifier",), reason: format!("There should be an equal sign `=` after the identifier",),
error_start: identifier.position, error_start: identifier.position,
error_end: identifier.get_end_position(), error_end: identifier.get_end_position(),
})); });
} }
}; };
let expression = expression::try_parse(tokens, current_pos + 3); let expression = expression::try_parse(tokens, current_pos + 1);
if expression.is_none() { if expression.is_none() {
return Some(SyntaxResult::Err(SyntaxError { return ParseResult::Err(SyntaxError {
reason: String::from("Expected an expression after the equal `=` operator"), reason: String::from("Expected an expression after the equal `=` operator"),
error_start: equal_operator.position, error_start: equal_operator.position,
error_end: equal_operator.get_end_position(), error_end: equal_operator.get_end_position(),
})); });
} }
let expression = expression.unwrap(); let expression = expression.unwrap();
let binding = if is_val { let binding = if is_val {
Binding::Val(ValBinding { Binding::Val(ValBinding {
datatype: datatype_annotation, datatype: None,
identifier: Box::new(identifier.value.clone()), identifier: Box::new(identifier.value.clone()),
expression, expression,
}) })
} else { } else {
Binding::Var(VarBinding { Binding::Var(VarBinding {
datatype: datatype_annotation, datatype: None,
identifier: Box::new(identifier.value.clone()), identifier: Box::new(identifier.value.clone()),
expression, expression,
}) })
}; };
Some(SyntaxResult::Ok( ParseResult::Ok(binding, current_pos + 2)
super::ast::TopLevelDeclaration::Binding(binding),
current_pos + 4,
))
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{lexic::get_tokens, syntax::ast::TopLevelDeclaration}; use crate::lexic::get_tokens;
#[test] #[test]
fn should_parse_val_binding() { fn should_parse_val_binding() {
let tokens = get_tokens(&String::from("val identifier = 20")).unwrap(); let tokens = get_tokens(&String::from("val identifier = 20")).unwrap();
let binding = try_parse(&tokens, 0).unwrap(); let ParseResult::Ok(Binding::Val(binding), _) = try_parse(&tokens, 0) else {
panic!()
};
match binding { assert_eq!("identifier", format!("{}", binding.identifier));
SyntaxResult::Ok(TopLevelDeclaration::Binding(Binding::Val(binding)), _) => {
assert_eq!("identifier", format!("{}", binding.identifier));
}
_ => panic!(),
}
} }
#[test] #[test]
@ -169,40 +140,36 @@ mod tests {
assert_eq!("=", token.value); assert_eq!("=", token.value);
} }
/*
#[test] #[test]
fn should_parse_binding_with_datatype() { fn should_parse_binding_with_datatype() {
let tokens = get_tokens(&String::from("Num val identifier = 20")).unwrap(); let tokens = get_tokens(&String::from("Num val identifier = 20")).unwrap();
let binding = try_parse(&tokens, 0).unwrap(); let ParseResult::Ok(Binding::Val(binding), _) = try_parse(&tokens, 0) else {
panic!()
};
match binding { assert_eq!(Some(String::from("Num")), binding.datatype);
SyntaxResult::Ok(TopLevelDeclaration::Binding(Binding::Val(binding)), _) => { assert_eq!("identifier", format!("{}", binding.identifier));
assert_eq!(Some(String::from("Num")), binding.datatype);
assert_eq!("identifier", format!("{}", binding.identifier));
}
_ => panic!(),
}
let tokens = get_tokens(&String::from("Bool var identifier = true")).unwrap(); let tokens = get_tokens(&String::from("Bool var identifier = 20")).unwrap();
let binding = try_parse(&tokens, 0).unwrap(); let ParseResult::Ok(Binding::Var(binding), _) = try_parse(&tokens, 0) else {
panic!()
};
match binding { assert_eq!(Some(String::from("Bool")), binding.datatype);
SyntaxResult::Ok(TopLevelDeclaration::Binding(Binding::Var(binding)), _) => { assert_eq!("identifier", format!("{}", binding.identifier));
assert_eq!(Some(String::from("Bool")), binding.datatype);
assert_eq!("identifier", format!("{}", binding.identifier));
}
_ => panic!("D: {:?}", binding),
}
} }
*/
#[test] #[test]
fn should_return_correct_error() { fn should_return_correct_error() {
let tokens = get_tokens(&String::from("val")).unwrap(); let tokens = get_tokens(&String::from("val")).unwrap();
assert_eq!(TokenType::VAL, tokens[0].token_type); assert_eq!(TokenType::VAL, tokens[0].token_type);
assert_eq!(0, tokens[0].position); assert_eq!(0, tokens[0].position);
let binding = try_parse(&tokens, 0).unwrap(); let binding = try_parse(&tokens, 0);
match binding { match binding {
SyntaxResult::Err(error) => { ParseResult::Err(error) => {
assert_eq!(0, error.error_start); assert_eq!(0, error.error_start);
assert_eq!(3, error.error_end); assert_eq!(3, error.error_end);
} }
@ -215,10 +182,10 @@ mod tests {
let tokens = get_tokens(&String::from("val 322")).unwrap(); let tokens = get_tokens(&String::from("val 322")).unwrap();
assert_eq!(TokenType::VAL, tokens[0].token_type); assert_eq!(TokenType::VAL, tokens[0].token_type);
assert_eq!(0, tokens[0].position); assert_eq!(0, tokens[0].position);
let binding = try_parse(&tokens, 0).unwrap(); let binding = try_parse(&tokens, 0);
match binding { match binding {
SyntaxResult::Err(error) => { ParseResult::Err(error) => {
assert_eq!(4, error.error_start); assert_eq!(4, error.error_start);
assert_eq!(7, error.error_end); assert_eq!(7, error.error_end);
} }
@ -226,10 +193,10 @@ mod tests {
} }
let tokens = get_tokens(&String::from("val \"hello\"")).unwrap(); let tokens = get_tokens(&String::from("val \"hello\"")).unwrap();
let binding = try_parse(&tokens, 0).unwrap(); let binding = try_parse(&tokens, 0);
match binding { match binding {
SyntaxResult::Err(error) => { ParseResult::Err(error) => {
assert_eq!(4, error.error_start); assert_eq!(4, error.error_start);
assert_eq!(11, error.error_end); assert_eq!(11, error.error_end);
} }
@ -240,10 +207,10 @@ mod tests {
#[test] #[test]
fn should_return_error_when_equal_op_is_wrong() { fn should_return_error_when_equal_op_is_wrong() {
let tokens = get_tokens(&String::from("val id \"error\"")).unwrap(); let tokens = get_tokens(&String::from("val id \"error\"")).unwrap();
let binding = try_parse(&tokens, 0).unwrap(); let binding = try_parse(&tokens, 0);
match binding { match binding {
SyntaxResult::Err(error) => { ParseResult::Err(error) => {
assert_eq!(7, error.error_start); assert_eq!(7, error.error_start);
assert_eq!(14, error.error_end); assert_eq!(14, error.error_end);
} }

View File

@ -18,6 +18,37 @@ pub fn parse_block<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<Block,
}; };
current_pos = next_pos; current_pos = next_pos;
// Parse block statements
let mut statements = Vec::new();
// First statement
match super::statement::try_parse(tokens, current_pos) {
ParseResult::Ok(statement, next_pos) => {
current_pos = next_pos;
statements.push(statement);
}
ParseResult::Err(err) => return ParseResult::Err(err),
ParseResult::Unmatched => {}
ParseResult::Mismatch(_) => {}
}
// More statements separated by new lines
while let Some(t) = tokens.get(current_pos) {
if t.token_type != TokenType::NewLine {
break;
}
current_pos += 1;
match super::statement::try_parse(tokens, current_pos) {
ParseResult::Ok(statement, next_pos) => {
current_pos = next_pos;
statements.push(statement);
}
ParseResult::Err(err) => return ParseResult::Err(err),
_ => break,
}
}
// Parse closing brace // Parse closing brace
let (_closing_brace, next_pos) = let (_closing_brace, next_pos) =
match parse_token_type(tokens, current_pos, TokenType::RightBrace) { match parse_token_type(tokens, current_pos, TokenType::RightBrace) {
@ -40,5 +71,37 @@ pub fn parse_block<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<Block,
}; };
current_pos = next_pos; current_pos = next_pos;
ParseResult::Ok(Block {}, current_pos) ParseResult::Ok(Block { statements }, current_pos)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexic::get_tokens;
#[test]
fn test_parse_block() {
let tokens = get_tokens(&String::from("{f()}")).unwrap();
let block = parse_block(&tokens, 0);
let block = match block {
ParseResult::Ok(p, _) => p,
_ => panic!("Expected a block, got: {:?}", block),
};
assert_eq!(block.statements.len(), 1);
}
#[test]
fn test_parse_block_2() {
let tokens = get_tokens(&String::from("{f()\ng()}")).unwrap();
let block = parse_block(&tokens, 0);
let block = match block {
ParseResult::Ok(p, _) => p,
_ => panic!("Expected a block, got: {:?}", block),
};
assert_eq!(block.statements.len(), 2);
}
} }

View File

@ -0,0 +1,92 @@
use crate::{
error_handling::SyntaxError,
lexic::token::{Token, TokenType},
syntax::{ast::functions::ArgumentsList, utils::parse_token_type, ParseResult},
};
pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<ArgumentsList, &Token> {
let mut current_pos = pos;
let (opening_paren, next_pos) =
match parse_token_type(tokens, current_pos, TokenType::LeftParen) {
ParseResult::Ok(t, next) => (t, next),
ParseResult::Err(err) => return ParseResult::Err(err),
ParseResult::Mismatch(t) => return ParseResult::Mismatch(t),
ParseResult::Unmatched => return ParseResult::Unmatched,
};
current_pos = next_pos;
// Parse closing paren
let (_closing_paren, next_pos) =
match parse_token_type(tokens, current_pos, TokenType::RightParen) {
ParseResult::Ok(t, next) => (t, next),
ParseResult::Err(err) => return ParseResult::Err(err),
ParseResult::Mismatch(t) => {
return ParseResult::Err(SyntaxError {
reason: String::from("Expected a closing paren after the function identifier."),
error_start: t.position,
error_end: t.get_end_position(),
});
}
ParseResult::Unmatched => {
return ParseResult::Err(SyntaxError {
reason: String::from("Expected a closing paren after the function identifier."),
error_start: opening_paren.position,
error_end: opening_paren.get_end_position(),
});
}
};
current_pos = next_pos;
ParseResult::Ok(
ArgumentsList {
arguments: Vec::new(),
},
current_pos,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexic::get_tokens;
#[test]
fn should_parse_empty_list() {
let tokens = get_tokens(&String::from("()")).unwrap();
let fun_decl = try_parse(&tokens, 0);
let ParseResult::Ok(list, next) = fun_decl else {
panic!("Expected an unmatched result: {:?}", fun_decl);
};
assert_eq!(next, 2);
assert_eq!(list.arguments.len(), 0);
}
#[test]
fn should_parse_empty_list_with_whitespace() {
let tokens = get_tokens(&String::from("( ) ")).unwrap();
let fun_decl = try_parse(&tokens, 0);
let ParseResult::Ok(list, next) = fun_decl else {
panic!("Expected a result, got: {:?}", fun_decl);
};
assert_eq!(next, 2);
assert_eq!(list.arguments.len(), 0);
}
#[test]
fn should_parse_empty_list_with_whitespace_2() {
let tokens = get_tokens(&String::from("(\n \n)")).unwrap();
let fun_decl = try_parse(&tokens, 0);
let ParseResult::Ok(list, next) = fun_decl else {
panic!("Expected a result, got: {:?}", fun_decl);
};
assert_eq!(next, 3);
assert_eq!(list.arguments.len(), 0);
}
}

View File

@ -0,0 +1,96 @@
use crate::{
error_handling::SyntaxError,
lexic::token::{Token, TokenType},
syntax::{ast::functions::FunctionCall, utils::parse_token_type, ParseResult},
};
pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<FunctionCall, ()> {
let mut current_pos = pos;
// TODO: Use an expression instead of a fixed identifier
// Parse identifier
let (identifier, next_pos) = match parse_token_type(tokens, current_pos, TokenType::Identifier)
{
ParseResult::Ok(id, next) => (id, next),
ParseResult::Err(err) => return ParseResult::Err(err),
ParseResult::Mismatch(_) => {
return ParseResult::Unmatched;
}
ParseResult::Unmatched => {
return ParseResult::Unmatched;
}
};
current_pos = next_pos;
// Parse arguments list
let (args_list, next_pos) = match super::arguments_list::try_parse(tokens, current_pos) {
ParseResult::Ok(args, next) => (args, next),
ParseResult::Err(err) => return ParseResult::Err(err),
ParseResult::Mismatch(_) => {
return ParseResult::Unmatched;
}
ParseResult::Unmatched => {
return ParseResult::Unmatched;
}
};
current_pos = next_pos;
ParseResult::Ok(
FunctionCall {
identifier: Box::new(identifier.value.clone()),
},
current_pos,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lexic::get_tokens;
#[test]
fn should_not_parse_identifier_alone() {
let tokens = get_tokens(&String::from("function_name")).unwrap();
let fun_decl = try_parse(&tokens, 0);
let ParseResult::Unmatched = fun_decl else {
panic!("Expected an unmatched result: {:?}", fun_decl);
};
}
#[test]
fn should_parse_minimal_construct() {
let tokens = get_tokens(&String::from("function_name()")).unwrap();
let fun_decl = try_parse(&tokens, 0);
let ParseResult::Ok(_call, next) = fun_decl else {
panic!("Expected a result, got: {:?}", fun_decl);
};
assert_eq!(next, 3);
}
#[test]
fn should_parse_minimal_construct_2() {
let tokens = get_tokens(&String::from("function_name ( )")).unwrap();
let fun_decl = try_parse(&tokens, 0);
let ParseResult::Ok(_call, next) = fun_decl else {
panic!("Expected a result, got: {:?}", fun_decl);
};
assert_eq!(next, 3);
}
#[test]
fn should_parse_minimal_construct_3() {
let tokens = get_tokens(&String::from("function_name\n(\n \n)")).unwrap();
let fun_decl = try_parse(&tokens, 0);
let ParseResult::Ok(_call, next) = fun_decl else {
panic!("Expected a result, got: {:?}", fun_decl);
};
assert_eq!(next, 5);
}
}

View File

@ -4,8 +4,8 @@ use crate::{
utils::Result3, utils::Result3,
}; };
use super::{ use super::super::{
ast::{FunctionDeclaration, ParamsList}, ast::FunctionDeclaration,
block::parse_block, block::parse_block,
params_list::parse_params_list, params_list::parse_params_list,
utils::{parse_token_type, try_token_type}, utils::{parse_token_type, try_token_type},
@ -98,7 +98,7 @@ pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<Function
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{lexic::get_tokens, syntax::ast::TopLevelDeclaration}; use crate::lexic::get_tokens;
use super::*; use super::*;
@ -317,7 +317,7 @@ mod tests {
#[cfg(test)] #[cfg(test)]
mod whitespace_test { mod whitespace_test {
use crate::{lexic::get_tokens, syntax::ast::TopLevelDeclaration}; use crate::lexic::get_tokens;
use super::*; use super::*;

View File

@ -0,0 +1,3 @@
pub mod arguments_list;
pub mod function_call;
pub mod function_declaration;

View File

@ -38,8 +38,28 @@ return type = ;
### Block ### Block
```ebnf ```ebnf
block = "{", "}" block = "{", (statement, (new line, statement)*)?, "}"
``` ```
### Statement
```ebnf
statement = function call | binding
```
## Function call
```ebnf
function call = identifier, arguments list
```
### Arguments list
```ebnf
arguments list = "(", ")"
```

View File

@ -3,8 +3,9 @@ use crate::error_handling::{MistiError, SyntaxError};
mod binding; mod binding;
mod block; mod block;
mod expression; mod expression;
mod function_declaration; mod functions;
mod params_list; mod params_list;
mod statement;
mod utils; mod utils;
pub mod ast; pub mod ast;
@ -14,24 +15,18 @@ use ast::ModuleAST;
use self::ast::TopLevelDeclaration; use self::ast::TopLevelDeclaration;
#[derive(Debug)]
pub enum SyntaxResult {
///
/// A construct has been found
Ok(TopLevelDeclaration, usize),
///
/// No construct was found
None,
///
/// A construct was found, but there was an error parsing it
Err(SyntaxError),
}
#[derive(Debug)] #[derive(Debug)]
pub enum ParseResult<A, B> { pub enum ParseResult<A, B> {
/// The parsing was a success
Ok(A, usize), Ok(A, usize),
/// The parsing failed past a point of no return.
///
/// For example, when parsing a function declaration
/// the `fun` token is found, but then no identifier
Err(SyntaxError), Err(SyntaxError),
/// A construct different from the one expected was found
Mismatch(B), Mismatch(B),
/// No construct was found
Unmatched, Unmatched,
} }
@ -76,7 +71,7 @@ fn next_construct<'a>(
current_pos: usize, current_pos: usize,
) -> ParseResult<TopLevelDeclaration, ()> { ) -> ParseResult<TopLevelDeclaration, ()> {
None.or_else( None.or_else(
|| match function_declaration::try_parse(tokens, current_pos) { || match functions::function_declaration::try_parse(tokens, current_pos) {
ParseResult::Ok(declaration, next_pos) => Some(ParseResult::Ok( ParseResult::Ok(declaration, next_pos) => Some(ParseResult::Ok(
TopLevelDeclaration::FunctionDeclaration(declaration), TopLevelDeclaration::FunctionDeclaration(declaration),
next_pos, next_pos,

56
src/syntax/statement.rs Normal file
View File

@ -0,0 +1,56 @@
use crate::lexic::token::Token;
use super::{ast::statement::Statement, binding, functions::function_call, ParseResult};
pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> ParseResult<Statement, ()> {
None.or_else(|| match binding::try_parse(tokens, pos) {
ParseResult::Ok(b, next) => Some(ParseResult::Ok(Statement::Binding(b), next)),
ParseResult::Err(err) => Some(ParseResult::Err(err)),
_ => None,
})
.or_else(|| match function_call::try_parse(tokens, pos) {
ParseResult::Ok(f, next) => Some(ParseResult::Ok(Statement::FunctionCall(f), next)),
ParseResult::Err(err) => Some(ParseResult::Err(err)),
_ => None,
})
.unwrap_or_else(|| ParseResult::Unmatched)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn should_parse_function_call() {
let input = String::from("f1()");
let tokens = crate::lexic::get_tokens(&input).unwrap();
let statement = try_parse(&tokens, 0);
let statement = match statement {
ParseResult::Ok(s, _) => s,
_ => panic!("Expected a statement"),
};
match statement {
Statement::FunctionCall(_) => assert!(true),
_ => panic!("Expected a function call"),
}
}
#[test]
fn should_parse_binding() {
let input = String::from("val identifier = 20");
let tokens = crate::lexic::get_tokens(&input).unwrap();
let statement = try_parse(&tokens, 0);
let statement = match statement {
ParseResult::Ok(s, _) => s,
_ => panic!("Expected a statement"),
};
match statement {
Statement::Binding(_) => assert!(true),
_ => panic!("Expected a binding"),
}
}
}

View File

@ -1,10 +1,9 @@
use crate::{ use crate::{
error_handling::SyntaxError,
lexic::token::{Token, TokenType}, lexic::token::{Token, TokenType},
utils::Result3, utils::Result3,
}; };
use super::{ParseResult, SyntaxResult}; use super::ParseResult;
/// Expects the token at `pos` to be of type `token_type` /// Expects the token at `pos` to be of type `token_type`
pub fn try_token_type(tokens: &Vec<Token>, pos: usize, token_type: TokenType) -> Result3<&Token> { pub fn try_token_type(tokens: &Vec<Token>, pos: usize, token_type: TokenType) -> Result3<&Token> {
@ -18,6 +17,18 @@ pub fn try_token_type(tokens: &Vec<Token>, pos: usize, token_type: TokenType) ->
} }
} }
pub fn try_operator(tokens: &Vec<Token>, pos: usize, operator: String) -> Result3<&Token> {
match tokens.get(pos) {
Some(t) if t.token_type == TokenType::Operator && t.value == operator => Result3::Ok(t),
Some(t) if t.token_type == TokenType::NewLine || t.token_type == TokenType::EOF => {
Result3::None
}
Some(t) => Result3::Err(t),
None => Result3::None,
}
}
/// Expects the token at `pos` to be of type `token_type` /// Expects the token at `pos` to be of type `token_type`
pub fn parse_token_type( pub fn parse_token_type(
tokens: &Vec<Token>, tokens: &Vec<Token>,
@ -48,58 +59,3 @@ pub fn parse_token_type(
} }
} }
pub fn try_operator(tokens: &Vec<Token>, pos: usize, operator: String) -> Result3<&Token> {
match tokens.get(pos) {
Some(t) if t.token_type == TokenType::Operator && t.value == operator => Result3::Ok(t),
Some(t) if t.token_type == TokenType::NewLine || t.token_type == TokenType::EOF => {
Result3::None
}
Some(t) => Result3::Err(t),
None => Result3::None,
}
}
pub fn _try_operator_w<'a>(
tokens: &'a Vec<Token>,
pos: usize,
operator: String,
error_message: String,
prev_token: &Token,
) -> Result<(&'a Token, usize), Option<SyntaxResult>> {
let mut current_pos = pos;
// Ignore all whitespace and newlines
while let Some(t) = tokens.get(current_pos) {
if t.token_type == TokenType::INDENT
|| t.token_type == TokenType::DEDENT
|| t.token_type == TokenType::NewLine
{
current_pos += 1;
} else {
break;
}
}
match tokens.get(current_pos) {
Some(t) if t.token_type == TokenType::Operator && t.value == operator => {
Ok((t, current_pos + 1))
}
Some(t) if t.token_type == TokenType::NewLine || t.token_type == TokenType::EOF => {
Err(Some(SyntaxResult::Err(SyntaxError {
reason: error_message,
error_start: prev_token.position,
error_end: prev_token.get_end_position(),
})))
}
Some(t) => Err(Some(SyntaxResult::Err(SyntaxError {
reason: error_message,
error_start: t.position,
error_end: t.get_end_position(),
}))),
None => Err(Some(SyntaxResult::Err(SyntaxError {
reason: error_message,
error_start: prev_token.position,
error_end: prev_token.get_end_position(),
}))),
}
}