@@ -19,3 +19,10 @@ target
|
|||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
# 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.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
|
||||||
|
/target
|
||||||
|
|
||||||
|
/test/build
|
||||||
Generated
+241
@@ -0,0 +1,241 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# 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 = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[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",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[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 = "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"
|
||||||
|
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"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||||
+10
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "mist"
|
||||||
|
version = "0.1.0"
|
||||||
|
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"
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
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.json)");
|
||||||
|
});
|
||||||
|
|
||||||
|
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<PathBuf> {
|
||||||
|
let mut dir = std::env::current_dir().ok()?;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if dir.join("mist.json").exists() {
|
||||||
|
return Some(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !dir.pop() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_config(root: &std::path::Path) -> Config {
|
||||||
|
let content =
|
||||||
|
std::fs::read_to_string(root.join("mist.json")).expect("failed to read mist.json");
|
||||||
|
|
||||||
|
serde_json::from_str(&content).expect("invalid mist.json format")
|
||||||
|
}
|
||||||
@@ -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::<Vec<_>>().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::<Vec<_>>()
|
||||||
|
.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::<String>() + 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 => "||",
|
||||||
|
}
|
||||||
|
}
|
||||||
+91
@@ -0,0 +1,91 @@
|
|||||||
|
pub mod compiler;
|
||||||
|
pub mod errors;
|
||||||
|
pub mod gogen;
|
||||||
|
pub mod parser;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
|
||||||
|
if args.len() < 2 {
|
||||||
|
print_usage();
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
match args[1].as_str() {
|
||||||
|
"build" => {
|
||||||
|
if args.len() < 2 {
|
||||||
|
eprintln!("error: expected a file path\n usage: mist build");
|
||||||
|
process::exit(1);
|
||||||
|
}
|
||||||
|
cmd_build();
|
||||||
|
}
|
||||||
|
"check" => {
|
||||||
|
if args.len() < 3 {
|
||||||
|
eprintln!("error: expected a file path\n usage: mist check <file.ms>");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cmd_build() {
|
||||||
|
compiler::build();
|
||||||
|
}
|
||||||
|
|
||||||
|
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 the project in the current directory");
|
||||||
|
println!(" mist check <file.ms> parse and validate without compiling");
|
||||||
|
println!(" mist version print the compiler version");
|
||||||
|
println!(" mist help print this message");
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Span {
|
||||||
|
pub start: usize,
|
||||||
|
pub end: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Program {
|
||||||
|
pub statements: Vec<TopLevel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Param>,
|
||||||
|
pub return_type: Option<TypeExpr>,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
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<StructField>,
|
||||||
|
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<StructField>,
|
||||||
|
pub methods: Vec<Function>,
|
||||||
|
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<TypeExpr>,
|
||||||
|
pub value: Expression,
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ReturnStatement {
|
||||||
|
pub value: Option<Expression>,
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct IfStatement {
|
||||||
|
pub condition: Expression,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
pub else_body: Option<Vec<Statement>>,
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ForStatement {
|
||||||
|
pub var: String,
|
||||||
|
pub iterator: Expression,
|
||||||
|
pub body: Vec<Statement>,
|
||||||
|
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<BinaryOp>),
|
||||||
|
UnaryOp(Box<UnaryOp>),
|
||||||
|
Call(Box<CallExpr>),
|
||||||
|
FieldAccess(Box<FieldAccess>),
|
||||||
|
StructInit(Box<StructInit>),
|
||||||
|
ArrayLiteral(Box<ArrayLiteral>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<Expression>,
|
||||||
|
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<TypeExpr>),
|
||||||
|
Optional(Box<TypeExpr>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ArrayLiteral {
|
||||||
|
pub elements: Vec<Expression>,
|
||||||
|
pub span: Span,
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
WHITESPACE = _{ " " | "\t" | "\r" | "\n" }
|
||||||
|
COMMENT = _{ "//" ~ (!"\n" ~ ANY)* }
|
||||||
|
|
||||||
|
program = { SOI ~ item* ~ EOI }
|
||||||
|
item = _{ import_decl | function_decl | struct_decl | class_decl }
|
||||||
|
|
||||||
|
// primitives
|
||||||
|
identifier = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
|
||||||
|
integer = @{ ASCII_DIGIT+ }
|
||||||
|
float = @{ ASCII_DIGIT+ ~ "." ~ ASCII_DIGIT+ }
|
||||||
|
boolean = { "true" | "false" }
|
||||||
|
string_lit = { "\"" ~ inner_str ~ "\"" }
|
||||||
|
inner_str = @{ (!"\"" ~ ANY)* }
|
||||||
|
|
||||||
|
// ================= 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 = {
|
||||||
|
"function" ~ identifier ~ "(" ~ param_list? ~ ")" ~ ("->" ~ type_expr)? ~ block
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct fields: TS-style semicolon consistency (already good)
|
||||||
|
struct_field = { identifier ~ ":" ~ type_expr ~ ";" }
|
||||||
|
|
||||||
|
// params: TS optional typing style feel (unchanged structure)
|
||||||
|
param_list = { param ~ ("," ~ param)* ~ ","? }
|
||||||
|
param = { identifier ~ ":" ~ type_expr }
|
||||||
|
|
||||||
|
// ================= STATEMENTS =================
|
||||||
|
|
||||||
|
block = { "{" ~ statement* ~ "}" }
|
||||||
|
|
||||||
|
statement = _{
|
||||||
|
let_stmt
|
||||||
|
| return_stmt
|
||||||
|
| if_stmt
|
||||||
|
| for_stmt
|
||||||
|
| assign_stmt
|
||||||
|
| 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 = {
|
||||||
|
"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)?
|
||||||
|
}
|
||||||
|
|
||||||
|
// for: TS-like "of"
|
||||||
|
for_stmt = {
|
||||||
|
"for" ~ identifier ~ "of" ~ expression ~ block
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= TYPES (KEEP STRUCTURE, TS FEEL) =================
|
||||||
|
|
||||||
|
type_expr = { base_type ~ "?"? }
|
||||||
|
base_type = _{ array_type | identifier }
|
||||||
|
array_type = { "[" ~ type_expr ~ "]" }
|
||||||
|
|
||||||
|
// ================= EXPRESSIONS =================
|
||||||
|
|
||||||
|
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_list? ~ "}"
|
||||||
|
}
|
||||||
|
|
||||||
|
struct_init_list = {
|
||||||
|
struct_init_field ~ ("," ~ struct_init_field)* ~ ","?
|
||||||
|
}
|
||||||
|
|
||||||
|
struct_init_field = {
|
||||||
|
identifier ~ ":" ~ expression
|
||||||
|
}
|
||||||
|
|
||||||
|
array_literal = {
|
||||||
|
"[" ~ (expression ~ ("," ~ expression)*)? ~ ","? ~ "]"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= CHAINING =================
|
||||||
|
|
||||||
|
term = { primary ~ (field_access | call_suffix)* }
|
||||||
|
|
||||||
|
field_access = { "." ~ identifier }
|
||||||
|
|
||||||
|
call_suffix = {
|
||||||
|
"(" ~ (expression ~ ("," ~ expression)*)? ~ ","? ~ ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= PRIMARY =================
|
||||||
|
|
||||||
|
primary = _{
|
||||||
|
struct_literal
|
||||||
|
| array_literal
|
||||||
|
| float
|
||||||
|
| integer
|
||||||
|
| string_lit
|
||||||
|
| boolean
|
||||||
|
| self_kw
|
||||||
|
| null_kw
|
||||||
|
| identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
self_kw = { "this" }
|
||||||
|
null_kw = { "null" }
|
||||||
@@ -0,0 +1,363 @@
|
|||||||
|
use pest::Parser;
|
||||||
|
use pest::iterators::Pair;
|
||||||
|
use pest_derive::Parser;
|
||||||
|
|
||||||
|
pub mod ast;
|
||||||
|
use ast::*;
|
||||||
|
|
||||||
|
#[derive(Parser)]
|
||||||
|
#[grammar = "./src/parser/grammar.pest"]
|
||||||
|
pub struct MistParser;
|
||||||
|
|
||||||
|
// convenience alias for pest errors
|
||||||
|
pub type ParseError = pest::error::Error<Rule>;
|
||||||
|
|
||||||
|
pub fn parse(source: &str) -> Result<Program, ParseError> {
|
||||||
|
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::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 => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Program { statements })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn span_of(pair: &Pair<Rule>) -> Span {
|
||||||
|
let s = pair.as_span();
|
||||||
|
Span {
|
||||||
|
start: s.start(),
|
||||||
|
end: s.end(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_function(pair: Pair<Rule>) -> 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<Rule>) -> Vec<Param> {
|
||||||
|
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<Rule>) -> 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<Rule>) -> 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<Rule>) -> 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<Rule>) -> 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<Rule>) -> Vec<Statement> {
|
||||||
|
pair.into_inner()
|
||||||
|
.filter_map(|p| parse_statement(p))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_statement(pair: Pair<Rule>) -> Option<Statement> {
|
||||||
|
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_stmt => {
|
||||||
|
let expr = pair.into_inner().next().unwrap();
|
||||||
|
Some(Statement::Expression(parse_expression(expr)))
|
||||||
|
}
|
||||||
|
Rule::expression => Some(Statement::Expression(parse_expression(pair))),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_let(pair: Pair<Rule>) -> 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<Rule>) -> 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<Rule>) -> 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<Rule>) -> 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<Rule>) -> Expression {
|
||||||
|
match pair.as_rule() {
|
||||||
|
Rule::expression => {
|
||||||
|
let mut inner = pair.into_inner();
|
||||||
|
let mut expr = parse_term(inner.next().unwrap());
|
||||||
|
|
||||||
|
// 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_term(pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_term(pair: Pair<Rule>) -> 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<Rule>) -> 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();
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_type_expr(pair: Pair<Rule>) -> 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"entry": "src/main.ms",
|
||||||
|
"out_dir": "build"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import "fmt";
|
||||||
|
|
||||||
|
function add(a: int, b: int) -> int {
|
||||||
|
return a + b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
fmt.Println(add(10, 10) / 2);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user