diff --git a/Cargo.lock b/Cargo.lock index d1e4cb8..57bae6a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,13 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "codegen" +version = "0.1.0" +dependencies = [ + "parser", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -78,6 +85,7 @@ checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" name = "mist" version = "0.1.0" dependencies = [ + "codegen", "parser", "semantic", "serde", @@ -160,6 +168,8 @@ name = "semantic" version = "0.1.0" dependencies = [ "parser", + "serde", + "serde_json", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2a9e8f2..178909d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ serde = { version = "1.0.228", features = ["serde_derive"] } serde_json = "1.0.149" parser = { path = "./parser" } semantic = { path = "./semantic" } +codegen = { path = "./codegen" } diff --git a/codegen/Cargo.lock b/codegen/Cargo.lock new file mode 100644 index 0000000..e2dce17 --- /dev/null +++ b/codegen/Cargo.lock @@ -0,0 +1,248 @@ +# 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 = "codegen" +version = "0.1.0" +dependencies = [ + "parser", +] + +[[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 = "parser" +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.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[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" diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml new file mode 100644 index 0000000..c64ec89 --- /dev/null +++ b/codegen/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "codegen" +version = "0.1.0" +edition = "2024" + +[dependencies] +parser = { path = "../parser" } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs new file mode 100644 index 0000000..4b01aef --- /dev/null +++ b/codegen/src/lib.rs @@ -0,0 +1,359 @@ +use parser::ast::{BinaryOp, Block, Expression, Postfix, Statement, TopLevel, TypeExpr, VarKind}; + +pub struct GoCodegen { + output: String, + indent: usize, +} + +impl GoCodegen { + pub fn new() -> Self { + Self { + output: String::new(), + indent: 0, + } + } + + fn indent_str(&self) -> String { + " ".repeat(self.indent) + } + + fn add_indented(&mut self, s: &str) { + self.output.push_str(&format!("{}{}", self.indent_str(), s)); + } + + fn add(&mut self, s: &str) { + self.output.push_str(s); + } + + fn addln(&mut self, s: &str) { + self.add(s); + self.add("\n"); + } + + fn add_indentedln(&mut self, s: &str) { + self.add_indented(s); + self.add("\n"); + } + + pub fn generate(&mut self, toplevels: &[TopLevel]) -> String { + for tl in toplevels { + self.generate_toplevel(tl); + } + self.output.clone() + } + + fn generate_toplevel(&mut self, tl: &TopLevel) { + match tl { + TopLevel::Import(path) => { + let import_path = path.replace("\"", ""); + if import_path.starts_with("./") || import_path.starts_with("/") { + self.addln(&format!("import \"{}\"", import_path)); + } else { + self.addln(&format!("import \"{}\"", import_path)); + } + self.addln(""); + } + TopLevel::StructDecl { + export, + name, + fields, + } => { + let name = if *export { name } else { name }; + self.addln(&format!("type {} struct {{", name)); + self.indent += 1; + for (field_name, (_, ty)) in &fields.0 { + let go_ty = self.translate_type(ty); + self.addln(&format!("{} {}", field_name, go_ty)); + } + self.indent -= 1; + self.addln("}\n"); + } + TopLevel::FunctionDecl { + export, + name, + params, + return_type, + body, + } => { + let name = if *export { + format!("{}", name) + } else { + name.clone() + }; + let params_str = params + .0 + .iter() + .map(|(n, (_, t))| format!("{} {}", n, self.translate_type(t))) + .collect::>() + .join(", "); + + let ret_ty = return_type + .as_ref() + .map(|t| self.translate_type(t)) + .unwrap_or_else(|| "".to_string()); + + if ret_ty.is_empty() { + self.addln(&format!("func {}({}) {{", name, params_str)); + } else { + self.addln(&format!("func {}({}) {} {{", name, params_str, ret_ty)); + } + self.indent += 1; + self.generate_block(body); + self.indent -= 1; + self.addln("}\n"); + } + TopLevel::Package(pkg) => { + self.addln(&format!("package {}", pkg)); + self.addln(""); + } + } + } + + fn translate_type(&self, ty: &TypeExpr) -> String { + match ty { + TypeExpr::Identifier(name) => match name.as_str() { + "int" => "int".to_string(), + "float" | "float64" => "float64".to_string(), + "float32" => "float32".to_string(), + "bool" => "bool".to_string(), + "string" => "string".to_string(), + "byte" => "byte".to_string(), + "rune" => "rune".to_string(), + _ => name.clone(), + }, + } + } + + fn generate_block(&mut self, block: &Block) { + for stmt in &block.0 { + self.generate_statement(stmt); + } + } + + fn generate_statement(&mut self, stmt: &Statement) { + match stmt { + Statement::Expression(expr) => { + self.add_indentedln(&format!("{};", self.generate_expression(expr))); + } + Statement::Block(block) => { + self.add_indentedln("{"); + self.indent += 1; + self.generate_block(block); + self.indent -= 1; + self.add_indentedln("}\n"); + } + Statement::VarDecl { kind, name, init } => { + let go_kind = match kind { + VarKind::Let | VarKind::Const => "var", + VarKind::Var => "var", + }; + let init_expr = init + .as_ref() + .map(|e| format!(" = {}", self.generate_expression(e))) + .unwrap_or_else(|| "".to_string()); + self.add_indentedln(&format!("{} {}{};\n", go_kind, name, init_expr)); + } + Statement::VarAssign { target, value } => { + self.add_indentedln(&format!( + "{} = {};\n", + self.generate_expression(target), + self.generate_expression(value) + )); + } + Statement::If { + condition, + then_branch, + else_branch, + } => { + self.add_indented(&format!("if {} ", self.generate_expression(condition))); + self.generate_statement(then_branch); + if let Some(else_br) = else_branch { + self.add_indented("else "); + self.generate_statement(else_br); + } + } + Statement::While { condition, body } => { + self.add_indented(&format!("for {} ", self.generate_expression(condition))); + self.generate_statement(body); + } + Statement::For { + init, + condition, + update, + body, + } => { + let (kind, init_name, init_val) = init; + let init_expr = init_val + .as_ref() + .map(|e| format!(" = {}", self.generate_expression(e))) + .unwrap_or_else(|| "".to_string()); + let init_str = format!("{} {}{}", self.var_kind_to_go(kind), init_name, init_expr); + + let cond_str = condition + .as_ref() + .map(|e| self.generate_expression(e)) + .unwrap_or_else(|| "true".to_string()); + + let update_str = update + .as_ref() + .map(|s| self.generate_expression(&self.stmt_to_expr(s))) + .unwrap_or_else(|| "".to_string()); + + self.add_indented(&format!("for {}; {}; {} ", init_str, cond_str, update_str)); + self.generate_statement(body); + } + Statement::Return(expr) => { + let ret_val = expr + .as_ref() + .map(|e| self.generate_expression(e)) + .unwrap_or_else(|| "".to_string()); + self.add_indentedln(&format!("return {};\n", ret_val)); + } + Statement::Break => { + self.add_indentedln("break;\n"); + } + Statement::Continue => { + self.add_indentedln("continue;\n"); + } + } + } + + fn stmt_to_expr(&self, stmt: &Statement) -> Expression { + match stmt { + Statement::Expression(e) => e.clone(), + _ => Expression::Identifier(String::new()), + } + } + + fn var_kind_to_go(&self, kind: &VarKind) -> String { + match kind { + VarKind::Let | VarKind::Const => "var".to_string(), + VarKind::Var => "var".to_string(), + } + } + + fn generate_expression(&self, expr: &Expression) -> String { + match expr { + Expression::Identifier(name) => name.clone(), + Expression::IntLiteral(n) => n.to_string(), + Expression::FloatLiteral(n) => n.to_string(), + Expression::BoolLiteral(b) => b.to_string(), + Expression::StringLiteral(s) => format!("\"{}\"", s), + Expression::Postfix { initial, postfixes } => { + let base = self.generate_expression(initial); + self.apply_postfixes(&base, postfixes) + } + } + } + + fn apply_postfixes(&self, base: &str, postfixes: &[Postfix]) -> String { + let mut result = base.to_string(); + for postfix in postfixes { + result = match postfix { + Postfix::FieldAccess(field) => format!("{}.{}", result, field), + Postfix::Call(args) => { + let args_str = args + .iter() + .map(|a| self.generate_expression(a)) + .collect::>() + .join(", "); + format!("{}({})", result, args_str) + } + Postfix::Index(idx) => format!("{}[{}]", result, self.generate_expression(idx)), + Postfix::Binary(op, rhs) => { + let op_str = match op { + BinaryOp::Plus => "+", + BinaryOp::Minus => "-", + BinaryOp::Multiply => "*", + BinaryOp::Divide => "/", + BinaryOp::Modulo => "%", + BinaryOp::Equal => "==", + BinaryOp::NotEqual => "!=", + BinaryOp::LessThan => "<", + BinaryOp::GreaterThan => ">", + BinaryOp::LessThanOrEqual => "<=", + BinaryOp::GreaterThanOrEqual => ">=", + }; + format!("{} {} {}", result, op_str, self.generate_expression(rhs)) + } + }; + } + result + } +} + +impl Default for GoCodegen { + fn default() -> Self { + Self::new() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use parser::ast::{Block, Expression, ParamList, Statement, TopLevel, TypeExpr}; + use std::collections::HashMap; + + #[test] + fn test_int_literal() { + let cg = GoCodegen::new(); + let expr = Expression::IntLiteral(42); + let result = cg.generate_expression(&expr); + assert_eq!(result, "42"); + } + + #[test] + fn test_string_literal() { + let cg = GoCodegen::new(); + let expr = Expression::StringLiteral("hello".to_string()); + let result = cg.generate_expression(&expr); + assert_eq!(result, "\"hello\""); + } + + #[test] + fn test_function_decl() { + let mut cg = GoCodegen::new(); + let toplevel = TopLevel::FunctionDecl { + export: true, + name: "main".to_string(), + params: ParamList(HashMap::new()), + return_type: None, + body: Block(vec![Statement::Return(None)]), + }; + cg.generate_toplevel(&toplevel); + let output = cg.output.clone(); + assert!(output.contains("func main()")); + } + + #[test] + fn test_struct_decl() { + let mut cg = GoCodegen::new(); + let mut fields = HashMap::new(); + fields.insert( + "x".to_string(), + (true, TypeExpr::Identifier("int".to_string())), + ); + let toplevel = TopLevel::StructDecl { + export: true, + name: "Point".to_string(), + fields: ParamList(fields), + }; + cg.generate_toplevel(&toplevel); + let output = cg.output.clone(); + assert!(output.contains("type Point struct")); + assert!(output.contains("x int")); + } + + #[test] + fn test_if_statement() { + let mut cg = GoCodegen::new(); + let stmt = Statement::If { + condition: Expression::Identifier("x".to_string()), + then_branch: Box::new(Statement::Return(Some(Expression::IntLiteral(1)))), + else_branch: None, + }; + cg.generate_statement(&stmt); + let output = cg.output.clone(); + assert!(output.contains("if x")); + } +} diff --git a/mapgen/go.mod b/mapgen/go.mod new file mode 100644 index 0000000..787464e --- /dev/null +++ b/mapgen/go.mod @@ -0,0 +1,9 @@ +module mapgen + +go 1.25.5 + +require ( + golang.org/x/mod v0.35.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/tools v0.44.0 // indirect +) diff --git a/mapgen/go.sum b/mapgen/go.sum new file mode 100644 index 0000000..d570929 --- /dev/null +++ b/mapgen/go.sum @@ -0,0 +1,6 @@ +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= diff --git a/mapgen/main.go b/mapgen/main.go new file mode 100644 index 0000000..b51fad5 --- /dev/null +++ b/mapgen/main.go @@ -0,0 +1,188 @@ +package main + +import ( + "encoding/json" + "fmt" + "go/token" + "go/types" + "os" + "unicode" + + "golang.org/x/tools/go/packages" +) + +type TypeSymbol string + +type VarSymbol struct { + Export bool `json:"export"` + VarType TypeSymbol `json:"var_type"` + Name string `json:"name"` +} + +type FunctionSymbol struct { + Export bool `json:"export"` + Name string `json:"name"` + Params map[string]VarSymbol `json:"params"` + ReturnTypes []TypeSymbol `json:"return_types"` +} + +type StructSymbol struct { + Export bool `json:"export"` + Name string `json:"name"` + Fields map[string]VarSymbol `json:"fields"` + Methods map[string]FunctionSymbol `json:"methods"` +} + +type TopLevelSymbolScope struct { + PackageName string `json:"package_name"` + Structs map[string]StructSymbol `json:"structs"` + Functions map[string]FunctionSymbol `json:"functions"` +} + +func main() { + cfg := &packages.Config{ + Mode: packages.NeedName | + packages.NeedTypes | + packages.NeedTypesInfo | + packages.NeedModule, + } + + // you can expand this to multiple packages later + pkgs, err := packages.Load(cfg, "fmt") + if err != nil { + panic(err) + } + + if packages.PrintErrors(pkgs) > 0 { + os.Exit(1) + } + + // 🔑 FINAL RESULT: map[import_path]scope + result := make(map[string]TopLevelSymbolScope) + + for _, pkg := range pkgs { + scopeData := TopLevelSymbolScope{ + PackageName: pkg.Name, // e.g. "http" + Structs: make(map[string]StructSymbol), + Functions: make(map[string]FunctionSymbol), + } + + scope := pkg.Types.Scope() + + for _, name := range scope.Names() { + if !token.IsExported(name) { + continue + } + + obj := scope.Lookup(name) + + switch obj := obj.(type) { + + case *types.Func: + scopeData.Functions[LowerFirst(name)] = buildFunction(obj) + + case *types.TypeName: + if strct, ok := buildStruct(obj); ok { + scopeData.Structs[name] = strct + } + } + } + + // 🔑 key = import path (e.g. "net/http") + result[pkg.PkgPath] = scopeData + } + + out, err := json.MarshalIndent(result, "", " ") + if err != nil { + panic(err) + } + + fmt.Println(string(out)) +} + +func LowerFirst(s string) string { + if s == "" { + return "" + } + r := []rune(s) + r[0] = unicode.ToLower(r[0]) + return string(r) +} + +func buildFunction(fn *types.Func) FunctionSymbol { + sig := fn.Type().(*types.Signature) + + params := make(map[string]VarSymbol) + for i := 0; i < sig.Params().Len(); i++ { + p := sig.Params().At(i) + + name := p.Name() + if name == "" { + name = fmt.Sprintf("param%d", i) + } + + params[name] = VarSymbol{ + Export: true, + Name: name, + VarType: TypeSymbol(p.Type().String()), + } + } + + var returnTypes []TypeSymbol + for i := 0; i < sig.Results().Len(); i++ { + r := sig.Results().At(i) + returnTypes = append(returnTypes, TypeSymbol(r.Type().String())) + } + + return FunctionSymbol{ + Export: true, // already filtered before calling + Name: fn.Name(), + Params: params, + ReturnTypes: returnTypes, + } +} + +func buildStruct(tn *types.TypeName) (StructSymbol, bool) { + underlying := tn.Type().Underlying() + + strct, ok := underlying.(*types.Struct) + if !ok { + return StructSymbol{}, false + } + + fields := make(map[string]VarSymbol) + + for i := 0; i < strct.NumFields(); i++ { + f := strct.Field(i) + + if !token.IsExported(f.Name()) { + continue + } + + fields[f.Name()] = VarSymbol{ + Export: true, + Name: f.Name(), + VarType: TypeSymbol(f.Type().String()), + } + } + + methods := make(map[string]FunctionSymbol) + methodSet := types.NewMethodSet(tn.Type()) + + for i := 0; i < methodSet.Len(); i++ { + m := methodSet.At(i).Obj().(*types.Func) + + if !token.IsExported(m.Name()) { + continue + } + + methods[LowerFirst(m.Name())] = buildFunction(m) + } + + return StructSymbol{ + Export: true, + Name: tn.Name(), + Fields: fields, + Methods: methods, + }, true +} diff --git a/parser/src/ast.rs b/parser/src/ast.rs index a84e4a9..0313478 100644 --- a/parser/src/ast.rs +++ b/parser/src/ast.rs @@ -31,6 +31,7 @@ pub enum BinaryOp { #[derive(Debug, Clone, Serialize)] pub enum TopLevel { Import(String), + Package(String), StructDecl { export: bool, name: String, diff --git a/parser/src/grammar.pest b/parser/src/grammar.pest index ec4c62e..4cc1a94 100644 --- a/parser/src/grammar.pest +++ b/parser/src/grammar.pest @@ -13,7 +13,9 @@ inner_str = @{ (!"\"" ~ ANY)* } // ================= TOP LEVEL ================= -top_level = _{ import | function_decl | struct_decl } +top_level = _{ import | function_decl | struct_decl | package } + +package = { "package" ~ identifier ~ ";" } export = { "export" } diff --git a/parser/src/lib.rs b/parser/src/lib.rs index 7e48fab..2dbe131 100644 --- a/parser/src/lib.rs +++ b/parser/src/lib.rs @@ -70,6 +70,10 @@ impl TopLevel { let path = pair.into_inner().next().unwrap().as_str().to_string(); Some(TopLevel::Import(path)) } + Rule::package => { + let name = pair.into_inner().next().unwrap().as_str().to_string(); + Some(TopLevel::Package(name)) + } Rule::function_decl => { let mut inner = pair.into_inner(); diff --git a/semantic/Cargo.toml b/semantic/Cargo.toml index 69caf32..cbebdd1 100644 --- a/semantic/Cargo.toml +++ b/semantic/Cargo.toml @@ -5,3 +5,5 @@ edition = "2024" [dependencies] parser = { path = "../parser" } +serde = { version = "1.0.228", features = ["serde_derive"] } +serde_json = "1.0.149" diff --git a/semantic/src/hir.rs b/semantic/src/hir.rs index ed5129f..c5640d5 100644 --- a/semantic/src/hir.rs +++ b/semantic/src/hir.rs @@ -6,13 +6,22 @@ use std::{ }, }; -use crate::top_level::{FunctionSymbol, StructSymbol, TopLevelSymbolScope, TypeSymbol, VarSymbol}; +use crate::top_level::{ + FunctionSymbol, JSONScope, StructSymbol, TopLevelSymbolScope, TypeSymbol, VarSymbol, +}; #[derive(Clone, Debug)] pub enum TypeRef { Struct(StructRef), Function(FunctionRef), - Int, + Package(PackageRef), + Name(String), +} + +#[derive(Clone, Debug)] +pub struct PackageRef { + pub name: String, + pub variables: HashMap>, } #[derive(Clone, Debug)] @@ -58,6 +67,17 @@ impl TopLevelHirScope { scope.struct_ref(tlss, symbol); } + for (_, symbol) in &tlss.imports { + scope.variables.insert( + symbol.package_name.clone(), + Arc::new(VarRef { + export: false, + name: symbol.package_name.clone(), + var_type: Arc::new(TypeRef::Package(PackageRef::from_tlss(symbol))), + }), + ); + } + scope } @@ -142,23 +162,15 @@ impl TopLevelHirScope { if let Some(tlss_rf) = tlss.structs.get(&symbol.0) { self.struct_ref(tlss, tlss_rf) } else { - match symbol.0.as_str() { - "int" => { - let var_ref = Arc::new(VarRef { - export: false, - name: symbol.0.clone(), - var_type: Arc::new(TypeRef::Int), - }); + let var_ref = Arc::new(VarRef { + export: false, + name: symbol.0.clone(), + var_type: Arc::new(TypeRef::Name(symbol.0.clone())), + }); - self.variables.insert(symbol.0.clone(), var_ref.clone()); + self.variables.insert(symbol.0.clone(), var_ref.clone()); - var_ref.var_type.clone() - } - - _ => { - unimplemented!("{:?}", symbol) - } - } + var_ref.var_type.clone() } } } @@ -181,7 +193,126 @@ impl TypeRef { match self { TypeRef::Function(f) => f.name.clone(), TypeRef::Struct(s) => s.name.clone(), - TypeRef::Int => "int".to_string(), + TypeRef::Package(p) => p.name.clone(), + TypeRef::Name(n) => n.clone(), } } } + +impl PackageRef { + pub fn from_tlss(json_scope: &JSONScope) -> Self { + let mut scope = Self { + name: json_scope.package_name.clone(), + variables: HashMap::new(), + }; + + for (token_name, symbol) in &json_scope.functions { + scope.function_ref(json_scope, token_name, symbol); + } + + for (_, symbol) in &json_scope.structs { + scope.struct_ref(json_scope, symbol); + } + + scope + } + + pub fn function_ref( + &mut self, + json_scope: &JSONScope, + token_name: &String, + symbol: &FunctionSymbol, + ) { + if self.variables.get(&symbol.name).is_none() { + let name = symbol.name.clone(); + + if let Some(_) = json_scope.functions.get(token_name) { + let rf = FunctionRef { + export: symbol.export, + name: name.clone(), + params: symbol + .params + .iter() + .map(|(name, v)| (name.clone(), self.var_ref(json_scope, v))) + .collect(), + return_type: symbol + .return_type + .clone() + .map(|rt| self.type_ref(json_scope, &rt)), + }; + + self.variables.insert( + token_name.clone(), + Arc::new(VarRef { + export: false, + name: name.clone(), + var_type: Arc::new(TypeRef::Function(rf)), + }), + ); + } else { + unimplemented!() + } + } + } + + pub fn struct_ref(&mut self, json_scope: &JSONScope, symbol: &StructSymbol) -> Arc { + if let Some(rf) = self.variables.get(&symbol.name) { + rf.var_type.clone() + } else { + let name = symbol.name.clone(); + + let rf = Arc::new(TypeRef::Struct(StructRef { + export: symbol.export, + name: name.clone(), + fields: symbol + .fields + .iter() + .map(|(name, v)| (name.clone(), self.var_ref(json_scope, v))) + .collect(), + })); + + self.variables.insert( + symbol.name.clone(), + Arc::new(VarRef { + export: symbol.export, + name: name.clone(), + var_type: rf.clone(), + }), + ); + + rf + } + } + + pub fn var_ref(&mut self, json_scope: &JSONScope, symbol: &VarSymbol) -> Arc { + Arc::new(VarRef { + export: symbol.export, + var_type: self.type_ref(json_scope, &symbol.var_type), + name: symbol.name.clone(), + }) + } + + pub fn type_ref(&mut self, json_scope: &JSONScope, symbol: &TypeSymbol) -> Arc { + if let Some(var_ref) = self.variables.get(&symbol.0) { + var_ref.var_type.clone() + } else { + if let Some(json_scope_rf) = json_scope.structs.get(&symbol.0) { + self.struct_ref(json_scope, json_scope_rf) + } else { + let var_ref = Arc::new(VarRef { + export: false, + name: symbol.0.clone(), + var_type: Arc::new(TypeRef::Name(symbol.0.clone())), + }); + + self.variables.insert(symbol.0.clone(), var_ref.clone()); + + var_ref.var_type.clone() + } + } + } + + pub fn get_reference(&self, name: &String) -> Option> { + self.variables.get(name).cloned() + } +} diff --git a/semantic/src/lib.rs b/semantic/src/lib.rs index 24057de..9af6dfa 100644 --- a/semantic/src/lib.rs +++ b/semantic/src/lib.rs @@ -14,13 +14,17 @@ pub mod top_level; pub fn walk_ast(top_scope: Arc, tl: &mut Vec) { for tl in tl { match tl { - parser::ast::TopLevel::Import(_) => unimplemented!(), + parser::ast::TopLevel::Import(_) => {} + parser::ast::TopLevel::Package(_) => {} parser::ast::TopLevel::FunctionDecl { params, body, name, .. } => { let rf = top_scope.get_reference(name).unwrap(); - *name = rf.name.clone(); + + if name != "main" { + *name = rf.name.clone(); + } match &*rf.var_type { hir::TypeRef::Function(f) => walk_param_list(&f.params, params), diff --git a/semantic/src/scope.rs b/semantic/src/scope.rs index dff29cf..4ad0655 100644 --- a/semantic/src/scope.rs +++ b/semantic/src/scope.rs @@ -17,8 +17,11 @@ pub enum Scope { } impl Scope { - pub fn from_top(top_level: &Vec) -> Arc { - let tl = TopLevelSymbolScope::from(top_level); + pub fn from_top( + path: &std::path::PathBuf, + top_level: &Vec, + ) -> Arc { + let tl = TopLevelSymbolScope::from(path, top_level); Arc::new(Self::TopLevel(TopLevelHirScope::from_tlss(&tl))) } @@ -52,12 +55,31 @@ impl LocalScope { } pub fn get_reference(&self, name: &String) -> Option> { - self.variables + let rf = self + .variables .lock() .unwrap() .get(name) .cloned() - .or_else(|| self.parent.get_reference(name)) + .or_else(|| self.parent.get_reference(name)); + + match rf { + Some(v) => Some(v), + None => { + let var_ref = Arc::new(VarRef { + export: false, + name: name.clone(), + var_type: Arc::new(TypeRef::Name(name.clone())), + }); + + self.variables + .lock() + .unwrap() + .insert(name.clone(), var_ref.clone()); + + Some(var_ref) + } + } } pub fn with_block(self: &Arc, block: &mut parser::ast::Block) { @@ -78,6 +100,9 @@ impl LocalScope { ); } } + Statement::Expression(e) => { + self.get_type_from_expr(e); + } _ => {} } } @@ -98,6 +123,11 @@ impl LocalScope { *id = field.name.clone(); current_type = field.var_type.clone(); } + TypeRef::Package(p) => { + let var_ref = p.variables.get(id)?; + *id = var_ref.name.clone(); + current_type = var_ref.var_type.clone(); + } _ => unimplemented!(), }, Postfix::Call(_args) => match &*current_type { @@ -120,22 +150,18 @@ impl LocalScope { ) -> Option> { match expr { ast::Expression::IntLiteral(_) => self - .parent .get_reference(&"int".to_string()) .map(|r| r.var_type.clone()), ast::Expression::FloatLiteral(_) => self - .parent .get_reference(&"float".to_string()) .map(|r| r.var_type.clone()), ast::Expression::BoolLiteral(_) => self - .parent .get_reference(&"bool".to_string()) .map(|r| r.var_type.clone()), ast::Expression::StringLiteral(_) => self - .parent .get_reference(&"string".to_string()) .map(|r| r.var_type.clone()), diff --git a/semantic/src/top_level.rs b/semantic/src/top_level.rs index f58fa96..9be616f 100644 --- a/semantic/src/top_level.rs +++ b/semantic/src/top_level.rs @@ -1,20 +1,23 @@ use std::collections::HashMap; +use std::fs; +use std::path::PathBuf; use parser::ast::TopLevel; use parser::ast::{ParamList, TypeExpr}; +use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct TypeSymbol(pub String); -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct VarSymbol { pub export: bool, pub var_type: TypeSymbol, pub name: String, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct FunctionSymbol { pub export: bool, pub name: String, @@ -22,7 +25,7 @@ pub struct FunctionSymbol { pub return_type: Option, } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct StructSymbol { pub export: bool, pub name: String, @@ -96,22 +99,45 @@ impl StructSymbol { } } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct JSONScope { + pub structs: HashMap, + pub functions: HashMap, + pub package_name: String, +} + +type JsonScopeMap = HashMap; + #[derive(Clone, Debug)] pub struct TopLevelSymbolScope { + pub imports: HashMap, pub structs: HashMap, pub functions: HashMap, } impl TopLevelSymbolScope { - pub fn from(top_level: &Vec) -> Self { + pub fn from(path: &PathBuf, top_level: &Vec) -> Self { let mut scope = TopLevelSymbolScope { + imports: HashMap::new(), structs: HashMap::new(), functions: HashMap::new(), }; for top in top_level { match top { - TopLevel::Import(_) => unimplemented!(), + TopLevel::Import(package) => { + let json = fs::read(path.join("mist.map.json")).unwrap(); + let json_scope_map: JsonScopeMap = serde_json::from_slice(&json).unwrap(); + let json_scope = json_scope_map + .get(&package.replace("\"", "")) + .unwrap() + .clone(); + + scope + .imports + .insert(json_scope.package_name.clone(), json_scope); + } + TopLevel::Package(_) => {} TopLevel::FunctionDecl { export, diff --git a/src/compiler.rs b/src/compiler.rs new file mode 100644 index 0000000..c7cbbe9 --- /dev/null +++ b/src/compiler.rs @@ -0,0 +1,95 @@ +use std::{fs, path::PathBuf, process, time::Instant}; + +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 mut ast = match parser::parse(&source) { + Ok(ast) => ast, + Err(e) => { + eprintln!("error: parse failed\n{}", e); + process::exit(1); + } + }; + + println!(" → type checking..."); + + semantic::walk_ast(semantic::scope::Scope::from_top(&root, &ast), &mut ast); + + println!(" → generating Go code..."); + + let mut gc = codegen::GoCodegen::new(); + let output = gc.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.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") +} diff --git a/src/main.rs b/src/main.rs index a9e6d3a..6331f9c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +pub mod compiler; + use std::fs; use std::path::PathBuf; use std::process; @@ -18,7 +20,7 @@ fn main() { eprintln!("error: expected a file path\n usage: mist build"); process::exit(1); } - cmd_build(); + compiler::build(); } "check" => { if args.len() < 3 { @@ -48,17 +50,13 @@ fn main() { } } -pub fn cmd_build() { - unimplemented!("build command is not implemented yet"); -} - fn cmd_check(path: &str) { let source = read_ms_file(path); match parser::parse(&source) { Ok(mut ast) => { println!("parse: ok"); - walk_ast(semantic::scope::Scope::from_top(&ast), &mut ast); + // walk_ast(semantic::scope::Scope::from_top(&ast), &mut ast); println!("{:#?}", ast) } @@ -73,7 +71,7 @@ fn cmd_parse(path: &str) { let source = read_ms_file(path); match parser::parse(&source) { Ok(mut ast) => { - walk_ast(semantic::scope::Scope::from_top(&ast), &mut ast); + // walk_ast(semantic::scope::Scope::from_top(&ast), &mut ast); fs::write("output.json", serde_json::to_string_pretty(&ast).unwrap()).unwrap_or_else( |e| { diff --git a/test/mist.map.json b/test/mist.map.json new file mode 100644 index 0000000..529a5df --- /dev/null +++ b/test/mist.map.json @@ -0,0 +1,450 @@ +{ + "fmt": { + "package_name": "fmt", + "structs": {}, + "functions": { + "append": { + "export": true, + "name": "Append", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "b": { + "export": true, + "var_type": "[]byte", + "name": "b" + } + }, + "return_types": [ + "[]byte" + ] + }, + "appendf": { + "export": true, + "name": "Appendf", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "b": { + "export": true, + "var_type": "[]byte", + "name": "b" + }, + "format": { + "export": true, + "var_type": "string", + "name": "format" + } + }, + "return_types": [ + "[]byte" + ] + }, + "appendln": { + "export": true, + "name": "Appendln", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "b": { + "export": true, + "var_type": "[]byte", + "name": "b" + } + }, + "return_types": [ + "[]byte" + ] + }, + "errorf": { + "export": true, + "name": "Errorf", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "format": { + "export": true, + "var_type": "string", + "name": "format" + } + }, + "return_types": [ + "error" + ] + }, + "formatString": { + "export": true, + "name": "FormatString", + "params": { + "state": { + "export": true, + "var_type": "fmt.State", + "name": "state" + }, + "verb": { + "export": true, + "var_type": "rune", + "name": "verb" + } + }, + "return_types": [ + "string" + ] + }, + "fprint": { + "export": true, + "name": "Fprint", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "w": { + "export": true, + "var_type": "io.Writer", + "name": "w" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "fprintf": { + "export": true, + "name": "Fprintf", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "format": { + "export": true, + "var_type": "string", + "name": "format" + }, + "w": { + "export": true, + "var_type": "io.Writer", + "name": "w" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "fprintln": { + "export": true, + "name": "Fprintln", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "w": { + "export": true, + "var_type": "io.Writer", + "name": "w" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "fscan": { + "export": true, + "name": "Fscan", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "r": { + "export": true, + "var_type": "io.Reader", + "name": "r" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "fscanf": { + "export": true, + "name": "Fscanf", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "format": { + "export": true, + "var_type": "string", + "name": "format" + }, + "r": { + "export": true, + "var_type": "io.Reader", + "name": "r" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "fscanln": { + "export": true, + "name": "Fscanln", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "r": { + "export": true, + "var_type": "io.Reader", + "name": "r" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "print": { + "export": true, + "name": "Print", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "printf": { + "export": true, + "name": "Printf", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "format": { + "export": true, + "var_type": "string", + "name": "format" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "println": { + "export": true, + "name": "Println", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "scan": { + "export": true, + "name": "Scan", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "scanf": { + "export": true, + "name": "Scanf", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "format": { + "export": true, + "var_type": "string", + "name": "format" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "scanln": { + "export": true, + "name": "Scanln", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "sprint": { + "export": true, + "name": "Sprint", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + } + }, + "return_types": [ + "string" + ] + }, + "sprintf": { + "export": true, + "name": "Sprintf", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "format": { + "export": true, + "var_type": "string", + "name": "format" + } + }, + "return_types": [ + "string" + ] + }, + "sprintln": { + "export": true, + "name": "Sprintln", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + } + }, + "return_types": [ + "string" + ] + }, + "sscan": { + "export": true, + "name": "Sscan", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "str": { + "export": true, + "var_type": "string", + "name": "str" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "sscanf": { + "export": true, + "name": "Sscanf", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "format": { + "export": true, + "var_type": "string", + "name": "format" + }, + "str": { + "export": true, + "var_type": "string", + "name": "str" + } + }, + "return_types": [ + "int", + "error" + ] + }, + "sscanln": { + "export": true, + "name": "Sscanln", + "params": { + "a": { + "export": true, + "var_type": "[]any", + "name": "a" + }, + "str": { + "export": true, + "var_type": "string", + "name": "str" + } + }, + "return_types": [ + "int", + "error" + ] + } + } + } +} \ No newline at end of file diff --git a/test/src/main.ms b/test/src/main.ms index 59ea0e1..d994a44 100644 --- a/test/src/main.ms +++ b/test/src/main.ms @@ -1,5 +1,7 @@ -export struct MyType { - export x: int, -} +package main; -export function test(a: MyType) { } +import "fmt"; + +function main() { + fmt.println("hello"); +}