Parse minimal function declaration

master
Araozu 2023-09-08 20:17:46 -05:00
parent c61f88aaaa
commit 5f25bae3e0
11 changed files with 420 additions and 14 deletions

View File

@ -3,6 +3,7 @@ use crate::syntax::ast::ModuleAST;
mod binding; mod binding;
mod expression; mod expression;
mod module_ast; mod module_ast;
mod top_level_construct;
/// Trait that the AST and its nodes implement to support transformation to PHP /// Trait that the AST and its nodes implement to support transformation to PHP
trait Transpilable { trait Transpilable {

View File

@ -18,7 +18,7 @@ impl Transpilable for ModuleAST {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::syntax::ast::{Binding, Expression, ValBinding}; use crate::syntax::ast::{Binding, Expression, TopLevelConstruct, ValBinding};
#[test] #[test]
fn module_ast_should_transpile() { fn module_ast_should_transpile() {
@ -31,7 +31,7 @@ mod tests {
}); });
let module = ModuleAST { let module = ModuleAST {
bindings: vec![binding], bindings: vec![TopLevelConstruct::Binding(binding)],
}; };
let result = module.transpile(); let result = module.transpile();

View File

@ -0,0 +1,12 @@
use crate::syntax::ast::TopLevelConstruct;
use super::Transpilable;
impl Transpilable for TopLevelConstruct {
fn transpile(&self) -> String {
match self {
TopLevelConstruct::Binding(binding) => binding.transpile(),
TopLevelConstruct::FunctionDeclaration(_) => todo!(),
}
}
}

View File

@ -6,6 +6,7 @@ fn str_is_keyword(s: &String) -> Option<TokenType> {
match s.as_str() { match s.as_str() {
"var" => Some(TokenType::VAR), "var" => Some(TokenType::VAR),
"val" => Some(TokenType::VAL), "val" => Some(TokenType::VAL),
"fun" => Some(TokenType::FUN),
_ => None, _ => None,
} }
} }

View File

@ -16,6 +16,7 @@ pub enum TokenType {
VAR, VAR,
VAL, VAL,
EOF, EOF,
FUN,
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -51,9 +51,7 @@ fn main() {
let cli = Cli::parse(); let cli = Cli::parse();
match &cli.command { match &cli.command {
Some(Commands::C { Some(Commands::C { file: input }) => file::compile_file(input),
file: input,
}) => file::compile_file(input),
Some(Commands::R {}) => { Some(Commands::R {}) => {
println!("{}", get_copyright()); println!("{}", get_copyright());
let _ = repl::run(); let _ = repl::run();

View File

@ -1,24 +1,39 @@
pub struct ModuleAST { pub struct ModuleAST {
pub bindings: Vec<Binding>, pub bindings: Vec<TopLevelConstruct>,
} }
#[derive(Debug)]
pub enum TopLevelConstruct {
Binding(Binding),
FunctionDeclaration(FunctionDeclaration),
}
#[derive(Debug)]
pub struct FunctionDeclaration {
pub identifier: Box<String>,
}
#[derive(Debug)]
pub enum Binding { pub enum Binding {
Val(ValBinding), Val(ValBinding),
Var(VarBinding), Var(VarBinding),
} }
#[derive(Debug)]
pub struct ValBinding { pub struct ValBinding {
pub datatype: Option<String>, pub datatype: Option<String>,
pub identifier: Box<String>, pub identifier: Box<String>,
pub expression: Expression, pub expression: Expression,
} }
#[derive(Debug)]
pub struct VarBinding { pub struct VarBinding {
pub datatype: Option<String>, pub datatype: Option<String>,
pub identifier: Box<String>, pub identifier: Box<String>,
pub expression: Expression, pub expression: Expression,
} }
#[derive(Debug)]
pub enum Expression { pub enum Expression {
Number(Box<String>), Number(Box<String>),
String(Box<String>), String(Box<String>),

View File

@ -120,7 +120,9 @@ pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> Option<SyntaxResult>
}) })
}; };
Some(SyntaxResult::Ok(binding)) Some(SyntaxResult::Ok(super::ast::TopLevelConstruct::Binding(
binding,
)))
} }
/// Expects the token at `pos` to be of type `token_type` /// Expects the token at `pos` to be of type `token_type`
@ -149,7 +151,7 @@ fn try_operator(tokens: &Vec<Token>, pos: usize, operator: String) -> Result3<&T
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::lexic::get_tokens; use crate::{lexic::get_tokens, syntax::ast::TopLevelConstruct};
#[test] #[test]
fn should_parse_val_binding() { fn should_parse_val_binding() {
@ -157,7 +159,7 @@ mod tests {
let binding = try_parse(&tokens, 0).unwrap(); let binding = try_parse(&tokens, 0).unwrap();
match binding { match binding {
SyntaxResult::Ok(Binding::Val(binding)) => { SyntaxResult::Ok(TopLevelConstruct::Binding(Binding::Val(binding))) => {
assert_eq!("identifier", format!("{}", binding.identifier)); assert_eq!("identifier", format!("{}", binding.identifier));
} }
_ => panic!(), _ => panic!(),
@ -195,7 +197,7 @@ mod tests {
let binding = try_parse(&tokens, 0).unwrap(); let binding = try_parse(&tokens, 0).unwrap();
match binding { match binding {
SyntaxResult::Ok(Binding::Val(binding)) => { SyntaxResult::Ok(TopLevelConstruct::Binding(Binding::Val(binding))) => {
assert_eq!(Some(String::from("Num")), binding.datatype); assert_eq!(Some(String::from("Num")), binding.datatype);
assert_eq!("identifier", format!("{}", binding.identifier)); assert_eq!("identifier", format!("{}", binding.identifier));
} }
@ -206,11 +208,11 @@ mod tests {
let binding = try_parse(&tokens, 0).unwrap(); let binding = try_parse(&tokens, 0).unwrap();
match binding { match binding {
SyntaxResult::Ok(Binding::Var(binding)) => { SyntaxResult::Ok(TopLevelConstruct::Binding(Binding::Var(binding))) => {
assert_eq!(Some(String::from("Bool")), binding.datatype); assert_eq!(Some(String::from("Bool")), binding.datatype);
assert_eq!("identifier", format!("{}", binding.identifier)); assert_eq!("identifier", format!("{}", binding.identifier));
} }
_ => panic!(), _ => panic!("D: {:?}", binding),
} }
} }

View File

@ -0,0 +1,356 @@
use crate::{
error_handling::SyntaxError,
lexic::token::{Token, TokenType},
utils::Result3,
};
use super::{
ast::{FunctionDeclaration, TopLevelConstruct},
utils::try_token_type,
SyntaxResult,
};
pub fn try_parse<'a>(tokens: &'a Vec<Token>, pos: usize) -> Option<SyntaxResult> {
let mut current_pos = pos;
// `fun` keyword
let fun_keyword = match try_token_type(tokens, current_pos, TokenType::FUN) {
Result3::Ok(t) => t,
Result3::Err(_token) => return None,
Result3::None => return None,
};
current_pos += 1;
// Parse identifier
let identifier = match try_token_type(tokens, current_pos, TokenType::Identifier) {
Result3::Ok(t) => t,
Result3::Err(t) => {
// The parser found a token, but it's not an identifier
return Some(SyntaxResult::Err(SyntaxError {
reason: format!(
"There should be an identifier after a `fun` token, but found `{}`",
t.value
),
error_start: t.position,
error_end: t.get_end_position(),
}));
}
Result3::None => {
// The parser didn't find any token
return Some(SyntaxResult::Err(SyntaxError {
reason: format!(
"There should be an identifier after a `fun` token, but found nothing"
),
error_start: fun_keyword.position,
error_end: fun_keyword.get_end_position(),
}));
}
};
current_pos += 1;
// Parse an opening paren
let opening_paren = match try_token_type(tokens, current_pos, TokenType::LeftParen) {
Result3::Ok(t) => t,
Result3::Err(t) => {
// The parser found a token, but it's not an opening paren
return Some(SyntaxResult::Err(SyntaxError {
reason: format!(
"There should be an opening paren after the identifier, but found `{}`",
t.value
),
error_start: t.position,
error_end: t.get_end_position(),
}));
}
Result3::None => {
// The parser didn't find any token
return Some(SyntaxResult::Err(SyntaxError {
reason: format!(
"There should be an opening paren after the identifier, but found nothing"
),
error_start: identifier.position,
error_end: identifier.get_end_position(),
}));
}
};
current_pos += 1;
// Parse a closing paren
let closing_paren = match try_token_type(tokens, current_pos, TokenType::RightParen) {
Result3::Ok(t) => t,
Result3::Err(t) => {
// The parser found a token, but it's not an opening paren
return Some(SyntaxResult::Err(SyntaxError {
reason: format!(
"There should be a closing paren after the parameter list, but found `{}`",
t.value
),
error_start: t.position,
error_end: t.get_end_position(),
}));
}
Result3::None => {
// The parser didn't find any token
return Some(SyntaxResult::Err(SyntaxError {
reason: format!(
"There should be a closing paren after the parameter list, but found nothing"
),
error_start: opening_paren.position,
error_end: opening_paren.get_end_position(),
}));
}
};
current_pos += 1;
// Parse opening brace
let opening_brace = match try_token_type(tokens, current_pos, TokenType::LeftBrace) {
Result3::Ok(t) => t,
Result3::Err(t) => {
// The parser found a token, but it's not an opening brace
return Some(SyntaxResult::Err(SyntaxError {
reason: format!(
"There should be an opening brace after the parameter list, but found `{}`",
t.value
),
error_start: t.position,
error_end: t.get_end_position(),
}));
}
Result3::None => {
// The parser didn't find any token
return Some(SyntaxResult::Err(SyntaxError {
reason: format!(
"There should be an opening brace after the parameter list, but found nothing"
),
error_start: closing_paren.position,
error_end: closing_paren.get_end_position(),
}));
}
};
current_pos += 1;
// Parse closing brace
let closing_brace = match try_token_type(tokens, current_pos, TokenType::RightBrace) {
Result3::Ok(t) => t,
Result3::Err(t) => {
// The parser found a token, but it's not an opening brace
return Some(SyntaxResult::Err(SyntaxError {
reason: format!(
"There should be a closing brace after the function body, but found `{}`",
t.value
),
error_start: t.position,
error_end: t.get_end_position(),
}));
}
Result3::None => {
// The parser didn't find any token
return Some(SyntaxResult::Err(SyntaxError {
reason: format!(
"There should be a closing brace after the function body, but found nothing"
),
error_start: opening_brace.position,
error_end: opening_brace.get_end_position(),
}));
}
};
// Construct and return the function declaration
Some(SyntaxResult::Ok(TopLevelConstruct::FunctionDeclaration(
FunctionDeclaration {
identifier: Box::new(identifier.value.clone()),
},
)))
}
#[cfg(test)]
mod tests {
use crate::{lexic::get_tokens, syntax::ast::TopLevelConstruct};
use super::*;
#[test]
fn should_return_none_on_wrong_initial_token() {
let tokens = get_tokens(&String::from("val identifier = 20")).unwrap();
let fun_decl = try_parse(&tokens, 0);
assert!(fun_decl.is_none());
}
#[test]
fn should_not_parse_fun_without_identifier() {
let tokens = get_tokens(&String::from("fun = 20")).unwrap();
let fun_decl = try_parse(&tokens, 0);
match fun_decl {
Some(SyntaxResult::Err(err)) => {
assert_eq!(
err.reason,
"There should be an identifier after a `fun` token, but found `=`"
);
assert_eq!(err.error_start, 4);
assert_eq!(err.error_end, 5);
}
_ => panic!("Expected an error: {:?}", fun_decl),
}
let tokens = get_tokens(&String::from("fun")).unwrap();
let fun_decl = try_parse(&tokens, 0);
match fun_decl {
Some(SyntaxResult::Err(err)) => {
assert_eq!(
err.reason,
"There should be an identifier after a `fun` token, but found nothing"
);
assert_eq!(err.error_start, 0);
assert_eq!(err.error_end, 3);
}
_ => panic!("Expected an error: {:?}", fun_decl),
}
}
#[test]
fn should_not_parse_fun_without_parens() {
let tokens = get_tokens(&String::from("fun id =")).unwrap();
let fun_decl = try_parse(&tokens, 0);
match fun_decl {
Some(SyntaxResult::Err(err)) => {
assert_eq!(
err.reason,
"There should be an opening paren after the identifier, but found `=`"
);
assert_eq!(err.error_start, 7);
assert_eq!(err.error_end, 8);
}
_ => panic!("Expected an error: {:?}", fun_decl),
}
let tokens = get_tokens(&String::from("fun id")).unwrap();
let fun_decl = try_parse(&tokens, 0);
match fun_decl {
Some(SyntaxResult::Err(err)) => {
assert_eq!(
err.reason,
"There should be an opening paren after the identifier, but found nothing"
);
assert_eq!(err.error_start, 4);
assert_eq!(err.error_end, 6);
}
_ => panic!("Expected an error: {:?}", fun_decl),
}
}
#[test]
fn should_not_parse_fun_without_closing_paren() {
let tokens = get_tokens(&String::from("fun id(=")).unwrap();
let fun_decl = try_parse(&tokens, 0);
match fun_decl {
Some(SyntaxResult::Err(err)) => {
assert_eq!(
err.reason,
"There should be a closing paren after the parameter list, but found `=`"
);
assert_eq!(err.error_start, 7);
assert_eq!(err.error_end, 8);
}
_ => panic!("Expected an error: {:?}", fun_decl),
}
let tokens = get_tokens(&String::from("fun id(")).unwrap();
let fun_decl = try_parse(&tokens, 0);
match fun_decl {
Some(SyntaxResult::Err(err)) => {
assert_eq!(
err.reason,
"There should be a closing paren after the parameter list, but found nothing"
);
assert_eq!(err.error_start, 6);
assert_eq!(err.error_end, 7);
}
_ => panic!("Expected an error: {:?}", fun_decl),
}
}
#[test]
fn should_not_parse_fun_without_opening_brace() {
let tokens = get_tokens(&String::from("fun id() =")).unwrap();
let fun_decl = try_parse(&tokens, 0);
match fun_decl {
Some(SyntaxResult::Err(err)) => {
assert_eq!(
err.reason,
"There should be an opening brace after the parameter list, but found `=`"
);
assert_eq!(err.error_start, 9);
assert_eq!(err.error_end, 10);
}
_ => panic!("Expected an error: {:?}", fun_decl),
}
let tokens = get_tokens(&String::from("fun id()")).unwrap();
let fun_decl = try_parse(&tokens, 0);
match fun_decl {
Some(SyntaxResult::Err(err)) => {
assert_eq!(
err.reason,
"There should be an opening brace after the parameter list, but found nothing"
);
assert_eq!(err.error_start, 7);
assert_eq!(err.error_end, 8);
}
_ => panic!("Expected an error: {:?}", fun_decl),
}
}
#[test]
fn should_not_parse_fun_without_closing_brace() {
let tokens = get_tokens(&String::from("fun id() { 20")).unwrap();
let fun_decl = try_parse(&tokens, 0);
match fun_decl {
Some(SyntaxResult::Err(err)) => {
assert_eq!(
err.reason,
"There should be a closing brace after the function body, but found `20`"
);
assert_eq!(err.error_start, 11);
assert_eq!(err.error_end, 13);
}
_ => panic!("Expected an error: {:?}", fun_decl),
}
let tokens = get_tokens(&String::from("fun id() {")).unwrap();
let fun_decl = try_parse(&tokens, 0);
match fun_decl {
Some(SyntaxResult::Err(err)) => {
assert_eq!(
err.reason,
"There should be a closing brace after the function body, but found nothing"
);
assert_eq!(err.error_start, 9);
assert_eq!(err.error_end, 10);
}
_ => panic!("Expected an error: {:?}", fun_decl),
}
}
#[test]
fn should_parse_simple_function_declaration() {
let tokens = get_tokens(&String::from("fun id() {}")).unwrap();
let function_declaration = try_parse(&tokens, 0).unwrap();
match function_declaration {
SyntaxResult::Ok(TopLevelConstruct::FunctionDeclaration(declaration)) => {
assert_eq!(declaration.identifier, Box::new(String::from("id")));
}
_ => panic!(
"Expected a function declaration: {:?}",
function_declaration
),
}
}
}

View File

@ -2,16 +2,21 @@ use crate::error_handling::{MistiError, SyntaxError};
mod binding; mod binding;
mod expression; mod expression;
mod function_declaration;
mod utils;
pub mod ast; pub mod ast;
use ast::{Binding, ModuleAST};
use crate::lexic::token::Token; use crate::lexic::token::Token;
use ast::{Binding, ModuleAST};
use self::ast::TopLevelConstruct;
#[derive(Debug)]
pub enum SyntaxResult { pub enum SyntaxResult {
/// ///
/// A construct has been found /// A construct has been found
Ok(Binding), Ok(TopLevelConstruct),
/// ///
/// No construct was found /// No construct was found
None, None,

15
src/syntax/utils.rs Normal file
View File

@ -0,0 +1,15 @@
use crate::{
lexic::token::{Token, TokenType},
utils::Result3,
};
pub fn try_token_type(tokens: &Vec<Token>, pos: usize, token_type: TokenType) -> Result3<&Token> {
match tokens.get(pos) {
Some(t) if t.token_type == token_type => Result3::Ok(t),
Some(t) if t.token_type == TokenType::Semicolon || t.token_type == TokenType::EOF => {
Result3::None
}
Some(t) => Result3::Err(t),
None => Result3::None,
}
}