Compare commits
3 Commits
2a2da32245
...
b4e5caa0f0
Author | SHA1 | Date | |
---|---|---|---|
b4e5caa0f0 | |||
aede32a068 | |||
68d3e0dba4 |
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)]
|
||||||
|
100
src/syntax/parsers/assignment.rs
Normal file
100
src/syntax/parsers/assignment.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
Loading…
Reference in New Issue
Block a user