From 16397a4e71165779fc9c566ab2ebf5d695b24bd4 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 13:51:16 +0200 Subject: [PATCH 01/18] Init --- .gitignore | 5 +++++ Cargo.toml | 6 ++++++ src/main.rs | 3 +++ 3 files changed, 14 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index ad67955..0728338 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,8 @@ target # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + + +# Added by cargo + +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..fbbfb93 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "mist" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} From 933f8a4edbc0c576ebbef81520bc487121966c7b Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 13:55:02 +0200 Subject: [PATCH 02/18] Init lock --- Cargo.lock | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..b6f60e7 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "mist" +version = "0.1.0" From 80eac73865ee4fe382f7bbed604a32238ed919e6 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 13:59:01 +0200 Subject: [PATCH 03/18] Structure --- src/codegen/mod.rs | 1 + src/codegen/sourcemap.rs | 0 src/errors.rs | 0 src/main.rs | 4 ++++ src/parser/ast.rs | 0 src/parser/grammar.pest | 0 src/parser/mod.rs | 1 + 7 files changed, 6 insertions(+) create mode 100644 src/codegen/mod.rs create mode 100644 src/codegen/sourcemap.rs create mode 100644 src/errors.rs create mode 100644 src/parser/ast.rs create mode 100644 src/parser/grammar.pest create mode 100644 src/parser/mod.rs diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/codegen/mod.rs @@ -0,0 +1 @@ + diff --git a/src/codegen/sourcemap.rs b/src/codegen/sourcemap.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs index e7a11a9..7e0f87e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +pub mod codegen; +pub mod errors; +pub mod parser; + fn main() { println!("Hello, world!"); } diff --git a/src/parser/ast.rs b/src/parser/ast.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/parser/grammar.pest b/src/parser/grammar.pest new file mode 100644 index 0000000..e69de29 diff --git a/src/parser/mod.rs b/src/parser/mod.rs new file mode 100644 index 0000000..1333fbf --- /dev/null +++ b/src/parser/mod.rs @@ -0,0 +1 @@ +pub mod ast; \ No newline at end of file From 88f3bcb7f4db28866d4ab4c42f3f14f25cb7d889 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 14:01:02 +0200 Subject: [PATCH 04/18] Pest --- Cargo.lock | 177 ++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 + src/parser/mod.rs | 8 ++- 3 files changed, 186 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index b6f60e7..f0dd88e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,183 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.185" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + [[package]] name = "mist" version = "0.1.0" +dependencies = [ + "pest", + "pest_derive", +] + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" +dependencies = [ + "pest", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" diff --git a/Cargo.toml b/Cargo.toml index fbbfb93..6c7bdce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +pest = "2.8.6" +pest_derive = "2.8.6" diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 1333fbf..18dd6db 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1 +1,7 @@ -pub mod ast; \ No newline at end of file +use pest_derive::Parser; + +pub mod ast; + +#[derive(Parser)] +#[grammar = "./src/parser/grammar.pest"] // relative to src +pub struct MistParser; From 48c5e4132e0308aebd23a7062222c7ceb732ae55 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 14:05:46 +0200 Subject: [PATCH 05/18] Ast --- src/parser/ast.rs | 180 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 180 insertions(+) diff --git a/src/parser/ast.rs b/src/parser/ast.rs index e69de29..b17caf1 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -0,0 +1,180 @@ +#[derive(Debug, Clone)] +pub struct Span { + pub start: usize, + pub end: usize, +} + +#[derive(Debug, Clone)] +pub struct Program { + pub statements: Vec, +} + +#[derive(Debug, Clone)] +pub enum TopLevel { + Function(Function), + Struct(Struct), + Class(Class), + Import(Import), +} + +#[derive(Debug, Clone)] +pub struct Function { + pub name: String, + pub params: Vec, + pub return_type: Option, + pub body: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct Param { + pub name: String, + pub type_expr: TypeExpr, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct Struct { + pub name: String, + pub fields: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct StructField { + pub name: String, + pub type_expr: TypeExpr, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct Class { + pub name: String, + pub fields: Vec, + pub methods: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct Import { + pub path: String, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum Statement { + Let(LetStatement), + Return(ReturnStatement), + Expression(Expression), + If(IfStatement), + For(ForStatement), +} + +#[derive(Debug, Clone)] +pub struct LetStatement { + pub name: String, + pub type_expr: Option, + pub value: Expression, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct ReturnStatement { + pub value: Option, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct IfStatement { + pub condition: Expression, + pub body: Vec, + pub else_body: Option>, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct ForStatement { + pub var: String, + pub iterator: Expression, + pub body: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum Expression { + Identifier(String, Span), + Integer(i64, Span), + Float(f64, Span), + StringLit(String, Span), + Bool(bool, Span), + BinaryOp(Box), + UnaryOp(Box), + Call(Box), + FieldAccess(Box), + StructInit(Box), +} + +#[derive(Debug, Clone)] +pub struct BinaryOp { + pub left: Expression, + pub op: BinOperator, + pub right: Expression, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum BinOperator { + Add, + Sub, + Mul, + Div, + Eq, + NotEq, + Lt, + Gt, + LtEq, + GtEq, + And, + Or, +} + +#[derive(Debug, Clone)] +pub struct UnaryOp { + pub op: UnaryOperator, + pub expr: Expression, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum UnaryOperator { + Neg, + Not, +} + +#[derive(Debug, Clone)] +pub struct CallExpr { + pub callee: Expression, + pub args: Vec, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct FieldAccess { + pub object: Expression, + pub field: String, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub struct StructInit { + pub name: String, + pub fields: Vec<(String, Expression)>, + pub span: Span, +} + +#[derive(Debug, Clone)] +pub enum TypeExpr { + Named(String), + Array(Box), + Optional(Box), +} From 6c31221cee78ffc8623802489b2b6104f66e082a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 14:10:29 +0200 Subject: [PATCH 06/18] Parser --- src/parser/grammar.pest | 43 +++++++ src/parser/mod.rs | 274 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 316 insertions(+), 1 deletion(-) diff --git a/src/parser/grammar.pest b/src/parser/grammar.pest index e69de29..9841b2a 100644 --- a/src/parser/grammar.pest +++ b/src/parser/grammar.pest @@ -0,0 +1,43 @@ +WHITESPACE = _{ " " | "\t" | "\r" | "\n" } +COMMENT = _{ "//" ~ (!"\n" ~ ANY)* } + +program = { SOI ~ (import_decl | function_decl | struct_decl | class_decl)* ~ EOI } + +// primitives +identifier = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } +integer = @{ ASCII_DIGIT+ } +float = @{ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ } +boolean = { "true" | "false" } +string_lit = { "\"" ~ inner_str ~ "\"" } +inner_str = @{ (!"\"" ~ ANY)* } + +// top level +import_decl = { "import" ~ string_lit } +struct_decl = { "struct" ~ identifier ~ "{" ~ struct_field* ~ "}" } +class_decl = { "class" ~ identifier ~ "{" ~ (struct_field | function_decl)* ~ "}" } +function_decl = { "fn" ~ identifier ~ "(" ~ param_list? ~ ")" ~ ("->" ~ type_expr)? ~ block } + +struct_field = { identifier ~ ":" ~ type_expr } +param_list = { param ~ ("," ~ param)* } +param = { identifier ~ ":" ~ type_expr } + +// statements +block = { "{" ~ statement* ~ "}" } +statement = _{ let_stmt | return_stmt | if_stmt | for_stmt | 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 ~ "]" } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 18dd6db..3f20605 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1,7 +1,279 @@ +use pest::Parser; +use pest::iterators::Pair; use pest_derive::Parser; pub mod ast; +use ast::*; #[derive(Parser)] -#[grammar = "./src/parser/grammar.pest"] // relative to src +#[grammar = "./src/parser/grammar.pest"] pub struct MistParser; + +// convenience alias for pest errors +pub type ParseError = pest::error::Error; + +pub fn parse(source: &str) -> Result { + let pairs = MistParser::parse(Rule::program, source)?; + let mut statements = vec![]; + + for pair in pairs { + match pair.as_rule() { + Rule::function_decl => statements.push(TopLevel::Function(parse_function(pair))), + Rule::struct_decl => statements.push(TopLevel::Struct(parse_struct(pair))), + Rule::class_decl => statements.push(TopLevel::Class(parse_class(pair))), + Rule::import_decl => statements.push(TopLevel::Import(parse_import(pair))), + Rule::EOI => {} + _ => {} + } + } + + Ok(Program { statements }) +} + +fn span_of(pair: &Pair) -> Span { + let s = pair.as_span(); + Span { + start: s.start(), + end: s.end(), + } +} + +fn parse_function(pair: Pair) -> Function { + let span = span_of(&pair); + let mut inner = pair.into_inner(); + + let name = inner.next().unwrap().as_str().to_string(); + + let mut params = vec![]; + let mut return_type = None; + let mut body = vec![]; + + for part in inner { + match part.as_rule() { + Rule::param_list => params = parse_param_list(part), + Rule::type_expr => return_type = Some(parse_type_expr(part)), + Rule::block => body = parse_block(part), + _ => {} + } + } + + Function { + name, + params, + return_type, + body, + span, + } +} + +fn parse_param_list(pair: Pair) -> Vec { + pair.into_inner() + .map(|p| { + let span = span_of(&p); + let mut inner = p.into_inner(); + let name = inner.next().unwrap().as_str().to_string(); + let type_expr = parse_type_expr(inner.next().unwrap()); + Param { + name, + type_expr, + span, + } + }) + .collect() +} + +fn parse_struct(pair: Pair) -> Struct { + let span = span_of(&pair); + let mut inner = pair.into_inner(); + let name = inner.next().unwrap().as_str().to_string(); + let fields = inner.map(|f| parse_struct_field(f)).collect(); + Struct { name, fields, span } +} + +fn parse_struct_field(pair: Pair) -> StructField { + let span = span_of(&pair); + let mut inner = pair.into_inner(); + let name = inner.next().unwrap().as_str().to_string(); + let type_expr = parse_type_expr(inner.next().unwrap()); + StructField { + name, + type_expr, + span, + } +} + +fn parse_class(pair: Pair) -> Class { + let span = span_of(&pair); + let mut inner = pair.into_inner(); + let name = inner.next().unwrap().as_str().to_string(); + let mut fields = vec![]; + let mut methods = vec![]; + + for part in inner { + match part.as_rule() { + Rule::struct_field => fields.push(parse_struct_field(part)), + Rule::function_decl => methods.push(parse_function(part)), + _ => {} + } + } + + Class { + name, + fields, + methods, + span, + } +} + +fn parse_import(pair: Pair) -> Import { + let span = span_of(&pair); + let path = pair.into_inner().next().unwrap().as_str().to_string(); + Import { path, span } +} + +fn parse_block(pair: Pair) -> Vec { + pair.into_inner() + .filter_map(|p| parse_statement(p)) + .collect() +} + +fn parse_statement(pair: Pair) -> Option { + match pair.as_rule() { + Rule::let_stmt => Some(Statement::Let(parse_let(pair))), + Rule::return_stmt => Some(Statement::Return(parse_return(pair))), + Rule::if_stmt => Some(Statement::If(parse_if(pair))), + Rule::for_stmt => Some(Statement::For(parse_for(pair))), + Rule::expression => Some(Statement::Expression(parse_expression(pair))), + _ => None, + } +} + +fn parse_let(pair: Pair) -> LetStatement { + let span = span_of(&pair); + let mut inner = pair.into_inner(); + let name = inner.next().unwrap().as_str().to_string(); + + // peek ahead — next is either a type or an expression + let next = inner.next().unwrap(); + let (type_expr, value) = if next.as_rule() == Rule::type_expr { + ( + Some(parse_type_expr(next)), + parse_expression(inner.next().unwrap()), + ) + } else { + (None, parse_expression(next)) + }; + + LetStatement { + name, + type_expr, + value, + span, + } +} + +fn parse_return(pair: Pair) -> ReturnStatement { + let span = span_of(&pair); + let value = pair.into_inner().next().map(|p| parse_expression(p)); + ReturnStatement { value, span } +} + +fn parse_if(pair: Pair) -> IfStatement { + let span = span_of(&pair); + let mut inner = pair.into_inner(); + let condition = parse_expression(inner.next().unwrap()); + let body = parse_block(inner.next().unwrap()); + let else_body = inner.next().map(|p| parse_block(p)); + IfStatement { + condition, + body, + else_body, + span, + } +} + +fn parse_for(pair: Pair) -> ForStatement { + let span = span_of(&pair); + let mut inner = pair.into_inner(); + let var = inner.next().unwrap().as_str().to_string(); + let iterator = parse_expression(inner.next().unwrap()); + let body = parse_block(inner.next().unwrap()); + ForStatement { + var, + iterator, + body, + span, + } +} + +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()); + + // 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, + })); + } + _ => {} + } + } + + expr + } + _ => parse_primary(pair), + } +} + +fn parse_primary(pair: Pair) -> Expression { + let span = span_of(&pair); + match pair.as_rule() { + 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::identifier => Expression::Identifier(pair.as_str().to_string(), span), + _ => unreachable!("unexpected primary rule: {:?}", pair.as_rule()), + } +} + +fn parse_type_expr(pair: Pair) -> TypeExpr { + let mut inner = pair.into_inner(); + let base = inner.next().unwrap(); + + let base_type = match base.as_rule() { + Rule::array_type => { + let inner_type = parse_type_expr(base.into_inner().next().unwrap()); + TypeExpr::Array(Box::new(inner_type)) + } + Rule::identifier => TypeExpr::Named(base.as_str().to_string()), + _ => unreachable!(), + }; + + // if a "?" suffix was present, wrap in Optional + if inner.next().is_some() { + TypeExpr::Optional(Box::new(base_type)) + } else { + base_type + } +} From 032d7761d8e7cb540649a9cc45665b451fd52633 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 14:13:02 +0200 Subject: [PATCH 07/18] Cli --- src/main.rs | 95 +++++++++++++++++++++++++++++++++++++++++++++++++++- test/main.ms | 0 2 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 test/main.ms diff --git a/src/main.rs b/src/main.rs index 7e0f87e..2548b55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,99 @@ pub mod codegen; pub mod errors; pub mod parser; +use std::fs; +use std::path::PathBuf; +use std::process; + fn main() { - println!("Hello, world!"); + let args: Vec = std::env::args().collect(); + + if args.len() < 2 { + print_usage(); + process::exit(1); + } + + match args[1].as_str() { + "build" => { + if args.len() < 3 { + eprintln!("error: expected a file path\n usage: mist build "); + process::exit(1); + } + cmd_build(&args[2]); + } + "check" => { + if args.len() < 3 { + eprintln!("error: expected a file path\n usage: mist check "); + process::exit(1); + } + cmd_check(&args[2]); + } + "version" | "--version" | "-v" => { + println!("mist {}", env!("CARGO_PKG_VERSION")); + } + "help" | "--help" | "-h" => { + print_usage(); + } + unknown => { + eprintln!("error: unknown command '{}'\n", unknown); + print_usage(); + process::exit(1); + } + } +} + +fn cmd_build(path: &str) { + let source = read_ms_file(path); + match parser::parse(&source) { + Ok(ast) => { + println!("parsed {} top-level items", ast.statements.len()); + // codegen will go here + } + Err(e) => { + eprintln!("parse error:\n{}", e); + process::exit(1); + } + } +} + +fn cmd_check(path: &str) { + let source = read_ms_file(path); + match parser::parse(&source) { + Ok(_) => { + println!("ok"); + } + Err(e) => { + eprintln!("parse error:\n{}", e); + process::exit(1); + } + } +} + +fn read_ms_file(path: &str) -> String { + let pb = PathBuf::from(path); + + if !pb.exists() { + eprintln!("error: file '{}' not found", path); + process::exit(1); + } + + if pb.extension().and_then(|e| e.to_str()) != Some("ms") { + eprintln!("error: expected a .ms file, got '{}'", path); + process::exit(1); + } + + fs::read_to_string(&pb).unwrap_or_else(|e| { + eprintln!("error: could not read '{}': {}", path, e); + process::exit(1); + }) +} + +fn print_usage() { + println!("mist - the mist compiler"); + println!(); + println!("usage:"); + println!(" mist build compile a .ms file to go"); + println!(" mist check parse and validate without compiling"); + println!(" mist version print the compiler version"); + println!(" mist help print this message"); } diff --git a/test/main.ms b/test/main.ms new file mode 100644 index 0000000..e69de29 From 6b908754e323c89cc9a025b709bf2c4316bbb9f7 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 14:17:14 +0200 Subject: [PATCH 08/18] Working parser --- src/main.rs | 1 + src/parser/mod.rs | 20 ++++++++++++++++---- test/main.ms | 4 ++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2548b55..6675a01 100644 --- a/src/main.rs +++ b/src/main.rs @@ -48,6 +48,7 @@ fn cmd_build(path: &str) { match parser::parse(&source) { Ok(ast) => { println!("parsed {} top-level items", ast.statements.len()); + println!("{:#?}", ast.statements) // codegen will go here } Err(e) => { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 3f20605..723005f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -16,12 +16,24 @@ pub fn parse(source: &str) -> Result { let pairs = MistParser::parse(Rule::program, source)?; let mut statements = vec![]; + // pairs is an iterator over the top-level program pair + // we need to get its inner children for pair in pairs { match pair.as_rule() { - Rule::function_decl => statements.push(TopLevel::Function(parse_function(pair))), - Rule::struct_decl => statements.push(TopLevel::Struct(parse_struct(pair))), - Rule::class_decl => statements.push(TopLevel::Class(parse_class(pair))), - Rule::import_decl => statements.push(TopLevel::Import(parse_import(pair))), + Rule::program => { + for inner in pair.into_inner() { + match inner.as_rule() { + Rule::function_decl => { + statements.push(TopLevel::Function(parse_function(inner))) + } + Rule::struct_decl => statements.push(TopLevel::Struct(parse_struct(inner))), + Rule::class_decl => statements.push(TopLevel::Class(parse_class(inner))), + Rule::import_decl => statements.push(TopLevel::Import(parse_import(inner))), + Rule::EOI => {} + _ => {} + } + } + } Rule::EOI => {} _ => {} } diff --git a/test/main.ms b/test/main.ms index e69de29..8b154dd 100644 --- a/test/main.ms +++ b/test/main.ms @@ -0,0 +1,4 @@ +fn main() { + let message: string = "Hello, World!" + print(message) +} From 3758305a2323ed6b4052828da670817ab1842289 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 14:30:22 +0200 Subject: [PATCH 09/18] Working full parser --- src/parser/ast.rs | 7 +++ src/parser/grammar.pest | 45 ++++++++++++--- src/parser/mod.rs | 112 +++++++++++++++++++++++++++++--------- test/main.ms | 118 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 248 insertions(+), 34 deletions(-) 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) + } } From 2ef7c1c1846b3958ac5d9b11fa4203c424f91637 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 14:32:15 +0200 Subject: [PATCH 10/18] Small update --- src/parser/grammar.pest | 107 +++++++++++++++++++++++++++++----------- 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/src/parser/grammar.pest b/src/parser/grammar.pest index fdf0ac9..bb18fae 100644 --- a/src/parser/grammar.pest +++ b/src/parser/grammar.pest @@ -1,10 +1,11 @@ WHITESPACE = _{ " " | "\t" | "\r" | "\n" } COMMENT = _{ "//" ~ (!"\n" ~ ANY)* } -program = { SOI ~ (import_decl | function_decl | struct_decl | class_decl)* ~ EOI } +program = { SOI ~ item* ~ EOI } +item = _{ import_decl | function_decl | struct_decl | class_decl } // primitives -identifier = @{ ASCII_ALPHA ~ (ASCII_ALPHANUMERIC | "_")* } +identifier = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* } integer = @{ ASCII_DIGIT+ } float = @{ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ } boolean = { "true" | "false" } @@ -12,9 +13,11 @@ string_lit = { "\"" ~ inner_str ~ "\"" } inner_str = @{ (!"\"" ~ ANY)* } // top level -import_decl = { "import" ~ string_lit } -struct_decl = { "struct" ~ identifier ~ "{" ~ struct_field* ~ "}" } -class_decl = { "class" ~ identifier ~ "{" ~ (struct_field | function_decl)* ~ "}" } +import_decl = { "import" ~ string_lit } +struct_decl = { "struct" ~ identifier ~ "{" ~ struct_field* ~ "}" } +class_decl = { "class" ~ identifier ~ "{" ~ class_member* ~ "}" } +class_member = _{ struct_field | function_decl } + function_decl = { "fn" ~ identifier ~ "(" ~ param_list? ~ ")" ~ ("->" ~ type_expr)? ~ block } struct_field = { identifier ~ ":" ~ type_expr } @@ -23,7 +26,14 @@ param = { identifier ~ ":" ~ type_expr } // statements block = { "{" ~ statement* ~ "}" } -statement = _{ let_stmt | return_stmt | if_stmt | for_stmt | assign_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 } @@ -31,29 +41,49 @@ return_stmt = { "return" ~ expression? } if_stmt = { "if" ~ expression ~ block ~ ("else" ~ block)? } for_stmt = { "for" ~ identifier ~ "in" ~ expression ~ block } -// types — optional is a suffix, not a recursive wrapper +// types 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 = { "||" } +// expressions (still simple, no precedence yet) +expression = { term ~ (bin_op ~ term)* } +bin_op = _{ + eq + | neq + | lte + | gte + | lt + | gt + | and + | or + | add + | sub + | mul + | div +} + +add = { "+" } +sub = { "-" } +mul = { "*" } +div = { "/" } +eq = { "==" } +neq = { "!=" } +lte = { "<=" } +gte = { ">=" } +lt = { "<" } +gt = { ">" } +and = { "&&" } +or = { "||" } + +// literals struct_literal = { - identifier ~ "{" ~ (struct_init_field ~ ("," ~ struct_init_field)*)? ~ "}" + identifier ~ "{" ~ struct_init_list? ~ "}" +} + +struct_init_list = { + struct_init_field ~ ("," ~ struct_init_field)* ~ ","? } struct_init_field = { @@ -61,12 +91,29 @@ struct_init_field = { } array_literal = { - "[" ~ (expression ~ ("," ~ expression)*)? ~ "]" + "[" ~ (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" } +// expressions continuation +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" } From 9882626397cc4ece597c7ca052a69f8cbce421c2 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 14:33:48 +0200 Subject: [PATCH 11/18] Improved grammar --- src/parser/grammar.pest | 59 +++++++++++++++++++++-------- test/main.ms | 82 ++++++++++++++++++++--------------------- 2 files changed, 84 insertions(+), 57 deletions(-) diff --git a/src/parser/grammar.pest b/src/parser/grammar.pest index bb18fae..aaee271 100644 --- a/src/parser/grammar.pest +++ b/src/parser/grammar.pest @@ -13,40 +13,65 @@ string_lit = { "\"" ~ inner_str ~ "\"" } inner_str = @{ (!"\"" ~ ANY)* } // top level -import_decl = { "import" ~ string_lit } -struct_decl = { "struct" ~ identifier ~ "{" ~ struct_field* ~ "}" } -class_decl = { "class" ~ identifier ~ "{" ~ class_member* ~ "}" } +import_decl = { "import" ~ string_lit ~ ";" } + +struct_decl = { + "struct" ~ identifier ~ "{" ~ struct_field* ~ "}" +} + +class_decl = { + "class" ~ identifier ~ "{" ~ class_member* ~ "}" +} + class_member = _{ struct_field | function_decl } -function_decl = { "fn" ~ identifier ~ "(" ~ param_list? ~ ")" ~ ("->" ~ type_expr)? ~ block } +function_decl = { + "fn" ~ identifier ~ "(" ~ param_list? ~ ")" ~ ("->" ~ type_expr)? ~ block +} -struct_field = { identifier ~ ":" ~ type_expr } -param_list = { param ~ ("," ~ param)* } -param = { identifier ~ ":" ~ type_expr } +// struct fields (NO commas, Go-style) +struct_field = { identifier ~ ":" ~ type_expr ~ ";" } + +// params (allow trailing comma) +param_list = { param ~ ("," ~ param)* ~ ","? } +param = { identifier ~ ":" ~ type_expr } // statements -block = { "{" ~ statement* ~ "}" } +block = { "{" ~ statement* ~ "}" } + statement = _{ let_stmt | return_stmt | if_stmt | for_stmt | assign_stmt - | expression + | expression_stmt } -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 } +expression_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 +} // types type_expr = { base_type ~ "?"? } base_type = _{ array_type | identifier } array_type = { "[" ~ type_expr ~ "]" } -// expressions (still simple, no precedence yet) +// expressions (still no precedence yet) expression = { term ~ (bin_op ~ term)* } bin_op = _{ @@ -78,6 +103,7 @@ and = { "&&" } or = { "||" } // literals + struct_literal = { identifier ~ "{" ~ struct_init_list? ~ "}" } @@ -94,7 +120,7 @@ array_literal = { "[" ~ (expression ~ ("," ~ expression)*)? ~ ","? ~ "]" } -// expressions continuation +// chaining term = { primary ~ (field_access | call_suffix)* } field_access = { "." ~ identifier } @@ -103,6 +129,7 @@ call_suffix = { "(" ~ (expression ~ ("," ~ expression)*)? ~ ","? ~ ")" } +// primary primary = _{ struct_literal | array_literal diff --git a/test/main.ms b/test/main.ms index d75832f..b8ebcb6 100644 --- a/test/main.ms +++ b/test/main.ms @@ -1,120 +1,120 @@ -import "fmt" -import "strings" +import "fmt"; +import "strings"; // structs are plain data struct Point { - x: float - y: float + x: float; + y: float; } // classes have fields and methods class Animal { - name: string - age: int + name: string; + age: int; fn speak() -> string { - return "..." + return "..."; } fn describe() -> string { - return "I am " + self.name + return "I am " + self.name; } } class Dog { - name: string - age: int + name: string; + age: int; fn speak() -> string { - return "Woof!" + return "Woof!"; } fn fetch(item: string) -> string { - return self.name + " fetched the " + item + return self.name + " fetched the " + item; } } // basic function fn add(a: int, b: int) -> int { - return a + b + return a + b; } // optional return type fn find(name: string) -> string? { if name == "mist" { - return name + return name; } - return null + return null; } // array types fn sum(numbers: [int]) -> int { - let total: int = 0 + let total: int = 0; for n in numbers { - total = total + n + total = total + n; } - return total + return total; } // multiple params, no return fn greet(first: string, last: string) { - let full: string = first + " " + last - print(full) + 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 - } + y: y, + }; } // if / else fn classify(n: int) -> string { if n < 0 { - return "negative" + return "negative"; } else { - return "positive" + return "positive"; } } // entry point fn main() { // variables - let x: int = 10 - let y: int = 20 - let result: int = add(x, y) + 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) + let message = "Hello from Mist!"; + print(message); // struct usage - let p: Point = make_point(1.5, 2.5) - print(p.x) + 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")) + 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) + let nums: [int] = [1, 2, 3, 4, 5]; + let total: int = sum(nums); + print(total); // for loop for n in nums { - print(n) + print(n); } // optional - let found: string? = find("mist") + let found: string? = find("mist"); if found { - print(found) + print(found); } } From f4580aeaa2e2eb09f94987a24722bc4cb420cc42 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 15:18:53 +0200 Subject: [PATCH 12/18] Working codegen --- src/codegen/mod.rs | 1 - src/gogen/mod.rs | 283 ++++++++++++++++++++++++++++ src/{codegen => gogen}/sourcemap.rs | 0 src/main.rs | 7 +- 4 files changed, 287 insertions(+), 4 deletions(-) delete mode 100644 src/codegen/mod.rs create mode 100644 src/gogen/mod.rs rename src/{codegen => gogen}/sourcemap.rs (100%) diff --git a/src/codegen/mod.rs b/src/codegen/mod.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/codegen/mod.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/gogen/mod.rs b/src/gogen/mod.rs new file mode 100644 index 0000000..4402e36 --- /dev/null +++ b/src/gogen/mod.rs @@ -0,0 +1,283 @@ +use crate::parser::ast::{ + BinOperator, Class, Expression, Function, Program, Statement, Struct, TopLevel, TypeExpr, +}; + +pub fn generate(program: &Program) -> String { + let mut out = String::new(); + + out.push_str("package main\n\n"); + + // imports + let mut imports = vec![]; + for item in &program.statements { + if let TopLevel::Import(i) = item { + imports.push(i.path.clone()); + } + } + + if !imports.is_empty() { + out.push_str("import (\n"); + for imp in imports { + out.push_str(&format!(" {}\n", imp)); + } + out.push_str(")\n\n"); + } + + // rest + for item in &program.statements { + match item { + TopLevel::Function(f) => out.push_str(&gen_function(f)), + TopLevel::Struct(s) => out.push_str(&gen_struct(s)), + TopLevel::Class(c) => out.push_str(&gen_class(c)), + TopLevel::Import(_) => {} + } + out.push('\n'); + } + + out +} + +fn gen_struct(s: &Struct) -> String { + let mut out = format!("type {} struct {{\n", s.name); + + for field in &s.fields { + out.push_str(&format!( + " {} {}\n", + capitalize(&field.name), + gen_type(&field.type_expr) + )); + } + + out.push_str("}\n"); + out +} + +fn gen_class(c: &Class) -> String { + let mut out = String::new(); + + // struct + out.push_str(&format!("type {} struct {{\n", c.name)); + for field in &c.fields { + out.push_str(&format!( + " {} {}\n", + capitalize(&field.name), + gen_type(&field.type_expr) + )); + } + out.push_str("}\n\n"); + + // methods + for method in &c.methods { + out.push_str(&gen_method(c, method)); + out.push('\n'); + } + + out +} + +fn gen_function(f: &Function) -> String { + let mut out = format!("func {}(", f.name); + + // params + for (i, p) in f.params.iter().enumerate() { + if i > 0 { + out.push_str(", "); + } + out.push_str(&format!("{} {}", p.name, gen_type(&p.type_expr))); + } + + out.push(')'); + + // return + if let Some(ret) = &f.return_type { + out.push_str(&format!(" {}", gen_type(ret))); + } + + out.push_str(" {\n"); + + for stmt in &f.body { + out.push_str(&gen_statement(stmt)); + } + + out.push_str("}\n"); + out +} + +fn gen_method(class: &Class, f: &Function) -> String { + let mut out = format!("func (self *{}) {}(", class.name, f.name); + + for (i, p) in f.params.iter().enumerate() { + if i > 0 { + out.push_str(", "); + } + out.push_str(&format!("{} {}", p.name, gen_type(&p.type_expr))); + } + + out.push(')'); + + if let Some(ret) = &f.return_type { + out.push_str(&format!(" {}", gen_type(ret))); + } + + out.push_str(" {\n"); + + for stmt in &f.body { + out.push_str(&gen_statement(stmt)); + } + + out.push_str("}\n"); + + out +} + +fn gen_statement(stmt: &Statement) -> String { + match stmt { + Statement::Let(s) => { + let mut out = format!(" {} := {}", s.name, gen_expr(&s.value)); + + out.push_str(";\n"); + out + } + + Statement::Return(r) => match &r.value { + Some(v) => format!(" return {};\n", gen_expr(v)), + None => " return;\n".to_string(), + }, + + Statement::Expression(e) => { + format!(" {};\n", gen_expr(e)) + } + + Statement::If(i) => { + let mut out = format!(" if {} {{\n", gen_expr(&i.condition)); + + for stmt in &i.body { + out.push_str(&gen_statement(stmt)); + } + + out.push_str(" }"); + + if let Some(else_body) = &i.else_body { + out.push_str(" else {\n"); + for stmt in else_body { + out.push_str(&gen_statement(stmt)); + } + out.push_str(" }"); + } + + out.push('\n'); + out + } + + Statement::For(f) => { + let mut out = format!( + " for _, {} := range {} {{\n", + f.var, + gen_expr(&f.iterator) + ); + + for stmt in &f.body { + out.push_str(&gen_statement(stmt)); + } + + out.push_str(" }\n"); + out + } + } +} + +fn gen_expr(expr: &Expression) -> String { + match expr { + Expression::Identifier(name, _) => name.clone(), + Expression::Integer(v, _) => v.to_string(), + Expression::Float(v, _) => v.to_string(), + Expression::StringLit(s, _) => format!("\"{}\"", s), + Expression::Bool(b, _) => b.to_string(), + + Expression::BinaryOp(b) => format!( + "{} {} {}", + gen_expr(&b.left), + op_to_str(&b.op), + gen_expr(&b.right) + ), + + Expression::Call(c) => { + let args = c.args.iter().map(gen_expr).collect::>().join(", "); + format!("{}({})", gen_expr(&c.callee), args) + } + + Expression::FieldAccess(f) => { + format!("{}.{}", gen_expr(&f.object), capitalize(&f.field)) + } + + Expression::StructInit(s) => { + let mut out = format!("{}{{", s.name); + + for (i, (name, val)) in s.fields.iter().enumerate() { + if i > 0 { + out.push_str(", "); + } + out.push_str(&format!("{}: {}", capitalize(name), gen_expr(val))); + } + + out.push('}'); + out + } + + Expression::ArrayLiteral(arr) => { + let elems = arr + .elements + .iter() + .map(gen_expr) + .collect::>() + .join(", "); + format!("[]any{{{}}}", elems) // simple version + } + + _ => todo!(), + } +} + +fn gen_type(t: &TypeExpr) -> String { + match t { + TypeExpr::Named(n) => match n.as_str() { + "int" => "int".into(), + "float" => "float64".into(), + "string" => "string".into(), + _ => n.clone(), + }, + + TypeExpr::Array(inner) => { + format!("[]{}", gen_type(inner)) + } + + TypeExpr::Optional(inner) => { + format!("*{}", gen_type(inner)) // pointer for optional + } + } +} + +fn capitalize(s: &str) -> String { + let mut chars = s.chars(); + match chars.next() { + Some(c) => c.to_uppercase().collect::() + chars.as_str(), + None => String::new(), + } +} + +fn op_to_str(op: &BinOperator) -> &'static str { + match op { + BinOperator::Add => "+", + BinOperator::Sub => "-", + BinOperator::Mul => "*", + BinOperator::Div => "/", + BinOperator::Eq => "==", + BinOperator::NotEq => "!=", + BinOperator::Lt => "<", + BinOperator::Gt => ">", + BinOperator::LtEq => "<=", + BinOperator::GtEq => ">=", + BinOperator::And => "&&", + BinOperator::Or => "||", + } +} diff --git a/src/codegen/sourcemap.rs b/src/gogen/sourcemap.rs similarity index 100% rename from src/codegen/sourcemap.rs rename to src/gogen/sourcemap.rs diff --git a/src/main.rs b/src/main.rs index 6675a01..91278ec 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ -pub mod codegen; pub mod errors; +pub mod gogen; pub mod parser; use std::fs; @@ -48,8 +48,9 @@ fn cmd_build(path: &str) { match parser::parse(&source) { Ok(ast) => { println!("parsed {} top-level items", ast.statements.len()); - println!("{:#?}", ast.statements) - // codegen will go here + let contents = gogen::generate(&ast); + fs::write(path.to_string() + ".go", contents).unwrap(); + println!("build successful"); } Err(e) => { eprintln!("parse error:\n{}", e); From c22ec4e0dbe325cf03426e3f268333e1af7acaf6 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 15:30:59 +0200 Subject: [PATCH 13/18] Better compiler --- .gitignore | 2 + src/compiler.rs | 104 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 25 +++------- test/mist.toml | 7 +++ test/{ => src}/main.ms | 0 5 files changed, 120 insertions(+), 18 deletions(-) create mode 100644 src/compiler.rs create mode 100644 test/mist.toml rename test/{ => src}/main.ms (100%) diff --git a/.gitignore b/.gitignore index 0728338..7e6aa69 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,5 @@ target # Added by cargo /target + +/test/build \ No newline at end of file diff --git a/src/compiler.rs b/src/compiler.rs new file mode 100644 index 0000000..c74ab7a --- /dev/null +++ b/src/compiler.rs @@ -0,0 +1,104 @@ +use std::{fs, path::PathBuf, process, time::Instant}; + +use crate::{gogen, parser}; + +pub fn build() { + let start = Instant::now(); + + // 1. find project root + let root = find_project_root().unwrap_or_else(|| { + panic!("error: could not find project root (mist.toml)"); + }); + + println!("mistc build ({})", root.display()); + + // 2. load config + let config = load_config(&root); + + let entry_path = root.join(&config.entry); + let out_dir = root.join(&config.out_dir); + + println!(" → entry: {}", entry_path.display()); + + // 3. read entry file + let source = match fs::read_to_string(&entry_path) { + Ok(s) => s, + Err(e) => { + eprintln!("error: failed to read entry file\n {}", e); + process::exit(1); + } + }; + + println!(" → parsing..."); + + let ast = match parser::parse(&source) { + Ok(ast) => { + println!(" ✓ parsed {} items", ast.statements.len()); + ast + } + Err(e) => { + eprintln!("error: parse failed\n{}", e); + process::exit(1); + } + }; + + println!(" → generating Go code..."); + + let output = gogen::generate(&ast); + + // 4. ensure build dir + if let Err(e) = fs::create_dir_all(&out_dir) { + eprintln!("error: failed to create build dir\n {}", e); + process::exit(1); + } + + let out_file = out_dir.join("main.go"); + + if let Err(e) = fs::write(&out_file, output) { + eprintln!("error: failed to write output\n {}", e); + process::exit(1); + } + + let elapsed = start.elapsed(); + + println!(" ✓ built {}", out_file.display()); + println!("build finished in {:.2?}", elapsed); +} + +pub fn find_project_root() -> Option { + let mut dir = std::env::current_dir().ok()?; + + loop { + if dir.join("mist.toml").exists() { + return Some(dir); + } + + if !dir.pop() { + return None; + } + } +} + +struct Config { + entry: String, + out_dir: String, +} + +fn load_config(root: &std::path::Path) -> Config { + let content = + std::fs::read_to_string(root.join("mist.toml")).expect("failed to read mist.toml"); + + // super minimal parsing (replace later with toml crate) + let entry = extract(&content, "entry").unwrap_or("src/main.ms".into()); + let out_dir = extract(&content, "out_dir").unwrap_or("build".into()); + + Config { entry, out_dir } +} + +fn extract(content: &str, key: &str) -> Option { + content + .lines() + .find(|l| l.trim().starts_with(key)) + .and_then(|l| l.split('=').nth(1)) + .map(|v| v.trim().trim_matches('"').to_string()) +} diff --git a/src/main.rs b/src/main.rs index 91278ec..56fd64a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +pub mod compiler; pub mod errors; pub mod gogen; pub mod parser; @@ -16,11 +17,11 @@ fn main() { match args[1].as_str() { "build" => { - if args.len() < 3 { - eprintln!("error: expected a file path\n usage: mist build "); + if args.len() < 2 { + eprintln!("error: expected a file path\n usage: mist build"); process::exit(1); } - cmd_build(&args[2]); + cmd_build(); } "check" => { if args.len() < 3 { @@ -43,20 +44,8 @@ fn main() { } } -fn cmd_build(path: &str) { - let source = read_ms_file(path); - match parser::parse(&source) { - Ok(ast) => { - println!("parsed {} top-level items", ast.statements.len()); - let contents = gogen::generate(&ast); - fs::write(path.to_string() + ".go", contents).unwrap(); - println!("build successful"); - } - Err(e) => { - eprintln!("parse error:\n{}", e); - process::exit(1); - } - } +pub fn cmd_build() { + compiler::build(); } fn cmd_check(path: &str) { @@ -95,7 +84,7 @@ fn print_usage() { println!("mist - the mist compiler"); println!(); println!("usage:"); - println!(" mist build compile a .ms file to go"); + println!(" mist build compile the project in the current directory"); println!(" mist check parse and validate without compiling"); println!(" mist version print the compiler version"); println!(" mist help print this message"); diff --git a/test/mist.toml b/test/mist.toml new file mode 100644 index 0000000..071051b --- /dev/null +++ b/test/mist.toml @@ -0,0 +1,7 @@ +[package] +name = "myapp" +version = "0.1.0" + +[build] +entry = "src/main.ms" +out_dir = "build" diff --git a/test/main.ms b/test/src/main.ms similarity index 100% rename from test/main.ms rename to test/src/main.ms From 932163ee546139555a4ced004ff2f42c049c1f38 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 16:03:18 +0200 Subject: [PATCH 14/18] Fixed fields --- src/parser/mod.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 5d2bb50..3dedecd 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -295,11 +295,16 @@ fn parse_primary(pair: Pair) -> Expression { let mut inner = pair.into_inner(); let name = inner.next().unwrap().as_str().to_string(); + inner = inner.next().unwrap().into_inner(); + + // println!("{inner:#?}"); + 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)); } From c73b387230394f542e9f132e8e030308c39562c3 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 16:15:04 +0200 Subject: [PATCH 15/18] Fixed expr statements --- src/parser/mod.rs | 7 +++ test/src/main.ms | 119 +--------------------------------------------- 2 files changed, 9 insertions(+), 117 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 3dedecd..8be2197 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -14,6 +14,9 @@ pub type ParseError = pest::error::Error; pub fn parse(source: &str) -> Result { let pairs = MistParser::parse(Rule::program, source)?; + + println!("Parsed pairs: {:#?}", pairs); + let mut statements = vec![]; // pairs is an iterator over the top-level program pair @@ -155,6 +158,10 @@ fn parse_statement(pair: Pair) -> Option { Rule::return_stmt => Some(Statement::Return(parse_return(pair))), Rule::if_stmt => Some(Statement::If(parse_if(pair))), Rule::for_stmt => Some(Statement::For(parse_for(pair))), + Rule::expression_stmt => { + let expr = pair.into_inner().next().unwrap(); + Some(Statement::Expression(parse_expression(expr))) + } Rule::expression => Some(Statement::Expression(parse_expression(pair))), _ => None, } diff --git a/test/src/main.ms b/test/src/main.ms index b8ebcb6..cf8f728 100644 --- a/test/src/main.ms +++ b/test/src/main.ms @@ -1,120 +1,5 @@ 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() { - // 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); - } -} + fmt.Println("Hello World!"); +} \ No newline at end of file From f99968c638fa5ee4896ef8c3a832886d1c98cc5a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 16:23:17 +0200 Subject: [PATCH 16/18] Typescript-like syntax --- src/parser/grammar.pest | 45 ++++++++++++++++++++++++++++------------- src/parser/mod.rs | 2 -- test/src/main.ms | 10 ++++++++- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/parser/grammar.pest b/src/parser/grammar.pest index aaee271..8a59ca2 100644 --- a/src/parser/grammar.pest +++ b/src/parser/grammar.pest @@ -12,31 +12,37 @@ boolean = { "true" | "false" } string_lit = { "\"" ~ inner_str ~ "\"" } inner_str = @{ (!"\"" ~ ANY)* } -// top level +// ================= TYPE SCRIPT STYLE CHANGES ================= + +// import becomes TS-like (still identical structure) import_decl = { "import" ~ string_lit ~ ";" } +// struct stays, but feels like TS "type object shape" struct_decl = { "struct" ~ identifier ~ "{" ~ struct_field* ~ "}" } +// class already TS-like — just improved consistency class_decl = { "class" ~ identifier ~ "{" ~ class_member* ~ "}" } class_member = _{ struct_field | function_decl } +// function keyword changed feel: fn → function function_decl = { - "fn" ~ identifier ~ "(" ~ param_list? ~ ")" ~ ("->" ~ type_expr)? ~ block + "function" ~ identifier ~ "(" ~ param_list? ~ ")" ~ ("->" ~ type_expr)? ~ block } -// struct fields (NO commas, Go-style) +// struct fields: TS-style semicolon consistency (already good) struct_field = { identifier ~ ":" ~ type_expr ~ ";" } -// params (allow trailing comma) +// params: TS optional typing style feel (unchanged structure) param_list = { param ~ ("," ~ param)* ~ ","? } param = { identifier ~ ":" ~ type_expr } -// statements +// ================= STATEMENTS ================= + block = { "{" ~ statement* ~ "}" } statement = _{ @@ -48,30 +54,39 @@ statement = _{ | expression_stmt } +// expression statements: TS allows no semicolon in real life, +// but we keep your structure expression_stmt = { expression ~ ";" } +// assignment unchanged (JS/TS feel already) assign_stmt = { identifier ~ "=" ~ expression ~ ";" } +// let → const/let feel (still your rule, just TS naming) let_stmt = { - "let" ~ identifier ~ (":" ~ type_expr)? ~ "=" ~ expression ~ ";" + "const" ~ identifier ~ (":" ~ type_expr)? ~ "=" ~ expression ~ ";" } +// return: TS-like (no change except feel consistency) return_stmt = { "return" ~ expression? ~ ";" } +// if: add TS-style parentheses (MAJOR feel upgrade, same structure) if_stmt = { - "if" ~ expression ~ block ~ ("else" ~ block)? + "if" ~ "(" ~ expression ~ ")" ~ block ~ ("else" ~ block)? } +// for: TS-like "of" for_stmt = { - "for" ~ identifier ~ "in" ~ expression ~ block + "for" ~ identifier ~ "of" ~ expression ~ block } -// types +// ================= TYPES (KEEP STRUCTURE, TS FEEL) ================= + type_expr = { base_type ~ "?"? } base_type = _{ array_type | identifier } array_type = { "[" ~ type_expr ~ "]" } -// expressions (still no precedence yet) +// ================= EXPRESSIONS ================= + expression = { term ~ (bin_op ~ term)* } bin_op = _{ @@ -102,7 +117,7 @@ gt = { ">" } and = { "&&" } or = { "||" } -// literals +// ================= LITERALS ================= struct_literal = { identifier ~ "{" ~ struct_init_list? ~ "}" @@ -120,7 +135,8 @@ array_literal = { "[" ~ (expression ~ ("," ~ expression)*)? ~ ","? ~ "]" } -// chaining +// ================= CHAINING ================= + term = { primary ~ (field_access | call_suffix)* } field_access = { "." ~ identifier } @@ -129,7 +145,8 @@ call_suffix = { "(" ~ (expression ~ ("," ~ expression)*)? ~ ","? ~ ")" } -// primary +// ================= PRIMARY ================= + primary = _{ struct_literal | array_literal @@ -142,5 +159,5 @@ primary = _{ | identifier } -self_kw = { "self" } +self_kw = { "this" } null_kw = { "null" } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 8be2197..a8ad54b 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15,8 +15,6 @@ pub type ParseError = pest::error::Error; pub fn parse(source: &str) -> Result { let pairs = MistParser::parse(Rule::program, source)?; - println!("Parsed pairs: {:#?}", pairs); - let mut statements = vec![]; // pairs is an iterator over the top-level program pair diff --git a/test/src/main.ms b/test/src/main.ms index cf8f728..2d85d4a 100644 --- a/test/src/main.ms +++ b/test/src/main.ms @@ -1,5 +1,13 @@ import "fmt"; -fn main() { +function add(a: int, b: int) -> int { + const x = 10; + if (x > 5) { + return x; + } + return 0; +} + +function main() { fmt.Println("Hello World!"); } \ No newline at end of file From 0763117633b834e57461e89f42fd040c66a95837 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 16:25:26 +0200 Subject: [PATCH 17/18] Working math --- test/src/main.ms | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/test/src/main.ms b/test/src/main.ms index 2d85d4a..ce922de 100644 --- a/test/src/main.ms +++ b/test/src/main.ms @@ -1,13 +1,9 @@ import "fmt"; function add(a: int, b: int) -> int { - const x = 10; - if (x > 5) { - return x; - } - return 0; + return a + b; } function main() { - fmt.Println("Hello World!"); + fmt.Println(add(10, 10) / 2); } \ No newline at end of file From b84811539c50d19b6bdce3386270d2918c361750 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 13 Apr 2026 16:39:06 +0200 Subject: [PATCH 18/18] .json instead of toml --- Cargo.lock | 57 +++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 2 ++ src/compiler.rs | 33 +++++++++++----------------- test/mist.json | 4 ++++ test/mist.toml | 7 ------ 5 files changed, 75 insertions(+), 28 deletions(-) create mode 100644 test/mist.json delete mode 100644 test/mist.toml diff --git a/Cargo.lock b/Cargo.lock index f0dd88e..d08efc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -56,6 +56,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "libc" version = "0.2.185" @@ -74,6 +80,8 @@ version = "0.1.0" dependencies = [ "pest", "pest_derive", + "serde", + "serde_json", ] [[package]] @@ -137,6 +145,49 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "sha2" version = "0.10.9" @@ -182,3 +233,9 @@ name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml index 6c7bdce..3f1f1f9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,5 @@ edition = "2024" [dependencies] pest = "2.8.6" pest_derive = "2.8.6" +serde = { version = "1.0.228", features = ["serde_derive"] } +serde_json = "1.0.149" diff --git a/src/compiler.rs b/src/compiler.rs index c74ab7a..7091a9a 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -2,12 +2,20 @@ use std::{fs, path::PathBuf, process, time::Instant}; use crate::{gogen, parser}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Config { + entry: String, + out_dir: String, +} + pub fn build() { let start = Instant::now(); // 1. find project root let root = find_project_root().unwrap_or_else(|| { - panic!("error: could not find project root (mist.toml)"); + panic!("error: could not find project root (mist.json)"); }); println!("mistc build ({})", root.display()); @@ -69,7 +77,7 @@ pub fn find_project_root() -> Option { let mut dir = std::env::current_dir().ok()?; loop { - if dir.join("mist.toml").exists() { + if dir.join("mist.json").exists() { return Some(dir); } @@ -79,26 +87,9 @@ pub fn find_project_root() -> Option { } } -struct Config { - entry: String, - out_dir: String, -} - fn load_config(root: &std::path::Path) -> Config { let content = - std::fs::read_to_string(root.join("mist.toml")).expect("failed to read mist.toml"); + std::fs::read_to_string(root.join("mist.json")).expect("failed to read mist.json"); - // super minimal parsing (replace later with toml crate) - let entry = extract(&content, "entry").unwrap_or("src/main.ms".into()); - let out_dir = extract(&content, "out_dir").unwrap_or("build".into()); - - Config { entry, out_dir } -} - -fn extract(content: &str, key: &str) -> Option { - content - .lines() - .find(|l| l.trim().starts_with(key)) - .and_then(|l| l.split('=').nth(1)) - .map(|v| v.trim().trim_matches('"').to_string()) + serde_json::from_str(&content).expect("invalid mist.json format") } diff --git a/test/mist.json b/test/mist.json new file mode 100644 index 0000000..c639516 --- /dev/null +++ b/test/mist.json @@ -0,0 +1,4 @@ +{ + "entry": "src/main.ms", + "out_dir": "build" +} \ No newline at end of file diff --git a/test/mist.toml b/test/mist.toml deleted file mode 100644 index 071051b..0000000 --- a/test/mist.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "myapp" -version = "0.1.0" - -[build] -entry = "src/main.ms" -out_dir = "build"