diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f8b8404 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,55 @@ +# Contributing to Klang + +Thank you for your interest in contributing to Klang! We welcome contributions from everyone. This document provides guidelines and instructions for contributing to the project. + +## Development Setup + +1. Ensure you have Rust and Cargo installed. If not, follow the instructions [here](https://www.rust-lang.org/tools/install). + +2. Build the project: + +``` +cargo build +``` + +3. Run the tests: + +``` +cargo test +``` + +## Development Workflow + +1. Create a new branch for your feature or bug fix: + +``` +git checkout -b feature-or-fix-name +``` + +2. Make your changes and commit them with a clear commit message. + +3. Push your changes to your fork: + +``` +git push origin feature-or-fix-name +``` + +4. Open a pull request against the main repository. + +## Coding Standards + +- Follow the Rust style guide. You can use `rustfmt` to automatically format your code: + +``` +cargo fmt +``` + +- Run `clippy` to catch common mistakes and improve your code: + +``` +cargo clippy +``` + +## Running Klang + +To run the Klang interpreter: diff --git a/klang/src/ast.rs b/klang/src/ast.rs index 49d6a24..7689eac 100644 --- a/klang/src/ast.rs +++ b/klang/src/ast.rs @@ -1,103 +1,128 @@ use pest::error::Error as PestError; use pest::error::LineColLocation; +use pest::pratt_parser::{Assoc, Op, PrattParser}; use pest::Parser; use pest::Position; use pest_derive::Parser; use std::fs; use std::path::Path; -use thiserror::Error; #[derive(Parser)] #[grammar = "klang.pest"] pub struct Klang; +use lazy_static::lazy_static; + +lazy_static! { + static ref PRATT_PARSER: PrattParser = { + use Assoc::*; + use Rule::*; + + PrattParser::new() + .op(Op::infix(add, Left) | Op::infix(subtract, Left)) + .op(Op::infix(multiply, Left) | Op::infix(divide, Left)) + .op(Op::prefix(unary_minus)) + }; +} + #[derive(Debug, PartialEq, Eq)] pub struct Program { - pub statements: Vec, + pub import_statement: Option, + pub functions: Vec, } -impl Program { - pub fn new(statements: Vec) -> Self { - Program { statements } - } +#[derive(Debug, PartialEq, Eq)] +pub struct ImportStatement { + pub module: String, + pub imports: Vec, } #[derive(Debug, PartialEq, Eq)] -pub enum Stmt { - ActionDef(ActionDefStmt), - ActionCall(ActionCallStmt), - Loop(LoopStmt), +pub struct FunctionDef { + pub name: String, + pub parameters: Vec, + pub body: Block, } #[derive(Debug, PartialEq, Eq)] -pub struct ActionDefStmt { - pub notes: Option, - pub outcomes: Option, +pub struct Block { + pub statements: Vec, } -impl ActionDefStmt { - pub fn new(notes: Option, outcomes: Option) -> Self { - ActionDefStmt { notes, outcomes } - } +#[derive(Debug, PartialEq, Eq)] +pub enum Statement { + Expression(Expression), + Assignment(Assignment), + ForLoop(ForLoop), + WhileLoop(WhileLoop), + IfStatement(IfStatement), + ParallelExecution(ParallelExecution), } #[derive(Debug, PartialEq, Eq)] -pub struct ActionCallStmt { - pub name: String, +pub struct Assignment { + pub identifier: String, + pub expression: Expression, } -impl ActionCallStmt { - pub fn new(name: String) -> Self { - ActionCallStmt { name } - } +#[derive(Debug, PartialEq, Eq)] +pub struct ForLoop { + pub iterator: String, + pub iterable: Expression, + pub body: Block, } #[derive(Debug, PartialEq, Eq)] -pub struct LoopStmt { - pub actions: Vec, - pub condition: Option>, +pub struct WhileLoop { + pub condition: Expression, + pub body: Block, } -impl LoopStmt { - pub fn new(actions: Vec, condition: Option>) -> Self { - LoopStmt { actions, condition } - } +#[derive(Debug, PartialEq, Eq)] +pub struct IfStatement { + pub condition: Expression, + pub body: Block, } #[derive(Debug, PartialEq, Eq)] -pub struct NotesBlock { - pub notes: Vec, +pub struct ParallelExecution { + pub expressions: Vec, } -impl NotesBlock { - pub fn new(notes: Vec) -> Self { - NotesBlock { notes } - } +#[derive(Debug, PartialEq, Eq)] +pub enum Expression { + FunctionCall(FunctionCall), + BinaryOperation(Box), + UnaryOperation(Box), + Literal(Literal), + Identifier(String), } #[derive(Debug, PartialEq, Eq)] -pub enum Note { - Prefer(String), - Avoid(String), +pub struct FunctionCall { + pub name: String, + pub arguments: Vec, } #[derive(Debug, PartialEq, Eq)] -pub struct OutcomesBlock { - pub outcomes: Vec, +pub struct BinaryOperation { + pub left: Expression, + pub operator: String, + pub right: Expression, } -impl OutcomesBlock { - pub fn new(outcomes: Vec) -> Self { - OutcomesBlock { outcomes } - } +#[derive(Debug, PartialEq, Eq)] +pub struct UnaryOperation { + pub operator: String, + pub operand: Expression, } #[derive(Debug, PartialEq, Eq)] -pub enum Outcome { - Success(String), - Failure(String), - Retry(String), - Handler(String), +pub enum Literal { + Number(String), + String(String), + Boolean(bool), + Array(Vec), } #[derive(Debug, Error)] @@ -168,114 +193,178 @@ mod builders { use super::*; pub fn build_ast(pair: pest::iterators::Pair) -> Result { - let mut statements = Vec::new(); + let mut import_statement = None; + let mut functions = Vec::new(); for inner_pair in pair.into_inner() { match inner_pair.as_rule() { - Rule::stmt => { - let mut inner_rules = inner_pair.into_inner(); - let stmt = build_stmt(inner_rules.next().unwrap())?; - statements.push(stmt); + Rule::import_statement => { + import_statement = Some(build_import_statement(inner_pair)?); + } + Rule::function_def => { + functions.push(build_function_def(inner_pair)?); } Rule::EOI => {} _ => unreachable!(), } } - Ok(Program::new(statements)) + Ok(Program { + import_statement, + functions, + }) } - fn build_stmt(pair: pest::iterators::Pair) -> Result { - match pair.as_rule() { - Rule::action_def_stmt => build_action_def_stmt(pair).map(Stmt::ActionDef), - Rule::action_call_stmt => build_action_call_stmt(pair).map(Stmt::ActionCall), - Rule::loop_stmt => build_loop_stmt(pair).map(Stmt::Loop), - _ => unreachable!(), - } + fn build_import_statement( + pair: pest::iterators::Pair, + ) -> Result { + let mut inner_rules = pair.into_inner(); + let module = inner_rules.next().unwrap().as_str().to_string(); + let imports = inner_rules.map(|p| p.as_str().to_string()).collect(); + + Ok(ImportStatement { module, imports }) } - fn build_action_def_stmt(pair: pest::iterators::Pair) -> Result { - for inner_pair in pair.into_inner() { - if inner_pair.as_rule() == Rule::action_def_body { - let mut inner_rules = inner_pair.into_inner(); - let notes = inner_rules - .clone() // Clone the iterator to use it later - .find(|p| p.as_rule() == Rule::notes_block) - .map(build_notes_block) - .transpose()?; - let outcomes = inner_rules - .find(|p| p.as_rule() == Rule::outcomes_block) - .map(build_outcomes_block) - .transpose()?; - return Ok(ActionDefStmt::new(notes, outcomes)); - } - } + fn build_function_def(pair: pest::iterators::Pair) -> Result { + let mut inner_rules = pair.into_inner(); + let name = inner_rules.next().unwrap().as_str().to_string(); + let parameters = build_parameter_list(inner_rules.next().unwrap())?; + let body = build_block(inner_rules.next().unwrap())?; + + Ok(FunctionDef { + name, + parameters, + body, + }) + } - unreachable!() + fn build_parameter_list(pair: pest::iterators::Pair) -> Result, String> { + pair.into_inner() + .map(|p| Ok(p.as_str().to_string())) + .collect() } - fn build_notes_block(pair: pest::iterators::Pair) -> Result { - let notes = pair + fn build_block(pair: pest::iterators::Pair) -> Result { + let statements = pair .into_inner() - .filter(|p| p.as_rule() == Rule::note) - .map(|note_pair| { - let mut inner_rules = note_pair.into_inner(); - let note_type = inner_rules.next().unwrap().as_rule(); - let note_name = inner_rules.next().unwrap().as_str().to_string(); - match note_type { - Rule::PREFER => Note::Prefer(note_name), - Rule::AVOID => Note::Avoid(note_name), - _ => unreachable!(), - } - }) - .collect(); + .map(build_statement) + .collect::, _>>()?; - Ok(NotesBlock::new(notes)) + Ok(Block { statements }) } - fn build_outcomes_block(pair: pest::iterators::Pair) -> Result { - let outcomes = pair - .into_inner() - .filter(|p| p.as_rule() == Rule::outcome) - .map(|outcome_pair| { - let mut inner_rules = outcome_pair.into_inner(); - let outcome_type = inner_rules.next().unwrap().as_rule(); - let outcome_name = inner_rules.next().unwrap().as_str().to_string(); - match outcome_type { - Rule::SUCCESS => Outcome::Success(outcome_name), - Rule::FAILURE => Outcome::Failure(outcome_name), - Rule::RETRY => Outcome::Retry(outcome_name), - Rule::HANDLER => Outcome::Handler(outcome_name), - _ => unreachable!(), - } - }) - .collect(); + fn build_statement(pair: pest::iterators::Pair) -> Result { + match pair.as_rule() { + Rule::expression_stmt => Ok(Statement::Expression(build_expression( + pair.into_inner().next().unwrap(), + )?)), + Rule::assignment_stmt => { + let mut inner_rules = pair.into_inner(); + let identifier = inner_rules.next().unwrap().as_str().to_string(); + let expression = build_expression(inner_rules.next().unwrap())?; + Ok(Statement::Assignment(Assignment { + identifier, + expression, + })) + } + Rule::for_loop => { + let mut inner_rules = pair.into_inner(); + let iterator = inner_rules.next().unwrap().as_str().to_string(); + let iterable = build_expression(inner_rules.next().unwrap())?; + let body = build_block(inner_rules.next().unwrap())?; + Ok(Statement::ForLoop(ForLoop { + iterator, + iterable, + body, + })) + } + Rule::while_loop => { + let mut inner_rules = pair.into_inner(); + let condition = build_expression(inner_rules.next().unwrap())?; + let body = build_block(inner_rules.next().unwrap())?; + Ok(Statement::WhileLoop(WhileLoop { condition, body })) + } + Rule::if_statement => { + let mut inner_rules = pair.into_inner(); + let condition = build_expression(inner_rules.next().unwrap())?; + let body = build_block(inner_rules.next().unwrap())?; + Ok(Statement::IfStatement(IfStatement { condition, body })) + } + Rule::parallel_execution => { + let expressions = pair + .into_inner() + .map(build_expression) + .collect::, _>>()?; + Ok(Statement::ParallelExecution(ParallelExecution { + expressions, + })) + } + _ => unreachable!(), + } + } - Ok(OutcomesBlock::new(outcomes)) + fn build_expression(pair: pest::iterators::Pair) -> Result { + match pair.as_rule() { + Rule::function_call => build_function_call(pair).map(Expression::FunctionCall), + Rule::binary_operation => { + build_binary_operation(pair).map(|bo| Expression::BinaryOperation(Box::new(bo))) + } + Rule::unary_operation => { + build_unary_operation(pair).map(|uo| Expression::UnaryOperation(Box::new(uo))) + } + Rule::literal => build_literal(pair).map(Expression::Literal), + Rule::identifier => Ok(Expression::Identifier(pair.as_str().to_string())), + _ => unreachable!(), + } } - fn build_action_call_stmt(pair: pest::iterators::Pair) -> Result { - let name = pair.into_inner().next().unwrap().as_str().to_string(); - Ok(ActionCallStmt::new(name)) + fn build_function_call(pair: pest::iterators::Pair) -> Result { + let mut inner_rules = pair.into_inner(); + let name = inner_rules.next().unwrap().as_str().to_string(); + let arguments = inner_rules + .map(build_expression) + .collect::, _>>()?; + + Ok(FunctionCall { name, arguments }) } - fn build_loop_stmt(pair: pest::iterators::Pair) -> Result { + fn build_binary_operation( + pair: pest::iterators::Pair, + ) -> Result { let mut inner_rules = pair.into_inner(); + let left = build_expression(inner_rules.next().unwrap())?; + let operator = inner_rules.next().unwrap().as_str().to_string(); + let right = build_expression(inner_rules.next().unwrap())?; + + Ok(BinaryOperation { + left, + operator, + right, + }) + } - let actions: Vec<_> = inner_rules - .clone() // Clone the iterator to use it later - .filter(|p| p.as_rule() == Rule::action_call_stmt) - .map(build_action_call_stmt) - .collect::, _>>()?; + fn build_unary_operation(pair: pest::iterators::Pair) -> Result { + let mut inner_rules = pair.into_inner(); + let operator = inner_rules.next().unwrap().as_str().to_string(); + let operand = build_expression(inner_rules.next().unwrap())?; - let condition = inner_rules - .find(|p| p.as_rule() == Rule::condition) - .map(|p| { - p.into_inner() - .map(|outcome_name| outcome_name.as_str().to_string()) - .collect() - }); + Ok(UnaryOperation { operator, operand }) + } - Ok(LoopStmt::new(actions, condition)) + fn build_literal(pair: pest::iterators::Pair) -> Result { + let inner_pair = pair.into_inner().next().unwrap(); + match inner_pair.as_rule() { + Rule::number => Ok(Literal::Number(inner_pair.as_str().to_string())), + Rule::string => Ok(Literal::String(inner_pair.as_str().to_string())), + Rule::boolean => Ok(Literal::Boolean(inner_pair.as_str() == "true")), + Rule::array => { + let elements = inner_pair + .into_inner() + .map(build_expression) + .collect::, _>>()?; + Ok(Literal::Array(elements)) + } + _ => unreachable!(), + } } } diff --git a/klang/src/klang.pest b/klang/src/klang.pest index bed45bf..1b56ca7 100644 --- a/klang/src/klang.pest +++ b/klang/src/klang.pest @@ -1,84 +1,67 @@ -// Pest Grammar for the Custom Language +// Pest grammar for Klang. + +WHITESPACE = _{ " " | "\t" | "\r" | "\n" } +COMMENT = _{ "//" ~ (!"\n" ~ ANY)* ~ "\n" } + +program = { SOI ~ import_statement? ~ function_def* ~ EOI } + +import_statement = { "from" ~ identifier ~ "import" ~ (identifier ~ ("," ~ identifier)*) } + +function_def = { "fn" ~ identifier ~ "(" ~ parameter_list? ~ ")" ~ block } +parameter_list = { identifier ~ ("," ~ identifier)* } + +block = { "{" ~ statement* ~ "}" } + +statement = { + expression_stmt | + assignment_stmt | + for_loop | + while_loop | + if_statement | + parallel_execution +} + +expression_stmt = { expression ~ ";" } +assignment_stmt = { identifier ~ "=" ~ expression ~ ";" } + +for_loop = { "for" ~ identifier ~ ":" ~ expression ~ block } +while_loop = { "while" ~ expression ~ block } +if_statement = { "if" ~ "(" ~ expression ~ ")" ~ block } + +parallel_execution = { "*" ~ "(" ~ expression ~ ("," ~ expression)* ~ ")" ~ ";" } + +expression = { + function_call | + binary_operation | + unary_operation | + literal | + identifier +} + +function_call = { identifier ~ "(" ~ (expression ~ ("," ~ expression)*)? ~ ")" } +binary_operation = { expression ~ operator ~ expression } +unary_operation = { operator ~ expression } + +literal = { number | string | boolean | array } +number = @{ + ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? ~ unit? +} + +unit = @{ + // Length units (metric) + "mm" | "cm" | "m" | "km" | + // Length units (imperial) + "in" | "ft" | "yd" | "mi" | + // Time units + "ms" | "s" | "min" | "hr" | "day" | + // Angle units + "deg" | "rad" +} -// The root rule that starts the parsing process. -program = _{ SOI ~ stmt* ~ EOI } - -// Statements include function definitions, function calls, and other constructs. -stmt = _{ ( import_stmt | function_def | expr_stmt ) } - -// Import statement -import_stmt = _{ "from" ~ IDENT ~ "import" ~ IDENT ~ ("," ~ IDENT)* } - -// Function definition -function_def = _{ "fn" ~ IDENT ~ "(" ~ param_list? ~ ")" ~ block } - -param_list = _{ (IDENT ~ "=" ~ expr) ~ ("," ~ IDENT ~ "=" ~ expr)* } - -// Block of statements enclosed in curly braces -block = _{ "{" ~ stmt* ~ "}" } - -// Expression statement -expr_stmt = _{ expr ~ ";"? } - -// Expression (can be a function call, assignment, or binary operation) -expr = _{ ( - function_call - | assignment - | binary_op - | unary_op - | literal - | loop - | COMMENT - | IDENT -) } - -// Function call with potential async operator `*` -function_call = _{ ( async_call | standard_call ) } -async_call = _{ "*" ~ standard_call } -standard_call = _{ IDENT ~ "(" ~ arg_list? ~ ")" } -arg_list = _{ expr ~ ("," ~ expr)* } - -// Containers -container = _{ list | dict -list = _{ "[" ~ expr ~ "," ~ expr ~ "]" } - -// Assignment statement -assignment = _{ IDENT ~ "=" ~ expr } - -// Binary operation (addition, subtraction, etc.) -binary_op = _{ expr ~ binary_operator ~ expr } -binary_operator = _{ "+" | "-" | "*" | "/" | "?" | ":" } - -// Unary operation (negation, etc.) -unary_op = _{ unary_operator ~ expr } -unary_operator = _{ "-" | "!" } - -// Literals include numbers and units like `30deg`, `0.1rad` -literal = _{ ( - number_with_unit - | number - | string - | boolean -) } - -number_with_unit = _{ number ~ unit } -unit = _{ "deg" | "rad" | "m" | "cm" | "mm" | "ft" | "in" } - -number = @{ ASCII_DIGIT+ ~ ("." ~ ASCII_DIGIT+)? } string = @{ "\"" ~ (!"\"" ~ ANY)* ~ "\"" } -boolean = _{ "true" | "false" } - -// Identifiers (function names, variable names, etc.) -IDENT = @{ ASCII_ALPHANUMERIC+ } - -// A loop with `for` and `while` constructs -loop = _{ ( for_loop | while_loop ) } - -for_loop = _{ "for" ~ IDENT ~ ":" ~ range ~ block } -while_loop = _{ "while" ~ expr ~ block } +boolean = { "true" | "false" } +array = { "[" ~ (expression ~ ("," ~ expression)*)? ~ "]" } -// Comments -COMMENT = _{ "//" ~ (!"\n" ~ ANY)* } +identifier = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } +operator = { "+" | "-" | "*" | "/" | "=" | "!" | "?" | ":" } -// Whitespaces -WHITESPACE = _{ " " | "\t" | "\n" | "\r" }