diff --git a/src/parser/ast.rs b/src/parser/ast.rs index b17caf1..057fac0 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -112,6 +112,7 @@ pub enum Expression { Call(Box), FieldAccess(Box), StructInit(Box), + ArrayLiteral(Box), } #[derive(Debug, Clone)] @@ -178,3 +179,9 @@ pub enum TypeExpr { Array(Box), Optional(Box), } + +#[derive(Debug, Clone)] +pub struct ArrayLiteral { + pub elements: Vec, + pub span: Span, +} diff --git a/src/parser/grammar.pest b/src/parser/grammar.pest index 9841b2a..fdf0ac9 100644 --- a/src/parser/grammar.pest +++ b/src/parser/grammar.pest @@ -23,21 +23,50 @@ param = { identifier ~ ":" ~ type_expr } // statements block = { "{" ~ statement* ~ "}" } -statement = _{ let_stmt | return_stmt | if_stmt | for_stmt | expression } +statement = _{ let_stmt | return_stmt | if_stmt | for_stmt | assign_stmt | expression } +assign_stmt = { identifier ~ "=" ~ expression } let_stmt = { "let" ~ identifier ~ (":" ~ type_expr)? ~ "=" ~ expression } return_stmt = { "return" ~ expression? } if_stmt = { "if" ~ expression ~ block ~ ("else" ~ block)? } for_stmt = { "for" ~ identifier ~ "in" ~ expression ~ block } -// expressions — broken into precedence layers, no cycles -expression = { primary ~ (access_chain)* } -access_chain = _{ field_access | call_suffix } -field_access = { "." ~ identifier } -call_suffix = { "(" ~ (expression ~ ("," ~ expression)*)? ~ ")" } -primary = _{ float | integer | string_lit | boolean | identifier } - // types — optional is a suffix, not a recursive wrapper type_expr = { base_type ~ "?"? } base_type = _{ array_type | identifier } array_type = { "[" ~ type_expr ~ "]" } + +// expressions +expression = { term ~ (bin_op ~ term)* } +bin_op = _{ add | sub | mul | div | eq | neq | lte | gte | lt | gt | and | or } +add = { "+" } +sub = { "-" } +mul = { "*" } +div = { "/" } +eq = { "==" } +neq = { "!=" } +lte = { "<=" } +gte = { ">=" } +lt = { "<" } +gt = { ">" } +and = { "&&" } +or = { "||" } + +struct_literal = { + identifier ~ "{" ~ (struct_init_field ~ ("," ~ struct_init_field)*)? ~ "}" +} + +struct_init_field = { + identifier ~ ":" ~ expression +} + +array_literal = { + "[" ~ (expression ~ ("," ~ expression)*)? ~ "]" +} + +term = { primary ~ (field_access | call_suffix)* } +field_access = { "." ~ identifier } +call_suffix = { "(" ~ (expression ~ ("," ~ expression)*)? ~ ")" } +primary = _{ struct_literal | array_literal | float | integer | string_lit | boolean | self_kw | null_kw | identifier } +self_kw = { "self" } +null_kw = { "null" } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 723005f..5d2bb50 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -219,52 +219,114 @@ fn parse_for(pair: Pair) -> ForStatement { } fn parse_expression(pair: Pair) -> Expression { - // let span = span_of(&pair); match pair.as_rule() { Rule::expression => { let mut inner = pair.into_inner(); - let mut expr = parse_primary(inner.next().unwrap()); + let mut expr = parse_term(inner.next().unwrap()); - // chain field accesses and calls as left-to-right suffixes - for part in inner { - let span = span_of(&part); - match part.as_rule() { - Rule::field_access => { - let field = part.into_inner().next().unwrap().as_str().to_string(); - expr = Expression::FieldAccess(Box::new(FieldAccess { - object: expr, - field, - span, - })); - } - Rule::call_suffix => { - let args = part.into_inner().map(|p| parse_expression(p)).collect(); - expr = Expression::Call(Box::new(CallExpr { - callee: expr, - args, - span, - })); - } - _ => {} - } + // consume pairs of (bin_op, term) + while let Some(op_pair) = inner.next() { + let right = parse_term(inner.next().unwrap()); + let span = span_of(&op_pair); + let op = match op_pair.as_rule() { + Rule::add => BinOperator::Add, + Rule::sub => BinOperator::Sub, + Rule::mul => BinOperator::Mul, + Rule::div => BinOperator::Div, + Rule::eq => BinOperator::Eq, + Rule::neq => BinOperator::NotEq, + Rule::lt => BinOperator::Lt, + Rule::gt => BinOperator::Gt, + Rule::lte => BinOperator::LtEq, + Rule::gte => BinOperator::GtEq, + Rule::and => BinOperator::And, + Rule::or => BinOperator::Or, + _ => unreachable!(), + }; + expr = Expression::BinaryOp(Box::new(BinaryOp { + left: expr, + op, + right, + span, + })); } expr } - _ => parse_primary(pair), + _ => parse_term(pair), } } +fn parse_term(pair: Pair) -> Expression { + let mut inner = pair.into_inner(); + let mut expr = parse_primary(inner.next().unwrap()); + + for part in inner { + let span = span_of(&part); + match part.as_rule() { + Rule::field_access => { + let field = part.into_inner().next().unwrap().as_str().to_string(); + expr = Expression::FieldAccess(Box::new(FieldAccess { + object: expr, + field, + span, + })); + } + Rule::call_suffix => { + let args = part.into_inner().map(|p| parse_expression(p)).collect(); + expr = Expression::Call(Box::new(CallExpr { + callee: expr, + args, + span, + })); + } + _ => {} + } + } + + expr +} + fn parse_primary(pair: Pair) -> Expression { let span = span_of(&pair); + match pair.as_rule() { + Rule::struct_literal => { + let mut inner = pair.into_inner(); + let name = inner.next().unwrap().as_str().to_string(); + + let mut fields = vec![]; + for field in inner { + let mut f_inner = field.into_inner(); + let field_name = f_inner.next().unwrap().as_str().to_string(); + let value = parse_expression(f_inner.next().unwrap()); + fields.push((field_name, value)); + } + + Expression::StructInit(Box::new(StructInit { name, fields, span })) + } + + Rule::array_literal => { + let elements = pair.into_inner().map(|p| parse_expression(p)).collect(); + + Expression::ArrayLiteral(Box::new(ArrayLiteral { elements, span })) + } + Rule::integer => Expression::Integer(pair.as_str().parse().unwrap(), span), Rule::float => Expression::Float(pair.as_str().parse().unwrap(), span), + Rule::string_lit => { Expression::StringLit(pair.into_inner().next().unwrap().as_str().to_string(), span) } + Rule::boolean => Expression::Bool(pair.as_str() == "true", span), + + Rule::self_kw => Expression::Identifier("self".to_string(), span), + Rule::null_kw => Expression::Identifier("null".to_string(), span), Rule::identifier => Expression::Identifier(pair.as_str().to_string(), span), + + Rule::term => parse_term(pair), + _ => unreachable!("unexpected primary rule: {:?}", pair.as_rule()), } } diff --git a/test/main.ms b/test/main.ms index 8b154dd..d75832f 100644 --- a/test/main.ms +++ b/test/main.ms @@ -1,4 +1,120 @@ +import "fmt" +import "strings" + +// structs are plain data +struct Point { + x: float + y: float +} + +// classes have fields and methods +class Animal { + name: string + age: int + + fn speak() -> string { + return "..." + } + + fn describe() -> string { + return "I am " + self.name + } +} + +class Dog { + name: string + age: int + + fn speak() -> string { + return "Woof!" + } + + fn fetch(item: string) -> string { + return self.name + " fetched the " + item + } +} + +// basic function +fn add(a: int, b: int) -> int { + return a + b +} + +// optional return type +fn find(name: string) -> string? { + if name == "mist" { + return name + } + return null +} + +// array types +fn sum(numbers: [int]) -> int { + let total: int = 0 + for n in numbers { + total = total + n + } + return total +} + +// multiple params, no return +fn greet(first: string, last: string) { + let full: string = first + " " + last + print(full) +} + +// structs initialized with fields +fn make_point(x: float, y: float) -> Point { + return Point { + x: x, + y: y + } +} + +// if / else +fn classify(n: int) -> string { + if n < 0 { + return "negative" + } else { + return "positive" + } +} + +// entry point fn main() { - let message: string = "Hello, World!" + // variables + let x: int = 10 + let y: int = 20 + let result: int = add(x, y) + + // inferred (no type annotation) + let message = "Hello from Mist!" print(message) + + // struct usage + let p: Point = make_point(1.5, 2.5) + print(p.x) + + // class usage + let dog: Dog = Dog { + name: "Rex", + age: 3 + } + print(dog.speak()) + print(dog.fetch("ball")) + + // arrays + let nums: [int] = [1, 2, 3, 4, 5] + let total: int = sum(nums) + print(total) + + // for loop + for n in nums { + print(n) + } + + // optional + let found: string? = find("mist") + if found { + print(found) + } }