use crate::{
lexer::{tokens::Token, LexicalError, ErrorTip},
ast::{self, Condition, Operator, Operand, Expr, VarType, context::Context},
};
use lalrpop_util::ParseError;
grammar(context: &mut Context);
extern {
type Location = usize;
type Error = LexicalError;
enum Token {
"+" => Token::Add,
"-" => Token::Sub,
"*" => Token::Mul,
"/" => Token::Div,
"=" => Token::Assign,
"<" => Token::LessThan,
">" => Token::GreaterThan,
"<=" => Token::LessThanOrEqual,
">=" => Token::GreaterThanOrEqual,
"==" => Token::Equal,
"!=" => Token::NotEqual,
"&&" => Token::And,
"||" => Token::Or,
"if" => Token::If,
"goto" => Token::Goto,
"load" => Token::Load,
"store" => Token::Store,
"call" => Token::Call,
"identifier" => Token::Identifier(<String>),
"dereference" => Token::Dereference(<String>),
"type" => Token::Type(<String>),
"cast" => Token::Cast(<String>),
"literal_i8" => Token::LiteralI8(<i8>),
"literal_i16" => Token::LiteralI16(<i16>),
"literal_i32" => Token::LiteralI32(<i32>),
"literal_i64" => Token::LiteralI64(<i64>),
"literal_u8" => Token::LiteralU8(<u8>),
"literal_u16" => Token::LiteralU16(<u16>),
"literal_u32" => Token::LiteralU32(<u32>),
"literal_u64" => Token::LiteralU64(<u64>),
"literal_f32" => Token::LiteralF32(<f32>),
"literal_f64" => Token::LiteralF64(<f64>),
"literal_true" => Token::LiteralTrue,
"literal_false" => Token::LiteralFalse,
"literal_string" => Token::LiteralString(<String>),
"func" => Token::Function,
"begin" => Token::Begin,
"end" => Token::End,
"return" => Token::Return,
"[" => Token::OpenBracket,
"]" => Token::CloseBracket,
"(" => Token::OpenParen,
")" => Token::CloseParen,
":" => Token::Colon,
}
}
pub Program: Vec<ast::Statement> = {
<stmts:Statement*> => stmts
};
Statement: ast::Statement = {
// Variable declaration/definition
<l1:@L> <name:"identifier"> <r1:@R> ":" <l2:@L> <var_type:"type"> <r2:@R> "=" <l3:@L> <value:Expr> <r3:@R> =>? {
let variable = match value.clone() {
Expr::Operand(operand) => {
match operand {
Operand::Identifier(ref variable_name) => {
let variable = context.get_variable(variable_name.clone()).ok_or(ParseError::User { error: LexicalError::UnknownVariable {
error: vec![
ErrorTip { message: format!("unknown variable `{}`", variable_name), location: l3..r3 },
],
help: None
}})?;
if variable.var_type != var_type.clone().into() {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: format!("expected `{}`", Into::<String>::into(var_type.clone())), location: l2..r2 },
ErrorTip { message: format!("found variable {} which is `{}`", variable_name, Into::<String>::into(variable.var_type)), location: l3..r3 },
],
help: Some(
format!(
"You can either try to cast the value to `{}` or change the variable type to `{}`",
Into::<String>::into(var_type),
Into::<String>::into(variable.var_type)
)
),
}});
}
let value = if context.optimization_level > 0 {
variable.value.clone()
} else {
ast::Expr::Operand(operand)
};
ast::Statement::VariableDeclaration(ast::Variable {
name,
value,
var_type: variable.var_type,
location: l1..r1,
})
},
_ => {
let var_type: VarType = var_type.try_into().unwrap();
let value_type: VarType = operand.clone().try_into().unwrap();
if var_type != value_type {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: format!("expected `{}`", Into::<String>::into(var_type)), location: l2..r2 },
ErrorTip { message: format!("found `{}`", Into::<String>::into(value_type)), location: l3..r3 },
],
help: Some(
format!(
"You can either try to cast the value to `{}` or change the variable type to `{}`",
Into::<String>::into(var_type),
Into::<String>::into(value_type)
)
),
}});
}
ast::Statement::VariableDeclaration(ast::Variable {
name,
var_type,
value,
location: l1..r1,
})
},
}
},
Expr::BinaryOperation(bin_op) => {
let var_type: VarType = var_type.try_into().unwrap();
let value_type: VarType = match bin_op {
ast::BinaryOperation::Arithmetic { operation_type, .. } => operation_type,
ast::BinaryOperation::Conditional { operation_type, .. } => operation_type,
};
if var_type != value_type {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: format!("expected `{}`", Into::<String>::into(var_type)), location: l2..r2 },
ErrorTip { message: format!("found `{}`", Into::<String>::into(value_type)), location: l3..r3 },
],
help: Some(
format!(
"You can either try to cast the value to `{}` or change the variable type to `{}`",
Into::<String>::into(var_type),
Into::<String>::into(value_type)
)
),
}});
}
ast::Statement::VariableDeclaration(ast::Variable {
name,
var_type,
value: Expr::BinaryOperation(bin_op),
location: l1..r1,
})
},
Expr::FunctionCall(function) => {
let var_type: VarType = var_type.try_into().unwrap();
let value_type: VarType = function.return_type;
if var_type != value_type {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: format!("expected `{}`", Into::<String>::into(var_type)), location: l2..r2 },
ErrorTip { message: format!("found `{}`", Into::<String>::into(value_type)), location: l3..r3 },
],
help: Some(
format!(
"You can either try to cast the value to `{}` or change the variable type to `{}`",
Into::<String>::into(var_type),
Into::<String>::into(value_type)
)
),
}});
}
ast::Statement::VariableDeclaration(ast::Variable {
name,
var_type,
value: Expr::FunctionCall(function),
location: l1..r1,
})
}
};
context.add_variable(variable.clone());
Ok(variable)
},
<l0:@L> "if" <r0:@R> <l1:@L> <condition:Expr> <r1:@R> "goto" <l2:@L> <label:"identifier"> <r2:@R> =>? {
match condition {
Expr::Operand(operand) => {
match operand {
Operand::LiteralBool(value) => {
if value {
Ok(ast::Statement::UnconditionalJump {
label,
location: l1..r2,
})
} else {
Ok(ast::Statement::NoOperation)
}
},
Operand::Identifier(ident) => {
let variable = context.get_variable(ident.clone()).ok_or(ParseError::User { error: LexicalError::UnknownVariable {
error: vec![
ErrorTip { message: format!("unknown variable `{}`", ident), location: l1..r1 },
],
help: None
}})?;
if variable.var_type != VarType::Bool {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: "expected `bool`".into(), location: l0..r0 },
ErrorTip { message: format!("found `{}`", Into::<String>::into(variable.var_type)), location: l1..r1 },
],
help: Some(
"You can either try to cast the value to `bool` or change the variable type to `bool`".to_string()
),
}});
}
Ok(ast::Statement::ConditionalJump {
condition: ast::Expr::Operand(ast::Operand::Identifier(ident)),
label,
location: l1..r2,
})
},
_ => {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: "expected `bool`".into(), location: l0..r0 },
ErrorTip { message: format!("found `{}`", Into::<String>::into(operand.get_type(context, l1..r1)?)), location: l1..r1 },
],
help: Some(
"You can either try to cast the value to `bool` or change the variable type to `bool`".to_string()
),
}});
},
}
},
Expr::BinaryOperation(op) => {
match op {
ast::BinaryOperation::Conditional { operation_type, .. } => {
if operation_type != VarType::Bool {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: "expected `bool`".into(), location: l0..r0 },
ErrorTip { message: format!("found `{}`", Into::<String>::into(operation_type)), location: l1..r1 },
],
help: Some(
"You can either try to cast the value to `bool` or change the variable type to `bool`".to_string()
),
}});
}
Ok(ast::Statement::ConditionalJump {
condition: ast::Expr::BinaryOperation(op),
label,
location: l1..r2,
})
},
ast::BinaryOperation::Arithmetic { operation_type, .. } => {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: "expected `bool`".into(), location: l0..r0 },
ErrorTip { message: format!("found `{}`", Into::<String>::into(operation_type)), location: l1..r1 },
],
help: Some(
"You can either try to cast the value to `bool` or change the binary operation type to a `comparison`".to_string()
),
}});
},
}
},
_ => {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: "expected `bool`".into(), location: l1..r1 },
ErrorTip { message: format!("found `{:?}`", condition), location: l1..r1 },
],
help: Some(
"You can either try to cast the value to `bool` or change the variable type to `bool`".to_string()
),
}});
}
}
},
"goto" <label:"identifier"> => {
ast::Statement::UnconditionalJump {
label,
location: 0..0,
}
},
<name:"identifier"> ":" => {
ast::Statement::Label {
name,
location: 0..0,
}
},
"func" <l1:@L> <name:"identifier"> <r1:@R> "(" <args:Arguments> ")" <return_type:Return?> "begin" <body:Statement*> "end" =>? {
let return_type = match return_type {
Some(return_type) => return_type.var_type,
None => VarType::Void,
};
let function = ast::Function {
name,
args,
return_type,
body,
location: l1..r1,
is_builtin: false,
};
context.add_function(&function).map_err(|error| ParseError::User { error: LexicalError::FunctionIsBuiltin {
error: vec![
ErrorTip { message: format!("function `{}` is already defined", function.name), location: l1..r1 },
],
help: Some(format!("You can either try to change the function name or remove the function definition"))
}})?;
Ok(ast::Statement::FunctionDefinition(function))
},
<l:@L> "store" <at:"dereference"> <from:"identifier"> <r:@R> => {
ast::Statement::Store {
at: ast::Operand::Dereference(at),
from: ast::Operand::Identifier(from),
location: l..r,
}
},
<l:@L> <function:FunctionCall> <r:@R> =>? {
if function.return_type != VarType::Void {
// The function return a value that is not being used
return Err(ParseError::User { error: LexicalError::UnusedValue {
error: vec![
ErrorTip { message: format!("unused value of type `{}`", Into::<String>::into(function.return_type)), location: l..r },
],
help: Some("You can either assign the value to a variable or remove the function call".to_string())
}});
}
Ok(ast::Statement::Call(function))
},
};
Return: ast::Return = {
":" <var_type:"type"> => {
ast::Return { var_type: var_type.into() }
},
}
Arguments: Vec<ast::Argument> = {
<args:Argument*> => args
};
Argument: ast::Argument = {
<name:"identifier"> ":" <var_type:"type"> => {
ast::Argument {
name,
var_type: var_type.into(),
}
},
};
Parameters: Vec<ast::Operand> = {
<params:Operand*> => params
};
FunctionCall: ast::FunctionCall = {
<l:@L> "call" <r:@R> <l1:@L> <name:"identifier"> <r1:@R> "(" <l2:@L> <params:Parameters> <r2:@R> ")" =>? {
let function = context.get_function(&name).ok_or(ParseError::User { error: LexicalError::UnknownFunction {
error: vec![
ErrorTip { message: format!("unknown function `{}`", name), location: l1..r1 },
],
help: None
}})?;
if function.args.len() != params.len() {
return Err(ParseError::User { error: LexicalError::WrongArgumentCount {
error: vec![
ErrorTip { message: format!("expected `{}` arguments", function.args.len()), location: l1..r1 },
ErrorTip { message: format!("found `{}` arguments", params.len()), location: l1..r1 },
],
help: Some(
format!(
"You can either try to cast the value to `{}` or change the variable type to `{}`",
function.args.len(),
params.len()
)
),
}});
}
for (i, (arg, param)) in function.args.iter().zip(params.iter()).enumerate() {
if arg.var_type != param.get_type(context, l2..r2)? {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: format!("expected {i}th argument to be `{}`", Into::<String>::into(arg.var_type)), location: l1..r1 },
ErrorTip { message: format!("found `{}`", Into::<String>::into(param.get_type(context, l2..r2)?)), location: l2..r2 },
],
help: Some(
format!(
"You can either try to cast the value to `{}` or change the variable type to `{}`",
Into::<String>::into(arg.var_type),
Into::<String>::into(param.get_type(context, l2..r2)?)
)
),
}});
}
}
Ok(ast::FunctionCall {
name,
params,
return_type: function.return_type,
location: l..r,
})
}
};
BinaryOperation: ast::BinaryOperation = {
<l1:@L> <lhs:Operand> <r1:@R> <operator:Operator> <l2:@L> <rhs:Operand> <r2:@R> =>? {
let lhs_type = lhs.clone().get_type(context, l1..r1)?;
let rhs_type = rhs.clone().get_type(context, l2..r2)?;
if lhs_type != rhs_type {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: format!("expected `{}`", Into::<String>::into(lhs_type)), location: l1..r1 },
ErrorTip { message: format!("found `{}`", Into::<String>::into(rhs_type)), location: l2..r2 },
],
help: Some(
format!(
"Cannot perform the operation `{}` with `{}` and `{}`, you can either try to cast the value to `{}` or change the variable type to `{}`",
operator,
lhs_type,
rhs_type,
lhs_type,
rhs_type
)
),
}});
}
Ok(ast::BinaryOperation::Arithmetic {
lhs,
rhs,
operator,
operation_type: lhs_type,
})
},
<l1:@L> <lhs:Operand> <r1:@R> <condition:Condition> <l2:@L> <rhs:Operand> <r2:@R> =>? {
let lhs_type = lhs.clone().get_type(context, l1..r1)?;
let rhs_type = rhs.clone().get_type(context, l2..r2)?;
if lhs_type != rhs_type {
return Err(ParseError::User { error: LexicalError::WrongType {
error: vec![
ErrorTip { message: format!("expected `{}`", Into::<String>::into(lhs_type)), location: l1..r1 },
ErrorTip { message: format!("found `{}`", Into::<String>::into(rhs_type)), location: l2..r2 },
],
help: Some(
format!(
"Cannot compare `{}` with `{}`, you can either try to cast the value to `{}` or change the variable type to `{}`",
Into::<String>::into(lhs_type),
Into::<String>::into(rhs_type),
Into::<String>::into(lhs_type),
Into::<String>::into(rhs_type)
)
),
}});
}
Ok(ast::BinaryOperation::Conditional {
lhs,
rhs,
condition,
operation_type: VarType::Bool,
})
},
};
Operand: ast::Operand = {
<name:"identifier"> => Operand::Identifier(name),
<value:"literal_i8"> => Operand::LiteralI8(value),
<value:"literal_i16"> => Operand::LiteralI16(value),
<value:"literal_i32"> => Operand::LiteralI32(value),
<value:"literal_i64"> => Operand::LiteralI64(value),
<value:"literal_u8"> => Operand::LiteralU8(value),
<value:"literal_u16"> => Operand::LiteralU16(value),
<value:"literal_u32"> => Operand::LiteralU32(value),
<value:"literal_u64"> => Operand::LiteralU64(value),
<value:"literal_f32"> => Operand::LiteralF32(value),
<value:"literal_f64"> => Operand::LiteralF64(value),
"literal_true" => Operand::LiteralBool(true),
"literal_false" => Operand::LiteralBool(false),
<value:"literal_string"> => Operand::LiteralStr(value),
};
Operator: ast::Operator = {
"+" => Operator::Add,
"-" => Operator::Sub,
"*" => Operator::Mul,
"/" => Operator::Div,
};
Expr: ast::Expr = {
BinaryOperation => Expr::BinaryOperation(<>),
Operand => Expr::Operand(<>),
FunctionCall => Expr::FunctionCall(<>),
};
Condition: ast::Condition = {
"<" => Condition::LessThan,
">" => Condition::GreaterThan,
"<=" => Condition::LessThanOrEqual,
">=" => Condition::GreaterThanOrEqual,
"==" => Condition::Equal,
"!=" => Condition::NotEqual,
"&&" => Condition::And,
"||" => Condition::Or,
};