├── .gitignore ├── macro ├── .gitignore ├── Cargo.toml ├── Cargo.lock └── src │ └── lib.rs ├── notebooks └── .gitignore ├── monoteny ├── core │ ├── bool.monoteny │ ├── run.monoteny │ ├── transpilation.monoteny │ ├── strings.monoteny │ └── debug.monoteny ├── common.monoteny ├── core.monoteny └── common │ ├── precedence.monoteny │ ├── debug.monoteny │ └── math.monoteny ├── src ├── util │ ├── graphs.rs │ ├── hash.rs │ ├── strings.rs │ ├── file_writer.rs │ ├── multimap.rs │ ├── iter.rs │ ├── position.rs │ ├── vec.rs │ ├── fmt.rs │ └── graphs │ │ ├── change_graph.rs │ │ ├── task_dependency_graph.rs │ │ └── node_tree.rs ├── interpreter │ ├── builtins.rs │ ├── compile.rs │ ├── vm │ │ ├── util.rs │ │ └── call_frame.rs │ ├── opcode.rs │ ├── data.rs │ ├── data_layout.rs │ ├── builtins │ │ └── modules.rs │ ├── disassembler.rs │ ├── chunks.rs │ ├── run.rs │ └── compile │ │ └── function_descriptor_compiler.rs ├── parser │ ├── strings.rs │ ├── expressions │ │ └── token.rs │ ├── lexer │ │ └── token.rs │ ├── tests.rs │ ├── error.rs │ └── grammar.rs ├── static_analysis │ └── implicit_clones.rs ├── util.rs ├── program.rs ├── static_analysis.rs ├── interpreter.rs ├── resolver │ ├── structs.rs │ ├── ambiguous.rs │ ├── interpreter_mock.rs │ ├── fields.rs │ ├── precedence_order.rs │ ├── decorations.rs │ ├── ambiguous │ │ └── abstract_call.rs │ ├── imports.rs │ ├── referencible.rs │ ├── imperative_builder.rs │ ├── conformance.rs │ └── type_factory.rs ├── ast │ ├── trait_.rs │ ├── string.rs │ ├── block.rs │ ├── conformance.rs │ ├── function.rs │ ├── struct_.rs │ ├── array.rs │ ├── expression.rs │ ├── decorated.rs │ ├── term.rs │ └── statement.rs ├── transpiler │ ├── python │ │ ├── strings.rs │ │ ├── types.rs │ │ ├── class.rs │ │ └── representations.rs │ ├── structs.rs │ ├── namespaces.rs │ └── tests.rs ├── resolver.rs ├── program │ ├── traits.rs │ ├── functions.rs │ ├── functions │ │ ├── implementation.rs │ │ ├── overload.rs │ │ ├── function_binding.rs │ │ ├── representation.rs │ │ ├── logic.rs │ │ └── head.rs │ ├── traits │ │ ├── structs.rs │ │ ├── binding.rs │ │ ├── trait_.rs │ │ └── conformance.rs │ ├── primitives.rs │ ├── debug.rs │ ├── expression_tree.rs │ ├── allocation.rs │ ├── module.rs │ └── types.rs ├── main.rs ├── repository.rs ├── ast.rs ├── cli │ ├── run.rs │ ├── expression.rs │ ├── logging.rs │ ├── check.rs │ └── transpile.rs ├── refactor │ ├── analyze.rs │ ├── call_graph.rs │ ├── locals.rs │ └── simplify.rs ├── parser.rs ├── cli.rs ├── source.rs └── transpiler.rs ├── resources ├── Monoteny.afdesign ├── MonotenyLogo.png ├── ExampleInheritance.afdesign └── Monoteny.tmbundle │ └── info.plist ├── .idea ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── other.xml ├── .gitignore ├── modules.xml ├── misc.xml └── runConfigurations │ ├── Transpile.xml │ ├── Run.xml │ ├── Test.xml │ └── Check.xml ├── test-code ├── hello_world.monoteny ├── requirements │ ├── eq0.monoteny │ ├── eq1.monoteny │ └── eq2.monoteny ├── debug │ └── assert.monoteny ├── control_flow │ ├── if_then_else.monoteny │ └── and_or.monoteny ├── grammar │ ├── string_interpolation.monoteny │ └── custom_grammar.monoteny ├── monomorphization │ └── branch.monoteny └── traits │ ├── simple.monoteny │ ├── fields.monoteny │ └── conformance.monoteny ├── python ├── pyproject.toml └── poetry.lock ├── CONTRIBUTING.md ├── .github └── workflows │ ├── check-rust.yml │ └── build.yml ├── Cargo.toml └── monoteny.iml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /macro/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /notebooks/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /monoteny/core/bool.monoteny: -------------------------------------------------------------------------------- 1 | def true -> Bool; 2 | def false -> Bool; 3 | -------------------------------------------------------------------------------- /src/util/graphs.rs: -------------------------------------------------------------------------------- 1 | pub mod task_dependency_graph; 2 | pub mod change_graph; 3 | pub mod node_tree; 4 | -------------------------------------------------------------------------------- /resources/Monoteny.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivorforce/Monoteny/HEAD/resources/Monoteny.afdesign -------------------------------------------------------------------------------- /resources/MonotenyLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivorforce/Monoteny/HEAD/resources/MonotenyLogo.png -------------------------------------------------------------------------------- /src/interpreter/builtins.rs: -------------------------------------------------------------------------------- 1 | pub mod primitives; 2 | pub mod traits; 3 | pub mod vm; 4 | pub mod modules; 5 | -------------------------------------------------------------------------------- /resources/ExampleInheritance.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ivorforce/Monoteny/HEAD/resources/ExampleInheritance.afdesign -------------------------------------------------------------------------------- /src/interpreter/compile.rs: -------------------------------------------------------------------------------- 1 | pub mod function_compiler; 2 | pub mod compile_server; 3 | pub mod function_descriptor_compiler; 4 | -------------------------------------------------------------------------------- /monoteny/common.monoteny: -------------------------------------------------------------------------------- 1 | include!( 2 | module!(".precedence"), 3 | module!(".math"), 4 | module!(".debug"), 5 | ); 6 | -------------------------------------------------------------------------------- /macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "monoteny-macro" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate_type = ["proc-macro"] 8 | -------------------------------------------------------------------------------- /monoteny/core/run.monoteny: -------------------------------------------------------------------------------- 1 | use!( 2 | module!("core.bool"), 3 | module!("core.strings"), 4 | module!("core.debug"), 5 | ); 6 | 7 | def main(); 8 | -------------------------------------------------------------------------------- /monoteny/core.monoteny: -------------------------------------------------------------------------------- 1 | include!( 2 | module!(".bool"), 3 | module!(".strings"), 4 | module!(".debug"), 5 | module!(".run"), 6 | module!(".transpilation"), 7 | ); 8 | -------------------------------------------------------------------------------- /src/parser/strings.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use regex::Regex; 3 | 4 | lazy_static! { 5 | pub static ref REGEX_UNESCAPE: Regex = Regex::new(r"\\(.)").unwrap(); 6 | } 7 | -------------------------------------------------------------------------------- /src/util/hash.rs: -------------------------------------------------------------------------------- 1 | use std::hash::{Hash, Hasher}; 2 | 3 | pub fn one(object: impl Hash, mut hasher: impl Hasher) -> u64 { 4 | object.hash(&mut hasher); 5 | hasher.finish() 6 | } 7 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /macro/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "monoteny-macro" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /src/static_analysis/implicit_clones.rs: -------------------------------------------------------------------------------- 1 | use crate::program::functions::FunctionImplementation; 2 | 3 | pub fn explicitize_clones(implementation: &mut FunctionImplementation) { 4 | todo!() 5 | } 6 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | pub mod fmt; 2 | pub mod multimap; 3 | pub mod hash; 4 | pub mod position; 5 | pub mod iter; 6 | pub mod vec; 7 | pub mod strings; 8 | pub mod file_writer; 9 | pub mod graphs; 10 | -------------------------------------------------------------------------------- /test-code/hello_world.monoteny: -------------------------------------------------------------------------------- 1 | use!(module!("common")); 2 | 3 | def main! :: { 4 | write_line("Hello World!"); 5 | }; 6 | 7 | def transpile! :: { 8 | transpiler.add(main); 9 | }; 10 | -------------------------------------------------------------------------------- /src/interpreter/vm/util.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreter::data::Value; 2 | 3 | #[inline(always)] 4 | pub unsafe fn pop_stack(sp: &mut *mut Value) -> Value { 5 | *sp = (*sp).offset(-8); 6 | **sp 7 | } 8 | -------------------------------------------------------------------------------- /.idea/other.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /src/program.rs: -------------------------------------------------------------------------------- 1 | pub mod allocation; 2 | pub mod expression_tree; 3 | pub mod functions; 4 | pub mod generics; 5 | pub mod primitives; 6 | pub mod traits; 7 | pub mod types; 8 | pub mod module; 9 | pub mod debug; 10 | -------------------------------------------------------------------------------- /test-code/requirements/eq0.monoteny: -------------------------------------------------------------------------------- 1 | -- Tests whether we can call is_equal statically. 2 | 3 | def main! :: { 4 | is_equal(1, 1 'Int32); 5 | }; 6 | 7 | def transpile! :: { 8 | transpiler.add(main); 9 | }; 10 | -------------------------------------------------------------------------------- /test-code/debug/assert.monoteny: -------------------------------------------------------------------------------- 1 | -- Tests that assertions can fail. 2 | 3 | use!(module!("common")); 4 | 5 | def main! :: { 6 | assert(false); 7 | }; 8 | 9 | def transpile! :: { 10 | transpiler.add(main); 11 | }; 12 | -------------------------------------------------------------------------------- /src/static_analysis.rs: -------------------------------------------------------------------------------- 1 | use crate::error::RResult; 2 | use crate::program::functions::FunctionImplementation; 3 | 4 | pub mod implicit_clones; 5 | 6 | pub fn check(function: &mut FunctionImplementation) -> RResult<()> { 7 | Ok(()) 8 | } 9 | -------------------------------------------------------------------------------- /src/interpreter.rs: -------------------------------------------------------------------------------- 1 | pub mod vm; 2 | pub mod run; 3 | pub mod chunks; 4 | pub mod builtins; 5 | pub mod opcode; 6 | pub mod disassembler; 7 | pub mod data; 8 | pub mod runtime; 9 | pub mod data_layout; 10 | pub mod compile; 11 | mod tests; 12 | -------------------------------------------------------------------------------- /src/interpreter/vm/call_frame.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreter::chunks::Chunk; 2 | use crate::interpreter::data::Value; 3 | use std::rc::Rc; 4 | 5 | pub struct CallFrame { 6 | pub chunk: Rc, 7 | pub ip: *const u8, 8 | pub fp: *mut Value, 9 | } 10 | -------------------------------------------------------------------------------- /test-code/control_flow/if_then_else.monoteny: -------------------------------------------------------------------------------- 1 | def main! :: { 2 | if true :: _write_line("true") 3 | else :: if false :: _write_line("false") 4 | else :: _write_line("maybe"); 5 | }; 6 | 7 | def transpile! :: { 8 | transpiler.add(main); 9 | }; 10 | -------------------------------------------------------------------------------- /test-code/grammar/string_interpolation.monoteny: -------------------------------------------------------------------------------- 1 | -- This tests whether we strings can be interpolated. 2 | 3 | def main! :: { 4 | _write_line("Left: \("String"), Right: \(2 'Int32)"); 5 | }; 6 | 7 | def transpile! :: { 8 | transpiler.add(main); 9 | }; 10 | -------------------------------------------------------------------------------- /monoteny/core/transpilation.monoteny: -------------------------------------------------------------------------------- 1 | use!( 2 | module!("core.bool"), 3 | module!("core.strings"), 4 | module!("core.debug"), 5 | ); 6 | 7 | trait Transpiler { 8 | }; 9 | 10 | def (self 'Transpiler).add(object '#A); 11 | 12 | def transpile(transpiler 'Transpiler); 13 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/resolver/structs.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::hash::Hash; 3 | 4 | use crate::program::expression_tree::ExpressionID; 5 | use crate::program::functions::ParameterKey; 6 | 7 | #[derive(Clone)] 8 | pub struct AnonymousStruct { 9 | pub keys: Vec, 10 | pub values: Vec 11 | } 12 | -------------------------------------------------------------------------------- /monoteny/common/precedence.monoteny: -------------------------------------------------------------------------------- 1 | precedence_order!( 2 | LeftUnaryPrecedence: LeftUnary, 3 | ExponentiationPrecedence: Right, 4 | MultiplicationPrecedence: Left, 5 | AdditionPrecedence: Left, 6 | ComparisonPrecedence: LeftConjunctivePairs, 7 | LogicalConjunctionPrecedence: Left, 8 | LogicalDisjunctionPrecedence: Left, 9 | ); 10 | -------------------------------------------------------------------------------- /test-code/requirements/eq1.monoteny: -------------------------------------------------------------------------------- 1 | -- Tests whether we can call is_equal with one layer of indirection. 2 | -- This is_equal_1 calls is_equal from its requirements. 3 | 4 | def is_equal_1(lhs '$Eq, rhs '$Eq) -> Bool :: is_equal(lhs, rhs); 5 | 6 | def main! :: { 7 | is_equal_1(1, 1 'Int32); 8 | }; 9 | 10 | def transpile! :: { 11 | transpiler.add(main); 12 | }; 13 | -------------------------------------------------------------------------------- /python/pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "monoteny" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Lukas Tenbrink "] 6 | readme = "README.md" 7 | 8 | [tool.poetry.dependencies] 9 | python = "^3.9" 10 | numpy = "^1.26.4" 11 | 12 | 13 | [build-system] 14 | requires = ["poetry-core"] 15 | build-backend = "poetry.core.masonry.api" 16 | -------------------------------------------------------------------------------- /test-code/monomorphization/branch.monoteny: -------------------------------------------------------------------------------- 1 | -- Tests whether monomorphization can yield two separate functions. 2 | 3 | def (self '$Number).square() -> $Number :: multiply(self, self); 4 | 5 | def main! :: { 6 | _write_line("\(3.square() 'Int32)"); 7 | _write_line("\(3.square() 'Float32)"); 8 | }; 9 | 10 | def transpile! :: { 11 | transpiler.add(main); 12 | }; 13 | -------------------------------------------------------------------------------- /src/util/strings.rs: -------------------------------------------------------------------------------- 1 | pub fn map_chars(string: &str, fun: impl Fn(char) -> Option<&'static str>) -> String { 2 | let mut output = String::with_capacity(string.len()); 3 | for char in string.chars() { 4 | if let Some(map) = fun(char) { 5 | output.push_str(map); 6 | } 7 | else { 8 | output.push(char); 9 | } 10 | } 11 | output 12 | } 13 | -------------------------------------------------------------------------------- /monoteny/core/strings.monoteny: -------------------------------------------------------------------------------- 1 | use!( 2 | module!("core.bool"), 3 | ); 4 | 5 | -- The easiest way to format an object: It formats itself! 6 | def format(object '$ToString) -> String :: object.to_string(); 7 | 8 | declare String is ToString :: { 9 | def (self 'Self).to_string() -> String :: self; 10 | }; 11 | 12 | -- Provided by the transpiler. 13 | def add(lhs 'String, rhs 'String) -> String; 14 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/ast/trait_.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | use crate::ast::Block; 4 | 5 | #[derive(Eq, PartialEq, Clone)] 6 | pub struct TraitDefinition { 7 | pub name: String, 8 | pub block: Box, 9 | } 10 | 11 | impl Display for TraitDefinition { 12 | fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { 13 | write!(fmt, "trait {} {{\n{}}}", self.name, self.block) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /monoteny/core/debug.monoteny: -------------------------------------------------------------------------------- 1 | use!( 2 | module!("core.bool"), 3 | module!("core.strings"), 4 | ); 5 | 6 | -- Supplied by transpiler. 7 | def _exit_with_error() -> #; 8 | 9 | -- TODO This should be attached to a Console trait. 10 | -- But that only makes sense once we can constant fold away objects without storage. 11 | -- - otherwise, we'll have ugly write_line(console, "...") calls! 12 | def _write_line(value 'String); 13 | -------------------------------------------------------------------------------- /src/transpiler/python/strings.rs: -------------------------------------------------------------------------------- 1 | use crate::util::strings; 2 | 3 | pub fn escape_string(string: &str) -> String { 4 | strings::map_chars(string, |ch| { 5 | Some(match ch { 6 | '\\' => "\\\\", 7 | '\n' => "\\n", 8 | '\0' => "\\0", 9 | '\t' => "\\t", 10 | '\r' => "\\r", 11 | '\"' => "\\\"", 12 | _ => return None, 13 | }) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /test-code/requirements/eq2.monoteny: -------------------------------------------------------------------------------- 1 | -- Tests whether we can call is_equal with one layer of indirection. 2 | -- This is_equal_2 must pass on its requirements to is_equal_1. 3 | 4 | def is_equal_1(lhs '$Eq, rhs '$Eq) -> Bool :: is_equal(lhs, rhs); 5 | def is_equal_2(lhs '$Eq, rhs '$Eq) -> Bool :: is_equal_1(lhs, rhs); 6 | 7 | def main! :: { 8 | is_equal_2(1, 1 'Int32); 9 | }; 10 | 11 | def transpile! :: { 12 | transpiler.add(main); 13 | }; 14 | -------------------------------------------------------------------------------- /src/resolver.rs: -------------------------------------------------------------------------------- 1 | pub use crate::resolver::global::resolve_file; 2 | 3 | pub mod scopes; 4 | pub mod imperative; 5 | pub mod traits; 6 | pub mod conformance; 7 | pub mod global; 8 | pub mod ambiguous; 9 | pub mod type_factory; 10 | pub mod interface; 11 | pub mod fields; 12 | pub mod imports; 13 | pub mod interpreter_mock; 14 | pub mod referencible; 15 | pub mod structs; 16 | pub mod decorations; 17 | pub mod precedence_order; 18 | pub mod function; 19 | pub mod imperative_builder; 20 | -------------------------------------------------------------------------------- /src/program/traits.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::hash::{Hash, Hasher}; 3 | 4 | use itertools::Itertools; 5 | 6 | pub use binding::TraitBinding; 7 | pub use conformance::{RequirementsAssumption, RequirementsFulfillment, TraitConformance, TraitConformanceWithTail}; 8 | pub use graph::{TraitConformanceRule, TraitGraph}; 9 | pub use structs::StructInfo; 10 | pub use trait_::{FieldHint, Trait}; 11 | 12 | mod conformance; 13 | mod binding; 14 | mod graph; 15 | mod trait_; 16 | mod structs; 17 | -------------------------------------------------------------------------------- /src/ast/string.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | use crate::ast::Struct; 4 | 5 | #[derive(PartialEq, Eq, Clone)] 6 | pub enum StringPart { 7 | Literal(String), 8 | Object(Box), 9 | } 10 | 11 | impl Display for StringPart { 12 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 13 | match self { 14 | StringPart::Literal(s) => write!(f, "{}", s), 15 | StringPart::Object(struct_) => write!(f, "{}", struct_), 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /test-code/traits/simple.monoteny: -------------------------------------------------------------------------------- 1 | -- Tests traits with fields. 2 | 3 | use!(module!("common")); 4 | 5 | trait Animal { 6 | var height_cm 'Float32; 7 | }; 8 | 9 | def main! :: { 10 | -- Create a cat with pre-defined name and height. 11 | var animal = Animal(height_cm: 180); 12 | 13 | write_line("Height 1: \(animal.height_cm)cm"); 14 | 15 | upd animal.height_cm = 150; 16 | write_line("Height 2: \(animal.height_cm)cm"); 17 | }; 18 | 19 | def transpile! :: { 20 | transpiler.add(main); 21 | }; 22 | -------------------------------------------------------------------------------- /src/ast/block.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Error, Formatter}; 2 | 3 | use crate::ast::decorated::Decorated; 4 | use crate::ast::Statement; 5 | use crate::util::fmt::write_separated_display; 6 | use crate::util::position::Positioned; 7 | 8 | #[derive(Eq, PartialEq, Clone)] 9 | pub struct Block { 10 | pub statements: Vec>>> 11 | } 12 | 13 | impl Display for Block { 14 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { 15 | write_separated_display(fmt, "\n", self.statements.iter()) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ast/conformance.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | use crate::ast::expression::Expression; 4 | use crate::ast::Block; 5 | 6 | #[derive(Eq, PartialEq, Clone)] 7 | pub struct TraitConformanceDeclaration { 8 | pub declared_for: Expression, 9 | pub declared: Expression, 10 | pub block: Box, 11 | } 12 | 13 | impl Display for TraitConformanceDeclaration { 14 | fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { 15 | write!(fmt, "declare {} is {} {{}} :: {{\n{}}}", self.declared_for, self.declared, self.block) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate core; 2 | #[macro_use] 3 | extern crate lalrpop_util; 4 | 5 | use std::env; 6 | use std::process::ExitCode; 7 | 8 | use itertools::Itertools; 9 | 10 | lalrpop_mod!(pub monoteny_grammar); 11 | pub mod interpreter; 12 | pub mod resolver; 13 | pub mod parser; 14 | pub mod program; 15 | pub mod transpiler; 16 | pub mod util; 17 | pub mod error; 18 | pub mod repository; 19 | pub mod refactor; 20 | pub mod source; 21 | pub mod cli; 22 | pub mod static_analysis; 23 | pub mod ast; 24 | 25 | fn main() -> ExitCode { 26 | println!("{}", env::args().join(" ")); 27 | cli::run_command() 28 | } 29 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Monoteny is in its early stages of development. Its design and implementation are highly exploratory, and features are expected to stop and start working regularly as the language is expanded. 2 | 3 | I do not recommend using the language in its current form. If you want to get involved, discuss the language design or make contributions, please reach out to me. 4 | 5 | #### Socials 6 | 7 | - [Discord](https://discord.gg/zgyNx9K7mE) (for discussions) 8 | - [Other Socials](https://tenbrink.me) (if you do not use discord) 9 | - [Issues](https://github.com/Ivorforce/Monoteny/issues) (for tracking implementation issues) 10 | -------------------------------------------------------------------------------- /src/repository.rs: -------------------------------------------------------------------------------- 1 | use crate::program::module::ModuleName; 2 | use std::collections::HashMap; 3 | use std::path::PathBuf; 4 | 5 | pub enum Loader { 6 | Path(PathBuf), 7 | Intrinsic(HashMap) 8 | } 9 | 10 | pub struct Repository { 11 | pub entries: HashMap, 12 | } 13 | 14 | impl Repository { 15 | pub fn new() -> Box { 16 | Box::new(Repository { 17 | entries: Default::default(), 18 | }) 19 | } 20 | 21 | pub fn add(&mut self, name: &str, loader: Loader) { 22 | self.entries.insert(name.to_string(), loader); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /resources/Monoteny.tmbundle/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | contactEmailRot13 6 | yhxnf.graoevax@tznvy.pbz 7 | contactName 8 | Lukas Tenbrink 9 | description 10 | Rudimentary grammar support for the Monoteny programming language. 11 | name 12 | Monoteny 13 | uuid 14 | 4A934ACC-37B3-48A2-8289-B0AEA3E0024B 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/util/file_writer.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::io::Write; 3 | use std::path::{Path, PathBuf}; 4 | 5 | pub fn write_file_safe(base_path: &Path, sub_path: &str, content: &str) -> PathBuf { 6 | let file_path = base_path.join(sub_path); 7 | 8 | if !file_path.starts_with(base_path) { 9 | panic!("Tried to write a file in unexpected directory: {}", file_path.as_os_str().to_string_lossy()); 10 | } 11 | 12 | let mut f = File::create(file_path.clone()).expect("Unable to create file"); 13 | let f: &mut (dyn Write) = &mut f; 14 | write!(f, "{}", content).expect("Error writing file"); 15 | 16 | file_path 17 | } -------------------------------------------------------------------------------- /test-code/control_flow/and_or.monoteny: -------------------------------------------------------------------------------- 1 | use!(module!("common")); 2 | 3 | def main! :: { 4 | if true and true :: _write_line("true and true"); 5 | if false and true :: _write_line("false and true"); 6 | if true and false :: _write_line("true and false"); 7 | if false and false :: _write_line("false and false"); 8 | 9 | if true or true :: _write_line("true or true"); 10 | if false or true :: _write_line("false or true"); 11 | if true or false :: _write_line("true or false"); 12 | if false or false :: _write_line("false or false"); 13 | }; 14 | 15 | def transpile! :: { 16 | transpiler.add(main); 17 | }; 18 | -------------------------------------------------------------------------------- /src/ast.rs: -------------------------------------------------------------------------------- 1 | pub use array::{Array, ArrayArgument}; 2 | pub use block::Block; 3 | pub use conformance::TraitConformanceDeclaration; 4 | pub use decorated::Decorated; 5 | pub use expression::Expression; 6 | pub use function::{Function, FunctionInterface}; 7 | pub use statement::Statement; 8 | pub use string::StringPart; 9 | pub use struct_::{Struct, StructArgument}; 10 | pub use term::{IfThenElse, Term}; 11 | pub use trait_::TraitDefinition; 12 | 13 | mod array; 14 | mod block; 15 | mod struct_; 16 | mod trait_; 17 | mod conformance; 18 | mod statement; 19 | mod expression; 20 | mod term; 21 | mod string; 22 | mod decorated; 23 | mod function; 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/check-rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust Tests 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | - run: rustup toolchain install stable --profile minimal 20 | - uses: Swatinem/rust-cache@v2 21 | with: 22 | prefix-key: "main" 23 | save-if: ${{ github.ref == 'refs/heads/main' }} 24 | - name: Build 25 | run: cargo build --verbose 26 | - name: Run tests 27 | run: cargo test --verbose 28 | -------------------------------------------------------------------------------- /src/resolver/ambiguous.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | use std::ops::Range; 3 | 4 | pub use abstract_call::AmbiguousAbstractCall; 5 | pub use function_call::{AmbiguousFunctionCall, AmbiguousFunctionCandidate}; 6 | 7 | use crate::error::RResult; 8 | use crate::resolver::imperative::ImperativeResolver; 9 | 10 | pub mod function_call; 11 | pub mod abstract_call; 12 | 13 | pub enum AmbiguityResult { 14 | Ok(V), 15 | Ambiguous, 16 | } 17 | 18 | pub trait ResolverAmbiguity: Display { 19 | fn attempt_to_resolve(&mut self, expressions: &mut ImperativeResolver) -> RResult>; 20 | 21 | fn get_position(&self) -> Range; 22 | } 23 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "monoteny" 3 | version = "0.1.0" 4 | edition = "2021" 5 | build = "build.rs" 6 | 7 | [build-dependencies] 8 | lalrpop = { version = "0.20.2" } 9 | 10 | [dependencies] 11 | clap = "4.5.4" 12 | custom_error = "1.9.2" 13 | itertools = "0.12.1" 14 | lalrpop-util = { version = "0.20.2", features = ["lexer"] } 15 | regex = "1" 16 | replace_with = "0.1.7" 17 | strum = { version = "0.26.2", features = ["derive"] } 18 | try_map = "0.3.1" 19 | uuid = { version = "1.1.2", features = ["v4"] } 20 | monoteny-macro = { path = "macro" } 21 | log = "0.4.17" 22 | lazy_static = "1.4.0" 23 | colored = "2.0.4" 24 | linked_hash_set = "0.1.4" 25 | linked-hash-map = "0.5.6" 26 | display_with_options = "0.1.0" 27 | annotate-snippets = "0.11.1" 28 | -------------------------------------------------------------------------------- /src/transpiler/python/types.rs: -------------------------------------------------------------------------------- 1 | use crate::program::types::{TypeProto, TypeUnit}; 2 | use crate::transpiler::python::{ast, FunctionContext}; 3 | 4 | pub fn transpile(type_def: &TypeProto, context: &FunctionContext) -> Box { 5 | match &type_def.unit { 6 | TypeUnit::Struct(s) => { 7 | let representation = &context.representations.type_ids.get(type_def).unwrap_or_else(|| panic!("Unable to find representation for type {:?}", s)); 8 | Box::new(ast::Expression::NamedReference(context.names[representation].clone())) 9 | }, 10 | TypeUnit::Generic(id) => panic!("Failed to transpile {:?}, generics shouldn't exist anymore at this point.", type_def), 11 | TypeUnit::Void => todo!(), 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/program/functions.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Display}; 2 | use std::hash::{Hash, Hasher}; 3 | 4 | use display_with_options::{DebugWithOptions, DisplayWithOptions}; 5 | 6 | pub use function_binding::{resolve_binding, FunctionBinding}; 7 | pub use head::{FunctionHead, FunctionType}; 8 | pub use implementation::FunctionImplementation; 9 | pub use interface::{FunctionInterface, Parameter, ParameterKey}; 10 | pub use logic::{FunctionLogic, FunctionLogicDescriptor, PrimitiveOperation}; 11 | pub use overload::FunctionOverload; 12 | pub use representation::{FunctionCallExplicity, FunctionRepresentation, FunctionTargetType}; 13 | 14 | mod interface; 15 | mod head; 16 | mod representation; 17 | mod overload; 18 | mod function_binding; 19 | mod logic; 20 | mod implementation; 21 | -------------------------------------------------------------------------------- /src/program/functions/implementation.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::rc::Rc; 3 | 4 | use crate::program::allocation::ObjectReference; 5 | use crate::program::expression_tree::ExpressionTree; 6 | use crate::program::functions::FunctionInterface; 7 | use crate::program::generics::TypeForest; 8 | use crate::program::traits::RequirementsAssumption; 9 | 10 | #[derive(Clone)] 11 | pub struct FunctionImplementation { 12 | pub interface: Rc, 13 | 14 | pub requirements_assumption: Box, 15 | 16 | pub expression_tree: Box, 17 | pub type_forest: Box, 18 | 19 | pub parameter_locals: Vec>, 20 | pub locals_names: HashMap, String>, 21 | } 22 | -------------------------------------------------------------------------------- /test-code/traits/fields.monoteny: -------------------------------------------------------------------------------- 1 | -- Tests traits with fields. 2 | 3 | use!(module!("common")); 4 | 5 | trait Animal { 6 | -- Immutable Field 7 | let species 'String; 8 | let name 'String; 9 | 10 | -- Mutable Field 11 | var height_cm 'Float32; 12 | }; 13 | 14 | def main! :: { 15 | -- Create a cat with pre-defined name and height. 16 | var animal = Animal(species: "Cat", name: "Noir", height_cm: 180); 17 | 18 | -- Write current status 19 | write_line("\(animal.name) (\(animal.species)) was: \(animal.height_cm)cm"); 20 | 21 | -- We can set height_cm, but not species or name. 22 | upd animal.height_cm = 25; 23 | write_line("And is now: \(animal.height_cm)cm"); 24 | }; 25 | 26 | def transpile! :: { 27 | transpiler.add(main); 28 | }; 29 | -------------------------------------------------------------------------------- /src/util/multimap.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::hash::Hash; 4 | 5 | pub fn insert_into_multimap(multimap: &mut HashMap>, key: K, value: V) where K: Hash, V: Hash, K: Eq, V: Eq, V: Clone, K: Clone { 6 | match multimap.entry(key) { 7 | Entry::Occupied(o) => { 8 | o.into_mut().insert(value); 9 | } 10 | Entry::Vacant(v) => { 11 | v.insert(HashSet::from([value])); 12 | } 13 | } 14 | } 15 | 16 | pub fn remove_from_multimap(multimap: &mut HashMap>, key: &K, value: &V) where K: Hash, V: Hash, K: Eq, V: Eq, V: Clone, K: Clone { 17 | if let Some(map) = multimap.get_mut(key) { 18 | map.remove(value); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/util/iter.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | pub struct Omega, F: FnMut(&E) -> I> { 4 | pub deeper: F, 5 | pub next: VecDeque, 6 | } 7 | 8 | impl, F: FnMut(&E) -> I> Iterator for Omega { 9 | type Item = E; 10 | 11 | fn next(&mut self) -> Option { 12 | self.next.pop_front().map(|current| { 13 | let next = (self.deeper)(¤t); 14 | self.next.extend(next); 15 | current 16 | }) 17 | } 18 | } 19 | 20 | pub fn omega, I: Iterator, F: FnMut(&E) -> I>(start: I0, mut deeper: F) -> Omega { 21 | Omega { 22 | deeper, 23 | next: VecDeque::from_iter(start), 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/util/position.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | use std::ops::Range; 3 | 4 | #[derive(PartialEq, Eq, Clone, Debug)] 5 | pub struct Positioned { 6 | pub position: Range, 7 | pub value: V, 8 | } 9 | 10 | impl Positioned { 11 | pub fn with_value(&self, v: V1) -> Positioned { 12 | Positioned { 13 | position: self.position.clone(), 14 | value: v, 15 | } 16 | } 17 | } 18 | 19 | pub fn positioned(v: V, start: usize, end: usize) -> Positioned { 20 | Positioned { 21 | position: start..end, 22 | value: v, 23 | } 24 | } 25 | 26 | impl Display for Positioned { 27 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 28 | write!(f, "{}", self.value) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/util/vec.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | pub fn swizzle(vec: &mut Vec, swizzle: &Vec) -> Vec { 4 | if swizzle.is_empty() { 5 | return vec.drain(..).collect_vec() 6 | } 7 | 8 | assert!(swizzle.iter().duplicates().next().is_none()); 9 | 10 | // TODO Would be nicer with unsafe, but eh. 11 | let mut tmp_array = vec.drain(..).enumerate() 12 | .map(|(idx, obj)| (swizzle.iter().position(|p| p == &idx).unwrap_or(usize::MAX), obj)) 13 | .collect_vec(); 14 | tmp_array.sort_by_key(|(idx, obj)| *idx); 15 | let mut removed = vec![]; 16 | for (idx, obj) in tmp_array { 17 | if idx != usize::MAX { 18 | vec.push(obj) 19 | } 20 | else { 21 | removed.push(obj) 22 | } 23 | } 24 | removed 25 | } 26 | -------------------------------------------------------------------------------- /src/parser/expressions/token.rs: -------------------------------------------------------------------------------- 1 | use crate::ast; 2 | use crate::util::position::Positioned; 3 | 4 | pub enum Value<'a, Function> { 5 | Operation(Function, Vec>>), 6 | Identifier(&'a String), 7 | MacroIdentifier(&'a String), 8 | RealLiteral(&'a String), 9 | IntLiteral(&'a String), 10 | StringLiteral(&'a Vec>>), 11 | StructLiteral(&'a ast::Struct), 12 | ArrayLiteral(&'a ast::Array), 13 | Block(&'a ast::Block), 14 | MemberAccess(Box>, &'a String), 15 | FunctionCall(Box>, &'a ast::Struct), 16 | Subscript(Box>, &'a ast::Array), 17 | IfThenElse(&'a ast::IfThenElse), 18 | } 19 | 20 | pub enum Token<'a, Function> { 21 | Keyword(Positioned<&'a String>), 22 | Value(Box>>), 23 | } 24 | -------------------------------------------------------------------------------- /src/program/traits/structs.rs: -------------------------------------------------------------------------------- 1 | use crate::program::allocation::ObjectReference; 2 | use crate::program::functions::FunctionHead; 3 | use crate::program::traits::Trait; 4 | use std::collections::HashMap; 5 | use std::hash::{Hash, Hasher}; 6 | use std::rc::Rc; 7 | 8 | #[derive(Clone, PartialEq, Eq, Debug)] 9 | pub struct StructInfo { 10 | pub trait_: Rc, 11 | 12 | pub clone: Rc, 13 | pub constructor: Rc, 14 | pub fields: Vec>, 15 | 16 | pub field_names: HashMap, String>, 17 | pub field_getters: HashMap, Rc>, 18 | pub field_setters: HashMap, Rc>, 19 | } 20 | 21 | impl Hash for StructInfo { 22 | fn hash(&self, state: &mut H) { 23 | self.trait_.hash(state) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /monoteny/common/debug.monoteny: -------------------------------------------------------------------------------- 1 | use!( 2 | module!("common.precedence"), 3 | module!("common.math"), 4 | ); 5 | 6 | def exit_with_error(error '$ToString) -> # :: { 7 | write_line(error); 8 | return _exit_with_error(); 9 | }; 10 | -- TODO These should accept Default / varargs parameters. 11 | def panic() -> # :: exit_with_error("internal error"); 12 | def panic(error '$ToString) -> # :: exit_with_error("internal error (\(error))"); 13 | def todo() -> # :: panic("not yet implemented"); 14 | def todo(error '$ToString) -> # :: panic("not yet implemented: \(error)"); 15 | def unreachable() -> # :: panic("entered unreachable code"); 16 | def unreachable(error '$ToString) -> # :: panic("entered unreachable code: \(error)"); 17 | 18 | def write_line(value '$ToString) :: _write_line(value.to_string()); 19 | 20 | def assert(value 'Bool) :: { 21 | if not value :: exit_with_error("Assertion failure."); 22 | }; 23 | -------------------------------------------------------------------------------- /test-code/traits/conformance.monoteny: -------------------------------------------------------------------------------- 1 | -- Tests conformance to traits. 2 | 3 | use!(module!("common")); 4 | 5 | trait Animal { 6 | def (self 'Self).talk() -> String; 7 | }; 8 | 9 | trait Cat {}; 10 | 11 | -- Declare every Cat subtype is Animal. 12 | declare $Cat is Animal :: { 13 | def (self 'Self).talk() -> String :: "Meow"; 14 | }; 15 | 16 | trait Dog {}; 17 | 18 | -- Declare just the Dog struct is Animal. 19 | declare Dog is Animal :: { 20 | def (self 'Self).talk() -> String :: "Bark"; 21 | }; 22 | 23 | def converse(lhs '$Animal#lhs, rhs '$Animal#rhs) :: { 24 | write_line("Conversation: \n \(lhs.talk()) \n \(rhs.talk())"); 25 | }; 26 | 27 | def main! :: { 28 | let dog = Dog(); 29 | let cat = Cat(); 30 | 31 | write_line(dog.talk()); 32 | write_line(cat.talk()); 33 | converse(cat, dog); 34 | converse(dog, cat); 35 | }; 36 | 37 | def transpile! :: { 38 | transpiler.add(main); 39 | }; 40 | -------------------------------------------------------------------------------- /test-code/grammar/custom_grammar.monoteny: -------------------------------------------------------------------------------- 1 | -- This tests whether we can define a custom grammar. 2 | 3 | precedence_order!( 4 | LeftUnaryPrecedence: LeftUnary, 5 | ExponentiationPrecedence: Right, 6 | MultiplicationPrecedence: Left, 7 | AdditionPrecedence: Left, 8 | ComparisonPrecedence: LeftConjunctivePairs, 9 | LogicalConjunctionPrecedence: Left, 10 | LogicalDisjunctionPrecedence: Left, 11 | ); 12 | 13 | 14 | ![pattern(- val, LeftUnaryPrecedence)] 15 | def _neg(val '$Number) -> $Number :: negative(val); 16 | 17 | ![pattern(lhs + rhs, AdditionPrecedence)] 18 | def _add(lhs '$Number, rhs '$Number) -> $Number :: add(lhs, rhs); 19 | 20 | ![pattern(lhs * rhs, MultiplicationPrecedence)] 21 | def _multiply(lhs '$Number, rhs '$Number) -> $Number :: multiply(lhs, rhs); 22 | 23 | def main! :: { 24 | _write_line(format(1 + -2 * 2 'Int32)); 25 | }; 26 | 27 | def transpile! :: { 28 | transpiler.add(main); 29 | }; 30 | -------------------------------------------------------------------------------- /src/cli/run.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::process::ExitCode; 3 | 4 | use clap::{arg, ArgMatches, Command}; 5 | 6 | use crate::error::RResult; 7 | use crate::interpreter; 8 | use crate::interpreter::runtime::Runtime; 9 | use crate::program::module::module_name; 10 | 11 | pub fn make_command() -> Command { 12 | Command::new("run") 13 | .about("Run a file using the interpreter.") 14 | .arg_required_else_help(true) 15 | .arg(arg!( "file to run").value_parser(clap::value_parser!(PathBuf))) 16 | } 17 | 18 | pub fn run(args: &ArgMatches) -> RResult { 19 | let input_path = args.get_one::("PATH").unwrap(); 20 | 21 | let mut runtime = Runtime::new()?; 22 | runtime.add_common_repository(); 23 | 24 | let module = runtime.load_file_as_module(input_path, module_name("main"))?; 25 | 26 | interpreter::run::main(&module, &mut runtime)?; 27 | 28 | Ok(ExitCode::SUCCESS) 29 | } 30 | -------------------------------------------------------------------------------- /src/parser/lexer/token.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | /// Token returned by the lexers 4 | #[derive(Debug, Clone, PartialEq)] 5 | pub enum Token<'a> { 6 | Identifier(&'a str), 7 | MacroIdentifier(&'a str), 8 | OperatorIdentifier(&'a str), 9 | StringLiteral(String), 10 | IntLiteral(&'a str), 11 | RealLiteral(&'a str), 12 | Symbol(&'a str), 13 | } 14 | 15 | impl<'i> Display for Token<'i> { 16 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 17 | match self { 18 | Token::Identifier(s) => write!(f, "{}", s), 19 | Token::MacroIdentifier(s) => write!(f, "{}", s), 20 | Token::OperatorIdentifier(s) => write!(f, "{}", s), 21 | Token::IntLiteral(s) => write!(f, "{}", s), 22 | Token::RealLiteral(s) => write!(f, "{}", s), 23 | Token::Symbol(s) => write!(f, "{}", s), 24 | Token::StringLiteral(s) => write!(f, "{}", s), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Transpile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /src/interpreter/opcode.rs: -------------------------------------------------------------------------------- 1 | #[allow(non_camel_case_types)] 2 | #[repr(u8)] 3 | #[derive(Debug, Copy, Clone)] 4 | pub enum OpCode { 5 | NOOP, 6 | PANIC, 7 | LOAD0, 8 | LOAD8, 9 | LOAD16, 10 | LOAD32, 11 | LOAD64, 12 | LOAD_LOCAL_32, 13 | STORE_LOCAL_32, 14 | LOAD_CONSTANT_32, 15 | DUP64, 16 | POP64, 17 | SWAP64, 18 | JUMP, 19 | JUMP_IF_FALSE, 20 | AND, 21 | OR, 22 | NOT, 23 | NEG, 24 | ADD, 25 | SUB, 26 | MUL, 27 | DIV, 28 | MOD, 29 | EXP, 30 | LOG, 31 | EQ, 32 | NEQ, 33 | GR, 34 | GR_EQ, 35 | LE, 36 | LE_EQ, 37 | 38 | // Member 39 | ALLOC_32, 40 | SET_MEMBER_32, 41 | GET_MEMBER_32, 42 | 43 | CALL, 44 | CALL_INTRINSIC, 45 | RETURN, 46 | } 47 | 48 | #[repr(u8)] 49 | #[derive(Debug, Copy, Clone)] 50 | pub enum Primitive { 51 | BOOL, 52 | I8, 53 | I16, 54 | I32, 55 | I64, 56 | U8, 57 | U16, 58 | U32, 59 | U64, 60 | F32, 61 | F64, 62 | } 63 | -------------------------------------------------------------------------------- /.idea/runConfigurations/Check.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /src/cli/expression.rs: -------------------------------------------------------------------------------- 1 | use std::process::ExitCode; 2 | 3 | use clap::{arg, ArgMatches, Command}; 4 | 5 | use crate::error::RResult; 6 | use crate::interpreter::runtime::Runtime; 7 | use crate::interpreter; 8 | 9 | pub fn make_command() -> Command { 10 | Command::new("expression") 11 | .about("Run an expression using the interpreter.") 12 | .arg_required_else_help(true) 13 | .arg(arg!( "expression to run").value_parser(clap::value_parser!(String))) 14 | } 15 | 16 | pub fn run(args: &ArgMatches) -> RResult { 17 | let expression = args.get_one::("EXPRESSION").unwrap(); 18 | let full_expression = format!("use!(module!(\"common\")); def main! :: write_line({});", expression); 19 | 20 | let mut runtime = Runtime::new()?; 21 | runtime.add_common_repository(); 22 | 23 | let module = runtime.load_text_as_module(&full_expression, vec!["dynamic".to_string()])?; 24 | 25 | interpreter::run::main(&module, &mut runtime)?; 26 | 27 | Ok(ExitCode::SUCCESS) 28 | } 29 | -------------------------------------------------------------------------------- /src/interpreter/data.rs: -------------------------------------------------------------------------------- 1 | use std::alloc::{alloc, Layout}; 2 | use std::intrinsics::transmute; 3 | use uuid::Uuid; 4 | 5 | #[derive(Copy, Clone)] 6 | pub union Value { 7 | pub bool: bool, 8 | pub u8: u8, 9 | pub u16: u16, 10 | pub u32: u32, 11 | pub u64: u64, 12 | pub i8: i8, 13 | pub i16: i16, 14 | pub i32: i32, 15 | pub i64: i64, 16 | pub f32: f32, 17 | pub f64: f64, 18 | pub ptr: *mut (), 19 | } 20 | 21 | impl Value { 22 | pub fn alloc() -> Value { 23 | Value { u8: 0 } 24 | } 25 | } 26 | 27 | // TODO The constants should probably be alloced in the chunk's constants, not 'anywhere'. 28 | pub unsafe fn string_to_ptr(string: &String) -> *mut () { 29 | let data = alloc(Layout::new::()); 30 | std::ptr::write(data as *mut String, string.clone()); 31 | transmute(data) 32 | } 33 | 34 | pub unsafe fn uuid_to_ptr(uuid: Uuid) -> *mut () { 35 | let data = alloc(Layout::new::()); 36 | std::ptr::write(data as *mut Uuid, uuid.clone()); 37 | transmute(data) 38 | } 39 | -------------------------------------------------------------------------------- /src/refactor/analyze.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use linked_hash_set::LinkedHashSet; 4 | 5 | use crate::program::expression_tree::ExpressionOperation; 6 | use crate::program::functions::{resolve_binding, FunctionBinding, FunctionImplementation}; 7 | 8 | pub fn gather_callees(implementation: &FunctionImplementation) -> LinkedHashSet> { 9 | let mut callees = LinkedHashSet::new(); 10 | 11 | // TODO Generic function calls would break this logic 12 | for expression_id in implementation.expression_tree.deep_children(implementation.expression_tree.root) { 13 | let expression_op = &implementation.expression_tree.values[&expression_id]; 14 | match expression_op { 15 | ExpressionOperation::FunctionCall(f) => { 16 | callees.insert(resolve_binding(f, &implementation.type_forest)); 17 | } 18 | ExpressionOperation::PairwiseOperations { .. } => { 19 | todo!() 20 | } 21 | _ => {} 22 | } 23 | } 24 | 25 | callees 26 | } 27 | -------------------------------------------------------------------------------- /src/ast/function.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Error, Formatter}; 2 | 3 | use crate::ast::expression::Expression; 4 | 5 | #[derive(Eq, PartialEq, Clone)] 6 | pub struct Function { 7 | pub interface: FunctionInterface, 8 | pub body: Option, 9 | } 10 | 11 | impl Display for Function { 12 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { 13 | write!(fmt, "def {}", self.interface)?; 14 | 15 | if let Some(body) = &self.body { 16 | write!(fmt, " :: {}", body)?; 17 | } 18 | return Ok(()) 19 | } 20 | } 21 | 22 | #[derive(Eq, PartialEq, Clone)] 23 | pub struct FunctionInterface { 24 | pub expression: Expression, 25 | pub return_type: Option, 26 | } 27 | 28 | impl Display for FunctionInterface { 29 | fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { 30 | write!(fmt, "{}", &self.expression)?; 31 | 32 | if let Some(return_type) = &self.return_type { 33 | write!(fmt, " -> {}", return_type)?; 34 | } 35 | 36 | Ok(()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /monoteny.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/ast/struct_.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Error, Formatter}; 2 | 3 | use crate::ast::Expression; 4 | use crate::program::functions::ParameterKey; 5 | use crate::util::fmt::write_separated_display; 6 | use crate::util::position::Positioned; 7 | 8 | #[derive(Eq, PartialEq, Clone)] 9 | pub struct Struct { pub arguments: Vec>> } 10 | 11 | impl Struct { 12 | pub fn empty() -> Struct { 13 | Struct { arguments: vec![] } 14 | } 15 | } 16 | 17 | impl Display for Struct { 18 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 19 | write!(f, "(")?; 20 | write_separated_display(f, ", ", self.arguments.iter().map(|f| &f.value))?; 21 | write!(f, ")") 22 | } 23 | } 24 | 25 | #[derive(Eq, PartialEq, Clone)] 26 | pub struct StructArgument { 27 | pub key: ParameterKey, 28 | pub value: Expression, 29 | pub type_declaration: Option, 30 | } 31 | 32 | impl Display for StructArgument { 33 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { 34 | write!(fmt, "{}{}", self.key, self.value) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/cli/logging.rs: -------------------------------------------------------------------------------- 1 | use std::process::ExitCode; 2 | use std::time::Instant; 3 | 4 | use colored::Colorize; 5 | 6 | use crate::error::{print_errors, RResult, RuntimeError}; 7 | 8 | pub fn dump_start(name: &str) -> Instant { 9 | println!("{} {}", "Running".green().bold(), name); 10 | Instant::now() 11 | } 12 | 13 | pub fn dump_result(start: Instant, result: RResult) -> ExitCode { 14 | match result { 15 | Ok(_) => dump_success(start,), 16 | Err(e) => dump_failure(e), 17 | } 18 | } 19 | 20 | pub fn dump_named_failure(name: &str, err: Vec) -> ExitCode { 21 | print_errors(&err); 22 | println!("\n{} on {}: {} error(s)", "Failure".red().bold(), name, err.len()); 23 | ExitCode::FAILURE 24 | } 25 | 26 | pub fn dump_failure(err: Vec) -> ExitCode { 27 | print_errors(&err); 28 | println!("\n{}: {} error(s)", "Failure".red().bold(), err.len()); 29 | ExitCode::FAILURE 30 | } 31 | 32 | pub fn dump_success(start: Instant) -> ExitCode { 33 | println!("{} in {:.2}s", "Finished".green().bold(), start.elapsed().as_secs_f32()); 34 | ExitCode::SUCCESS 35 | } 36 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use lalrpop_util::ErrorRecovery; 3 | 4 | use crate::error::RResult; 5 | use crate::{ast, monoteny_grammar}; 6 | 7 | pub mod strings; 8 | pub mod lexer; 9 | pub mod error; 10 | pub mod grammar; 11 | pub mod expressions; 12 | mod tests; 13 | 14 | pub fn parse_program(content: &str) -> RResult<(ast::Block, Vec, error::Error>>)> { 15 | let lexer = lexer::Lexer::new(content); 16 | let mut errors = vec![]; 17 | let ast = monoteny_grammar::FileParser::new() 18 | .parse(&mut errors, content, lexer) 19 | .map_err(|e| { error::map_parse_error(&e).to_array() })?; 20 | 21 | Ok((ast, errors)) 22 | } 23 | 24 | pub fn parse_expression(content: &str) -> RResult<(ast::Expression, Vec, error::Error>>)> { 25 | let lexer = lexer::Lexer::new(content); 26 | let mut errors = vec![]; 27 | let ast = monoteny_grammar::ExpressionParser::new() 28 | .parse(&mut errors, content, lexer) 29 | .map_err(|e| { error::map_parse_error(&e).to_array() })?; 30 | 31 | Ok((ast, errors)) 32 | } 33 | -------------------------------------------------------------------------------- /src/ast/array.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Error, Formatter}; 2 | 3 | use crate::ast::Expression; 4 | use crate::util::fmt::write_separated_display; 5 | use crate::util::position::Positioned; 6 | 7 | #[derive(Eq, PartialEq, Clone)] 8 | pub struct Array { pub arguments: Vec>> } 9 | 10 | impl Array { 11 | pub fn empty() -> Array { 12 | Array { arguments: vec![] } 13 | } 14 | } 15 | 16 | impl Display for Array { 17 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 18 | write!(f, "[")?; 19 | write_separated_display(f, ", ", self.arguments.iter().map(|f| &f.value))?; 20 | write!(f, "]") 21 | } 22 | } 23 | 24 | #[derive(Eq, PartialEq, Clone)] 25 | pub struct ArrayArgument { 26 | pub key: Option, 27 | pub value: Expression, 28 | pub type_declaration: Option, 29 | } 30 | 31 | impl Display for ArrayArgument { 32 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { 33 | if let Some(key) = &self.key { 34 | write!(fmt, "{}: ", key)?; 35 | } 36 | write!(fmt, "{}", self.value) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/interpreter/data_layout.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use itertools::Itertools; 4 | 5 | use crate::program::allocation::ObjectReference; 6 | use crate::program::traits::StructInfo; 7 | use crate::program::types::TypeUnit; 8 | 9 | pub struct DataLayout { 10 | pub struct_info: Rc, 11 | pub fields: Vec>, 12 | } 13 | 14 | pub fn create_data_layout(struct_info: Rc) -> Rc { 15 | let mut fields = vec![]; 16 | let mut todo = struct_info.fields.iter().rev().collect_vec(); 17 | 18 | while let Some(next) = todo.pop() { 19 | match &next.type_.unit { 20 | TypeUnit::Void => unreachable!(), 21 | TypeUnit::Generic(_) => todo!(), 22 | TypeUnit::Struct(s) => { 23 | // TODO In the future, we probably want to merge nested structs into each 24 | // other to avoid indirection. For now, it's safer and easier to just accept the 25 | // indirection though. 26 | fields.push(Rc::clone(next)); 27 | } 28 | } 29 | } 30 | 31 | Rc::new(DataLayout { 32 | struct_info, 33 | fields 34 | }) 35 | } 36 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | use crate::cli::logging::dump_failure; 2 | use clap::Command; 3 | use std::process::ExitCode; 4 | 5 | pub mod run; 6 | pub mod expression; 7 | pub mod check; 8 | pub mod transpile; 9 | pub mod logging; 10 | 11 | pub fn make_command() -> Command { 12 | Command::new("monoteny") 13 | .about("A cli implementation for the monoteny language.") 14 | .subcommand_required(true) 15 | .arg_required_else_help(true) 16 | .allow_external_subcommands(true) 17 | .subcommand(run::make_command()) 18 | .subcommand(expression::make_command()) 19 | .subcommand(check::make_command()) 20 | .subcommand(transpile::make_command()) 21 | } 22 | 23 | pub fn run_command() -> ExitCode { 24 | let matches = make_command().get_matches(); 25 | 26 | let result = match matches.subcommand() { 27 | Some(("run", sub_matches)) => run::run(sub_matches), 28 | Some(("expression", sub_matches)) => expression::run(sub_matches), 29 | Some(("check", sub_matches)) => check::run(sub_matches), 30 | Some(("transpile", sub_matches)) => transpile::run(sub_matches), 31 | _ => panic!("Unsupported action."), 32 | }; 33 | 34 | result.unwrap_or_else(|e| dump_failure(e)) 35 | } 36 | -------------------------------------------------------------------------------- /src/program/functions/overload.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::rc::Rc; 3 | 4 | use crate::error::RResult; 5 | use crate::program::functions::{FunctionHead, FunctionRepresentation}; 6 | 7 | /// Reference to a multiplicity of functions, usually resolved when attempting to call 8 | #[derive(Clone, PartialEq, Eq)] 9 | pub struct FunctionOverload { 10 | pub functions: HashSet>, 11 | // Note: If representation is NOT an implicit, the functions within are getters. 12 | pub representation: FunctionRepresentation, 13 | } 14 | 15 | impl FunctionOverload { 16 | pub fn from(function: &Rc, representation: FunctionRepresentation) -> Rc { 17 | Rc::new(FunctionOverload { 18 | functions: HashSet::from([Rc::clone(function)]), 19 | representation, 20 | }) 21 | } 22 | 23 | pub fn adding_function(&self, function: &Rc) -> RResult> { 24 | Ok(Rc::new(FunctionOverload { 25 | functions: self.functions.iter() 26 | .chain([function]) 27 | .map(Rc::clone) 28 | .collect(), 29 | representation: self.representation.clone(), 30 | })) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/program/primitives.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] 4 | pub enum Type { 5 | Bool, 6 | Int(usize), 7 | UInt(usize), 8 | Float(usize), 9 | } 10 | 11 | impl Type { 12 | pub fn is_number(&self) -> bool { 13 | match self { 14 | Type::Bool => false, 15 | _ => true, 16 | } 17 | } 18 | 19 | pub fn is_float(&self) -> bool { 20 | match self { 21 | Type::Float(_) => true, 22 | _ => false, 23 | } 24 | } 25 | 26 | pub fn is_int(&self) -> bool { 27 | match self { 28 | Type::Bool => false, 29 | Type::Float(_) => false, 30 | _ => true, 31 | } 32 | } 33 | 34 | pub fn is_signed_number(&self) -> bool { 35 | match self { 36 | Type::Bool => false, 37 | Type::UInt(_) => false, 38 | _ => true, 39 | } 40 | } 41 | 42 | pub fn identifier_string(&self) -> String { 43 | match self { 44 | Type::Bool => "Bool".to_string(), 45 | Type::Int(bits) => format!("Int{}", bits), 46 | Type::UInt(bits) => format!("UInt{}", bits), 47 | Type::Float(bits) => format!("Float{}", bits), 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/ast/expression.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | use std::ops::{Deref, DerefMut}; 3 | 4 | use crate::ast::term::Term; 5 | use crate::error::{RResult, TryCollectMany}; 6 | use crate::util::fmt::write_separated_display; 7 | use crate::util::position::Positioned; 8 | 9 | #[derive(Eq, PartialEq, Clone)] 10 | pub struct Expression(Vec>>); 11 | 12 | impl Expression { 13 | pub fn no_errors(&self) -> RResult<()> { 14 | self.iter() 15 | .map(|t| match &t.value { 16 | Term::Error(e) => Err(e.clone().to_array()), 17 | _ => Ok(()) 18 | }) 19 | .try_collect_many() 20 | } 21 | } 22 | 23 | impl Deref for Expression { 24 | type Target = Vec>>; 25 | 26 | fn deref(&self) -> &Self::Target { 27 | &self.0 28 | } 29 | } 30 | 31 | impl DerefMut for Expression { 32 | fn deref_mut(&mut self) -> &mut Self::Target { 33 | &mut self.0 34 | } 35 | } 36 | 37 | impl From>>> for Expression { 38 | fn from(value: Vec>>) -> Self { 39 | Expression(value) 40 | } 41 | } 42 | 43 | impl Display for Expression { 44 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 45 | write_separated_display(f, " ", self.0.iter().map(|b| &b.value)) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/program/debug.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | use display_with_options::with_options; 4 | use itertools::Itertools; 5 | use uuid::Uuid; 6 | 7 | use crate::program::expression_tree::ExpressionID; 8 | use crate::program::functions::{FunctionInterface, FunctionRepresentation, Parameter, ParameterKey}; 9 | use crate::program::generics::TypeForest; 10 | use crate::program::types::{TypeProto, TypeUnit}; 11 | 12 | pub struct MockFunctionInterface<'a> { 13 | pub representation: FunctionRepresentation, 14 | pub argument_keys: Vec, 15 | pub arguments: Vec, 16 | pub types: &'a TypeForest, 17 | } 18 | 19 | impl<'a> Display for MockFunctionInterface<'a> { 20 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 21 | let signature = FunctionInterface { 22 | parameters: self.argument_keys.iter().zip(&self.arguments).map(|(key, expression_id)| Parameter { 23 | external_key: (*key).clone(), 24 | type_: self.types.prototype_binding_alias(expression_id), 25 | }).collect_vec(), 26 | return_type: TypeProto::unit(TypeUnit::Generic(Uuid::new_v4())), 27 | requirements: Default::default(), 28 | generics: Default::default(), 29 | }; 30 | write!(f, "{:?}", with_options(&signature, &self.representation)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/resolver/interpreter_mock.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | use crate::ast; 4 | use crate::error::{ErrInRange, RResult, RuntimeError}; 5 | use crate::program::functions::ParameterKey; 6 | use crate::util::position::Positioned; 7 | 8 | pub fn plain_parameter<'a>(cause: &str, struct_: &'a ast::Struct) -> RResult<&'a ast::Expression> { 9 | let body = struct_.arguments.iter().exactly_one() 10 | .map_err(|_| RuntimeError::error(format!("{} needs exactly one parameter.", cause).as_str()).to_array())?; 11 | 12 | if body.value.key != ParameterKey::Positional { 13 | return Err(RuntimeError::error(format!("{} needs exactly one parameter.", cause).as_str()).to_array()); 14 | } 15 | 16 | if body.value.type_declaration.is_some() { 17 | return Err(RuntimeError::error(format!("{} needs exactly one parameter.", cause).as_str()).to_array()); 18 | } 19 | 20 | Ok(&body.value.value) 21 | } 22 | 23 | pub fn plain_string_literal<'a>(cause: &str, literal: &'a Vec>>) -> RResult<&'a str> { 24 | let [part] = &literal[..] else { 25 | return Err(RuntimeError::error(format!("{} needs a plain string literal.", cause).as_str()).to_array()); 26 | }; 27 | 28 | let ast::StringPart::Literal(literal) = &part.value else { 29 | return Err(RuntimeError::error(format!("{} needs a plain string literal.", cause).as_str()).to_array()).err_in_range(&part.position); 30 | }; 31 | 32 | Ok(literal) 33 | } 34 | -------------------------------------------------------------------------------- /src/cli/check.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | use std::process::ExitCode; 3 | 4 | use crate::cli::logging::{dump_named_failure, dump_start, dump_success}; 5 | use clap::{arg, ArgMatches, Command}; 6 | 7 | use crate::error::RResult; 8 | use crate::interpreter::runtime::Runtime; 9 | use crate::program::module::module_name; 10 | 11 | pub fn make_command() -> Command { 12 | Command::new("check") 13 | .about("Parse files to check for validity.") 14 | .arg_required_else_help(true) 15 | .arg(arg!( ... "files to check").value_parser(clap::value_parser!(PathBuf))) 16 | } 17 | 18 | pub fn run(args: &ArgMatches) -> RResult { 19 | let paths = args 20 | .get_many::("PATH") 21 | .into_iter() 22 | .flatten() 23 | .collect::>(); 24 | 25 | let start = dump_start(format!("check for {} file(s)", paths.len()).as_str()); 26 | 27 | let mut runtime = Runtime::new()?; 28 | runtime.add_common_repository(); 29 | 30 | let mut error_count = 0; 31 | for path in paths { 32 | match runtime.load_file_as_module(path, module_name("main")) { 33 | Ok(_) => {}, 34 | Err(e) => { 35 | dump_named_failure(format!("import({})", path.as_os_str().to_string_lossy()).as_str(), e); 36 | error_count += 1; 37 | }, 38 | }; 39 | } 40 | 41 | if error_count == 0 { 42 | dump_success(start); 43 | } 44 | 45 | Ok(ExitCode::from(error_count)) 46 | } 47 | -------------------------------------------------------------------------------- /src/program/expression_tree.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | use std::rc::Rc; 3 | 4 | use crate::program::allocation::ObjectReference; 5 | use crate::program::functions::FunctionBinding; 6 | use crate::program::generics::GenericAlias; 7 | use crate::util::graphs::node_tree::NodeTree; 8 | 9 | pub type ExpressionID = GenericAlias; 10 | 11 | #[derive(Clone, PartialEq, Eq, Debug)] 12 | pub enum ExpressionOperation { 13 | // TODO Blocks are a tough one to transpile as no language supports yields. 14 | // They will probably have to be inlined as a variable, like e.g.: 15 | // var x: Int; 16 | // for i in 0 ..< 1 { 17 | // ... 18 | // // yield 5; 19 | // x = 5; 20 | // break; 21 | // } 22 | // This syntax, while stupid, is at least supported in pretty much every language. 23 | Block, 24 | IfThenElse, 25 | 26 | GetLocal(Rc), 27 | SetLocal(Rc), 28 | 29 | // 0 arguments if no return type is set, otherwise 1 30 | Return, 31 | 32 | FunctionCall(Rc), 33 | PairwiseOperations { calls: Vec> }, 34 | 35 | // TODO This is required because it has a variable number of arguments (its elements). 36 | // This is not supported in functions otherwise, and we'd have to make an exception. 37 | // Which might be fair in the future, but for now it's not a pressing concern. 38 | ArrayLiteral, 39 | StringLiteral(String), 40 | } 41 | 42 | pub type ExpressionTree = NodeTree; 43 | -------------------------------------------------------------------------------- /src/interpreter/builtins/modules.rs: -------------------------------------------------------------------------------- 1 | use crate::repository; 2 | use std::collections::HashMap; 3 | 4 | pub fn create_core_loader() -> repository::Loader { 5 | repository::Loader::Intrinsic( 6 | HashMap::from([ 7 | (vec!["core".to_string()], include_str!("../../../monoteny/core.monoteny")), 8 | (vec!["core".to_string(), "bool".to_string()], include_str!("../../../monoteny/core/bool.monoteny")), 9 | (vec!["core".to_string(), "debug".to_string()], include_str!("../../../monoteny/core/debug.monoteny")), 10 | (vec!["core".to_string(), "run".to_string()], include_str!("../../../monoteny/core/run.monoteny")), 11 | (vec!["core".to_string(), "strings".to_string()], include_str!("../../../monoteny/core/strings.monoteny")), 12 | (vec!["core".to_string(), "transpilation".to_string()], include_str!("../../../monoteny/core/transpilation.monoteny")), 13 | ]), 14 | ) 15 | } 16 | 17 | pub fn create_common_loader() -> repository::Loader { 18 | repository::Loader::Intrinsic( 19 | HashMap::from([ 20 | (vec!["common".to_string()], include_str!("../../../monoteny/common.monoteny")), 21 | (vec!["common".to_string(), "debug".to_string()], include_str!("../../../monoteny/common/debug.monoteny")), 22 | (vec!["common".to_string(), "math".to_string()], include_str!("../../../monoteny/common/math.monoteny")), 23 | (vec!["common".to_string(), "precedence".to_string()], include_str!("../../../monoteny/common/precedence.monoteny")), 24 | ]), 25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /src/util/fmt.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::{Debug, Display, Error, Formatter}; 3 | 4 | pub fn fmta std::fmt::Result>(fun: F) -> String { 5 | struct Mock std::fmt::Result> { 6 | fun: F, 7 | } 8 | 9 | impl std::fmt::Result> Display for Mock { 10 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 11 | (&self.fun)(f) 12 | } 13 | } 14 | 15 | format!("{}", Mock { fun }) 16 | } 17 | 18 | pub fn write_separated_display(fmt: &mut Formatter, separator: &str, mut list: impl Iterator) -> Result<(), Error> where E: Display { 19 | if let Some(first) = list.next() { 20 | write!(fmt, "{}", first)? 21 | } 22 | for item in list { write!(fmt, "{}{}", separator, item)? } 23 | Ok(()) 24 | } 25 | 26 | pub fn write_separated_debug(fmt: &mut Formatter, separator: &str, mut list: impl Iterator) -> Result<(), Error> where E: Debug { 27 | if let Some(first) = list.next() { 28 | write!(fmt, "{:?}", first)? 29 | } 30 | for item in list { write!(fmt, "{}{:?}", separator, item)? } 31 | Ok(()) 32 | } 33 | 34 | pub fn write_keyval(fmt: &mut Formatter, mapping: &HashMap) -> Result<(), Error> where K: Debug, V: Debug { 35 | let mut iterator = mapping.iter(); 36 | 37 | if let Some((key, val)) = iterator.next() { 38 | write!(fmt, "{:?}: {:?}", key, val)? 39 | } 40 | for (key, val) in iterator.skip(1) { 41 | write!(fmt, ", {:?}: {:?}", key, val)? 42 | } 43 | 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /src/program/allocation.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter}; 2 | use std::hash::{Hash, Hasher}; 3 | use std::rc::Rc; 4 | 5 | use uuid::Uuid; 6 | 7 | use crate::program::types::TypeProto; 8 | 9 | #[derive(Copy, Clone, PartialEq, Eq)] 10 | pub enum Mutability { 11 | Immutable, 12 | Mutable, 13 | } 14 | 15 | impl Mutability { 16 | pub fn variable_declaration_keyword(&self) -> &str { 17 | match *self { 18 | Mutability::Mutable => "var", 19 | Mutability::Immutable => "let", 20 | } 21 | } 22 | } 23 | 24 | #[derive(Clone, Eq)] 25 | pub struct ObjectReference { 26 | pub id: Uuid, 27 | pub type_: Rc, 28 | pub mutability: Mutability, 29 | } 30 | 31 | impl ObjectReference { 32 | pub fn new_immutable(type_: Rc) -> Rc { 33 | Rc::new(ObjectReference { 34 | id: Uuid::new_v4(), 35 | type_, 36 | mutability: Mutability::Immutable 37 | }) 38 | } 39 | } 40 | 41 | impl PartialEq for ObjectReference { 42 | fn eq(&self, other: &Self) -> bool { 43 | self.id == other.id 44 | } 45 | } 46 | 47 | impl Hash for ObjectReference { 48 | fn hash(&self, state: &mut H) { 49 | self.id.hash(state); 50 | } 51 | } 52 | 53 | impl Debug for ObjectReference { 54 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 55 | let mut_keyword = match self.mutability { 56 | Mutability::Immutable => "let", 57 | Mutability::Mutable => "var", 58 | }; 59 | write!(f, "{} <{}> '{:?}", mut_keyword, self.id, self.type_) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/util/graphs/change_graph.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::hash::Hash; 3 | 4 | use linked_hash_set::LinkedHashSet; 5 | 6 | use crate::util::multimap::insert_into_multimap; 7 | 8 | pub struct ChangeGraph { 9 | pub next: LinkedHashSet, 10 | pub dependents: HashMap>, 11 | } 12 | 13 | impl ChangeGraph { 14 | pub fn new() -> ChangeGraph { 15 | ChangeGraph { 16 | next: LinkedHashSet::new(), 17 | dependents: Default::default(), 18 | } 19 | } 20 | 21 | pub fn pop(&mut self) -> Option { 22 | self.next.pop_front() 23 | } 24 | 25 | pub fn mark_change(&mut self, object: I) { 26 | if self.next.insert(object.clone()) { 27 | // Could use a LinkedHashSet here too, but that's not really worth it speed wise. 28 | let mut next = vec![object]; 29 | 30 | while let Some(object) = next.pop() { 31 | if self.next.insert(object.clone()) { 32 | next.push(object); 33 | } 34 | } 35 | } 36 | } 37 | 38 | pub fn remove_dependency(&mut self, dependent: &I, dependency: &I) { 39 | self.dependents.get_mut(dependency).map(|s| s.remove(dependent)); 40 | } 41 | 42 | pub fn add_dependency(&mut self, dependent: I, dependency: I) { 43 | insert_into_multimap(&mut self.dependents, dependent, dependency); 44 | } 45 | 46 | pub fn add_dependencies(&mut self, dependent: &I, dependencies: impl Iterator) { 47 | for dependency in dependencies { 48 | self.add_dependency(dependent.clone(), dependency) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ast/decorated.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter}; 2 | 3 | use crate::ast::{Array, Expression}; 4 | use crate::error::{RResult, RuntimeError, TryCollectMany}; 5 | 6 | #[derive(PartialEq, Eq, Clone)] 7 | pub struct Decorated { 8 | pub decorations: Array, 9 | pub value: T, 10 | } 11 | 12 | impl Decorated { 13 | pub fn with_value(&self, n: N) -> Decorated { 14 | Decorated { 15 | decorations: self.decorations.clone(), 16 | value: n, 17 | } 18 | } 19 | 20 | pub fn undecorated(t: V) -> Decorated { 21 | Decorated { 22 | decorations: Array { arguments: vec![] }, 23 | value: t, 24 | } 25 | } 26 | 27 | pub fn decorations_as_vec(&self) -> RResult> { 28 | return self.decorations.arguments.iter().map(|d| { 29 | if d.value.key.is_some() { 30 | return Err(RuntimeError::error("Decorations cannot have keys.").to_array()) 31 | } 32 | if d.value.type_declaration.is_some() { 33 | return Err(RuntimeError::error("Decorations cannot have type declarations.").to_array()) 34 | } 35 | 36 | Ok(&d.value.value) 37 | }).try_collect_many() 38 | } 39 | 40 | pub fn no_decorations(&self) -> RResult<()> { 41 | if !self.decorations.arguments.is_empty() { 42 | return Err(RuntimeError::error("Decorations are not supported in this context.").to_array()) 43 | } 44 | 45 | return Ok(()) 46 | } 47 | } 48 | 49 | impl Display for Decorated { 50 | fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { 51 | if self.decorations.arguments.is_empty() { 52 | return write!(fmt, "{}", self.value) 53 | } 54 | write!(fmt, "!{}\n{}", self.decorations, self.value) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/source.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::rc::Rc; 3 | 4 | use uuid::Uuid; 5 | 6 | use crate::program::functions::{FunctionHead, FunctionLogic}; 7 | use crate::program::module::{Module, ModuleName}; 8 | use crate::program::traits::{StructInfo, Trait}; 9 | 10 | pub struct Source { 11 | pub module_by_name: HashMap>, 12 | 13 | // Cache of aggregated module_by_name fields for quick reference. 14 | 15 | /// For every getter, which trait it provides. 16 | pub trait_heads: HashMap>, 17 | /// For referencible functions, the trait for it as an object. 18 | /// For every getter, which trait it provides. 19 | pub trait_references: HashMap, Rc>, 20 | /// For referencible functions, the trait for it as an object. 21 | pub function_traits: HashMap, Rc>, 22 | /// For instantiatable traits, their struct info 23 | pub struct_by_trait: HashMap, Rc>, 24 | 25 | /// For each function_id, its head. 26 | pub fn_heads: HashMap>, 27 | /// For referencible functions, a way to load it. The getter itself does not get a getter. 28 | pub fn_getters: HashMap, Rc>, 29 | 30 | /// For all functions, their logic. 31 | pub fn_logic: HashMap, FunctionLogic>, 32 | } 33 | 34 | impl Source { 35 | pub fn new() -> Source { 36 | Source { 37 | module_by_name: Default::default(), 38 | trait_heads: Default::default(), 39 | trait_references: Default::default(), 40 | function_traits: Default::default(), 41 | struct_by_trait: Default::default(), 42 | fn_heads: Default::default(), 43 | fn_getters: Default::default(), 44 | fn_logic: Default::default(), 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/transpiler/python/class.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::rc::Rc; 3 | 4 | use uuid::Uuid; 5 | 6 | use crate::program::types::{TypeProto, TypeUnit}; 7 | use crate::transpiler::python::ast; 8 | use crate::transpiler::python::ast::Block; 9 | use crate::transpiler::python::representations::Representations; 10 | 11 | pub struct ClassContext<'a> { 12 | pub names: &'a HashMap, 13 | pub representations: &'a Representations, 14 | pub unestablished_structs: &'a HashSet>, 15 | } 16 | 17 | pub fn transpile_class(type_def: &TypeProto, context: &ClassContext) -> Box { 18 | // TODO If the type has no variables, we can fold it away from the program entirely 19 | let struct_id = context.representations.type_ids[type_def]; 20 | let mut statements = vec![]; 21 | 22 | // TODO Need to account for bindings 23 | match &type_def.unit { 24 | TypeUnit::Struct(struct_) => { 25 | for hint in &struct_.field_hints { 26 | let is_established = !context.unestablished_structs.contains(&hint.type_); 27 | let type_string = context.names[&context.representations.type_ids[&hint.type_]].clone(); 28 | 29 | statements.push(Box::new(ast::Statement::VariableAssignment { 30 | target: Box::new(ast::Expression::NamedReference(hint.name.clone())), 31 | value: None, 32 | type_annotation: Some(Box::new(match is_established { 33 | true => ast::Expression::NamedReference(type_string), 34 | false => ast::Expression::StringLiteral(type_string), 35 | })), 36 | })) 37 | } 38 | } 39 | _ => panic!() 40 | } 41 | 42 | Box::new(ast::Class { 43 | name: context.names[&struct_id].clone(), 44 | block: Block { statements }, 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /src/parser/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use std::fs; 4 | 5 | use itertools::Itertools; 6 | 7 | use crate::error::RResult; 8 | use crate::parser; 9 | use crate::parser::ast::*; 10 | 11 | #[test] 12 | fn hello_world() -> RResult<()> { 13 | let file_contents = fs::read_to_string("test-code/hello_world.monoteny").unwrap(); 14 | let (parsed, errors) = parser::parse_program( 15 | file_contents.as_str() 16 | )?; 17 | assert!(errors.is_empty()); 18 | 19 | assert_eq!(parsed.statements.len(), 3); 20 | 21 | let Statement::FunctionDeclaration(function) = &parsed.statements[1].as_ref().value.value else { 22 | panic!(); 23 | }; 24 | 25 | assert!(function.interface.return_type.is_none()); 26 | assert_eq!(function.interface.expression.len(), 1); 27 | assert!(function.interface.expression[0].value == Term::MacroIdentifier("main".to_string())); 28 | 29 | Ok(()) 30 | } 31 | 32 | #[test] 33 | fn custom_grammar() -> RResult<()> { 34 | let file_contents = fs::read_to_string("test-code/grammar/custom_grammar.monoteny").unwrap(); 35 | let (parsed, errors) = parser::parse_program(file_contents.as_str())?; 36 | assert!(errors.is_empty()); 37 | 38 | assert_eq!(parsed.statements.len(), 6); 39 | 40 | let Statement::FunctionDeclaration(floor_div) = &parsed.statements[2].as_ref().value.value else { 41 | panic!(); 42 | }; 43 | 44 | match floor_div.interface.expression.iter().map(|t| &t.value).collect_vec()[..] { 45 | [Term::Identifier(i), Term::Struct(s)] => { 46 | assert_eq!(i, "_add"); 47 | assert_eq!(s.arguments.len(), 2); 48 | } 49 | _ => panic!() 50 | } 51 | assert_eq!(parsed.statements[1].decorations.arguments.len(), 1); 52 | 53 | Ok(()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | workflow_call: 5 | workflow_dispatch: 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build: 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | target: 16 | # TODO Fails due to linker error 17 | # - { name: "linux-x86", target: "i686-unknown-linux-gnu", executable-name: "monoteny", os: ubuntu-latest } 18 | - { name: "linux-x86_64", target: "x86_64-unknown-linux-gnu", executable-name: "monoteny", os: ubuntu-latest, } 19 | # TODO Fails due to linker error 20 | # - { name: "linux-aarch64", target: "aarch64-unknown-linux-gnu", executable-name: "monoteny", os: ubuntu-latest, } 21 | - { name: "windows-x86", target: "i686-pc-windows-msvc", os: windows-latest, executable-name: "monoteny.exe" } 22 | - { name: "windows-x86_64", target: "x86_64-pc-windows-msvc", os: windows-latest, executable-name: "monoteny.exe" } 23 | - { name: "macos-x86", target: "x86_64-apple-darwin", executable-name: "monoteny", os: macos-latest } 24 | - { name: "macos-aarch64", target: "aarch64-apple-darwin", executable-name: "monoteny", os: macos-latest } 25 | 26 | runs-on: ${{ matrix.target.os }} 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | - run: rustup toolchain install stable --profile minimal && rustup target add ${{ matrix.target.target }} 31 | - uses: Swatinem/rust-cache@v2 32 | with: 33 | prefix-key: "main" 34 | save-if: ${{ github.ref == 'refs/heads/main' }} 35 | - name: Build 36 | run: cargo build --release --verbose --target ${{ matrix.target.target }} 37 | - name: Upload Artifact 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: Monoteny-${{ matrix.target.name }} 41 | path: | 42 | ${{ github.workspace }}/target/${{ matrix.target.target }}/release/${{ matrix.target.executable-name }} 43 | -------------------------------------------------------------------------------- /src/transpiler/structs.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::rc::Rc; 3 | 4 | use linked_hash_map::{Entry, LinkedHashMap}; 5 | 6 | use crate::program::expression_tree::ExpressionOperation; 7 | use crate::program::functions::{FunctionHead, FunctionImplementation, FunctionLogicDescriptor}; 8 | use crate::program::traits::StructInfo; 9 | use crate::program::types::TypeProto; 10 | 11 | pub fn find_in_interfaces(heads: impl Iterator>, map: &mut LinkedHashMap, Rc>) { 12 | for head in heads { 13 | for type_ in head.interface.parameters.iter().map(|p| &p.type_).chain([&head.interface.return_type].into_iter()) { 14 | todo!("From the type we SHOULD be able to deduce the struct info, but we can't for now.") 15 | } 16 | } 17 | } 18 | 19 | pub fn find_in_implementations<'a>(implementations: impl Iterator, logic: &HashMap, FunctionLogicDescriptor>, map: &mut LinkedHashMap, Rc>) { 20 | for implementation in implementations { 21 | for expression_id in implementation.expression_tree.deep_children(implementation.expression_tree.root) { 22 | let operation = &implementation.expression_tree.values[&expression_id]; 23 | 24 | if let ExpressionOperation::FunctionCall(binding) = operation { 25 | let Some(FunctionLogicDescriptor::Constructor(struct_info)) = logic.get(&binding.function) else { 26 | continue; 27 | }; 28 | 29 | let type_ = &binding.function.interface.return_type; // Fulfillment for Self 30 | if let Entry::Vacant(entry) = map.entry(type_.clone()) { 31 | // If it's already present and we insert, we shuffle it to the end, which is unnecessary 32 | entry.insert(Rc::clone(struct_info)); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/program/functions/function_binding.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::program::functions::FunctionHead; 4 | use crate::program::generics::TypeForest; 5 | use crate::program::traits::{RequirementsFulfillment, TraitConformance, TraitConformanceWithTail}; 6 | 7 | #[derive(Clone, Eq, PartialEq, Hash, Debug)] 8 | pub struct FunctionBinding { 9 | pub function: Rc, 10 | pub requirements_fulfillment: Rc, 11 | } 12 | 13 | impl FunctionBinding { 14 | pub fn pure(function: Rc) -> Rc { 15 | Rc::new(FunctionBinding { 16 | function, 17 | requirements_fulfillment: RequirementsFulfillment::empty(), 18 | }) 19 | } 20 | } 21 | 22 | pub fn resolve_binding(binding: &FunctionBinding, type_forest: &TypeForest) -> Rc { 23 | Rc::new(FunctionBinding { 24 | function: Rc::clone(&binding.function), 25 | requirements_fulfillment: resolve_fulfillment(&binding.requirements_fulfillment, type_forest), 26 | }) 27 | } 28 | 29 | pub fn resolve_fulfillment(fulfillment: &RequirementsFulfillment, type_forest: &TypeForest) -> Rc { 30 | Rc::new(RequirementsFulfillment { 31 | conformance: fulfillment.conformance.iter().map(|(b, f)| { 32 | let binding = b.mapping_types(&|t| type_forest.resolve_type(t).unwrap()); 33 | (Rc::clone(&binding), Rc::new(TraitConformanceWithTail { 34 | conformance: Rc::new(TraitConformance { 35 | binding, 36 | function_mapping: f.conformance.function_mapping.clone(), 37 | }), 38 | tail: resolve_fulfillment(&f.tail, type_forest), 39 | })) 40 | }).collect(), 41 | generic_mapping: fulfillment.generic_mapping.iter() 42 | .map(|(generic, type_)| (Rc::clone(generic), type_forest.resolve_type(type_).unwrap())) 43 | .collect(), 44 | }) 45 | } 46 | -------------------------------------------------------------------------------- /src/ast/term.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Error, Formatter}; 2 | 3 | use crate::ast::{Array, Block, Expression, StringPart, Struct}; 4 | use crate::error::RuntimeError; 5 | use crate::util::position::Positioned; 6 | 7 | #[derive(Eq, PartialEq, Clone)] 8 | pub enum Term { 9 | Error(RuntimeError), 10 | Identifier(String), 11 | MacroIdentifier(String), 12 | Dot, 13 | IntLiteral(String), 14 | RealLiteral(String), 15 | Struct(Box), 16 | Array(Box), 17 | StringLiteral(Vec>>), 18 | Block(Box), 19 | IfThenElse(Box), 20 | } 21 | 22 | impl Display for Term { 23 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { 24 | match self { 25 | Term::Error(err) => write!(fmt, "ERR"), 26 | Term::Identifier(s) => write!(fmt, "{}", s), 27 | Term::MacroIdentifier(s) => write!(fmt, "{}!", s), 28 | Term::IntLiteral(s) => write!(fmt, "{}", s), 29 | Term::RealLiteral(s) => write!(fmt, "{}", s), 30 | Term::StringLiteral(parts) => { 31 | write!(fmt, "\"")?; 32 | for part in parts { 33 | write!(fmt, "{}", part)?; 34 | } 35 | write!(fmt, "\"") 36 | }, 37 | Term::Struct(struct_) => write!(fmt, "{}", struct_), 38 | Term::Array(array) => write!(fmt, "{}", array), 39 | Term::Block(block) => write!(fmt, "{{\n{}}}", block), 40 | Term::Dot => write!(fmt, "."), 41 | Term::IfThenElse(if_then_else) => { 42 | write!(fmt, "if {} :: {}", if_then_else.condition, if_then_else.consequent)?; 43 | if let Some(alternative) = &if_then_else.alternative { 44 | write!(fmt, "else :: {}", alternative)?; 45 | } 46 | Ok(()) 47 | } 48 | } 49 | } 50 | } 51 | 52 | #[derive(Eq, PartialEq, Clone)] 53 | pub struct IfThenElse { 54 | pub condition: Expression, 55 | pub consequent: Expression, 56 | pub alternative: Option, 57 | } 58 | 59 | -------------------------------------------------------------------------------- /src/parser/error.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | use lalrpop_util::{ErrorRecovery, ParseError}; 3 | use std::fmt::{Display, Formatter}; 4 | 5 | use crate::error::RuntimeError; 6 | use crate::parser::lexer::Token; 7 | use crate::util::position::Positioned; 8 | 9 | #[derive(Debug, PartialEq, Clone)] 10 | pub struct Error(pub String); 11 | 12 | pub fn derive_error(error: &Positioned, Error>>) -> RuntimeError { 13 | map_parse_error(&error.value.error) 14 | .in_range(error.position.clone()) 15 | } 16 | 17 | pub fn map_parse_error(e: &ParseError) -> RuntimeError { 18 | match e { 19 | ParseError::InvalidToken { location } => { 20 | RuntimeError::error("Invalid token.").in_range(*location..*location) 21 | }, 22 | ParseError::UnrecognizedEof { location, expected } => { 23 | add_expected_note(RuntimeError::error("File ended unexpectedly.").in_range(*location..*location), expected) 24 | } 25 | ParseError::UnrecognizedToken { token: (start, token, end), expected } => { 26 | add_expected_note(RuntimeError::error("Unexpected token.").in_range(*start..*end), expected) 27 | } 28 | ParseError::ExtraToken { token: (start, token, end) } => { 29 | RuntimeError::error("Extraneous token.").in_range(*start..*end) 30 | } 31 | ParseError::User { error } => { 32 | panic!() 33 | } 34 | } 35 | } 36 | 37 | fn unquote(value: &str) -> &str { 38 | if !value.starts_with('\"') { 39 | return value 40 | } 41 | 42 | let mut chars = value.chars(); 43 | chars.next(); 44 | chars.next_back(); 45 | chars.as_str() 46 | } 47 | 48 | fn add_expected_note(error: RuntimeError, expected: &Vec) -> RuntimeError { 49 | match &expected[..] { 50 | [] => error, 51 | [one] => error.with_note(RuntimeError::note(format!("Expected: {}", unquote(one)).as_str())), 52 | expected => error.with_note(RuntimeError::note(format!("Expected one of: {}", expected.iter().map(|s| unquote(s)).join(" ")).as_str())), 53 | } 54 | } 55 | 56 | impl Display for Error { 57 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 58 | write!(f, "{}", self.0) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/ast/statement.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Error, Formatter}; 2 | 3 | use crate::ast::conformance::TraitConformanceDeclaration; 4 | use crate::ast::expression::Expression; 5 | use crate::ast::function::Function; 6 | use crate::ast::trait_::TraitDefinition; 7 | use crate::program::allocation::Mutability; 8 | 9 | #[derive(Eq, PartialEq, Clone)] 10 | pub enum Statement { 11 | VariableDeclaration { 12 | mutability: Mutability, 13 | identifier: String, 14 | type_declaration: Option>, 15 | assignment: Option> 16 | }, 17 | VariableUpdate { target: Box, new_value: Box }, 18 | Expression(Box), 19 | Return(Option>), 20 | FunctionDeclaration(Box), 21 | Trait(Box), 22 | Conformance(Box), 23 | } 24 | 25 | impl Display for Statement { 26 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { 27 | match self { 28 | Statement::VariableDeclaration { mutability, identifier, type_declaration, assignment} => { 29 | let mutability_string = mutability.variable_declaration_keyword(); 30 | write!(fmt, "{} {}", mutability_string, identifier)?; 31 | if let Some(type_declaration) = type_declaration { 32 | write!(fmt, " '{}", type_declaration)?; 33 | } 34 | if let Some(assignment) = assignment { 35 | write!(fmt, " = {}", assignment)?; 36 | } 37 | Ok(()) 38 | }, 39 | Statement::VariableUpdate { target, new_value } => { 40 | write!(fmt, "upd {} = {}", target, new_value) 41 | }, 42 | Statement::Return(Some(expression)) => write!(fmt, "return {}", expression), 43 | Statement::Return(None) => write!(fmt, "return"), 44 | Statement::Expression(ref expression) => write!(fmt, "{}", expression), 45 | Statement::FunctionDeclaration(function) => write!(fmt, "{}", function), 46 | Statement::Trait(trait_) => write!(fmt, "{}", trait_), 47 | Statement::Conformance(conformance) => write!(fmt, "{}", conformance), 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/transpiler/python/representations.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::rc::Rc; 3 | 4 | use uuid::Uuid; 5 | 6 | use crate::program::expression_tree::ExpressionOperation; 7 | use crate::program::functions::{FunctionHead, FunctionImplementation}; 8 | use crate::program::types::TypeProto; 9 | use crate::transpiler::namespaces; 10 | 11 | #[derive(Clone)] 12 | pub struct Representations { 13 | pub function_forms: HashMap, FunctionForm>, 14 | pub type_ids: HashMap, Uuid>, 15 | } 16 | 17 | impl Representations { 18 | pub fn new() -> Representations { 19 | Representations { 20 | function_forms: Default::default(), 21 | type_ids: Default::default(), 22 | } 23 | } 24 | } 25 | 26 | // The IDs are attached per object because theoretically it's possible for a representation to use 27 | // 0 names (direct keyword use) or 2 (using multiple keywords). They just 'happen' to all use one. 28 | #[derive(PartialEq, Eq, Clone, Debug)] 29 | pub enum FunctionForm { 30 | Identity, 31 | CallAsFunction, 32 | Constant(Uuid), 33 | FunctionCall(Uuid), 34 | SetMemberField(Uuid), 35 | GetMemberField(Uuid), 36 | MemberCall(Uuid), 37 | Unary(Uuid), 38 | Binary(Uuid), 39 | } 40 | 41 | pub fn find_for_function(forms: &mut HashMap, FunctionForm>, global_namespace: &mut namespaces::Level, function_head: &Rc, implementation: &FunctionImplementation) { 42 | if implementation.parameter_locals.is_empty() { 43 | // TODO We could make a helper function and still use a constant even if we use blocks. 44 | let has_blocks = implementation.expression_tree.values.values().any(|op| matches!(op, ExpressionOperation::Block)); 45 | if !has_blocks { 46 | global_namespace.insert_name(function_head.function_id, function_head.declared_representation.name.as_str()); 47 | forms.insert(Rc::clone(&function_head), FunctionForm::Constant(function_head.function_id)); 48 | return 49 | } 50 | } 51 | 52 | global_namespace.insert_name(function_head.function_id, function_head.declared_representation.name.as_str()); 53 | forms.insert(Rc::clone(&function_head), FunctionForm::FunctionCall(function_head.function_id)); 54 | } 55 | -------------------------------------------------------------------------------- /src/program/traits/binding.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::fmt::{Debug, Formatter}; 3 | use std::hash::{Hash, Hasher}; 4 | use std::rc::Rc; 5 | 6 | use itertools::Itertools; 7 | 8 | use crate::program::generics::GenericAlias; 9 | use crate::program::traits::Trait; 10 | use crate::program::types::TypeProto; 11 | use crate::util::fmt::write_keyval; 12 | 13 | /// Some application of a trait with specific types. 14 | #[derive(Clone, Eq, PartialEq)] 15 | pub struct TraitBinding { 16 | /// The trait that is bound. 17 | pub trait_: Rc, 18 | 19 | /// A mapping from each of the trait's generics to some type. 20 | pub generic_to_type: HashMap, Rc>, 21 | } 22 | 23 | impl TraitBinding { 24 | pub fn mapping_types(&self, map: &dyn Fn(&Rc) -> Rc) -> Rc { 25 | Rc::new(TraitBinding { 26 | trait_: Rc::clone(&self.trait_), 27 | generic_to_type: self.generic_to_type.iter().map(|(generic, type_) | (Rc::clone(generic), map(type_))).collect() 28 | }) 29 | } 30 | 31 | pub fn try_mapping_types(&self, map: &dyn Fn(&Rc) -> Result, B>) -> Result, B> { 32 | Ok(Rc::new(TraitBinding { 33 | trait_: Rc::clone(&self.trait_), 34 | generic_to_type: self.generic_to_type.iter().map(|(generic, type_) | Ok((Rc::clone(generic), map(type_)?))).try_collect()? 35 | })) 36 | } 37 | 38 | pub fn collect_generics(&self) -> HashSet { 39 | TypeProto::collect_generics(self.generic_to_type.values()) 40 | } 41 | } 42 | 43 | impl Hash for TraitBinding { 44 | fn hash(&self, state: &mut H) { 45 | self.trait_.hash(state); 46 | 47 | for keyval in self.generic_to_type.iter().sorted_by_key(|(trait_, type_)| trait_.id) { 48 | keyval.hash(state); 49 | } 50 | } 51 | } 52 | 53 | impl Debug for TraitBinding { 54 | fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { 55 | write!(fmt, "{}", self.trait_.name)?; 56 | if !self.generic_to_type.is_empty() { 57 | write!(fmt, "<")?; 58 | write_keyval(fmt, &self.generic_to_type)?; 59 | write!(fmt, ">")?; 60 | } 61 | Ok(()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/program/functions/representation.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] 2 | pub enum FunctionCallExplicity { 3 | Explicit, 4 | Implicit, 5 | } 6 | 7 | #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] 8 | pub enum FunctionTargetType { 9 | Global, 10 | Member 11 | } 12 | 13 | #[derive(PartialEq, Eq, Hash, Clone, Debug)] 14 | pub struct FunctionRepresentation { 15 | /// Name of the function. 16 | pub name: String, 17 | pub target_type: FunctionTargetType, 18 | pub call_explicity: FunctionCallExplicity, 19 | } 20 | 21 | 22 | impl FunctionRepresentation { 23 | pub fn dummy() -> FunctionRepresentation { 24 | FunctionRepresentation { 25 | name: "fn".into(), 26 | target_type: FunctionTargetType::Global, 27 | call_explicity: FunctionCallExplicity::Explicit, 28 | } 29 | } 30 | 31 | pub fn new(name: &str, target_type: FunctionTargetType, explicity: FunctionCallExplicity) -> FunctionRepresentation { 32 | FunctionRepresentation { 33 | name: name.into(), 34 | target_type, 35 | call_explicity: explicity, 36 | } 37 | } 38 | 39 | pub fn new_global_function(name: &str) -> FunctionRepresentation { 40 | FunctionRepresentation { 41 | name: name.into(), 42 | target_type: FunctionTargetType::Global, 43 | call_explicity: FunctionCallExplicity::Explicit, 44 | } 45 | } 46 | 47 | pub fn new_global_implicit(name: &str) -> FunctionRepresentation { 48 | FunctionRepresentation { 49 | name: name.into(), 50 | target_type: FunctionTargetType::Global, 51 | call_explicity: FunctionCallExplicity::Implicit, 52 | } 53 | } 54 | pub fn new_member_function(name: &str) -> FunctionRepresentation { 55 | FunctionRepresentation { 56 | name: name.into(), 57 | target_type: FunctionTargetType::Member, 58 | call_explicity: FunctionCallExplicity::Explicit, 59 | } 60 | } 61 | 62 | pub fn new_member_implicit(name: &str) -> FunctionRepresentation { 63 | FunctionRepresentation { 64 | name: name.into(), 65 | target_type: FunctionTargetType::Member, 66 | call_explicity: FunctionCallExplicity::Implicit, 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/resolver/fields.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::program::functions::{FunctionHead, FunctionInterface, FunctionRepresentation, Parameter, ParameterKey}; 4 | use crate::program::traits::{FieldHint, Trait}; 5 | use crate::program::types::TypeProto; 6 | 7 | pub fn make(name: &str, self_type: &Rc, field_type: &Rc, add_getter: bool, add_setter: bool) -> FieldHint { 8 | let getter = add_getter.then_some({ 9 | let head = FunctionHead::new_static( 10 | vec!["self".into()], 11 | FunctionRepresentation::new_member_implicit(name), 12 | Rc::new(FunctionInterface { 13 | parameters: vec![ 14 | Parameter { 15 | external_key: ParameterKey::Positional, 16 | type_: self_type.clone(), 17 | }], 18 | return_type: field_type.clone(), 19 | requirements: Default::default(), 20 | generics: Default::default(), 21 | }) 22 | ); 23 | head 24 | }); 25 | 26 | let setter = add_setter.then_some({ 27 | let head = FunctionHead::new_static( 28 | vec!["self".into(), "new_value".into()], 29 | FunctionRepresentation::new_member_implicit(name), 30 | Rc::new(FunctionInterface { 31 | parameters: vec![Parameter { 32 | external_key: ParameterKey::Positional, 33 | type_: self_type.clone(), 34 | }, Parameter { 35 | external_key: ParameterKey::Positional, 36 | type_: field_type.clone(), 37 | }], 38 | return_type: TypeProto::void(), 39 | requirements: Default::default(), 40 | generics: Default::default(), 41 | }) 42 | ); 43 | head 44 | }); 45 | 46 | FieldHint { 47 | name: name.to_string(), 48 | type_: field_type.clone(), 49 | setter, 50 | getter, 51 | } 52 | } 53 | 54 | pub fn add_to_trait(trait_: &mut Trait, field: FieldHint) { 55 | if let Some(getter) = &field.getter { 56 | trait_.abstract_functions.insert(Rc::clone(getter)); 57 | } 58 | if let Some(setter) = &field.setter { 59 | trait_.abstract_functions.insert(Rc::clone(setter)); 60 | } 61 | trait_.field_hints.push(field) 62 | } 63 | -------------------------------------------------------------------------------- /src/resolver/precedence_order.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use itertools::Itertools; 4 | use strum::IntoEnumIterator; 5 | use uuid::Uuid; 6 | 7 | use crate::ast; 8 | use crate::error::{RResult, RuntimeError, TryCollectMany}; 9 | use crate::parser::expressions; 10 | use crate::parser::grammar::{OperatorAssociativity, PrecedenceGroup}; 11 | use crate::program::functions::ParameterKey; 12 | use crate::resolver::scopes; 13 | 14 | pub fn resolve_precedence_order(call_struct: &ast::Struct, scope: &scopes::Scope) -> RResult>> { 15 | let order: Vec> = call_struct.arguments.iter().map(|arg| { 16 | let ParameterKey::Name(name) = &arg.value.key else { 17 | return Err(RuntimeError::error("Not a named argument.").in_range(arg.position.clone()).to_array()) 18 | }; 19 | if arg.value.type_declaration.is_some() { 20 | return Err(RuntimeError::error("Unexpected type declaration.").in_range(arg.position.clone()).to_array()) 21 | } 22 | 23 | let associativity = resolve_associativity(&arg.value.value, scope)?; 24 | 25 | Ok(Rc::new(PrecedenceGroup { 26 | trait_id: Uuid::new_v4(), 27 | name: name.to_string(), 28 | associativity, 29 | })) 30 | }).try_collect_many()?; 31 | 32 | order.iter().filter(|x| x.associativity == OperatorAssociativity::LeftUnary).at_most_one() 33 | .map_err(|_| RuntimeError::error("Cannot declare two LeftUnary associativities.").to_array())?; 34 | order.iter().filter(|x| x.associativity == OperatorAssociativity::RightUnary).at_most_one() 35 | .map_err(|_| RuntimeError::error("Cannot declare two RightUnary associativities.").to_array())?; 36 | 37 | Ok(order) 38 | } 39 | 40 | pub fn resolve_associativity(body: &ast::Expression, scope: &scopes::Scope) -> RResult { 41 | let error = RuntimeError::error( 42 | format!("Operator associativity needs to be one of {:?}.", OperatorAssociativity::iter().collect_vec()).as_str() 43 | ).to_array(); 44 | 45 | let parsed = expressions::parse(body, &scope.grammar)?; 46 | 47 | let expressions::Value::Identifier(identifier) = &parsed.value else { 48 | return Err(error); 49 | }; 50 | 51 | let Ok(associativity) = OperatorAssociativity::iter().filter(|a| &a.to_string() == identifier.as_str()).exactly_one() else { 52 | return Err(error) 53 | }; 54 | 55 | Ok(associativity) 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/program/functions/logic.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::error::{RResult, RuntimeError}; 4 | use crate::program::allocation::ObjectReference; 5 | use crate::program::functions::{FunctionHead, FunctionImplementation}; 6 | use crate::program::primitives; 7 | use crate::program::traits::{StructInfo, Trait}; 8 | use crate::program::types::TypeProto; 9 | 10 | #[derive(Clone)] 11 | pub enum FunctionLogic { 12 | Implementation(Box), 13 | Descriptor(FunctionLogicDescriptor), 14 | } 15 | 16 | 17 | #[derive(Clone, PartialEq, Eq, Debug)] 18 | pub enum FunctionLogicDescriptor { 19 | /// This function was not described by the implementer and is expected not to be called, 20 | /// or to be injected by a transpiler. 21 | Stub, 22 | TraitProvider(Rc), 23 | FunctionProvider(Rc), 24 | PrimitiveOperation { operation: PrimitiveOperation, type_: primitives::Type }, 25 | Clone(Rc), 26 | Constructor(Rc), 27 | GetMemberField(Rc, Rc), 28 | SetMemberField(Rc, Rc), 29 | } 30 | 31 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 32 | pub enum PrimitiveOperation { 33 | And, Or, Not, 34 | Negative, 35 | Add, Subtract, 36 | Multiply, Divide, 37 | Modulo, 38 | Exp, Log, 39 | EqualTo, NotEqualTo, 40 | GreaterThan, LesserThan, 41 | GreaterThanOrEqual, LesserThanOrEqual, 42 | ParseIntString, 43 | ParseRealString, 44 | ToString, 45 | Clone, 46 | } 47 | 48 | impl FunctionLogic { 49 | pub fn is_implementation(&self) -> bool { 50 | match self { 51 | FunctionLogic::Implementation(_) => true, 52 | FunctionLogic::Descriptor(_) => false, 53 | } 54 | } 55 | 56 | pub fn to_implementation(self) -> RResult> { 57 | match self { 58 | FunctionLogic::Implementation(i) => Ok(i), 59 | FunctionLogic::Descriptor(_) => Err(RuntimeError::error("Cannot use a function with described logic as an implementation.").to_array()), 60 | } 61 | } 62 | 63 | pub fn as_implementation(&self) -> RResult<&FunctionImplementation> { 64 | match self { 65 | FunctionLogic::Implementation(i) => Ok(i), 66 | FunctionLogic::Descriptor(_) => Err(RuntimeError::error("Cannot use a function with described logic as an implementation.").to_array()), 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/program/module.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::rc::Rc; 3 | 4 | use crate::parser::grammar::{Pattern, PrecedenceGroup}; 5 | use itertools::Itertools; 6 | use uuid::Uuid; 7 | 8 | use crate::program::functions::FunctionHead; 9 | use crate::program::traits::{TraitConformanceRule, TraitGraph}; 10 | use crate::resolver::scopes; 11 | use crate::source::Source; 12 | 13 | pub type ModuleName = Vec; 14 | 15 | pub fn module_name(name: &str) -> ModuleName { 16 | name.split(".").map(ToString::to_string).collect_vec() 17 | } 18 | 19 | pub struct Module { 20 | pub id: Uuid, 21 | pub name: ModuleName, 22 | 23 | /// For each trait, its metatype getter function. 24 | pub included_modules: Vec>, 25 | 26 | pub precedence_order: Option>>, 27 | pub patterns: HashSet>>>, 28 | pub trait_conformance: Box, 29 | 30 | /// Functions that are directly referencible. 31 | /// Usually, these are just getters for traits, function objects etc. 32 | pub exposed_functions: HashSet>, 33 | 34 | /// These come from decorators. 35 | /// Collecting all decorated functions allows us to fail late - the rest of the code is still 36 | /// valid even if multiple main! functions are declared! We just cannot run them as 'main'. 37 | pub main_functions: Vec>, 38 | pub transpile_functions: Vec>, 39 | } 40 | 41 | impl Module { 42 | pub fn new(name: ModuleName) -> Module { 43 | Module { 44 | id: Default::default(), 45 | name, 46 | included_modules: vec![], 47 | precedence_order: None, 48 | patterns: Default::default(), 49 | trait_conformance: Box::new(TraitGraph::new()), 50 | exposed_functions: Default::default(), 51 | main_functions: vec![], 52 | transpile_functions: vec![], 53 | } 54 | } 55 | } 56 | 57 | impl Module { 58 | pub fn explicit_functions<'a>(&'a self, source: &'a Source) -> Vec<&'a Rc> { 59 | self.exposed_functions.iter().collect_vec() 60 | } 61 | 62 | /// Add the rule to our conformance, but also the scope. 63 | pub fn add_conformance_rule(&mut self, rule: Rc, scope: &mut scopes::Scope) { 64 | self.trait_conformance.add_conformance_rule(rule.clone()); 65 | scope.trait_conformance.add_conformance_rule(rule); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/util/graphs/task_dependency_graph.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::hash::Hash; 4 | 5 | pub struct TaskDependencyGraph { 6 | pub done: HashSet, 7 | pub next: Vec, 8 | pub changed: HashSet, 9 | pub dependencies: HashMap>, 10 | pub dependents: HashMap>, 11 | } 12 | 13 | impl TaskDependencyGraph { 14 | pub fn new() -> TaskDependencyGraph { 15 | TaskDependencyGraph { 16 | done: Default::default(), 17 | next: vec![], 18 | changed: Default::default(), 19 | dependencies: Default::default(), 20 | dependents: Default::default(), 21 | } 22 | } 23 | 24 | pub fn mark_as_done(&mut self, object: I) { 25 | let mut next = vec![object]; 26 | 27 | while let Some(object) = next.pop() { 28 | self.dependencies.remove(&object); 29 | self.changed.remove(&object); 30 | 31 | for dependent in self.dependents.remove(&object).into_iter().flatten() { 32 | let dependencies = self.dependencies.get_mut(&dependent).unwrap(); 33 | dependencies.remove(&object); 34 | if dependencies.is_empty() { 35 | self.changed.insert(dependent.clone()); 36 | next.push(dependent); 37 | } 38 | } 39 | 40 | self.done.insert(object); 41 | } 42 | } 43 | 44 | pub fn add_task(&mut self, object: I, dependencies: HashSet) { 45 | let remaining: HashSet = dependencies.difference(&self.done).cloned().collect(); 46 | 47 | match remaining.is_empty() { 48 | true => { 49 | self.next.push(object.clone()); 50 | self.mark_as_done(object); 51 | } 52 | false => { 53 | for dependency in remaining.iter() { 54 | match self.dependents.entry(dependency.clone()) { 55 | Entry::Occupied(mut o) => _ = o.get_mut().insert(object.clone()), 56 | Entry::Vacant(v) => _ = v.insert(HashSet::from([object.clone()])) 57 | } 58 | } 59 | if remaining.len() < dependencies.len() { 60 | self.changed.insert(object.clone()); 61 | } 62 | self.dependencies.insert(object, remaining); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | use proc_macro::TokenStream; 3 | 4 | #[proc_macro] 5 | pub fn pop_ip(_item: TokenStream) -> TokenStream { 6 | let args_str = _item.to_string(); 7 | let mut args = args_str.split(" "); 8 | 9 | let type_ = args.next().unwrap(); 10 | assert!(args.next().is_none()); 11 | 12 | // TODO When it's stable, these should be replaced by quote!() 13 | format!(" 14 | {{ 15 | let val = read_unaligned(ip as *mut {type_}); 16 | ip = transmute((ip as *mut {type_}).add(1)); 17 | val 18 | }} 19 | ", type_=type_).parse().unwrap() 20 | } 21 | 22 | #[proc_macro] 23 | pub fn un_expr(_item: TokenStream) -> TokenStream { 24 | let args_str = _item.to_string(); 25 | let mut args = args_str.split(","); 26 | 27 | let type_ = args.next().unwrap(); 28 | let result_type = args.next().unwrap(); 29 | let fun = args.collect::>().join(","); 30 | 31 | // TODO When it's stable, these should be replaced by quote!() 32 | format!(" 33 | {{ 34 | let sp_last = sp.offset(-8); 35 | let val = (*sp_last).{type_}; 36 | (*sp_last).{result_type} = {fun}; 37 | }} 38 | ", type_=type_, result_type=result_type, fun=fun).parse().unwrap() 39 | } 40 | 41 | #[proc_macro] 42 | pub fn un_expr_try(_item: TokenStream) -> TokenStream { 43 | let args_str = _item.to_string(); 44 | let mut args = args_str.split(","); 45 | 46 | let type_ = args.next().unwrap(); 47 | let result_type = args.next().unwrap(); 48 | let fun = args.collect::>().join(","); 49 | 50 | // TODO When it's stable, these should be replaced by quote!() 51 | format!(" 52 | {{ 53 | let sp_last = sp.offset(-8); 54 | let val = (*sp_last).{type_}; 55 | (*sp_last).{result_type} = {fun}?; 56 | }} 57 | ", type_=type_, result_type=result_type, fun=fun).parse().unwrap() 58 | } 59 | 60 | #[proc_macro] 61 | pub fn bin_expr(_item: TokenStream) -> TokenStream { 62 | let args_str = _item.to_string(); 63 | let mut args = args_str.split(","); 64 | 65 | let type_ = args.next().unwrap(); 66 | let result_type = args.next().unwrap(); 67 | let fun = args.collect::>().join(","); 68 | 69 | // TODO When it's stable, these should be replaced by quote!() 70 | format!(" 71 | {{ 72 | sp = sp.offset(-8); 73 | let rhs = (*sp).{type_}; 74 | 75 | let sp_last = sp.offset(-8); 76 | let lhs = (*sp_last).{type_}; 77 | (*sp_last).{result_type} = {fun}; 78 | }} 79 | ", type_=type_, fun=fun, result_type=result_type).parse().unwrap() 80 | } 81 | -------------------------------------------------------------------------------- /src/interpreter/disassembler.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreter::chunks::Chunk; 2 | use crate::interpreter::opcode::{OpCode, Primitive}; 3 | use std::mem::transmute; 4 | use uuid::Uuid; 5 | 6 | pub fn disassemble(chunk: &Chunk) { 7 | unsafe { 8 | let mut idx = 0; 9 | 10 | while idx < chunk.code.len() { 11 | print!("{:04}\t", idx); 12 | idx += disassemble_one(transmute(&chunk.code[idx])); 13 | print!("\n"); 14 | } 15 | } 16 | } 17 | 18 | pub fn disassemble_one(ip: *const u8) -> usize { 19 | unsafe { 20 | let code = transmute::(*ip); 21 | // TODO Somehow, {:<20?} doesn't pad correctly. 22 | print!("{:<20}", format!("{:?}", code)); 23 | 24 | match code { 25 | OpCode::NEG | OpCode::ADD | OpCode::SUB | OpCode::MUL | OpCode::DIV | 26 | OpCode::EQ | OpCode::NEQ | OpCode::GR | OpCode::GR_EQ | OpCode::LE | OpCode::LE_EQ | 27 | OpCode::MOD | OpCode::EXP | OpCode::LOG => { 28 | print!("\t{:?}", transmute::(*ip.add(1))); 29 | return 1 + 1; 30 | }, 31 | OpCode::LOAD8 => { 32 | print!("\t{:?}", *ip.add(1)); 33 | return 1 + 1; 34 | } 35 | OpCode::LOAD16 => { 36 | print!("\t{:?}", *(ip.add(1) as *mut u16)); 37 | return 1 + 2; 38 | } 39 | OpCode::LOAD32 | OpCode::LOAD_CONSTANT_32 | OpCode::GET_MEMBER_32 | OpCode::SET_MEMBER_32 | OpCode::ALLOC_32 => { 40 | print!("\t{:?}", *(ip.add(1) as *mut u32)); 41 | return 1 + 4; 42 | } 43 | OpCode::LOAD64 => { 44 | print!("\t{:?}", *(ip.add(1) as *mut u64)); 45 | return 1 + 8; 46 | } 47 | OpCode::CALL_INTRINSIC => { 48 | print!("\t{:?}", *(ip.add(1) as *mut usize)); 49 | return 1 + size_of::(); 50 | } 51 | OpCode::JUMP | OpCode::JUMP_IF_FALSE | OpCode::LOAD_LOCAL_32 | OpCode::STORE_LOCAL_32 => { 52 | print!("\t{:?}", *(ip.add(1) as *mut i32)); 53 | return 1 + 4; 54 | } 55 | OpCode::CALL => { 56 | print!("\t{}", Uuid::from_u128(*(ip.add(1) as *mut u128))); 57 | return 1 + 16; 58 | } 59 | OpCode::NOOP | OpCode::PANIC | OpCode::RETURN | OpCode::AND | 60 | OpCode::OR | OpCode::POP64 | OpCode::NOT | 61 | OpCode::DUP64 | OpCode::LOAD0 | OpCode::SWAP64 => { 62 | return 1; 63 | }, 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/resolver/decorations.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use itertools::Itertools; 4 | use uuid::Uuid; 5 | 6 | use crate::ast; 7 | use crate::error::{RResult, RuntimeError, TryCollectMany}; 8 | use crate::parser::expressions; 9 | use crate::parser::grammar::{Pattern, PatternPart}; 10 | use crate::program::functions::{FunctionHead, ParameterKey}; 11 | use crate::resolver::scopes; 12 | use crate::util::position::Positioned; 13 | 14 | pub fn try_parse_pattern(decoration: &ast::Expression, function: Rc, scope: &scopes::Scope) -> RResult>>> { 15 | let parsed = expressions::parse(decoration, &scope.grammar)?; 16 | 17 | let expressions::Value::FunctionCall(target, call_struct) = &parsed.value else { 18 | return Err(RuntimeError::error("Unrecognized decoration.").to_array()); 19 | }; 20 | 21 | let expressions::Value::Identifier(decoration_name) = &target.value else { 22 | return Err(RuntimeError::error("Unrecognized decoration.").to_array()); 23 | }; 24 | 25 | if decoration_name.as_str() != "pattern" { 26 | return Err(RuntimeError::error("Unrecognized decoration.").to_array()); 27 | } 28 | 29 | let [a, b] = &call_struct.arguments[..] else { 30 | return Err(RuntimeError::error("pattern decoration needs two arguments.").to_array()) 31 | }; 32 | 33 | if a.value.key != ParameterKey::Positional || a.value.type_declaration.is_some() || 34 | b.value.key != ParameterKey::Positional || b.value.type_declaration.is_some() { 35 | return Err(RuntimeError::error("pattern decoration arguments are faulty.").to_array()) 36 | } 37 | 38 | let precedence_group = match &b.value.value.iter().map(|p| p.as_ref()).collect_vec()[..] { 39 | [Positioned { position, value: ast::Term::Identifier(precedence) }] => 40 | scope.resolve_precedence_group(&precedence)?, 41 | _ => return Err(RuntimeError::error("Second argument to pattern needs to be a precedence name.").to_array()) 42 | }; 43 | 44 | let parts: Vec> = a.value.value.iter() 45 | .map(|pterm| { 46 | match &pterm.value { 47 | ast::Term::Identifier(i) => { 48 | Ok(Box::new(function.declared_internal_parameter_names.iter() 49 | .position(|p| p == i) 50 | .map(|p| PatternPart::Parameter(p)) 51 | .unwrap_or(PatternPart::Keyword(i.clone())))) 52 | }, 53 | _ => Err(RuntimeError::error("Bad pattern.").to_array()), 54 | } 55 | }) 56 | .try_collect_many()?; 57 | 58 | Ok(Rc::new(Pattern { 59 | id: Uuid::new_v4(), 60 | precedence_group, 61 | parts, 62 | function, 63 | })) 64 | } 65 | -------------------------------------------------------------------------------- /src/resolver/ambiguous/abstract_call.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::{Display, Formatter}; 3 | use std::ops::Range; 4 | use std::rc::Rc; 5 | 6 | use crate::error::{ErrInRange, RResult}; 7 | use crate::program::expression_tree::{ExpressionID, ExpressionOperation}; 8 | use crate::program::functions::{FunctionBinding, FunctionHead}; 9 | use crate::program::traits::{RequirementsFulfillment, Trait, TraitGraph}; 10 | use crate::resolver::ambiguous::{AmbiguityResult, ResolverAmbiguity}; 11 | use crate::resolver::imperative::ImperativeResolver; 12 | 13 | pub struct AmbiguousAbstractCall { 14 | pub expression_id: ExpressionID, 15 | pub arguments: Vec, 16 | pub traits: TraitGraph, 17 | 18 | pub range: Range, 19 | 20 | pub trait_: Rc, 21 | pub abstract_function: Rc, 22 | } 23 | 24 | impl Display for AmbiguousAbstractCall { 25 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 26 | write!(f, "Ambiguous abstract function call.") 27 | } 28 | } 29 | 30 | impl ResolverAmbiguity for AmbiguousAbstractCall { 31 | fn attempt_to_resolve(&mut self, resolver: &mut ImperativeResolver) -> RResult> { 32 | let type_ = resolver.builder.types.resolve_binding_alias(&self.expression_id)?; 33 | 34 | let requirement = self.trait_.create_generic_binding(vec![("Self", type_.clone())]); 35 | let trait_conformance = self.traits.satisfy_requirement(&requirement, &resolver.builder.types) 36 | .err_in_range(&self.range)?; 37 | Ok(match trait_conformance { 38 | AmbiguityResult::Ambiguous => { 39 | AmbiguityResult::Ambiguous 40 | } 41 | AmbiguityResult::Ok(trait_conformance) => { 42 | let used_function = &trait_conformance.conformance.function_mapping[&self.abstract_function]; 43 | 44 | resolver.builder.expression_tree.values.insert( 45 | self.expression_id.clone(), 46 | ExpressionOperation::FunctionCall(Rc::new(FunctionBinding { 47 | function: Rc::clone(used_function), 48 | requirements_fulfillment: Rc::new(RequirementsFulfillment { 49 | conformance: HashMap::from([(requirement, trait_conformance)]), 50 | generic_mapping: HashMap::from([(Rc::clone(&self.trait_.generics["Self"]), type_.clone())]) 51 | }), 52 | })) 53 | ); 54 | resolver.builder.types.bind(self.expression_id.clone(), type_.as_ref()) 55 | .err_in_range(&self.range)?; 56 | 57 | AmbiguityResult::Ok(()) 58 | } 59 | }) 60 | } 61 | 62 | fn get_position(&self) -> Range { 63 | self.range.clone() 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/interpreter/chunks.rs: -------------------------------------------------------------------------------- 1 | use crate::interpreter::data::Value; 2 | use crate::interpreter::opcode::OpCode; 3 | use std::ptr::write_unaligned; 4 | 5 | pub struct Chunk { 6 | pub code: Vec, 7 | pub constants: Vec, 8 | } 9 | 10 | impl Chunk { 11 | pub fn new() -> Chunk { 12 | Chunk { 13 | code: vec![], 14 | constants: vec![], 15 | } 16 | } 17 | 18 | pub fn push(&mut self, code: OpCode) { 19 | self.code.push(code as u8) 20 | } 21 | 22 | pub fn push_with_u8(&mut self, code: OpCode, arg: u8) { 23 | unsafe { self.code.extend([code as u8, arg]) } 24 | } 25 | 26 | pub fn push_with_u16(&mut self, code: OpCode, arg: u16) { 27 | let len = self.code.len(); 28 | 29 | unsafe { 30 | self.code.reserve(1 + 2); 31 | *self.code.as_mut_ptr().add(len) = code as u8; 32 | write_unaligned(self.code.as_mut_ptr().add(len + 1) as *mut u16, arg); 33 | self.code.set_len(len + 1 + 2); 34 | } 35 | } 36 | 37 | pub fn push_with_u32(&mut self, code: OpCode, arg: u32) { 38 | let len = self.code.len(); 39 | 40 | unsafe { 41 | self.code.reserve(1 + 4); 42 | *self.code.as_mut_ptr().add(len) = code as u8; 43 | write_unaligned(self.code.as_mut_ptr().add(len + 1) as *mut u32, arg); 44 | self.code.set_len(len + 1 + 4); 45 | } 46 | } 47 | 48 | pub fn push_with_u64(&mut self, code: OpCode, arg: u64) { 49 | let len = self.code.len(); 50 | 51 | unsafe { 52 | self.code.reserve(1 + 8); 53 | *self.code.as_mut_ptr().add(len) = code as u8; 54 | write_unaligned(self.code.as_mut_ptr().add(len + 1) as *mut u64, arg); 55 | self.code.set_len(len + 1 + 8); 56 | } 57 | } 58 | 59 | pub fn push_with_usize(&mut self, code: OpCode, arg: usize) { 60 | // TODO Should be constexpr 61 | if size_of::() == size_of::() { 62 | self.push_with_u64(code, arg as u64); 63 | } 64 | else if size_of::() == size_of::() { 65 | self.push_with_u64(code, arg as u64); 66 | } 67 | else { 68 | panic!() 69 | } 70 | } 71 | 72 | pub fn push_with_u128(&mut self, code: OpCode, arg: u128) { 73 | let len = self.code.len(); 74 | 75 | unsafe { 76 | self.code.reserve(1 + 16); 77 | *self.code.as_mut_ptr().add(len) = code as u8; 78 | write_unaligned(self.code.as_mut_ptr().add(len + 1) as *mut u128, arg); 79 | self.code.set_len(len + 1 + 16); 80 | } 81 | } 82 | 83 | pub fn modify_u32(&mut self, position: usize, arg: u32) { 84 | unsafe { 85 | write_unaligned(self.code.as_mut_ptr().add(position) as *mut u32, arg); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/transpiler/namespaces.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | 3 | use itertools::Itertools; 4 | use linked_hash_set::LinkedHashSet; 5 | use uuid::Uuid; 6 | 7 | #[derive(Clone)] 8 | pub struct Level { 9 | claims: HashMap>, 10 | sublevels: Vec 11 | } 12 | 13 | impl Level { 14 | pub fn new() -> Level { 15 | Level { 16 | claims: HashMap::new(), 17 | sublevels: Vec::new(), 18 | } 19 | } 20 | 21 | pub fn insert_name(&mut self, uuid: Uuid, name: &str) -> Uuid { 22 | if let Some(existing) = self.claims.get_mut(name) { 23 | existing.insert_if_absent(uuid); 24 | } 25 | else { 26 | self.claims.insert( 27 | name.to_string(), 28 | LinkedHashSet::from_iter([uuid.clone()]) 29 | ); 30 | } 31 | 32 | uuid 33 | } 34 | 35 | pub fn add_sublevel(&mut self) -> &mut Level { 36 | let new_level = Level::new(); 37 | self.sublevels.push(new_level); 38 | self.sublevels.last_mut().unwrap() 39 | } 40 | 41 | fn insert_names(&self, mapping: &mut HashMap, reserved: &HashSet) { 42 | let mut reserved = reserved.clone(); 43 | 44 | fn make_name(prefix: &str, idx: usize) -> String { 45 | format!("{}{}", prefix, idx) 46 | } 47 | 48 | for (name, claims) in self.claims.iter().sorted_by_key(|(name, claims)| name.len()) { 49 | if let Ok(claim) = claims.iter().exactly_one() { 50 | // Can use plain name 51 | let mut name = name.clone(); 52 | while reserved.contains(&name) { 53 | name = format!("{}_", name); 54 | } 55 | reserved.insert(name.clone()); 56 | mapping.insert(*claim, name); 57 | } 58 | else { 59 | // Need to postfix each name with an idx 60 | let mut prefix = format!("{}_", name); 61 | while (0 .. claims.len()).any(|idx| reserved.contains(&make_name(&prefix, idx))) { 62 | prefix = format!("{}_", prefix); 63 | } 64 | 65 | let mut idx = 0; 66 | for claim in claims.iter() { 67 | let postfixed_name = make_name(&prefix, idx); 68 | reserved.insert(postfixed_name.clone()); 69 | 70 | mapping.insert(claim.clone(), postfixed_name); 71 | idx += 1; 72 | } 73 | } 74 | } 75 | 76 | for level in self.sublevels.iter() { 77 | level.insert_names(mapping, &reserved); 78 | } 79 | } 80 | 81 | pub fn map_names(&self) -> HashMap { 82 | let mut map = HashMap::new(); 83 | self.insert_names(&mut map, &HashSet::new()); 84 | map 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/interpreter/run.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use uuid::Uuid; 4 | 5 | use crate::error::{RResult, RuntimeError}; 6 | use crate::interpreter::data::Value; 7 | use crate::interpreter::runtime::Runtime; 8 | use crate::program::functions::FunctionHead; 9 | use crate::program::module::Module; 10 | use crate::transpiler::{TranspiledArtifact, Transpiler}; 11 | 12 | pub fn main(module: &Module, runtime: &mut Runtime) -> RResult<()> { 13 | let entry_function = get_main_function(&module)? 14 | .ok_or(RuntimeError::error("No main! function declared.").to_array())?; 15 | 16 | // TODO Should gather all used functions and compile them 17 | let compiled = runtime.compile_server.compile_deep(&runtime.source, entry_function)?; 18 | 19 | unsafe { 20 | runtime.vm.run(compiled, &runtime.compile_server, vec![])?; 21 | } 22 | 23 | Ok(()) 24 | } 25 | 26 | pub fn get_main_function(module: &Module) -> RResult>> { 27 | let entry_function = match &module.main_functions[..] { 28 | [] => return Ok(None), 29 | [f] => f, 30 | functions => return Err(RuntimeError::error(format!("Too many !main functions declared: {:?}", functions).as_str()).to_array()), 31 | }; 32 | if !entry_function.interface.parameters.is_empty() { 33 | return Err(RuntimeError::error("main! function has parameters.").to_array()); 34 | } 35 | if !entry_function.interface.return_type.unit.is_void() { 36 | return Err(RuntimeError::error("main! function has a return value.").to_array()); 37 | } 38 | Ok(Some(entry_function)) 39 | } 40 | 41 | // The function is written like this 42 | pub fn transpile(module: &Module, runtime: &mut Runtime) -> RResult> { 43 | let entry_function = get_transpile_function(module)?; 44 | assert!(entry_function.interface.return_type.unit.is_void(), "transpile! function has a return value."); 45 | 46 | // Set the transpiler object. 47 | let compiled = runtime.compile_server.compile_deep(&runtime.source, entry_function)?; 48 | 49 | unsafe { 50 | runtime.vm.run(compiled, &runtime.compile_server, vec![Value { u8: 0 }])?; 51 | } 52 | 53 | let exported_artifacts = gather_functions_logic(runtime, &runtime.vm.transpile_functions); 54 | 55 | Ok(Box::new(Transpiler { 56 | // TODO This should be one of the exported artifacts 57 | main_function: get_main_function(module)?.map(Rc::clone), 58 | exported_artifacts, 59 | })) 60 | } 61 | 62 | fn get_transpile_function(module: &Module) -> RResult<&Rc> { 63 | match &module.transpile_functions[..] { 64 | [] => Err(RuntimeError::error("No transpile! function declared.").to_array()), 65 | [f] => Ok(f), 66 | functions => Err(RuntimeError::error(format!("Too many transpile! functions declared: {:?}", functions).as_str()).to_array()), 67 | } 68 | } 69 | 70 | pub fn gather_functions_logic(runtime: &Runtime, transpile_functions: &Vec) -> Vec { 71 | transpile_functions.iter().map(|uuid| { 72 | TranspiledArtifact::Function(Rc::clone(&runtime.source.fn_heads[uuid])) 73 | }).collect() 74 | } 75 | -------------------------------------------------------------------------------- /src/refactor/call_graph.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::{HashMap, HashSet}; 3 | use std::rc::Rc; 4 | 5 | use itertools::Itertools; 6 | use linked_hash_set::LinkedHashSet; 7 | 8 | use crate::program::functions::{FunctionBinding, FunctionHead}; 9 | use crate::util::multimap::{insert_into_multimap, remove_from_multimap}; 10 | 11 | pub struct CallGraph { 12 | pub callers: HashMap, HashMap, HashSet>>>, 13 | pub callees: HashMap, LinkedHashSet>>, 14 | } 15 | 16 | impl CallGraph { 17 | pub fn new() -> CallGraph { 18 | CallGraph { 19 | callers: Default::default(), 20 | callees: Default::default(), 21 | } 22 | } 23 | 24 | pub fn get_callers(&self, head: &Rc) -> impl Iterator> { 25 | self.callers.get(head).into_iter() 26 | .flat_map(|cs| cs.values()) 27 | .flatten() 28 | .dedup() 29 | } 30 | 31 | pub fn get_binding_callers<'a>(&'a self, binding: &'a Rc) -> impl Iterator> { 32 | self.callers.get(&binding.function).into_iter() 33 | .flat_map(|c| c.get(binding).into_iter()) 34 | .flatten() 35 | } 36 | 37 | pub fn remove(&mut self, head: &Rc) { 38 | self.clear_callees(head); 39 | self.callers.remove(head); 40 | self.callees.remove(head); 41 | } 42 | 43 | pub fn clear_callees(&mut self, head: &Rc) { 44 | if let Some(previous_callees) = self.callees.get(head) { 45 | for previous_callee in previous_callees.iter() { 46 | if let Entry::Occupied(mut o) = self.callers.entry(Rc::clone(&previous_callee.function)) { 47 | remove_from_multimap(o.get_mut(), previous_callee, head); 48 | } 49 | } 50 | } 51 | } 52 | 53 | pub fn change_callees(&mut self, head: &Rc, new_callees: LinkedHashSet>) { 54 | self.clear_callees(head); 55 | for callee_binding in new_callees.iter() { 56 | match self.callers.entry(Rc::clone(&callee_binding.function)) { 57 | Entry::Occupied(mut o) => { 58 | insert_into_multimap(o.get_mut(), Rc::clone(callee_binding), Rc::clone(head)); 59 | } 60 | Entry::Vacant(mut v) => { 61 | v.insert(HashMap::from([(Rc::clone(callee_binding), HashSet::from([Rc::clone(head)]))])); 62 | } 63 | } 64 | } 65 | self.callees.insert(Rc::clone(head), new_callees); 66 | } 67 | 68 | pub fn deep_callees<'a>(&self, from: impl Iterator>) -> LinkedHashSet> { 69 | let mut next = from.collect_vec(); 70 | let mut gathered = LinkedHashSet::new(); 71 | while let Some(current) = next.pop() { 72 | let Some(callees) = self.callees.get(current) else { 73 | continue 74 | }; 75 | gathered.extend(callees.iter().map(|f| &f.function).cloned()); 76 | next.extend(callees.iter().map(|f| &f.function)) 77 | } 78 | gathered 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /monoteny/common/math.monoteny: -------------------------------------------------------------------------------- 1 | use!( 2 | module!("common.precedence"), 3 | ); 4 | 5 | def pi -> $Real :: 3.141592653589793; 6 | def tau -> $Real :: 6.283185307179586; 7 | 8 | def e -> $Real :: 2.718281828459045; 9 | 10 | -- Common mathematical patterns we can expect most people to understand. 11 | 12 | ![pattern(+ arg, LeftUnaryPrecedence)] 13 | def positive(arg '$Number) -> $Number :: arg; 14 | 15 | ![pattern(- arg, LeftUnaryPrecedence)] 16 | def _negative(arg '$Number) -> $Number :: negative(arg); 17 | 18 | ![pattern(not arg, LeftUnaryPrecedence)] 19 | def _not(arg 'Bool) -> Bool :: not_f(arg); 20 | 21 | ![pattern(lhs ** rhs, ExponentiationPrecedence)] 22 | def _pow(lhs '$Real, rhs '$Real) -> $Real :: pow(lhs, rhs); 23 | 24 | ![pattern(lhs * rhs, MultiplicationPrecedence)] 25 | def _multiply(lhs '$Number, rhs '$Number) -> $Number :: multiply(lhs, rhs); 26 | 27 | ![pattern(lhs / rhs, MultiplicationPrecedence)] 28 | def _divide(lhs '$Number, rhs '$Number) -> $Number :: divide(lhs, rhs); 29 | 30 | ![pattern(lhs % rhs, MultiplicationPrecedence)] 31 | def _modulo(lhs '$Number, rhs '$Number) -> $Number :: modulo(lhs, rhs); 32 | 33 | ![pattern(lhs + rhs, AdditionPrecedence)] 34 | def _add(lhs '$Number, rhs '$Number) -> $Number :: add(lhs, rhs); 35 | 36 | ![pattern(lhs - rhs, AdditionPrecedence)] 37 | def _subtract(lhs '$Number, rhs '$Number) -> $Number :: subtract(lhs, rhs); 38 | 39 | ![pattern(lhs == rhs, ComparisonPrecedence)] 40 | def _is_equal(lhs '$Eq, rhs '$Eq) -> Bool :: is_equal(lhs, rhs); 41 | 42 | ![pattern(lhs != rhs, ComparisonPrecedence)] 43 | def _is_not_equal(lhs '$Eq, rhs '$Eq) -> Bool :: is_not_equal(lhs, rhs); 44 | 45 | ![pattern(lhs > rhs, ComparisonPrecedence)] 46 | def _is_greater(lhs '$Number, rhs '$Number) -> Bool :: is_greater(lhs, rhs); 47 | 48 | ![pattern(lhs >= rhs, ComparisonPrecedence)] 49 | def _is_greater_or_equal(lhs '$Number, rhs '$Number) -> Bool :: is_greater_or_equal(lhs, rhs); 50 | 51 | ![pattern(lhs < rhs, ComparisonPrecedence)] 52 | def _is_lesser(lhs '$Number, rhs '$Number) -> Bool :: is_lesser(lhs, rhs); 53 | 54 | ![pattern(lhs <= rhs, ComparisonPrecedence)] 55 | def _is_lesser_or_equal(lhs '$Number, rhs '$Number) -> Bool :: is_lesser_or_equal(lhs, rhs); 56 | 57 | ![pattern(lhs and rhs, LogicalConjunctionPrecedence)] 58 | def _and(lhs 'Bool, rhs 'Bool) -> Bool :: and_f(lhs, rhs); 59 | 60 | ![pattern(lhs or rhs, LogicalDisjunctionPrecedence)] 61 | def _or(lhs 'Bool, rhs 'Bool) -> Bool :: or_f(lhs, rhs); 62 | 63 | -- The following functions are provided for ease of use in new number formats. 64 | -- Those that have no implementation will receive one eventually. 65 | -- For primitives, transpilers will usually insert a hardware implementation instead. 66 | 67 | def (self '$Real).factorial() -> $Real; 68 | 69 | -- TODO Use CORDIC for sin and cos 70 | def sin(x '$Real) -> $Real; 71 | def cos(x '$Real) -> $Real; 72 | def tan(x '$Real) -> $Real; 73 | def sinh(x '$Real) -> $Real; 74 | def cosh(x '$Real) -> $Real; 75 | def tanh(x '$Real) -> $Real; 76 | def arcsin(x '$Real) -> $Real; 77 | def arccos(x '$Real) -> $Real; 78 | def arctan(x '$Real) -> $Real; 79 | def arcsinh(x '$Real) -> $Real; 80 | def arccosh(x '$Real) -> $Real; 81 | def arctanh(x '$Real) -> $Real; 82 | 83 | def ceil(x '$Real) -> $Real; 84 | def floor(x '$Real) -> $Real; 85 | def round(x '$Real) -> $Real; 86 | 87 | def abs(x '$Real) -> $Real; 88 | -------------------------------------------------------------------------------- /src/resolver/imports.rs: -------------------------------------------------------------------------------- 1 | use itertools::Itertools; 2 | 3 | use crate::ast; 4 | use crate::error::{ErrInRange, RResult, RuntimeError, TryCollectMany}; 5 | use crate::interpreter::runtime::Runtime; 6 | use crate::parser::expressions; 7 | use crate::program::functions::ParameterKey; 8 | use crate::program::module::ModuleName; 9 | use crate::resolver::{interpreter_mock, scopes}; 10 | use crate::util::iter::omega; 11 | 12 | pub struct Import { 13 | pub is_relative: bool, 14 | pub elements: Vec, 15 | } 16 | 17 | impl Import { 18 | pub fn relative_to(&self, path: &Vec) -> Vec { 19 | match self.is_relative { 20 | true => path.iter().chain(&self.elements).cloned().collect_vec(), 21 | false => self.elements.clone(), 22 | } 23 | } 24 | } 25 | 26 | pub fn resolve_imports(body: &ast::Struct, scope: &scopes::Scope) -> RResult> { 27 | body.arguments.iter().map(|arg| { 28 | if arg.value.key != ParameterKey::Positional { 29 | return Err( 30 | RuntimeError::error("Imports cannot be renamed for now.").to_array() 31 | ); 32 | } 33 | if arg.value.type_declaration.is_some() { 34 | return Err( 35 | RuntimeError::error("Imports cannot have type declarations.").to_array() 36 | ); 37 | } 38 | 39 | resolve_module(&arg.value.value, scope) 40 | }).try_collect_many() 41 | } 42 | 43 | pub fn resolve_module(body: &ast::Expression, scope: &scopes::Scope) -> RResult { 44 | let error = RuntimeError::error("Import parameter is not a module.").to_array(); 45 | 46 | let parsed = expressions::parse(body, &scope.grammar)?; 47 | 48 | let expressions::Value::FunctionCall(target, call_struct) = &parsed.value else { 49 | return Err(error) 50 | }; 51 | 52 | let expressions::Value::MacroIdentifier(name) = &target.value else { 53 | return Err(error) 54 | }; 55 | 56 | if name.as_str() != "module" { 57 | return Err(error).err_in_range(&target.position); 58 | } 59 | let body = interpreter_mock::plain_parameter("module!", call_struct)?; 60 | 61 | let argument_parsed = expressions::parse(body, &scope.grammar)?; 62 | 63 | let expressions::Value::StringLiteral(parts) = &argument_parsed.value else { 64 | return Err(error); 65 | }; 66 | 67 | let mut literal = interpreter_mock::plain_string_literal("module!", parts)?; 68 | 69 | let is_relative = literal.starts_with("."); 70 | if is_relative { 71 | let mut chars = literal.chars(); 72 | chars.next(); 73 | literal = chars.as_str(); 74 | } 75 | 76 | let mut elements = literal.split(".").collect_vec(); 77 | 78 | if !elements.iter().all(|p| p.chars().all(|c| c.is_alphanumeric())) { 79 | return Err(error); 80 | } 81 | 82 | Ok(Import { 83 | is_relative, 84 | elements: elements.iter().map(|e| e.to_string()).collect_vec(), 85 | }) 86 | } 87 | 88 | pub fn deep(runtime: &Runtime, module_name: ModuleName, scope: &mut scopes::Scope) -> RResult<()> { 89 | let all_modules = omega([&module_name].into_iter(), |m| runtime.source.module_by_name[*m].included_modules.iter()); 90 | 91 | for module in all_modules { 92 | scope.import(&runtime.source.module_by_name[module], runtime)?; 93 | } 94 | 95 | Ok(()) 96 | } 97 | -------------------------------------------------------------------------------- /src/refactor/locals.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::rc::Rc; 3 | 4 | use itertools::Itertools; 5 | 6 | use crate::program::allocation::ObjectReference; 7 | use crate::program::expression_tree::ExpressionOperation; 8 | use crate::program::functions::{FunctionImplementation, FunctionInterface}; 9 | 10 | pub fn swizzle_retaining_parameters(function: &FunctionImplementation, removed: &HashSet>) -> Vec { 11 | function.parameter_locals.iter().enumerate() 12 | .filter_map(|(idx, local)| (!removed.contains(local)).then(|| idx)) 13 | .collect_vec() 14 | } 15 | 16 | pub fn find_unused_locals(function: &FunctionImplementation) -> HashSet> { 17 | let mut unused = HashSet::from_iter(function.locals_names.keys().cloned()); 18 | 19 | for operation in function.expression_tree.values.values() { 20 | match operation { 21 | ExpressionOperation::GetLocal(local) => _ = unused.remove(local), 22 | _ => {}, 23 | } 24 | } 25 | 26 | return unused 27 | } 28 | 29 | pub fn remove_locals(implementation: &mut FunctionImplementation, removed_locals: &HashSet>) -> Option> { 30 | let changes_interface = removed_locals.iter().any(|l| implementation.parameter_locals.contains(l)); 31 | 32 | let mut expression_forest = &mut implementation.expression_tree; 33 | // TODO Also truncate removed from type forest 34 | let mut type_forest = &mut implementation.type_forest; 35 | 36 | for expression_id in expression_forest.values.keys().cloned().collect_vec() { 37 | let Some(operation) = expression_forest.values.get(&expression_id) else { 38 | continue // Already trimmed 39 | }; 40 | 41 | match operation { 42 | ExpressionOperation::GetLocal(local) => { 43 | if removed_locals.contains(local) { 44 | expression_forest.truncate_up_and_down(vec![expression_id], |op| op == &ExpressionOperation::Block); 45 | } 46 | } 47 | ExpressionOperation::SetLocal(local) => { 48 | if removed_locals.contains(local) { 49 | expression_forest.truncate_up_and_down(vec![expression_id], |op| op == &ExpressionOperation::Block); 50 | } 51 | } 52 | _ => {}, 53 | } 54 | } 55 | 56 | implementation.locals_names = implementation.locals_names.drain() 57 | .filter(|(key, value)| !removed_locals.contains(key)) 58 | .collect(); 59 | 60 | if changes_interface { 61 | let swizzle = swizzle_retaining_parameters(implementation, removed_locals); 62 | 63 | // TODO We may be able to remove some generics and requirements. 64 | implementation.interface = Rc::new(FunctionInterface { 65 | parameters: swizzle.iter().map(|idx| implementation.interface.parameters[*idx].clone()).collect_vec(), 66 | return_type: implementation.interface.return_type.clone(), 67 | requirements: implementation.interface.requirements.clone(), 68 | generics: implementation.interface.generics.clone(), 69 | }); 70 | implementation.parameter_locals = swizzle.iter().map(|idx| implementation.parameter_locals[*idx].clone()).collect_vec(); 71 | 72 | return Some(swizzle) 73 | } 74 | else { 75 | return None 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/util/graphs/node_tree.rs: -------------------------------------------------------------------------------- 1 | use std::collections::hash_map::Entry; 2 | use std::collections::HashMap; 3 | use std::hash::Hash; 4 | 5 | use itertools::Itertools; 6 | 7 | use crate::util::iter::omega; 8 | use crate::util::vec; 9 | 10 | /// TODO We could also use an actual tree... 11 | #[derive(Clone)] 12 | pub struct NodeTree { 13 | pub root: Key, 14 | /// Will be set for every expression ID 15 | pub children: HashMap>, 16 | /// Will be set for every expression ID 17 | pub parents: HashMap, 18 | /// Might not be set for a while 19 | pub values: HashMap, 20 | } 21 | 22 | impl NodeTree { 23 | pub fn new(root: Key) -> NodeTree { 24 | NodeTree { 25 | root, 26 | values: HashMap::new(), 27 | children: HashMap::new(), 28 | parents: Default::default(), 29 | } 30 | } 31 | 32 | pub fn deep_children(&self, start: Key) -> Vec { 33 | omega([start].into_iter(), |e| self.children[e].iter().cloned()).collect_vec() 34 | } 35 | 36 | pub fn truncate_up_and_down(&mut self, mut trim: Vec, up_until: impl Fn(&Value) -> bool) { 37 | while let Some(current) = trim.pop() { 38 | let Entry::Occupied(o) = self.values.entry(current) else { 39 | continue // TODO This shouldn't happen but it does rn for some reason. Maybe because of the other truncations? 40 | }; 41 | 42 | if up_until(o.get()) { 43 | self.children.get_mut(o.key()).unwrap().retain(|a| self.values.contains_key(a)) 44 | } 45 | else { 46 | trim.push(self.parents.remove(o.key()).unwrap()); 47 | trim.extend(self.children.remove(o.key()).unwrap()); 48 | o.remove(); 49 | } 50 | } 51 | } 52 | 53 | pub fn truncate_down(&mut self, mut include: Vec) { 54 | while let Some(current) = include.pop() { 55 | // It's enough to remove arguments and operations. 56 | // The type forest may still contain orphans, but that's ok. And our type doesn't change 57 | // from inlining. 58 | self.values.remove(¤t); 59 | self.parents.remove(¤t); 60 | include.extend(self.children.remove(¤t).unwrap()); 61 | } 62 | } 63 | 64 | pub fn swizzle_arguments(&mut self, expression_id: Key, swizzle: &Vec) { 65 | let args = self.children.get_mut(&expression_id).unwrap(); 66 | let removed = vec::swizzle(args, swizzle); 67 | self.truncate_down(removed); 68 | } 69 | 70 | pub fn inline(&mut self, expression_id: Key, parameter_idx: usize) { 71 | let mut arguments_before = self.children[&expression_id].clone(); 72 | 73 | let replacement_id = arguments_before.remove(parameter_idx); 74 | let replacement_operation = self.values.remove(&replacement_id).unwrap(); 75 | let replacement_arguments = self.children.remove(&replacement_id).unwrap(); 76 | 77 | for arg in replacement_arguments.iter() { 78 | *self.parents.get_mut(arg).unwrap() = expression_id.clone(); 79 | } 80 | 81 | let operation = self.values.get_mut(&expression_id).unwrap(); 82 | *operation = replacement_operation; 83 | 84 | let arguments = self.children.get_mut(&expression_id).unwrap(); 85 | *arguments = replacement_arguments; 86 | 87 | self.truncate_down(arguments_before); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/program/functions/head.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Debug, Formatter}; 2 | use std::hash::{Hash, Hasher}; 3 | use std::rc::Rc; 4 | 5 | use display_with_options::with_options; 6 | use itertools::Itertools; 7 | use uuid::Uuid; 8 | 9 | use crate::program::functions::{FunctionInterface, FunctionRepresentation, Parameter, ParameterKey}; 10 | use crate::program::traits::TraitBinding; 11 | 12 | #[derive(Clone, PartialEq, Eq)] 13 | pub enum FunctionType { 14 | /// A normal function. 15 | Static, 16 | /// Not a real function; instead, it refers to a function of some requirement. 17 | Polymorphic { assumed_requirement: Rc, abstract_function: Rc }, 18 | } 19 | 20 | /// The 'head' of a function. It is identifiable by its ID and has an interface. 21 | /// Could be abstract or implemented, depending on whether an implementation is provided! 22 | /// It can also be polymorphic depending on the function_type. 23 | pub struct FunctionHead { 24 | pub function_id: Uuid, 25 | pub function_type: FunctionType, 26 | pub interface: Rc, 27 | pub declared_representation: FunctionRepresentation, 28 | pub declared_internal_parameter_names: Vec, 29 | } 30 | 31 | impl FunctionHead { 32 | pub fn new_static(declared_internal_parameter_names: Vec, declared_representation: FunctionRepresentation, interface: Rc) -> Rc { 33 | Self::new(declared_internal_parameter_names, declared_representation, interface, FunctionType::Static) 34 | } 35 | 36 | pub fn new(declared_internal_parameter_names: Vec, declared_representation: FunctionRepresentation, interface: Rc, function_type: FunctionType) -> Rc { 37 | assert_eq!(declared_internal_parameter_names.len(), interface.parameters.len()); 38 | 39 | Rc::new(FunctionHead { 40 | function_id: Uuid::new_v4(), 41 | interface, 42 | function_type, 43 | declared_representation, 44 | declared_internal_parameter_names, 45 | }) 46 | } 47 | 48 | pub fn unwrap_id(&self) -> Uuid { 49 | match &self.function_type { 50 | FunctionType::Static => self.function_id, 51 | FunctionType::Polymorphic { .. } => panic!("Cannot unwrap polymorphic implementation ID"), 52 | } 53 | } 54 | 55 | pub fn dummy_param_names(count: usize) -> Vec { 56 | (0..count).map(|p| format!("p{}", count)).collect_vec() 57 | } 58 | 59 | pub fn infer_param_names(params: &Vec) -> Vec { 60 | params.iter().enumerate().map(|(idx, p)| match &p.external_key { 61 | ParameterKey::Positional => format!("p{}", idx), 62 | ParameterKey::Name(n) => n.clone(), 63 | }).collect_vec() 64 | } 65 | } 66 | 67 | impl PartialEq for FunctionHead { 68 | fn eq(&self, other: &Self) -> bool { 69 | self.function_id == other.function_id 70 | } 71 | } 72 | 73 | impl Eq for FunctionHead {} 74 | 75 | impl Hash for FunctionHead { 76 | fn hash(&self, state: &mut H) { 77 | self.function_id.hash(state); 78 | } 79 | } 80 | 81 | impl Debug for FunctionHead { 82 | fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { 83 | let call_type_symbol = match self.function_type { 84 | FunctionType::Static => "|", 85 | FunctionType::Polymorphic { .. } => "?" 86 | }; 87 | write!(fmt, "-{}({})--> {:?}", call_type_symbol, &self.function_id, with_options(self.interface.as_ref(), &self.declared_representation)) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/resolver/referencible.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::error::RResult; 4 | use crate::interpreter::runtime::Runtime; 5 | use crate::program::functions::{FunctionCallExplicity, FunctionHead, FunctionInterface, FunctionLogic, FunctionLogicDescriptor, FunctionRepresentation}; 6 | use crate::program::module::Module; 7 | use crate::program::traits::{Trait, TraitConformanceRule}; 8 | use crate::program::types::TypeProto; 9 | use crate::resolver::scopes; 10 | 11 | pub fn add_trait(runtime: &mut Runtime, module: &mut Module, scope: Option<&mut scopes::Scope>, trait_: &Rc) -> RResult<()> { 12 | let meta_type = TypeProto::one_arg(&runtime.Metatype, TypeProto::unit_struct(trait_)); 13 | let getter = FunctionHead::new_static( 14 | vec![], 15 | FunctionRepresentation::new_global_implicit(&trait_.name), 16 | FunctionInterface::new_provider(&meta_type, vec![]), 17 | ); 18 | 19 | runtime.source.fn_heads.insert(getter.function_id, Rc::clone(&getter)); 20 | runtime.source.trait_references.insert( 21 | Rc::clone(&getter), 22 | Rc::clone(trait_), 23 | ); 24 | runtime.source.fn_logic.insert( 25 | Rc::clone(&getter), 26 | FunctionLogic::Descriptor(FunctionLogicDescriptor::TraitProvider(Rc::clone(trait_))), 27 | ); 28 | 29 | if let Some(scope) = scope { 30 | scope.overload_function(&getter, getter.declared_representation.clone())?; 31 | } 32 | 33 | module.exposed_functions.insert(getter); 34 | 35 | Ok(()) 36 | } 37 | 38 | pub fn add_function(runtime: &mut Runtime, module: &mut Module, scope: Option<&mut scopes::Scope>, function: &Rc) -> RResult<()> { 39 | // TODO Once functions are actually objects, we can call add_trait from here. 40 | let function_trait = Rc::new(Trait::new_with_self(&function.declared_representation.name)); 41 | let conformance_to_function = TraitConformanceRule::manual(runtime.traits.as_ref().unwrap().Function.create_generic_binding(vec![ 42 | ("Self", TypeProto::unit_struct(&function_trait)) 43 | ]), vec![]); 44 | module.trait_conformance.add_conformance_rule(Rc::clone(&conformance_to_function)); 45 | 46 | runtime.source.function_traits.insert(Rc::clone(&function_trait), Rc::clone(&function)); 47 | 48 | // The function should be implicit. 49 | // assert_eq!(function.declared_representation.call_explicity, FunctionCallExplicity::Explicit); 50 | let getter = FunctionHead::new_static( 51 | vec![], 52 | FunctionRepresentation::new(function.declared_representation.name.as_str(), function.declared_representation.target_type, FunctionCallExplicity::Implicit), 53 | FunctionInterface::new_provider(&TypeProto::unit_struct(&function_trait), vec![]), 54 | ); 55 | runtime.source.fn_heads.insert(function.function_id, Rc::clone(&function)); 56 | runtime.source.fn_heads.insert(getter.function_id, Rc::clone(&getter)); 57 | runtime.source.fn_logic.insert( 58 | Rc::clone(&getter), 59 | FunctionLogic::Descriptor(FunctionLogicDescriptor::FunctionProvider(Rc::clone(&function))), 60 | ); 61 | runtime.source.fn_getters.insert(Rc::clone(&function), Rc::clone(&getter)); 62 | 63 | // Implicits expose themselves, but functions will sit behind a getter 64 | let exposed_function = function; 65 | 66 | if let Some(scope) = scope { 67 | scope.overload_function(&exposed_function, exposed_function.declared_representation.clone())?; 68 | scope.trait_conformance.add_conformance_rule(conformance_to_function); 69 | } 70 | 71 | module.exposed_functions.insert(exposed_function.clone()); 72 | 73 | Ok(()) 74 | } 75 | -------------------------------------------------------------------------------- /src/resolver/imperative_builder.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::rc::Rc; 3 | 4 | use itertools::Itertools; 5 | 6 | use crate::error::{RResult, RuntimeError}; 7 | use crate::interpreter::runtime::Runtime; 8 | use crate::program::allocation::ObjectReference; 9 | use crate::program::expression_tree::{ExpressionID, ExpressionOperation, ExpressionTree}; 10 | use crate::program::functions::{FunctionBinding, FunctionOverload, FunctionTargetType}; 11 | use crate::program::generics::TypeForest; 12 | use crate::program::types::TypeProto; 13 | use crate::resolver::scopes; 14 | use crate::resolver::type_factory::TypeFactory; 15 | 16 | /// Note: This object should not know about the AST. 17 | pub struct ImperativeBuilder<'a> { 18 | pub runtime: &'a mut Runtime, 19 | pub type_factory: TypeFactory<'a>, 20 | pub types: Box, 21 | pub expression_tree: Box, 22 | pub locals_names: HashMap, String>, 23 | } 24 | 25 | impl<'a> ImperativeBuilder<'a> { 26 | pub fn make_expression(&mut self, arguments: Vec) -> ExpressionID { 27 | let id = ExpressionID::new_v4(); 28 | 29 | self.types.register(id); 30 | for argument in arguments.iter() { 31 | self.expression_tree.parents.insert(*argument, id); 32 | } 33 | self.expression_tree.children.insert(id, arguments); 34 | 35 | id 36 | } 37 | 38 | pub fn make_operation_expression(&mut self, arguments: Vec, operation: ExpressionOperation) -> ExpressionID { 39 | let id = self.make_expression(arguments); 40 | self.expression_tree.values.insert(id.clone(), operation); 41 | id 42 | } 43 | 44 | pub fn make_full_expression(&mut self, arguments: Vec, return_type: &TypeProto, operation: ExpressionOperation) -> RResult { 45 | let id = self.make_expression(arguments); 46 | 47 | self.expression_tree.values.insert(id.clone(), operation); 48 | 49 | self.types.bind(id, &return_type) 50 | .map(|_| id) 51 | } 52 | 53 | pub fn register_local(&mut self, identifier: &str, reference: Rc, scope: &mut scopes::Scope) -> RResult<()> { 54 | self.locals_names.insert(Rc::clone(&reference), identifier.to_string()); 55 | scope.override_reference(FunctionTargetType::Global, scopes::Reference::Local(reference), identifier) 56 | } 57 | 58 | pub fn add_string_primitive(&mut self, value: &str) -> RResult { 59 | self.make_full_expression( 60 | vec![], 61 | &TypeProto::unit_struct(&self.runtime.traits.as_ref().unwrap().String), 62 | ExpressionOperation::StringLiteral(value.to_string()) 63 | ) 64 | } 65 | 66 | pub fn add_function_reference(&mut self, overload: &Rc) -> RResult { 67 | match overload.functions.iter().exactly_one() { 68 | Ok(function) => { 69 | let getter = Rc::clone(&self.runtime.source.fn_getters[function]); 70 | let expression_id = self.make_full_expression( 71 | vec![], 72 | &getter.interface.return_type.clone(), 73 | // Call the getter of the function 'object' instead of the function itself. 74 | ExpressionOperation::FunctionCall(FunctionBinding::pure(getter)) 75 | )?; 76 | 77 | Ok(expression_id) 78 | } 79 | _ => return Err( 80 | RuntimeError::error("References to overloaded functions are not yet supported (need syntax to distinguish which to choose).").to_array() 81 | )?, 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/refactor/simplify.rs: -------------------------------------------------------------------------------- 1 | use linked_hash_set::LinkedHashSet; 2 | use std::collections::hash_map::RandomState; 3 | use std::rc::Rc; 4 | 5 | use crate::program::functions::{FunctionHead, FunctionLogic}; 6 | use crate::refactor::{locals, Refactor}; 7 | use crate::source::Source; 8 | 9 | pub struct Simplify { 10 | pub refactor: Refactor, 11 | pub inline: bool, 12 | pub trim_locals: bool, 13 | pub monomorphize: bool, 14 | } 15 | 16 | impl Simplify { 17 | pub fn run<'a>(&mut self, from_functions: impl Iterator>, source: &Source) { 18 | if self.monomorphize { 19 | // First, monomorphize everything we call 20 | let mut next: LinkedHashSet<_, RandomState> = LinkedHashSet::from_iter( 21 | self.refactor.explicit_functions.iter() 22 | .flat_map(|head| self.refactor.call_graph.callees[head].iter().cloned()) 23 | ); 24 | while let Some(current) = next.pop_front() { 25 | if let Some(monomorphized) = self.refactor.try_monomorphize(¤t, source) { 26 | next.extend(self.refactor.call_graph.callees.get(&monomorphized).unwrap().iter().cloned()); 27 | } 28 | } 29 | } 30 | 31 | // Make sure refactor has everything that's needed so we can simplify it. 32 | self.refactor.gather_deep_functions(from_functions, source); 33 | 34 | // Now, let's simplify! 35 | let mut next: LinkedHashSet<_, RandomState> = LinkedHashSet::from_iter(self.refactor.fn_logic.keys().cloned()); 36 | while let Some(current) = next.pop_front() { 37 | // TODO The explicit functions should be refactorable too, I think. 38 | let is_explicit = self.refactor.explicit_functions.contains(¤t); 39 | 40 | if !is_explicit && self.inline { 41 | // Try to inline the function if it's trivial. 42 | if let Ok(affected) = self.refactor.try_inline(¤t) { 43 | // Try inlining those that changed again. 44 | // TODO This could be more efficient: It only makes sense to change functions once. 45 | // The inlining call can be delayed until we're sure we can either be inlined 46 | // ourselves, or we just postpone it until everything else is done. 47 | next.extend(affected); 48 | 49 | // The function was inlined; there's no need to do anything else. 50 | continue 51 | } 52 | } 53 | 54 | // Try to remove unused parameters for the function. 55 | if self.trim_locals { 56 | if let FunctionLogic::Implementation(implementation) = &self.refactor.fn_logic[¤t] { 57 | // TODO What if the parameters' setters call I/O functions? 58 | // We should only remove those that aren't involved in I/O. We can actually 59 | // remove any as long as they're not involved in I/O. 60 | let mut remove = locals::find_unused_locals(implementation); 61 | 62 | if is_explicit { 63 | // TODO Cannot change interface for now because it replaces the function head, 64 | // which may be in use elsewhere. 65 | implementation.parameter_locals.iter().for_each(|l| _ = remove.remove(l)); 66 | } 67 | 68 | if !remove.is_empty() { 69 | next.extend(self.refactor.swizzle_implementation(¤t, |imp| { 70 | locals::remove_locals(imp, &remove) 71 | })); 72 | } 73 | }; 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/program/traits/trait_.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::fmt::{Debug, Formatter}; 3 | use std::hash::{Hash, Hasher}; 4 | use std::rc::Rc; 5 | 6 | use uuid::Uuid; 7 | 8 | use crate::program::functions::FunctionHead; 9 | use crate::program::traits::TraitBinding; 10 | use crate::program::types::TypeProto; 11 | use crate::util::fmt::write_separated_display; 12 | 13 | /// The definition of some trait. 14 | #[derive(Clone)] 15 | pub struct Trait { 16 | pub id: Uuid, 17 | pub name: String, 18 | 19 | // Generics declared for this trait, by name (via its declaration). 20 | // Used in abstract functions and requirements (collect_generics on those would yield the same GenericAliases). 21 | pub generics: HashMap>, 22 | 23 | // To conform to this trait, these other conformances are required. 24 | pub requirements: HashSet>, 25 | 26 | // Functions required by this trait specifically (not its requirements). 27 | pub abstract_functions: HashSet>, 28 | pub field_hints: Vec, 29 | } 30 | 31 | 32 | /// For traits, information about certain fields that have been declared. 33 | /// This is useful mostly if somebody wants to instantiate the trait without sub-traiting it. 34 | #[derive(Clone)] 35 | pub struct FieldHint { 36 | pub name: String, 37 | pub type_: Rc, 38 | pub setter: Option>, 39 | pub getter: Option>, 40 | } 41 | 42 | impl Trait { 43 | pub fn new_flat(name: &str) -> Trait { 44 | Trait { 45 | id: Uuid::new_v4(), 46 | name: name.to_string(), 47 | generics: Default::default(), 48 | requirements: Default::default(), 49 | abstract_functions: Default::default(), 50 | field_hints: Default::default(), 51 | } 52 | } 53 | 54 | pub fn new_with_self(name: &str) -> Trait { 55 | Trait { 56 | id: Uuid::new_v4(), 57 | name: name.to_string(), 58 | generics: HashMap::from([("Self".to_string(), Rc::new(Trait::new_flat("Self")))]), 59 | requirements: Default::default(), 60 | abstract_functions: Default::default(), 61 | field_hints: Default::default(), 62 | } 63 | } 64 | 65 | pub fn create_generic_type(self: &Trait, generic_name: &str) -> Rc { 66 | TypeProto::unit_struct(&self.generics[generic_name]) 67 | } 68 | 69 | pub fn create_generic_binding(self: &Rc, generic_to_type: Vec<(&str, Rc)>) -> Rc { 70 | Rc::new(TraitBinding { 71 | trait_: Rc::clone(self), 72 | generic_to_type: HashMap::from_iter( 73 | generic_to_type.into_iter() 74 | .map(|(generic_name, type_)| (Rc::clone(&self.generics[generic_name]), type_)) 75 | ), 76 | }) 77 | } 78 | 79 | pub fn add_simple_parent_requirement(&mut self, parent_trait: &Rc) { 80 | self.requirements.insert( 81 | parent_trait.create_generic_binding(vec![("Self", self.create_generic_type("Self"))]) 82 | ); 83 | } 84 | } 85 | 86 | impl PartialEq for Trait { 87 | fn eq(&self, other: &Self) -> bool { 88 | self.id == other.id 89 | } 90 | } 91 | 92 | impl Eq for Trait {} 93 | 94 | impl Hash for Trait { 95 | fn hash(&self, state: &mut H) { 96 | self.id.hash(state); 97 | } 98 | } 99 | 100 | impl Debug for Trait { 101 | fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { 102 | write!(fmt, "{}<{}>", self.name, self.id)?; 103 | if !self.generics.is_empty() { 104 | write!(fmt, "<")?; 105 | write_separated_display(fmt, ", ", self.generics.keys())?; 106 | write!(fmt, ">")?; 107 | } 108 | Ok(()) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/interpreter/compile/function_descriptor_compiler.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::interpreter::compile::compile_server::CompileServer; 4 | use crate::interpreter::data::{uuid_to_ptr, Value}; 5 | use crate::interpreter::opcode::OpCode; 6 | use crate::program::functions::{FunctionHead, FunctionLogicDescriptor}; 7 | 8 | pub fn compile_descriptor(function: &Rc, descriptor: &FunctionLogicDescriptor, compile_server: &mut CompileServer) { 9 | match descriptor { 10 | FunctionLogicDescriptor::Stub => todo!("{:?}", function), 11 | FunctionLogicDescriptor::Clone(_) => todo!("{:?}", function), 12 | FunctionLogicDescriptor::TraitProvider(trait_) => { 13 | let uuid = trait_.id; 14 | compile_server.function_inlines.insert(function.function_id, Rc::new(move |compiler, expression| { 15 | unsafe { compiler.chunk.constants.push(Value { ptr: uuid_to_ptr(uuid) }); } 16 | compiler.chunk.push_with_u32(OpCode::LOAD_CONSTANT_32, u32::try_from(compiler.chunk.constants.len() - 1).unwrap()); 17 | Ok(()) 18 | })); 19 | } 20 | FunctionLogicDescriptor::FunctionProvider(f) => { 21 | let uuid = f.function_id; 22 | compile_server.function_inlines.insert(function.function_id, Rc::new(move |compiler, expression| { 23 | unsafe { compiler.chunk.constants.push(Value { ptr: uuid_to_ptr(uuid) }); } 24 | compiler.chunk.push_with_u32(OpCode::LOAD_CONSTANT_32, u32::try_from(compiler.chunk.constants.len() - 1).unwrap()); 25 | Ok(()) 26 | })); 27 | } 28 | FunctionLogicDescriptor::PrimitiveOperation { .. } => todo!("{:?}", descriptor), 29 | FunctionLogicDescriptor::Constructor(struct_info) => { 30 | let data_layout = compile_server.get_data_layout(struct_info); 31 | 32 | compile_server.function_inlines.insert(function.function_id, Rc::new(move |compiler, expression| { 33 | let arguments = &compiler.implementation.expression_tree.children[&expression]; 34 | assert_eq!(arguments.len(), data_layout.fields.len() + 1); 35 | 36 | compiler.chunk.push_with_u32(OpCode::ALLOC_32, u32::try_from(data_layout.fields.len()).unwrap()); 37 | for (idx, arg) in arguments.iter().skip(1).enumerate() { 38 | // If needed, duplicate the object pointer. 39 | if idx < arguments.len() - 1 { 40 | compiler.chunk.push(OpCode::DUP64); 41 | } 42 | 43 | // Evaluate the field at the given index. 44 | compiler.compile_expression(arg)?; 45 | compiler.chunk.push_with_u32(OpCode::SET_MEMBER_32, u32::try_from(idx).unwrap()); 46 | } 47 | 48 | Ok(()) 49 | })); 50 | } 51 | FunctionLogicDescriptor::GetMemberField(struct_info, ref_) => { 52 | let data_layout = compile_server.get_data_layout(struct_info); 53 | let slot_index = data_layout.fields.iter().position(|r| r == ref_).unwrap(); 54 | 55 | compile_server.function_inlines.insert(function.function_id, Rc::new(move |compiler, expression| { 56 | let arguments = &compiler.implementation.expression_tree.children[&expression]; 57 | 58 | compiler.compile_expression(&arguments[0])?; 59 | 60 | compiler.chunk.push_with_u32(OpCode::GET_MEMBER_32, u32::try_from(slot_index).unwrap()); 61 | 62 | Ok(()) 63 | })); 64 | } 65 | FunctionLogicDescriptor::SetMemberField(struct_info, ref_) => { 66 | let data_layout = compile_server.get_data_layout(struct_info); 67 | let slot_index = data_layout.fields.iter().position(|r| r == ref_).unwrap(); 68 | 69 | compile_server.function_inlines.insert(function.function_id, Rc::new(move |compiler, expression| { 70 | let arguments = &compiler.implementation.expression_tree.children[&expression]; 71 | 72 | compiler.compile_expression(&arguments[0])?; 73 | compiler.compile_expression(&arguments[1])?; 74 | 75 | compiler.chunk.push_with_u32(OpCode::SET_MEMBER_32, u32::try_from(slot_index).unwrap()); 76 | 77 | Ok(()) 78 | })); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/transpiler/tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use itertools::Itertools; 4 | use std::path::PathBuf; 5 | use std::rc::Rc; 6 | 7 | use crate::error::RResult; 8 | use crate::interpreter::run::gather_functions_logic; 9 | use crate::interpreter::runtime::Runtime; 10 | use crate::program::module::module_name; 11 | use crate::transpiler::{LanguageContext, Transpiler}; 12 | use crate::{interpreter, transpiler}; 13 | 14 | fn test_transpiles(path: &str) -> RResult { 15 | let mut runtime = Runtime::new()?; 16 | runtime.add_common_repository(); 17 | 18 | let module = runtime.load_file_as_module(&PathBuf::from(path), module_name("main"))?; 19 | let context = transpiler::python::Context::new(&runtime); 20 | 21 | let transpiler = interpreter::run::transpile(&module, &mut runtime)?; 22 | let file_map = transpiler::transpile(transpiler, &mut runtime, &context, &transpiler::Config::default(), "main")?; 23 | 24 | let python_string = file_map["main.py"].to_string(); 25 | assert!(python_string.contains("def main():")); 26 | 27 | Ok(python_string) 28 | } 29 | 30 | #[test] 31 | fn uninterpreted_hello_world() -> RResult<()> { 32 | let mut runtime = Runtime::new()?; 33 | runtime.add_common_repository(); 34 | 35 | let module = runtime.load_file_as_module(&PathBuf::from("test-code/hello_world.monoteny"), module_name("main"))?; 36 | let main_function = module.main_functions.iter().at_most_one().unwrap().unwrap(); 37 | 38 | let transpiler = Box::new(Transpiler { 39 | main_function: Some(Rc::clone(main_function)), 40 | exported_artifacts: gather_functions_logic(&runtime, &vec![main_function.function_id]), 41 | }); 42 | 43 | let context = transpiler::python::Context::new(&runtime); 44 | let file_map = transpiler::transpile(transpiler, &mut runtime, &context, &transpiler::Config::default(), "main")?; 45 | 46 | let python_string = file_map["main.py"].to_string(); 47 | assert!(python_string.contains("def main():")); 48 | 49 | Ok(()) 50 | } 51 | 52 | #[test] 53 | fn hello_world() -> RResult<()> { 54 | test_transpiles("test-code/hello_world.monoteny")?; 55 | Ok(()) 56 | } 57 | 58 | /// This tests generics, algebra and printing. 59 | #[test] 60 | fn custom_grammar() -> RResult<()> { 61 | test_transpiles("test-code/grammar/custom_grammar.monoteny")?; 62 | Ok(()) 63 | } 64 | 65 | /// Tests if a static function created for a trait fulfillment (Eq) can be called. 66 | #[test] 67 | fn eq0() -> RResult<()> { 68 | test_transpiles("test-code/requirements/eq0.monoteny")?; 69 | Ok(()) 70 | } 71 | 72 | /// Tests if a function can call a requirements' function. 73 | #[test] 74 | fn eq1() -> RResult<()> { 75 | test_transpiles("test-code/requirements/eq1.monoteny")?; 76 | Ok(()) 77 | } 78 | 79 | /// Tests if a function can call another function, passing its requirements fulfillment down. 80 | #[test] 81 | fn eq2() -> RResult<()> { 82 | test_transpiles("test-code/requirements/eq2.monoteny")?; 83 | Ok(()) 84 | } 85 | 86 | #[test] 87 | fn monomorphize_branch() -> RResult<()> { 88 | let py_file = test_transpiles("test-code/monomorphization/branch.monoteny")?; 89 | assert_eq!(py_file.match_indices("square").count(), 4); 90 | 91 | Ok(()) 92 | } 93 | 94 | #[test] 95 | fn trait_conformance() -> RResult<()> { 96 | let py_file = test_transpiles("test-code/traits/conformance.monoteny")?; 97 | 98 | Ok(()) 99 | } 100 | 101 | #[test] 102 | fn trait_fields() -> RResult<()> { 103 | let py_file = test_transpiles("test-code/traits/fields.monoteny")?; 104 | 105 | Ok(()) 106 | } 107 | 108 | #[test] 109 | fn string_interpolation() -> RResult<()> { 110 | let py_file = test_transpiles("test-code/grammar/string_interpolation.monoteny")?; 111 | 112 | Ok(()) 113 | } 114 | 115 | #[test] 116 | fn if_then_else() -> RResult<()> { 117 | let py_file = test_transpiles("test-code/control_flow/if_then_else.monoteny")?; 118 | 119 | Ok(()) 120 | } 121 | 122 | #[test] 123 | fn and_or() -> RResult<()> { 124 | let py_file = test_transpiles("test-code/control_flow/and_or.monoteny")?; 125 | 126 | Ok(()) 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/program/traits/conformance.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::hash::{DefaultHasher, Hash, Hasher}; 3 | use std::rc::Rc; 4 | 5 | use itertools::Itertools; 6 | 7 | use crate::program::functions::FunctionHead; 8 | use crate::program::traits::{Trait, TraitBinding}; 9 | use crate::program::types::TypeProto; 10 | use crate::util::hash; 11 | 12 | /// How a trait binding is fulfilled. 13 | #[derive(Clone, Eq, PartialEq, Debug)] 14 | pub struct TraitConformance { 15 | /// The binding that is being fulfilled. 16 | pub binding: Rc, 17 | /// Mapping of: abstract function of the trait => the function that implements it. 18 | /// The functions have the same interfaces as the requirement (trait_.abstract_functions), 19 | /// except with the generics replaced (binding.generic_to_type). 20 | pub function_mapping: HashMap, Rc>, 21 | } 22 | 23 | #[derive(Clone, Eq, Hash, PartialEq, Debug)] 24 | pub struct TraitConformanceWithTail { 25 | /// The actual conformance. 26 | pub conformance: Rc, 27 | /// How the conformance was achieved (through dynamic rules). 28 | /// While the dynamic function call itself does not need this information, the dynamic dispatch (/monomorphization) 29 | /// later needs to know more because the conformance's functions might call the tail's functions. 30 | /// You can imagine it like so: Suppose a function is called that has a requirement. 31 | /// Not only need the requirement be resolved - but if the conformance was achieved through rules, the implementations 32 | /// of the requirements' functions might (will!) call functions from any of the rule's requirements! 33 | /// e.g. Animal is required, the implementation is for any $Cat, so animal.talk() will call self.purr() - a function 34 | /// declared only on Cats. 35 | /// So when we use this conformance, we must also bring along the tail 36 | /// which must be pre-resolved w.r.t. the conformance's requirements itself. 37 | pub tail: Rc, 38 | } 39 | 40 | #[derive(Clone, Eq, PartialEq, Debug)] 41 | pub struct RequirementsAssumption { 42 | pub conformance: HashMap, Rc>, 43 | } 44 | 45 | #[derive(Clone, Eq, PartialEq, Debug)] 46 | pub struct RequirementsFulfillment { 47 | // Requirement: (tail, conformance) 48 | pub conformance: HashMap, Rc>, 49 | pub generic_mapping: HashMap, Rc>, 50 | } 51 | 52 | 53 | impl TraitConformance { 54 | pub fn new(binding: Rc, function_mapping: HashMap, Rc>,) -> Rc { 55 | Rc::new(TraitConformance { 56 | binding, 57 | function_mapping, 58 | }) 59 | } 60 | 61 | pub fn pure(binding: Rc) -> Rc { 62 | if !binding.trait_.abstract_functions.is_empty() { 63 | panic!() 64 | } 65 | 66 | TraitConformance::new(binding, Default::default()) 67 | } 68 | } 69 | 70 | impl Hash for TraitConformance { 71 | fn hash(&self, state: &mut H) { 72 | self.binding.hash(state); 73 | for keyval in self.function_mapping.iter().sorted_by_key(|(src, dst)| src.function_id) { 74 | keyval.hash(state) 75 | } 76 | } 77 | } 78 | 79 | impl Hash for RequirementsFulfillment { 80 | fn hash(&self, state: &mut H) { 81 | for (binding, conformance) in self.conformance.iter().sorted_by_key(|(binding, mapping)| hash::one(binding, DefaultHasher::new())) { 82 | binding.hash(state); 83 | conformance.hash(state); 84 | } 85 | 86 | for (id, type_) in self.generic_mapping.iter().sorted_by_key(|(trait_, type_)| trait_.id) { 87 | id.hash(state); 88 | type_.hash(state); 89 | } 90 | } 91 | } 92 | 93 | impl RequirementsAssumption { 94 | pub fn empty() -> Box { 95 | Box::new(RequirementsAssumption { 96 | conformance: Default::default(), 97 | }) 98 | } 99 | } 100 | 101 | impl RequirementsFulfillment { 102 | pub fn empty() -> Rc { 103 | Rc::new(RequirementsFulfillment { 104 | conformance: Default::default(), 105 | generic_mapping: Default::default(), 106 | }) 107 | } 108 | 109 | pub fn is_empty(&self) -> bool { 110 | self.conformance.is_empty() && self.generic_mapping.is_empty() 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/cli/transpile.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::OsStr; 2 | use std::path::{Path, PathBuf}; 3 | use std::process::ExitCode; 4 | 5 | use clap::{arg, ArgAction, ArgMatches, Command}; 6 | use itertools::Itertools; 7 | 8 | use crate::cli::logging::{dump_failure, dump_start, dump_success}; 9 | use crate::error::{RResult, RuntimeError}; 10 | use crate::interpreter::runtime::Runtime; 11 | use crate::program::module::{module_name, Module}; 12 | use crate::transpiler::LanguageContext; 13 | use crate::util::file_writer::write_file_safe; 14 | use crate::{interpreter, transpiler}; 15 | 16 | pub fn make_command() -> Command { 17 | Command::new("transpile") 18 | .about("Transpile a file into another language.") 19 | .arg_required_else_help(true) 20 | .arg(arg!( "file to transpile").value_parser(clap::value_parser!(PathBuf)).long("input").short('i')) 21 | .arg(arg!( "output file path").required(false).value_parser(clap::value_parser!(PathBuf)).long("output").short('o')) 22 | .arg(arg!( "use all available transpilers").required(false).action(ArgAction::SetTrue).long("all")) 23 | .arg(arg!( "don't use ANY refactoring").required(false).action(ArgAction::SetTrue).long("norefactor")) 24 | .arg(arg!( "don't use constant folding").required(false).action(ArgAction::SetTrue).long("nofold")) 25 | .arg(arg!( "don't use inlining").required(false).action(ArgAction::SetTrue).long("noinline")) 26 | .arg(arg!( "don't trim unused locals code").required(false).action(ArgAction::SetTrue).long("notrimlocals")) 27 | } 28 | 29 | pub fn run(args: &ArgMatches) -> RResult { 30 | let input_path = args.get_one::("INPUT").unwrap(); 31 | let output_path_proto = match args.contains_id("OUTPUT") { 32 | true => args.get_one::("OUTPUT").unwrap().clone(), 33 | false => input_path.with_extension(""), 34 | }; 35 | let base_filename = output_path_proto.file_name().and_then(OsStr::to_str).unwrap(); 36 | let base_output_path = output_path_proto.parent().unwrap(); 37 | 38 | let can_refactor = !args.get_flag("NOREFACTOR"); 39 | let config = transpiler::Config { 40 | should_constant_fold: can_refactor && !args.get_flag("NOFOLD"), 41 | should_monomorphize: true, // TODO Cannot do without it for now 42 | should_inline: can_refactor && !args.get_flag("NOINLINE"), 43 | should_trim_locals: can_refactor && !args.get_flag("NOTRIMLOCALS"), 44 | }; 45 | let should_output_all = args.get_flag("ALL"); 46 | 47 | let output_extensions: Vec<&str> = match should_output_all { 48 | true => vec!["py"], 49 | false => vec![output_path_proto.extension().and_then(OsStr::to_str).ok_or_else(|| vec![RuntimeError::error("Error: must provide either output path or --all")])?] 50 | }; 51 | 52 | let mut runtime = Runtime::new()?; 53 | runtime.add_common_repository(); 54 | 55 | let module = runtime.load_file_as_module(input_path, module_name("main"))?; 56 | 57 | let mut error_count = 0; 58 | 59 | for output_extension in output_extensions { 60 | let start = dump_start(format!("{}:transpile! using {}", input_path.as_os_str().to_string_lossy(), output_extension).as_str()); 61 | match transpile_target(base_filename, base_output_path, &config, &mut runtime, &module, output_extension) { 62 | Ok(paths) => { 63 | for path in paths { 64 | println!("{}", path.to_str().unwrap()); 65 | } 66 | dump_success(start); 67 | } 68 | Err(e) => { 69 | dump_failure(e); 70 | error_count += 1; 71 | }, 72 | } 73 | println!(); 74 | } 75 | 76 | Ok(ExitCode::from(error_count)) 77 | } 78 | 79 | fn create_context(runtime: &Runtime, extension: &str) -> Box { 80 | match extension { 81 | "py" => Box::new(transpiler::python::Context::new(runtime)), 82 | _ => panic!("File type not supported: {}", extension) 83 | } 84 | } 85 | 86 | fn transpile_target(base_filename: &str, base_output_path: &Path, config: &transpiler::Config, mut runtime: &mut Box, module: &Box, output_extension: &str) -> RResult> { 87 | let context = create_context(&runtime, output_extension); 88 | let transpiler = interpreter::run::transpile(&module, runtime)?; 89 | let file_map = transpiler::transpile(transpiler, runtime, context.as_ref(), config, base_filename)?; 90 | 91 | let output_files = file_map.into_iter().map(|(filename, content)| { 92 | write_file_safe(base_output_path, &filename, &content) 93 | }).collect_vec(); 94 | Ok(output_files) 95 | } 96 | -------------------------------------------------------------------------------- /src/resolver/conformance.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::fmt::{Debug, Formatter}; 3 | use std::rc::Rc; 4 | 5 | use display_with_options::with_options; 6 | use itertools::Itertools; 7 | 8 | use crate::ast; 9 | use crate::error::{RResult, RuntimeError}; 10 | use crate::interpreter::runtime::Runtime; 11 | use crate::program::functions::FunctionHead; 12 | use crate::program::traits::{Trait, TraitBinding, TraitConformance}; 13 | use crate::refactor::monomorphize::map_interface_types; 14 | use crate::resolver::interface::resolve_function_interface; 15 | use crate::resolver::scopes; 16 | 17 | pub struct UnresolvedFunctionImplementation<'a> { 18 | pub function: Rc, 19 | pub body: &'a Option, 20 | } 21 | 22 | pub struct ConformanceResolver<'a, 'b> { 23 | pub runtime: &'b mut Runtime, 24 | pub functions: Vec>, 25 | } 26 | 27 | impl <'a, 'b> ConformanceResolver<'a, 'b> { 28 | pub fn resolve_statement(&mut self, statement: &'a ast::Statement, requirements: &HashSet>, generics: &HashSet>, scope: &scopes::Scope) -> RResult<()> { 29 | match statement { 30 | ast::Statement::FunctionDeclaration(syntax) => { 31 | // TODO For simplicity's sake, we should match the generics IDs of all conformances 32 | // to the ID of the parent abstract function. That way, we can avoid another 33 | // generic to generic mapping later. 34 | let function_head = resolve_function_interface(&syntax.interface, &scope, None, &mut self.runtime, requirements, generics)?; 35 | 36 | self.functions.push(UnresolvedFunctionImplementation { 37 | function: function_head, 38 | body: &syntax.body, 39 | }); 40 | } 41 | _ => { 42 | if let ast::Statement::Expression(exp) = statement { 43 | // It may still just be an error! 44 | exp.no_errors()?; 45 | } 46 | return Err( 47 | RuntimeError::error("Statement not valid in a conformance context.").to_array() 48 | ); 49 | } 50 | } 51 | 52 | Ok(()) 53 | } 54 | 55 | pub fn finalize_conformance(&self, binding: Rc, conformance_requirements: &HashSet>, conformance_generics: &HashSet>) -> RResult> { 56 | let mut function_bindings = HashMap::new(); 57 | let mut unmatched_implementations = self.functions.iter().collect_vec(); 58 | 59 | for abstract_function in binding.trait_.abstract_functions.iter() { 60 | let mut expected_interface = map_interface_types(&abstract_function.interface, &binding.generic_to_type); 61 | expected_interface.requirements.extend(conformance_requirements.clone()); 62 | expected_interface.generics.extend(conformance_generics.clone()); 63 | 64 | let matching_implementations = unmatched_implementations.iter().enumerate() 65 | .filter(|(i, imp)| imp.function.declared_representation == abstract_function.declared_representation && imp.function.interface.as_ref() == &expected_interface) 66 | .map(|(i, interface)| i) 67 | .collect_vec(); 68 | 69 | if matching_implementations.len() == 0 { 70 | return Err( 71 | RuntimeError::error(format!("Function {:?} missing for conformance.", with_options(&expected_interface, &abstract_function.declared_representation)).as_str()).to_array() 72 | ); 73 | } 74 | else if matching_implementations.len() > 1 { 75 | return Err( 76 | RuntimeError::error(format!("Function {:?} is implemented multiple times.", with_options(&expected_interface, &abstract_function.declared_representation)).as_str()).to_array() 77 | ); 78 | } 79 | else { 80 | function_bindings.insert( 81 | Rc::clone(abstract_function), 82 | Rc::clone(&unmatched_implementations.remove(matching_implementations[0]).function) 83 | ); 84 | } 85 | } 86 | 87 | if unmatched_implementations.len() > 0 { 88 | return Err( 89 | RuntimeError::error(format!("Unrecognized functions for declaration {:?}: {:?}.", binding, unmatched_implementations).as_str()).to_array() 90 | ); 91 | } 92 | 93 | Ok(TraitConformance::new(Rc::clone(&binding), function_bindings.clone())) 94 | } 95 | } 96 | 97 | impl<'a> Debug for UnresolvedFunctionImplementation<'a> { 98 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 99 | write!(f, "{:?}", self.function) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/program/types.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::fmt::{Debug, Formatter}; 3 | use std::hash::Hash; 4 | use std::rc::Rc; 5 | 6 | use itertools::Itertools; 7 | use uuid::Uuid; 8 | 9 | use crate::program::generics::GenericAlias; 10 | use crate::program::traits::Trait; 11 | use crate::util::fmt::write_separated_debug; 12 | 13 | #[derive(Clone, PartialEq, Eq, Hash)] 14 | pub struct TypeProto { 15 | pub unit: TypeUnit, 16 | pub arguments: Vec> 17 | } 18 | 19 | #[derive(Clone, PartialEq, Eq, Hash)] 20 | pub enum TypeUnit { 21 | /// Used because the expression_forest wants to bind a return type for an expression. 22 | /// If none is bound, that would rather indicate an error. 23 | /// If one is bound, and it's void, that means we KNOW it has return type void. 24 | /// Having it doesn't hurt anyway; an implementation might actually pass void objects around 25 | /// to simplify logic. 26 | Void, 27 | /// some type that isn't bound yet. This is fully unique and should not be created statically or imported. 28 | Generic(GenericAlias), 29 | /// Bound to an instance of a trait. The arguments are the generic bindings. 30 | Struct(Rc), 31 | } 32 | 33 | #[derive(Clone, PartialEq, Eq, Hash)] 34 | pub struct Generic { 35 | pub id: Uuid, 36 | pub name: String, 37 | } 38 | 39 | impl Debug for TypeProto { 40 | fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { 41 | write!(fmt, "{:?}", self.unit)?; 42 | if !self.arguments.is_empty() { 43 | write!(fmt, "<")?; 44 | write_separated_debug(fmt, ", ", self.arguments.iter())?; 45 | write!(fmt, ">")?; 46 | } 47 | Ok(()) 48 | } 49 | } 50 | 51 | impl Debug for TypeUnit { 52 | fn fmt(&self, fmt: &mut Formatter<'_>) -> std::fmt::Result { 53 | match self { 54 | TypeUnit::Struct(s) => write!(fmt, "{}", s.name), 55 | TypeUnit::Generic(g) => write!(fmt, "#({})", g), 56 | TypeUnit::Void => write!(fmt, "Void"), 57 | } 58 | } 59 | } 60 | 61 | impl TypeProto { 62 | pub fn void() -> Rc { 63 | TypeProto::unit(TypeUnit::Void) 64 | } 65 | 66 | pub fn unit(unit: TypeUnit) -> Rc { 67 | Rc::new(TypeProto { unit, arguments: vec![] }) 68 | } 69 | 70 | pub fn one_arg(trait_: &Rc, subtype: Rc) -> Rc { 71 | Rc::new(TypeProto { 72 | unit: TypeUnit::Struct(Rc::clone(trait_)), 73 | arguments: vec![subtype] 74 | }) 75 | } 76 | 77 | pub fn unit_struct(trait_: &Rc) -> Rc { 78 | TypeProto::unit(TypeUnit::Struct(Rc::clone(trait_))) 79 | } 80 | 81 | pub fn replacing_generics(self: &Rc, map: &HashMap>) -> Rc { 82 | match &self.unit { 83 | TypeUnit::Generic(id) => map.get(id) 84 | .cloned() 85 | .unwrap_or_else(|| self.clone()), 86 | _ => Rc::new(TypeProto { 87 | unit: self.unit.clone(), 88 | arguments: self.arguments.iter().map(|x| x.replacing_generics(map)).collect() 89 | }), 90 | } 91 | } 92 | 93 | pub fn replacing_structs(self: &Rc, map: &HashMap, Rc>) -> Rc { 94 | match &self.unit { 95 | TypeUnit::Struct(struct_) => map.get(struct_) 96 | .cloned() 97 | .unwrap_or_else(|| self.clone()), 98 | _ => Rc::new(TypeProto { 99 | unit: self.unit.clone(), 100 | arguments: self.arguments.iter().map(|x| x.replacing_structs(map)).collect() 101 | }), 102 | } 103 | } 104 | 105 | pub fn collect_generics<'a, C>(collection: C) -> HashSet where C: Iterator> { 106 | let mut anys = HashSet::new(); 107 | let mut todo = collection.collect_vec(); 108 | 109 | while let Some(next) = todo.pop() { 110 | match &next.unit { 111 | TypeUnit::Generic(id) => { anys.insert(*id); }, 112 | _ => {} 113 | }; 114 | todo.extend(&next.arguments); 115 | } 116 | 117 | anys 118 | } 119 | 120 | pub fn contains_generics<'a, C>(collection: C) -> bool where C: Iterator> { 121 | let mut todo = collection.collect_vec(); 122 | 123 | while let Some(next) = todo.pop() { 124 | match &next.unit { 125 | TypeUnit::Generic(_) => { return true }, 126 | _ => {} 127 | }; 128 | todo.extend(&next.arguments); 129 | } 130 | 131 | return false 132 | } 133 | } 134 | 135 | impl TypeUnit { 136 | pub fn is_void(&self) -> bool { 137 | match self { 138 | TypeUnit::Void => true, 139 | _ => false 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/transpiler.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::rc::Rc; 3 | 4 | use itertools::Itertools; 5 | 6 | use crate::error::{RResult, TryCollectMany}; 7 | use crate::interpreter::runtime::Runtime; 8 | use crate::program::functions::{FunctionHead, FunctionImplementation, FunctionLogic, FunctionLogicDescriptor}; 9 | use crate::refactor::simplify::Simplify; 10 | use crate::refactor::Refactor; 11 | 12 | pub mod python; 13 | pub mod namespaces; 14 | pub mod structs; 15 | mod tests; 16 | 17 | pub struct Config { 18 | pub should_constant_fold: bool, 19 | pub should_monomorphize: bool, 20 | pub should_inline: bool, 21 | pub should_trim_locals: bool, 22 | } 23 | 24 | impl Config { 25 | pub fn default() -> Config { 26 | Config { 27 | should_constant_fold: true, 28 | should_monomorphize: true, 29 | should_inline: true, 30 | should_trim_locals: true, 31 | } 32 | } 33 | } 34 | 35 | pub enum TranspiledArtifact { 36 | Function(Rc) 37 | } 38 | 39 | pub struct Transpiler { 40 | // In the future, this should all be accessible by monoteny code itself - including the context. 41 | pub main_function: Option>, 42 | pub exported_artifacts: Vec, 43 | } 44 | 45 | pub struct TranspilePackage<'a> { 46 | // In the future, this should all be accessible by monoteny code itself - including the context. 47 | pub main_function: Option>, 48 | pub explicit_functions: Vec<(Rc, &'a FunctionImplementation)>, 49 | pub implicit_functions: Vec<(Rc, &'a FunctionImplementation)>, 50 | pub used_native_functions: HashMap, FunctionLogicDescriptor>, 51 | } 52 | 53 | pub trait LanguageContext { 54 | fn new(runtime: &Runtime) -> Self where Self: Sized; 55 | fn register_builtins(&self, refactor: &mut Refactor); 56 | fn refactor_code(&self, refactor: &mut Refactor); 57 | fn make_files( 58 | &self, 59 | base_filename: &str, 60 | package: TranspilePackage, 61 | ) -> RResult>; 62 | } 63 | 64 | pub fn transpile(transpiler: Box, runtime: &mut Runtime, context: &dyn LanguageContext, config: &Config, base_filename: &str) -> RResult>{ 65 | let mut refactor = Refactor::new(); 66 | context.register_builtins(&mut refactor); 67 | 68 | let mut exported_function_order = vec![]; 69 | 70 | for artifact in transpiler.exported_artifacts { 71 | match artifact { 72 | TranspiledArtifact::Function(function_head) => { 73 | match &runtime.source.fn_logic[&function_head] { 74 | FunctionLogic::Implementation(implementation) => { 75 | exported_function_order.push(Rc::clone(&function_head)); 76 | refactor.add(function_head, implementation.clone()); 77 | } 78 | FunctionLogic::Descriptor(_) => panic!("Cannot transpile a function for which whe don't know an implementation!") 79 | } 80 | } 81 | } 82 | } 83 | 84 | if !config.should_monomorphize { 85 | todo!(); // Lots of reasons non-monomorphization doesn't work right now. 86 | } 87 | 88 | let mut simplify = Simplify { 89 | refactor, 90 | inline: config.should_inline, 91 | trim_locals: config.should_trim_locals, 92 | monomorphize: config.should_monomorphize, 93 | }; 94 | 95 | simplify.run(exported_function_order.iter(), &runtime.source); 96 | 97 | // --- Reclaim from Refactor and make the ast 98 | context.refactor_code(&mut simplify.refactor); 99 | 100 | // TODO The call_graph doesn't know about calls made outside the refactor. If there was no monomorphization, some functions may not even be caught by this. 101 | let deep_calls = simplify.refactor.gather_deep_functions(exported_function_order.iter(), &runtime.source); 102 | let mut fn_logic = simplify.refactor.fn_logic; 103 | 104 | let exported_functions = simplify.refactor.explicit_functions.iter() 105 | .map(|head| Ok((Rc::clone(head), fn_logic.get(head).unwrap().as_implementation()?))) 106 | .try_collect_many()?; 107 | let mut implicit_functions = vec![]; 108 | let mut native_functions = HashMap::new(); 109 | 110 | for head in deep_calls { 111 | // Either Refactor has it (because it invented it) or it's unchanged from source. 112 | match fn_logic.get(&head).unwrap() { 113 | FunctionLogic::Implementation(i) => { 114 | implicit_functions.push((head, i.as_ref())); 115 | } 116 | FunctionLogic::Descriptor(d) => { 117 | native_functions.insert(head, d.clone()); 118 | } 119 | } 120 | } 121 | 122 | context.make_files(base_filename, TranspilePackage { 123 | main_function: transpiler.main_function, 124 | explicit_functions: exported_functions, 125 | implicit_functions, 126 | used_native_functions: native_functions, 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /src/resolver/type_factory.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::rc::Rc; 3 | 4 | use crate::ast; 5 | use crate::error::{ErrInRange, RResult, RuntimeError}; 6 | use crate::interpreter::runtime::Runtime; 7 | use crate::parser::expressions; 8 | use crate::program::functions::FunctionTargetType; 9 | use crate::program::traits::{Trait, TraitBinding}; 10 | use crate::program::types::{TypeProto, TypeUnit}; 11 | use crate::resolver::scopes; 12 | use itertools::Itertools; 13 | 14 | pub struct TypeFactory<'a> { 15 | pub scope: &'a scopes::Scope<'a>, 16 | 17 | pub generics: HashMap>, 18 | pub requirements: HashSet>, 19 | } 20 | 21 | // TODO Essentially this is a form of mini interpreter. 22 | // In the future it might be easier to rewrite it as such. 23 | impl <'a> TypeFactory<'a> { 24 | pub fn new(scope: &'a scopes::Scope<'a>) -> TypeFactory<'a> { 25 | TypeFactory { 26 | scope, 27 | generics: HashMap::new(), 28 | requirements: HashSet::new(), 29 | } 30 | } 31 | 32 | pub fn resolve_trait(&mut self, name: &str, runtime: &mut Runtime) -> RResult> { 33 | let reference = self.scope.resolve(FunctionTargetType::Global, &name)?; 34 | let overload = reference.as_function_overload()?; 35 | 36 | let function = overload.functions.iter().exactly_one() 37 | .map_err(|_| RuntimeError::error("Function overload cannot be resolved to a type.").to_array())?; 38 | let trait_ = runtime.source.trait_references.get(function) 39 | .ok_or_else(|| RuntimeError::error(format!("Interpreted types aren't supported yet; please use an explicit type for now.\n{}", name).as_str()).to_array())?; 40 | 41 | return Ok(Rc::clone(trait_)) 42 | } 43 | 44 | fn register_generic(&mut self, name: &str) -> Rc { 45 | let trait_ = Rc::new(Trait::new_flat(name)); 46 | self.generics.insert(name.to_string(), Rc::clone(&trait_)); 47 | trait_ 48 | } 49 | 50 | fn register_requirement(&mut self, requirement: Rc) { 51 | self.requirements.insert(requirement); 52 | } 53 | 54 | pub fn resolve_type(&mut self, syntax: &ast::Expression, allow_anonymous_generics: bool, runtime: &mut Runtime) -> RResult> { 55 | syntax.no_errors()?; 56 | 57 | let parsed = expressions::parse(syntax, &self.scope.grammar)?; 58 | 59 | let expressions::Value::Identifier(identifier) = &parsed.value else { 60 | return Err(RuntimeError::error("Interpreted types aren't supported yet; please use an explicit type for now.").in_range(parsed.position).to_array()) 61 | }; 62 | 63 | // let (expression, _) = parse_expression(identifier)?; 64 | // // TODO We don't actually want to merge into Metatype, we want an instance of 65 | // let result = runtime.evaluate_anonymous_expression( 66 | // &expression, 67 | // FunctionInterface::new_provider( 68 | // &TypeProto::one_arg(&runtime.Metatype, TypeProto::unit_struct(&runtime.traits.as_ref().unwrap().String)), 69 | // vec![] 70 | // ), 71 | // )?; 72 | // 73 | // unsafe { 74 | // let uuid = *(result.ptr as *mut Uuid); 75 | // return Ok(TypeProto::unit_struct(&runtime.source.trait_heads[&uuid])); 76 | // } 77 | 78 | self.resolve_type_by_name(allow_anonymous_generics, &identifier, runtime) 79 | .err_in_range(&parsed.position) 80 | } 81 | 82 | fn resolve_type_by_name(&mut self, allow_anonymous_generics: bool, type_name: &str, runtime: &mut Runtime) -> RResult> { 83 | let arguments = vec![]; 84 | 85 | if let Some(type_) = self.generics.get(type_name) { 86 | return Ok(TypeProto::unit_struct(type_)) 87 | } 88 | 89 | if !allow_anonymous_generics || !(type_name.starts_with("#") || type_name.starts_with("$")) { 90 | // No special generic; let's try just resolving it normally. 91 | let trait_ = self.resolve_trait(type_name, runtime)?; 92 | // Found a trait! Until we actually interpret the expression, this is guaranteed to be unbound. 93 | return Ok(TypeProto::unit_struct(&trait_)); 94 | } 95 | 96 | let type_ = Rc::new(TypeProto { 97 | unit: TypeUnit::Struct(self.register_generic(type_name).clone()), 98 | arguments 99 | }); 100 | 101 | if type_name.starts_with("$") { 102 | let type_name = match type_name.find("#") { 103 | None => { String::from(&type_name[1..]) } 104 | Some(hash_start_index) => { String::from(&type_name[1..hash_start_index]) } 105 | }; 106 | 107 | let requirement_trait = self.resolve_trait(&type_name, runtime)?; 108 | self.register_requirement(Rc::new(TraitBinding { 109 | generic_to_type: HashMap::from([(Rc::clone(&requirement_trait.generics["Self"]), type_.clone())]), 110 | trait_: requirement_trait, 111 | })); 112 | } 113 | 114 | Ok(type_) 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/parser/grammar.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashMap, HashSet}; 2 | use std::fmt::{Debug, Display, Error, Formatter}; 3 | use std::hash::{Hash, Hasher}; 4 | use std::rc::Rc; 5 | 6 | use itertools::Itertools; 7 | use linked_hash_map::LinkedHashMap; 8 | use strum::{Display, EnumIter}; 9 | use uuid::Uuid; 10 | 11 | use crate::error::{RResult, RuntimeError}; 12 | 13 | #[derive(Copy, Clone, PartialEq, Eq, Debug, Display, EnumIter)] 14 | pub enum OperatorAssociativity { 15 | LeftUnary, // Evaluated with the operator left of the expression. 16 | RightUnary, // Evaluated with the operator right of the expression. 17 | Left, // Left evaluated first. 18 | Right, // Right evaluated first. 19 | None, // Fail parsing if more than one neighboring operator is found. 20 | LeftConjunctivePairs, // Evaluated in pairs left first, joined by && operations. 21 | } 22 | 23 | #[derive(Eq, Debug)] 24 | pub struct PrecedenceGroup { 25 | pub trait_id: Uuid, 26 | pub name: String, 27 | pub associativity: OperatorAssociativity, 28 | } 29 | 30 | #[derive(Clone, PartialEq, Eq, Hash)] 31 | pub struct Pattern { 32 | pub id: Uuid, 33 | pub precedence_group: Rc, 34 | 35 | pub parts: Vec>, 36 | pub function: Function, 37 | } 38 | 39 | #[derive(Clone, PartialEq, Eq, Hash)] 40 | pub enum PatternPart { 41 | Parameter(usize), 42 | Keyword(String), 43 | } 44 | 45 | #[derive(Clone, PartialEq, Eq)] 46 | pub struct Grammar { 47 | pub patterns: HashSet>>, 48 | pub keywords: HashSet, 49 | pub groups_and_keywords: LinkedHashMap, HashMap>, 50 | } 51 | 52 | impl Grammar where { 53 | pub fn new() -> Self { 54 | Grammar { 55 | patterns: Default::default(), 56 | keywords: Default::default(), 57 | groups_and_keywords: Default::default(), 58 | } 59 | } 60 | 61 | pub fn set_precedence_order(&mut self, precedence: Vec>) { 62 | self.groups_and_keywords = precedence.into_iter() 63 | .map(|p| (p, HashMap::new())) 64 | .collect(); 65 | self.patterns = HashSet::new(); 66 | self.keywords = HashSet::new(); 67 | } 68 | 69 | pub fn add_pattern(&mut self, pattern: Rc>) -> RResult> { 70 | let Some(keyword_map) = self.groups_and_keywords.get_mut(&pattern.precedence_group) else { 71 | panic!("Cannot find precedence group {:?} in: {:?}", pattern.precedence_group, self.groups_and_keywords); 72 | }; 73 | 74 | let keywords = match &pattern.parts.iter().map(|x| x.as_ref()).collect_vec()[..] { 75 | [_] => return Err(RuntimeError::error("Pattern is too short.").to_array()), 76 | [ 77 | PatternPart::Keyword(keyword), 78 | PatternPart::Parameter { .. }, 79 | ] => { 80 | if pattern.precedence_group.associativity != OperatorAssociativity::LeftUnary { 81 | return Err(RuntimeError::error("Unary pattern must use LeftUnary precedence.").to_array()) 82 | } 83 | keyword_map.insert(keyword.clone(), pattern.function.clone()); 84 | self.keywords.insert(keyword.clone()); 85 | vec![keyword.clone()] 86 | }, 87 | [ 88 | PatternPart::Parameter { .. }, 89 | PatternPart::Keyword(keyword), 90 | ] => { 91 | todo!("Right unary operators aren't supported yet.") 92 | }, 93 | [ 94 | PatternPart::Parameter { .. }, 95 | PatternPart::Keyword(keyword), 96 | PatternPart::Parameter { .. }, 97 | ] => { 98 | if pattern.precedence_group.associativity == OperatorAssociativity::LeftUnary { 99 | return Err(RuntimeError::error("Binary pattern must not use LeftUnary precedence.").to_array()) 100 | } 101 | 102 | keyword_map.insert(keyword.clone(), pattern.function.clone()); 103 | self.keywords.insert(keyword.clone()); 104 | vec![keyword.clone()] 105 | } 106 | _ => return Err(RuntimeError::error("This pattern form is not supported; try using unary or binary patterns.").to_array()), 107 | }; 108 | 109 | self.patterns.insert(pattern); 110 | Ok(keywords) 111 | } 112 | } 113 | 114 | impl PrecedenceGroup { 115 | pub fn new(name: &str, associativity: OperatorAssociativity) -> PrecedenceGroup { 116 | PrecedenceGroup { 117 | trait_id: Uuid::new_v4(), 118 | name: String::from(name), 119 | associativity, 120 | } 121 | } 122 | } 123 | 124 | impl PartialEq for PrecedenceGroup { 125 | fn eq(&self, other: &Self) -> bool { 126 | self.trait_id == other.trait_id 127 | } 128 | } 129 | 130 | impl Hash for PrecedenceGroup { 131 | fn hash(&self, state: &mut H) { 132 | self.trait_id.hash(state) 133 | } 134 | } 135 | 136 | 137 | impl Display for PatternPart { 138 | fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { 139 | match self { 140 | PatternPart::Parameter(p) => write!(fmt, "({})", p), 141 | PatternPart::Keyword(keyword) => write!(fmt, "{}", keyword), 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /python/poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.8.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "numpy" 5 | version = "1.26.4" 6 | description = "Fundamental package for array computing in Python" 7 | optional = false 8 | python-versions = ">=3.9" 9 | files = [ 10 | {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, 11 | {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, 12 | {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, 13 | {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, 14 | {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, 15 | {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, 16 | {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, 17 | {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, 18 | {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, 19 | {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, 20 | {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, 21 | {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, 22 | {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, 23 | {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, 24 | {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, 25 | {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, 26 | {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, 27 | {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, 28 | {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, 29 | {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, 30 | {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, 31 | {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, 32 | {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, 33 | {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, 34 | {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, 35 | {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, 36 | {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, 37 | {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, 38 | {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, 39 | {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, 40 | {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, 41 | {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, 42 | {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, 43 | {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, 44 | {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, 45 | {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, 46 | ] 47 | 48 | [metadata] 49 | lock-version = "2.0" 50 | python-versions = "^3.9" 51 | content-hash = "73104148745e647961282501b5fbe50c548b7829b9369051b6f32c8ba02cad44" 52 | --------------------------------------------------------------------------------