Compare commits

..

3 Commits

Author SHA1 Message Date
b4e5caa0f0 fix: fix assignment parser 2024-10-19 21:20:25 -05:00
aede32a068 feat: parse assignments 2024-10-19 20:57:09 -05:00
68d3e0dba4 test: add tests to binary operator parsing 2024-10-19 19:48:33 -05:00
10 changed files with 213 additions and 6 deletions

View File

@ -2,7 +2,6 @@
## TODO ## TODO
- Test correct operator precedence
- Implement functions as first class citizens - Implement functions as first class citizens
- Parse __more__ binary operators - Parse __more__ binary operators
- Parse more complex bindings - Parse more complex bindings
@ -17,12 +16,20 @@
- Remove all panic! and todo! - Remove all panic! and todo!
- Change REPL to execute code only after `;;` is found - Change REPL to execute code only after `;;` is found
- Forward the code generated by the REPL to the PHP repl - Forward the code generated by the REPL to the PHP repl
- Test assignment parsing
## v0.1.3 ## v0.1.3
- [ ] Test semantic analysis - [ ] Test semantic analysis
- [ ] Generate php code from current AST - [ ] Generate php code from current AST
- [x] Test correct operator precedence
- [x] Parse assignments
- [ ] Parse dot `.` operator
- [ ] Parse logic operators `&& ||`
- [ ] Parse Array access `arr[pos]`
- [ ] Parse namespace operator `::`
- [ ] Implement subtyping for numbers
## v0.1.2 ## v0.1.2

View File

@ -161,7 +161,7 @@ mod tests {
let label = &err.labels[0]; let label = &err.labels[0];
assert_eq!( assert_eq!(
label.message, label.message,
"Expected this expression to be a function, found a `Int`" "Expected this expression to be a function, found a Value(\"Int\")"
); );
assert_eq!(label.start, 0); assert_eq!(label.start, 0);
assert_eq!(label.end, 6); assert_eq!(label.end, 6);

View File

@ -25,6 +25,9 @@ impl SemanticCheck for Statement<'_> {
Statement::Conditional(c) => c.check_semantics(scope), Statement::Conditional(c) => c.check_semantics(scope),
Statement::ForLoop(f) => f.check_semantics(scope), Statement::ForLoop(f) => f.check_semantics(scope),
Statement::WhileLoop(w) => w.check_semantics(scope), Statement::WhileLoop(w) => w.check_semantics(scope),
Statement::Assignment(_assignment) => {
unimplemented!("Semantic check for an assignment")
}
} }
} }
} }

View File

@ -37,6 +37,15 @@ pub enum Statement<'a> {
Conditional(Conditional<'a>), Conditional(Conditional<'a>),
ForLoop(ForLoop<'a>), ForLoop(ForLoop<'a>),
WhileLoop(WhileLoop<'a>), WhileLoop(WhileLoop<'a>),
Assignment(Assignment<'a>),
}
#[derive(Debug)]
pub struct Assignment<'a> {
/// The left side of the assignment
pub identifier: &'a Token,
/// The right side of the assignment
pub expression: Box<Expression<'a>>,
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -0,0 +1,100 @@
use crate::{
error_handling::{error_messages::SYNTAX_INCOMPLETE_STATEMENT, ErrorContainer, ErrorLabel},
lexic::token::{self, TokenType},
syntax::{
ast::{Assignment, Expression},
parseable::{self, Parseable, ParsingError},
utils::{parse_token_type, try_many_operator},
},
};
/// https://thp-lang.org/spec/ast/ast/#assignment
impl<'a> Parseable<'a> for Assignment<'a> {
type Item = Assignment<'a>;
fn try_parse(
tokens: &'a Vec<token::Token>,
current_pos: usize,
) -> parseable::ParsingResult<'a, Self::Item> {
// parse the target identifier
let (identifier, next) = match parse_token_type(tokens, current_pos, TokenType::Identifier)
{
Ok(tuple) => tuple,
_ => return Err(ParsingError::Unmatched),
};
// parse the equal sign
let assignment_operators = vec![
String::from("="),
String::from("+="),
String::from("-="),
String::from("*="),
String::from("/="),
String::from("%="),
];
let (equal_operator, next) = match try_many_operator(tokens, next, assignment_operators) {
Ok((t, next)) => (t, next),
Err(ParsingError::Mismatch(t)) => {
// The parser found a token, but it's not the `=` operator
let label = ErrorLabel {
message: String::from("Expected an equal sign `=` here, the identifier"),
start: t.position,
end: t.get_end_position(),
};
let econtainer = ErrorContainer {
error_code: SYNTAX_INCOMPLETE_STATEMENT,
error_offset: t.position,
labels: vec![label],
note: None,
help: None,
};
return Err(ParsingError::Err(econtainer));
}
_ => {
// The parser didn't find the `=` operator after the identifier
let label = ErrorLabel {
message: String::from("Expected an equal sign `=` after this identifier"),
start: identifier.position,
end: identifier.get_end_position(),
};
let econtainer = ErrorContainer {
error_code: SYNTAX_INCOMPLETE_STATEMENT,
error_offset: identifier.position,
labels: vec![label],
note: None,
help: None,
};
return Err(ParsingError::Err(econtainer));
}
};
// parse the expression
let (expression, next) = match Expression::try_parse(tokens, next) {
Ok((exp, next)) => (exp, next),
_ => {
let label = ErrorLabel {
message: String::from("Expected an expression after this equal `=` operator"),
start: equal_operator.position,
end: equal_operator.get_end_position(),
};
let econtainer = ErrorContainer {
error_code: SYNTAX_INCOMPLETE_STATEMENT,
error_offset: equal_operator.position,
labels: vec![label],
note: None,
help: None,
};
return Err(ParsingError::Err(econtainer));
}
};
// Build and return the assignment object
let assignment = Assignment {
identifier,
expression: Box::new(expression),
};
Ok((assignment, next))
}
}

View File

@ -8,7 +8,7 @@ use crate::{
/// Parses a factor expression. /// Parses a factor expression.
/// ///
/// ```ebnf /// ```ebnf
/// factor = unary, (("/" | "*", "%"), unary)*; /// factor = unary, (("/" | "*" | "%"), unary)*;
/// ``` /// ```
pub fn try_parse(tokens: &Vec<Token>, pos: usize) -> ParsingResult<Expression> { pub fn try_parse(tokens: &Vec<Token>, pos: usize) -> ParsingResult<Expression> {
let (unary, next_pos) = match super::unary::try_parse(tokens, pos) { let (unary, next_pos) = match super::unary::try_parse(tokens, pos) {
@ -181,4 +181,34 @@ mod tests {
_ => panic!("Expected a binary operator"), _ => panic!("Expected a binary operator"),
} }
} }
#[test]
fn should_parse_mixed_with_unary_ops() {
let tokens = get_tokens(&String::from("2 * -4")).unwrap();
let (result, next) = try_parse(&tokens, 0).unwrap();
assert_eq!(next, 4);
match result {
Expression::BinaryOperator(lexpr, rexpr, op) => {
assert_eq!(op.value, "*");
let Expression::Int(left_value) = *lexpr else {
panic!("Expected an Int expression")
};
assert_eq!(left_value.value, "2");
let Expression::UnaryOperator(op, right_expr) = *rexpr else {
panic!("Expected a unary operator on the right")
};
assert_eq!(op.value, "-");
let Expression::Int(right_value) = *right_expr else {
panic!("Expected an Int expression");
};
assert_eq!(right_value.value, "4");
}
_ => {
panic!("Expected a binary operator, got {:?}", result)
}
}
}
} }

View File

@ -49,6 +49,8 @@ fn parse_many<'a>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use core::panic;
use super::*; use super::*;
use crate::lexic::get_tokens; use crate::lexic::get_tokens;
use crate::lexic::token::TokenType; use crate::lexic::token::TokenType;
@ -179,4 +181,37 @@ mod tests {
_ => panic!("Expected a binary operator"), _ => panic!("Expected a binary operator"),
} }
} }
#[test]
fn should_parse_correct_precedence() {
let tokens = get_tokens(&String::from("1 + 2 * 3")).unwrap();
let (result, next) = try_parse(&tokens, 0).unwrap();
assert_eq!(next, 5);
match result {
Expression::BinaryOperator(lexpr, rexpr, op) => {
assert_eq!(op.value, "+");
match (*lexpr, *rexpr) {
(Expression::Int(lvalue), Expression::BinaryOperator(llexpr, rrexpr, oop)) => {
assert_eq!(oop.value, "*");
assert_eq!(lvalue.value, "1");
match (*llexpr, *rrexpr) {
(Expression::Int(left), Expression::Int(right)) => {
assert_eq!(left.value, "2");
assert_eq!(right.value, "3");
}
_ => {
panic!("Expected left to be an int, right to be an int")
}
}
}
_ => {
panic!("Expected left to be an int, right to be a binary op")
}
}
}
_ => panic!("Expected a binary op, got {:?}", result),
}
}
} }

View File

@ -1,3 +1,4 @@
pub mod assignment;
pub mod binding; pub mod binding;
pub mod block; pub mod block;
pub mod conditional; pub mod conditional;

View File

@ -4,7 +4,7 @@ use crate::{
ast::{ ast::{
loops::{ForLoop, WhileLoop}, loops::{ForLoop, WhileLoop},
var_binding::VariableBinding, var_binding::VariableBinding,
Conditional, FunctionDeclaration, Statement, Assignment, Conditional, FunctionDeclaration, Statement,
}, },
parseable::{Parseable, ParsingError, ParsingResult}, parseable::{Parseable, ParsingError, ParsingResult},
}, },
@ -59,8 +59,13 @@ impl<'a> Parseable<'a> for Statement<'a> {
_ => {} _ => {}
} }
// Here nothing was parsed. // Try to parse an assignment
Err(ParsingError::Unmatched) // If this fails, return unmatched because there is still the
// possibility that an expression will be parsed later
match Assignment::try_parse(tokens, current_pos) {
Ok((prod, next)) => return Ok((Statement::Assignment(prod), next)),
_ => Err(ParsingError::Unmatched),
}
} }
} }

View File

@ -69,6 +69,23 @@ pub fn try_operator(tokens: &Vec<Token>, pos: usize, operator: String) -> Parsin
} }
} }
/// Expects the token at `pos` to be any of the passed operators. Doesn't ignore whitespace or newlines
pub fn try_many_operator(
tokens: &Vec<Token>,
pos: usize,
operators: Vec<String>,
) -> ParsingResult<&Token> {
//
for op in operators {
match try_operator(tokens, pos, op) {
Ok(v) => return Ok(v),
_ => {}
}
}
return Err(ParsingError::Unmatched);
}
/// Expects the token at `pos` to be of type `token_type`, and returns the token and the next position. /// Expects the token at `pos` to be of type `token_type`, and returns the token and the next position.
/// ///
/// Ignores all whitespace, newlines and comments. /// Ignores all whitespace, newlines and comments.