diff --git a/.gitignore b/.gitignore index 2a39a6a..88345f8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,4 @@ -# Generated by Cargo -# will have compiled files and executables -debug/ -target/ +/target # These are backup files generated by rustfmt **/*.rs.bk @@ -10,4 +7,4 @@ target/ *.pdb # Generated by the compiler -/*.ts \ No newline at end of file +/*.js \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index fbe9e56..2831ff0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,21 +4,20 @@ version = 3 [[package]] name = "ahash" -version = "0.3.8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "const-random", + "getrandom", + "once_cell", + "version_check", ] [[package]] -name = "ariadne" -version = "0.1.5" +name = "cc" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1cb2a2046bea8ce5e875551f5772024882de0b540c7f93dfc5d6cf1ca8b030c" -dependencies = [ - "yansi", -] +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" [[package]] name = "cfg-if" @@ -28,65 +27,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chumsky" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4" +checksum = "c4d619fba796986dd538d82660b76e0b9756c6e19b2e4d4559ba5a57f9f00810" dependencies = [ - "ahash", + "hashbrown", + "stacker", ] -[[package]] -name = "compiler" -version = "0.1.0" -dependencies = [ - "lower", - "vm", -] - -[[package]] -name = "const-random" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" -dependencies = [ - "const-random-macro", - "proc-macro-hack", -] - -[[package]] -name = "const-random-macro" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" -dependencies = [ - "getrandom", - "once_cell", - "proc-macro-hack", - "tiny-keccak", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "entry" -version = "0.1.0" -dependencies = [ - "compiler", - "lower", - "parser", - "vm", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "getrandom" version = "0.2.8" @@ -99,53 +47,60 @@ dependencies = [ ] [[package]] -name = "libc" -version = "0.2.138" +name = "hashbrown" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" - -[[package]] -name = "lower" -version = "0.1.0" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "parser", + "ahash", ] [[package]] -name = "once_cell" -version = "1.16.0" +name = "libc" +version = "0.2.139" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" [[package]] -name = "parser" +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "renxi" version = "0.1.0" dependencies = [ - "ariadne", "chumsky", ] [[package]] -name = "proc-macro-hack" -version = "0.5.19" +name = "stacker" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" dependencies = [ - "crunchy", + "cc", + "cfg-if", + "libc", + "psm", + "winapi", ] [[package]] -name = "vm" -version = "0.1.0" -dependencies = [ - "fnv", -] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wasi" @@ -154,7 +109,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "yansi" -version = "0.5.1" +name = "winapi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 1ac8f72..8ed5624 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,7 @@ -[workspace] -members = [ - "entry", - "parser", - "lower", - "compiler", - "vm", -] +[package] +name = "renxi" +version = "0.1.0" +edition = "2021" + +[dependencies] +chumsky = "0.9.0" diff --git a/FUNDING.yml b/FUNDING.yml deleted file mode 100644 index be65772..0000000 --- a/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -ko_fi: azur1s \ No newline at end of file diff --git a/LICENSE-APACHE b/LICENSE-APACHE deleted file mode 100644 index a7e77cb..0000000 --- a/LICENSE-APACHE +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/LICENSE-MIT b/LICENSE-MIT deleted file mode 100644 index 468cd79..0000000 --- a/LICENSE-MIT +++ /dev/null @@ -1,23 +0,0 @@ -Permission is hereby granted, free of charge, to any -person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the -Software without restriction, including without -limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software -is furnished to do so, subject to the following -conditions: - -The above copyright notice and this permission notice -shall be included in all copies or substantial portions -of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF -ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A -PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT -SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 6b53b7e..0000000 --- a/README.md +++ /dev/null @@ -1,13 +0,0 @@ -!!! In development !!! - -Update: There might be a stagnate in the development because my school is open and there will be a lot of assignments coming. Don't worry! I'm still learning more about compiler inner workings in my free time. I'll be back in a while. -# Holymer -A ML-inspired programming language for making back-end application. - -## Contributing -Please have [Rust programming language](https://github.com/rust-lang/rust) installed on your machine before building it. -```shell -$ git clone https://github.com/azur1s/holymer.git -$ cd holymer -# build with `cargo build` or just `cargo run -- filename` -``` diff --git a/a.hlm b/a.hlm new file mode 100644 index 0000000..899c35b --- /dev/null +++ b/a.hlm @@ -0,0 +1,43 @@ +println("Hello, " + name + "!"); + +let a = 17, b = 35 in + let c = a * 2 in + println(b + c); + +func foo (a: int, b: int) { + let c = a * 2; + + let res = b + c in + return res + a; +} + +println((\x: int -> x + 1)(1)); + +────────────────────────────────────────────────── + +(println (+ "Hello, " name "!")) + +(let [a 17] [b 35] + (let [c (* a 2)] + (println (+ b c)))) + +(func foo [a int b int] (block + (let [c (* a 2)]) + (let [res (+ b c)] + (return (+ res a))) +)) + +────────────────────────────────────────────────── + +console.log("Hello, " + name + "!"); + +let a = 17; +let b = 35; +let c = a * 2; +console.log(b + c); + +const foo = (a, b) => { + let c = a * 2; + let res = b + c; + return res + a; +} \ No newline at end of file diff --git a/b.hlm b/b.hlm new file mode 100644 index 0000000..2142a2b --- /dev/null +++ b/b.hlm @@ -0,0 +1,5 @@ +let foo : num = 1 in bar(foo) end + +lambda (foo : num) -> unknown = bar(foo) + +let x : t = e1 in e2 end \ No newline at end of file diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml deleted file mode 100644 index 1ec22b8..0000000 --- a/compiler/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "compiler" -version = "0.1.0" -edition = "2021" - -[dependencies] -lower = { path = "../lower" } -vm = { path = "../vm" } \ No newline at end of file diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs deleted file mode 100644 index cf66054..0000000 --- a/compiler/src/lib.rs +++ /dev/null @@ -1,167 +0,0 @@ -#![allow(clippy::new_without_default)] -#![allow(clippy::only_used_in_recursion)] -use lower::model::{BinaryOp, Expr, Literal, Stmt, UnaryOp}; -use vm::model::Instr; - -pub struct Compiler {} - -impl Compiler { - pub fn new() -> Self { - Self {} - } - - pub fn compile_expr(&mut self, expr: Expr) -> Vec { - match expr { - Expr::Error => { - println!("{:?}", expr); - unreachable!() - } - Expr::Literal(x) => match x { - Literal::Num(x) => vec![Instr::NumPush(x)], - Literal::Bool(x) => vec![Instr::BoolPush(x)], - Literal::Str(x) => vec![Instr::StrPush(x)], - }, - Expr::Sym(name) => vec![Instr::Get(name)], - Expr::Vec(xs) => { - let mut instrs = vec![]; - let count = xs.len(); - for x in xs { - instrs.extend(self.compile_expr(x)); - } - instrs.push(Instr::ListMake(count)); - instrs - } - Expr::Unary(op, x) => { - let mut instrs = self.compile_expr(*x); - instrs.extend(match op { - UnaryOp::Neg => vec![Instr::NumPush(-1), Instr::NumMul], - UnaryOp::Not => vec![Instr::BoolNot], - }); - instrs - } - Expr::Binary(op, x, y) => { - let mut instrs = self.compile_expr(*y); - instrs.extend(self.compile_expr(*x)); - instrs.push(match op { - BinaryOp::Add => Instr::NumAdd, - BinaryOp::Sub => Instr::NumSub, - BinaryOp::Mul => Instr::NumMul, - BinaryOp::Div => Instr::NumDiv, - BinaryOp::Eq => Instr::NumEq, - BinaryOp::Ne => Instr::NumNe, - BinaryOp::Lt => Instr::NumLt, - BinaryOp::Gt => Instr::NumGt, - BinaryOp::Le => Instr::NumLe, - BinaryOp::Ge => Instr::NumGe, - BinaryOp::And => Instr::BoolAnd, - BinaryOp::Or => Instr::BoolOr, - BinaryOp::Pipe => todo!(), - }); - instrs - } - Expr::Lambda(args, body) => { - vec![Instr::FuncMake(args, self.compile_expr(*body))] - } - Expr::Call(f, xs) => { - let mut instrs = vec![]; - for x in xs { - instrs.extend(self.compile_expr(x)); - } - match *f { - Expr::Sym(ref fname) => match fname.as_str() { - "print" => instrs.push(Instr::Print), - "println" => instrs.push(Instr::PrintLn), - _ => { - instrs.extend(self.compile_expr(*f)); - instrs.push(Instr::FuncApply); - } - }, - Expr::Lambda(_, _) => { - instrs.extend(self.compile_expr(*f)); - instrs.push(Instr::FuncApply); - } - _ => todo!(), - } - instrs - } - Expr::Let(binds, body) => { - let mut instrs = vec![]; - let binds = binds - .into_iter() - .flat_map(|(name, expr)| { - let mut instrs = self.compile_expr(expr); - instrs.extend(vec![Instr::Set(name)]); - instrs - }) - .collect::>(); - if let Some(e) = body { - // If there is a body then we put the bindings - // inside the closure so it gets undefined outside - // the scope - instrs.extend(vec![ - Instr::FuncMake( - vec![], - binds.into_iter().chain(self.compile_expr(*e)).collect(), - ), - Instr::FuncApply, - ]); - } else { - // If there is no body then we just push the bindings - // to the global scope - instrs.extend(binds); - } - instrs - } - Expr::If(c, t, f) => { - let mut instrs = self.compile_expr(*c); - let t = self.compile_expr(*t); - if let Some(f) = f { - let f = self.compile_expr(*f); - instrs.push(Instr::JumpIfFalse(t.len() + 1)); - instrs.extend(t); - instrs.push(Instr::Jump(f.len())); - instrs.extend(f); - } else { - instrs.push(Instr::JumpIfFalse(t.len())); - instrs.extend(t); - } - instrs - } - Expr::Do(es) => { - let mut instrs = vec![]; - for e in es { - instrs.extend(self.compile_expr(e)); - } - instrs - } - } - } - - pub fn compile_stmt(&mut self, stmt: Stmt) -> Vec { - match stmt { - Stmt::Fun(name, args, body) => { - let is_main = name == "main"; - let mut instrs = match body { - // If the body is a lambda then we don't have to compile - // it into a function - Expr::Lambda(_, _) => self.compile_expr(body), - _ => vec![Instr::FuncMake(args, self.compile_expr(body))], - }; - instrs.push(Instr::Set(name)); - if is_main { - instrs.pop(); - instrs.push(Instr::FuncApply); - } - instrs - } - } - } - - pub fn compile_program(&mut self, stmts: Vec) -> Vec { - let mut instrs = vec![]; - for stmt in stmts { - instrs.extend(self.compile_stmt(stmt)); - } - instrs - } -} diff --git a/entry/Cargo.toml b/entry/Cargo.toml deleted file mode 100644 index 804e31e..0000000 --- a/entry/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "entry" -version = "0.1.0" -edition = "2021" - -[dependencies] -parser = { path = "../parser" } -lower = { path = "../lower" } -compiler = { path = "../compiler" } -vm = { path = "../vm" } - -[[bin]] -name = "hmc" -path = "src/main.rs" \ No newline at end of file diff --git a/entry/src/main.rs b/entry/src/main.rs deleted file mode 100644 index 71fe80a..0000000 --- a/entry/src/main.rs +++ /dev/null @@ -1,40 +0,0 @@ -use compiler::Compiler; -use lower::{model::converts, Lower}; -use parser::{lex, parse, report}; -use vm::exec::Executor; - -fn main() { - let path = std::env::args().nth(1).expect("No file path provided"); - let src = std::fs::read_to_string(path).expect("Failed to read file"); - - let (tokens, lex_errors) = lex(src.to_string()); - let parse_errors = if let Some(tokens) = tokens { - let (ast, parse_errors) = parse(tokens, src.len()); - - if let Some(ast) = ast { - let stripped = converts(ast); - let mut lower = Lower::new(); - let lowered = lower.opt_stmts(stripped); - let mut compiler = Compiler::new(); - let instrs = compiler.compile_program(lowered); - // instrs.iter().for_each(|i| println!("{:?}", i)); - let mut executor = Executor::new(instrs); - match executor.run() { - Ok(_) => {} - Err(e) => println!("Runtime error: {:?}", e), - } - } - - parse_errors - } else { - Vec::new() - }; - - if !lex_errors.is_empty() || !parse_errors.is_empty() { - lex_errors - .into_iter() - .map(|e| e.map(|c| c.to_string())) - .chain(parse_errors.into_iter().map(|e| e.map(|t| t.to_string()))) - .for_each(|e| report(e, &src)); - } -} diff --git a/examples/a.hlm b/examples/a.hlm deleted file mode 100644 index 0aa71ff..0000000 --- a/examples/a.hlm +++ /dev/null @@ -1,16 +0,0 @@ -fun succ x = do - let one = 1 - - let y = x + one - in y -end - -fun double x = x * 2 - -fun main = do - - 1 - |> \a -> succ(a) - |> \b -> double(b) - |> \c -> println(c) -end \ No newline at end of file diff --git a/examples/factorial.hlm b/examples/factorial.hlm deleted file mode 100644 index 6bd9961..0000000 --- a/examples/factorial.hlm +++ /dev/null @@ -1,9 +0,0 @@ -fun factorial n = - let helper = \n acc -> - if n > 0 - then helper(n - 1, acc * n) - else acc - in - helper(n, 1) - -fun main = println(factorial(20)) \ No newline at end of file diff --git a/examples/ht.hlm b/examples/ht.hlm deleted file mode 100644 index 0ceedfd..0000000 --- a/examples/ht.hlm +++ /dev/null @@ -1,45 +0,0 @@ -// This is just proof of concept on what the language -// might look like in the future - -import http from "http" - -// Define a custom type to represent a user -type User = - id: number, - name: string, -end - -// Define a function to handle incoming HTTP requests -fun handle_request - req: http.IncomingMessage, - res: http.ServerResponse, -= do - let user_id = req.url.split("/")[1] - let name = - match user_id - | 12345 -> Some("John Smith") - | 727 -> Some("Foo Bar") - else None - - match name - | Some name -> do - let user = User(user_id, name) - res.statusCode = 200 - res.setHeader("Content-Type", "application/json") - res.write(JSON.stringify(user)) - end - | None -> do - res.statusCode = 404 - res.write("User not found") - end - - res.end() -end - -fun main = do - let - port = 8080, - server = http.createServer(handle_request), - in - server.listen(port, fun -> println("HTTP server listening on port 8080")) -end diff --git a/examples/sim.hlm b/examples/sim.hlm deleted file mode 100644 index f2b4455..0000000 --- a/examples/sim.hlm +++ /dev/null @@ -1,5 +0,0 @@ -fun main = do - 1 - |> \x -> x + 1 - |> \x -> println(x) -end \ No newline at end of file diff --git a/lower/Cargo.toml b/lower/Cargo.toml deleted file mode 100644 index 5b7c85a..0000000 --- a/lower/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "lower" -version = "0.1.0" -edition = "2021" - -[dependencies] -parser = { path = "../parser" } \ No newline at end of file diff --git a/lower/src/lib.rs b/lower/src/lib.rs deleted file mode 100644 index e17e54f..0000000 --- a/lower/src/lib.rs +++ /dev/null @@ -1,79 +0,0 @@ -#![allow(clippy::new_without_default)] -pub mod model; -use crate::model::{BinaryOp, Expr, Stmt}; - -pub struct Lower {} - -impl Lower { - pub fn new() -> Self { - Self {} - } - - pub fn opt_stmts(&self, ss: Vec) -> Vec { - ss.into_iter() - .map(|s| self.opt_stmt(s.clone()).unwrap_or(s)) - .collect() - } - - pub fn opt_stmt(&self, s: Stmt) -> Option { - match s { - Stmt::Fun(name, args, body) => Some(Stmt::Fun( - name, - args, - self.opt_expr(body.clone()).unwrap_or(body), - )), - _ => None, - } - } - - pub fn opt_exprs(&self, es: Vec) -> Vec { - es.into_iter() - .map(|e| self.opt_expr(e.clone()).unwrap_or(e)) - .collect() - } - - pub fn opt_expr(&self, e: Expr) -> Option { - match e { - Expr::Binary(BinaryOp::Pipe, left, right) => Some(self.fold_pipe(*left, *right)), - Expr::Lambda(args, body) => Some(Expr::Lambda( - args, - Box::new(self.opt_expr(*body.clone()).unwrap_or(*body)), - )), - Expr::Do(es) => Some(Expr::Do(self.opt_exprs(es))), - _ => None, - } - } - - fn fold_pipe(&self, left: Expr, right: Expr) -> Expr { - Expr::Call( - Box::new(self.opt_expr(right.clone()).unwrap_or(right)), - vec![self.opt_expr(left.clone()).unwrap_or(left)], - ) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::model::convert_expr; - use parser::{lex, parse_expr}; - - #[test] - fn test_fold_pipe() { - let s = "1 |> \\x -> x + 1"; - println!("{}", s); - let (ts, es) = lex(s.to_owned()); - - assert!(es.is_empty()); - - let (ex, es) = parse_expr(ts.unwrap(), s.chars().count()); - - assert!(es.is_empty()); - - let ex = ex.unwrap(); - let ex = convert_expr(ex); - let l = Lower::new(); - let ex = l.opt_expr(ex).unwrap(); - println!("{:?}", ex); - } -} diff --git a/lower/src/model.rs b/lower/src/model.rs deleted file mode 100644 index 36c258d..0000000 --- a/lower/src/model.rs +++ /dev/null @@ -1,129 +0,0 @@ -use crate::model; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Literal { - Num(i64), - Bool(bool), - Str(String), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum UnaryOp { - Neg, - Not, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum BinaryOp { - Add, - Sub, - Mul, - Div, - Lt, - Le, - Gt, - Ge, - Eq, - Ne, - And, - Or, - Pipe, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Expr { - Error, - - Literal(Literal), - Sym(String), - Vec(Vec), - Unary(UnaryOp, Box), - Binary(BinaryOp, Box, Box), - Lambda(Vec, Box), - Call(Box, Vec), - Let(Vec<(String, Self)>, Option>), - If(Box, Box, Option>), - Do(Vec), -} - -#[derive(Clone, Debug)] -pub enum Stmt { - Fun(String, Vec, Expr), -} - -pub fn converts(s: Vec<(parser::Stmt, std::ops::Range)>) -> Vec { - s.into_iter().map(|(s, _)| convert(s)).collect() -} - -pub fn convert(s: parser::Stmt) -> model::Stmt { - match s { - parser::Stmt::Fun(name, args, body) => model::Stmt::Fun(name, args, convert_expr(body)), - } -} - -pub fn convert_expr(e: (parser::Expr, std::ops::Range)) -> model::Expr { - match e.0 { - parser::Expr::Error => model::Expr::Error, - - parser::Expr::Literal(l) => match l { - parser::Literal::Num(n) => model::Expr::Literal(model::Literal::Num(n)), - parser::Literal::Bool(b) => model::Expr::Literal(model::Literal::Bool(b)), - parser::Literal::Str(s) => model::Expr::Literal(model::Literal::Str(s)), - }, - parser::Expr::Sym(s) => model::Expr::Sym(s), - parser::Expr::Vec(es) => model::Expr::Vec(es.into_iter().map(convert_expr).collect()), - parser::Expr::Unary(op, e) => { - model::Expr::Unary(convert_unary_op(op.0), Box::new(convert_expr(*e))) - } - parser::Expr::Binary(op, left, right) => model::Expr::Binary( - convert_binary_op(op.0), - Box::new(convert_expr(*left)), - Box::new(convert_expr(*right)), - ), - parser::Expr::Lambda(args, body) => { - model::Expr::Lambda(args, Box::new(convert_expr(*body))) - } - parser::Expr::Call(f, args) => model::Expr::Call( - Box::new(convert_expr(*f)), - args.into_iter().map(convert_expr).collect(), - ), - parser::Expr::Let(bindings, body) => model::Expr::Let( - bindings - .into_iter() - .map(|(s, e)| (s, convert_expr(e))) - .collect(), - body.map(|e| Box::new(convert_expr(*e))), - ), - parser::Expr::If(cond, then, else_) => model::Expr::If( - Box::new(convert_expr(*cond)), - Box::new(convert_expr(*then)), - else_.map(|e| Box::new(convert_expr(*e))), - ), - parser::Expr::Do(es) => model::Expr::Do(es.into_iter().map(convert_expr).collect()), - } -} - -pub fn convert_unary_op(op: parser::UnaryOp) -> model::UnaryOp { - match op { - parser::UnaryOp::Neg => model::UnaryOp::Neg, - parser::UnaryOp::Not => model::UnaryOp::Not, - } -} - -pub fn convert_binary_op(op: parser::BinaryOp) -> model::BinaryOp { - match op { - parser::BinaryOp::Add => model::BinaryOp::Add, - parser::BinaryOp::Sub => model::BinaryOp::Sub, - parser::BinaryOp::Mul => model::BinaryOp::Mul, - parser::BinaryOp::Div => model::BinaryOp::Div, - parser::BinaryOp::Lt => model::BinaryOp::Lt, - parser::BinaryOp::Le => model::BinaryOp::Le, - parser::BinaryOp::Gt => model::BinaryOp::Gt, - parser::BinaryOp::Ge => model::BinaryOp::Ge, - parser::BinaryOp::Eq => model::BinaryOp::Eq, - parser::BinaryOp::Ne => model::BinaryOp::Ne, - parser::BinaryOp::And => model::BinaryOp::And, - parser::BinaryOp::Or => model::BinaryOp::Or, - parser::BinaryOp::Pipe => model::BinaryOp::Pipe, - } -} diff --git a/parser/Cargo.lock b/parser/Cargo.lock deleted file mode 100644 index b26488f..0000000 --- a/parser/Cargo.lock +++ /dev/null @@ -1,122 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8fd72866655d1904d6b0997d0b07ba561047d070fbe29de039031c641b61217" -dependencies = [ - "const-random", -] - -[[package]] -name = "ariadne" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1cb2a2046bea8ce5e875551f5772024882de0b540c7f93dfc5d6cf1ca8b030c" -dependencies = [ - "yansi", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "chumsky" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d02796e4586c6c41aeb68eae9bfb4558a522c35f1430c14b40136c3706e09e4" -dependencies = [ - "ahash", -] - -[[package]] -name = "const-random" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e" -dependencies = [ - "const-random-macro", - "proc-macro-hack", -] - -[[package]] -name = "const-random-macro" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb" -dependencies = [ - "getrandom", - "once_cell", - "proc-macro-hack", - "tiny-keccak", -] - -[[package]] -name = "crunchy" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" - -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "libc" -version = "0.2.138" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" - -[[package]] -name = "once_cell" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "tiny-keccak" -version = "2.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" -dependencies = [ - "crunchy", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - -[[package]] -name = "yuushi" -version = "0.1.0" -dependencies = [ - "ariadne", - "chumsky", -] diff --git a/parser/Cargo.toml b/parser/Cargo.toml deleted file mode 100644 index c3c1b2d..0000000 --- a/parser/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "parser" -version = "0.1.0" -edition = "2021" - -[dependencies] -chumsky = "0.8.0" -ariadne = "0.1.5" \ No newline at end of file diff --git a/parser/src/lib.rs b/parser/src/lib.rs deleted file mode 100644 index dd47c8b..0000000 --- a/parser/src/lib.rs +++ /dev/null @@ -1,606 +0,0 @@ -#![feature(trait_alias)] -#![allow(clippy::type_complexity)] -use ariadne::{Color, Fmt, Label, Report, ReportKind, Source}; -use chumsky::{error, prelude::*, Stream}; - -pub type Span = std::ops::Range; -pub type Spanned = (T, Span); - -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum Delimiter { - Paren, - Brack, - Brace, -} - -#[derive(Clone, Debug, Eq, Hash, PartialEq)] -pub enum Token { - Num(i64), - Bool(bool), - Str(String), - Sym(String), - - Add, - Sub, - Mul, - Div, - Lt, - Le, - Gt, - Ge, - Eq, - Ne, - And, - Or, - Not, - Pipe, - Assign, - Arrow, - Backslash, - Comma, - Semi, - Open(Delimiter), - Close(Delimiter), - - Fun, - Let, - In, - If, - Then, - Else, - Do, - End, -} - -impl std::fmt::Display for Token { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Token::Num(n) => write!(f, "{}", n), - Token::Bool(b) => write!(f, "{}", b), - Token::Str(s) => write!(f, "{}", s), - Token::Sym(s) => write!(f, "{}", s), - - Token::Add => write!(f, "+"), - Token::Sub => write!(f, "-"), - Token::Mul => write!(f, "*"), - Token::Div => write!(f, "/"), - - Token::Lt => write!(f, "<"), - Token::Le => write!(f, "<="), - Token::Gt => write!(f, ">"), - Token::Ge => write!(f, ">="), - Token::Eq => write!(f, "=="), - Token::Ne => write!(f, "!="), - - Token::And => write!(f, "&&"), - Token::Or => write!(f, "||"), - Token::Not => write!(f, "!"), - - Token::Pipe => write!(f, "|>"), - - Token::Assign => write!(f, "="), - Token::Arrow => write!(f, "->"), - Token::Backslash => write!(f, "\\"), - Token::Comma => write!(f, ","), - Token::Semi => write!(f, ";"), - - Token::Open(d) => write!( - f, - "{}", - match d { - Delimiter::Paren => "(", - Delimiter::Brack => "[", - Delimiter::Brace => "{", - } - ), - Token::Close(d) => write!( - f, - "{}", - match d { - Delimiter::Paren => ")", - Delimiter::Brack => "]", - Delimiter::Brace => "}", - } - ), - - Token::Fun => write!(f, "fun"), - Token::Let => write!(f, "let"), - Token::In => write!(f, "in"), - Token::If => write!(f, "if"), - Token::Then => write!(f, "then"), - Token::Else => write!(f, "else"), - Token::Do => write!(f, "do"), - Token::End => write!(f, "end"), - } - } -} - -pub fn lexer() -> impl Parser, Error = Simple> { - let int = text::int(10).map(|s: String| Token::Num(s.parse().unwrap())); - - let string = just('"') - .ignore_then(filter(|c| *c != '"').repeated()) - .then_ignore(just('"')) - .collect::() - .map(Token::Str); - - let symbol = choice(( - just("->").to(Token::Arrow), - just('+').to(Token::Add), - just('-').to(Token::Sub), - just('*').to(Token::Mul), - just('/').to(Token::Div), - just("|>").to(Token::Pipe), - just("<=").to(Token::Le), - just('<').to(Token::Lt), - just(">=").to(Token::Ge), - just('>').to(Token::Gt), - just("!=").to(Token::Ne), - just("==").to(Token::Eq), - just("&&").to(Token::And), - just("||").to(Token::Or), - just('!').to(Token::Not), - just('=').to(Token::Assign), - just('\\').to(Token::Backslash), - just(',').to(Token::Comma), - just(';').to(Token::Semi), - )); - - let delim = choice(( - just('(').to(Token::Open(Delimiter::Paren)), - just(')').to(Token::Close(Delimiter::Paren)), - just('[').to(Token::Open(Delimiter::Brack)), - just(']').to(Token::Close(Delimiter::Brack)), - just('{').to(Token::Open(Delimiter::Brace)), - just('}').to(Token::Close(Delimiter::Brace)), - )); - - let keyword = text::ident().map(|s: String| match s.as_str() { - "true" => Token::Bool(true), - "false" => Token::Bool(false), - - "fun" => Token::Fun, - "let" => Token::Let, - "in" => Token::In, - "if" => Token::If, - "then" => Token::Then, - "else" => Token::Else, - "do" => Token::Do, - "end" => Token::End, - _ => Token::Sym(s), - }); - - let token = int - .or(string) - .or(symbol) - .or(delim) - .or(keyword) - .map_with_span(move |token, span| (token, span)) - .padded() - .recover_with(skip_then_retry_until([])); - - let comments = just('/') - .then_ignore( - just('*') - .ignore_then(take_until(just("*/")).ignored()) - .or(just('/').ignore_then(none_of('\n').ignored().repeated().ignored())), - ) - .padded() - .ignored() - .repeated(); - - token - .padded_by(comments) - .repeated() - .padded() - .then_ignore(end()) -} - -pub fn lex(src: String) -> (Option>, Vec>) { - let (tokens, lex_error) = lexer().parse_recovery(src.as_str()); - (tokens, lex_error) -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Literal { - Num(i64), - Bool(bool), - Str(String), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum UnaryOp { - Neg, - Not, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum BinaryOp { - Add, - Sub, - Mul, - Div, - Lt, - Le, - Gt, - Ge, - Eq, - Ne, - And, - Or, - Pipe, -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum Expr { - Error, - - Literal(Literal), - Sym(String), - Vec(Vec>), - Unary(Spanned, Box>), - Binary(Spanned, Box>, Box>), - Lambda(Vec, Box>), - Call(Box>, Vec>), - Let(Vec<(String, Spanned)>, Option>>), - If( - Box>, - Box>, - Option>>, - ), - Do(Vec>), -} - -#[derive(Clone, Debug)] -pub enum Stmt { - Fun(String, Vec, Spanned), -} - -pub trait P = chumsky::Parser> + Clone; - -pub fn literal_parser() -> impl P { - filter_map(|span, token| match token { - Token::Num(i) => Ok(Literal::Num(i)), - Token::Bool(b) => Ok(Literal::Bool(b)), - Token::Str(s) => Ok(Literal::Str(s)), - _ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))), - }) - .labelled("literal") -} - -pub fn symbol_parser() -> impl P { - filter_map(|span, token| match token { - Token::Sym(s) => Ok(s), - _ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))), - }) - .labelled("symbol") -} - -pub fn nested_parser<'a, T: 'a>( - parser: impl P + 'a, - delim: Delimiter, - f: impl Fn(Span) -> T + Clone + 'a, -) -> impl P + 'a { - parser - .delimited_by(just(Token::Open(delim)), just(Token::Close(delim))) - .recover_with(nested_delimiters( - Token::Open(delim), - Token::Close(delim), - [ - ( - Token::Open(Delimiter::Paren), - Token::Close(Delimiter::Paren), - ), - ( - Token::Open(Delimiter::Brack), - Token::Close(Delimiter::Brack), - ), - ( - Token::Open(Delimiter::Brace), - Token::Close(Delimiter::Brace), - ), - ], - f, - )) - .boxed() -} - -pub fn expr_parser() -> impl P> { - recursive(|expr| { - let lit = literal_parser().map(Expr::Literal); - let ident = symbol_parser().map(Expr::Sym); - - let vec = nested_parser( - expr.clone() - .separated_by(just(Token::Comma)) - .allow_trailing() - .map(Some), - Delimiter::Brack, - |_| None, - ) - .map(|elems| match elems { - Some(elems) => Expr::Vec(elems), - None => Expr::Vec(Vec::new()), - }) - .labelled("vector"); - - let paren_expr = just(Token::Open(Delimiter::Paren)) - .ignore_then(expr.clone()) - .then_ignore(just(Token::Close(Delimiter::Paren))) - .map(|e| e.0) - .labelled("parenthesized expression"); - - let lam = just(Token::Backslash) - .ignore_then(symbol_parser().repeated()) - .then_ignore(just(Token::Arrow)) - .then(expr.clone()) - .map(|(args, body)| Expr::Lambda(args, Box::new(body))) - .labelled("lambda"); - - let let_binds = symbol_parser() - .then_ignore(just(Token::Assign)) - .then(expr.clone()) - .map(|(sym, expr)| (sym, expr)) - .separated_by(just(Token::Comma)) - .allow_trailing() - .labelled("let bindings"); - - let let_in = just(Token::Let) - .ignore_then(let_binds.clone()) - .then_ignore(just(Token::In)) - .then(expr.clone()) - .map(|(binds, body)| Expr::Let(binds, Some(Box::new(body)))) - .boxed() - .labelled("let..in"); - - let let_def = just(Token::Let) - .ignore_then(let_binds) - .map(|binds| Expr::Let(binds, None)) - .labelled("let"); - - let if_ = just(Token::If) - .ignore_then(expr.clone()) - .then_ignore(just(Token::Then)) - .then(expr.clone()) - .then(just(Token::Else).ignore_then(expr.clone()).or_not()) - .map(|((cond, then), else_)| { - Expr::If(Box::new(cond), Box::new(then), else_.map(Box::new)) - }); - - let block = just(Token::Do) - .ignore_then(expr.clone().repeated()) - .then_ignore(just(Token::End)) - .map(Expr::Do) - .labelled("do block"); - - let atom = lit - .or(ident) - .or(vec) - .or(paren_expr) - .or(lam) - .or(let_in) - .or(let_def) - .or(if_) - .or(block) - .map_with_span(|e, s| (e, s)) - .boxed() - .labelled("atom"); - - let call = atom - .then( - nested_parser( - expr.clone() - .separated_by(just(Token::Comma)) - .allow_trailing() - .map(Some), - Delimiter::Paren, - |_| None, - ) - .or_not(), - ) - .map_with_span(|(f, args), s| match args { - Some(Some(args)) => (Expr::Call(Box::new(f), args), s), - Some(None) => (Expr::Error, s), - None => f, - }); - - let unary = choice(( - just(Token::Sub).to(UnaryOp::Neg), - just(Token::Not).to(UnaryOp::Not), - )) - .map_with_span(|op, s| (op, s)) - .repeated() - .then(call) - .foldr(|op, expr| { - let s = op.1.start()..expr.1.end(); - (Expr::Unary(op, Box::new(expr)), s) - }) - .boxed(); - - let product = unary - .clone() - .then( - choice(( - just(Token::Mul).to(BinaryOp::Mul), - just(Token::Div).to(BinaryOp::Div), - )) - .map_with_span(|op, s| (op, s)) - .then(unary) - .repeated(), - ) - .foldl(|lhs, (op, rhs)| { - let s = lhs.1.start()..rhs.1.end(); - (Expr::Binary(op, Box::new(lhs), Box::new(rhs)), s) - }) - .boxed(); - - let sum = product - .clone() - .then( - choice(( - just(Token::Add).to(BinaryOp::Add), - just(Token::Sub).to(BinaryOp::Sub), - )) - .map_with_span(|op, s| (op, s)) - .then(product) - .repeated(), - ) - .foldl(|lhs, (op, rhs)| { - let s = lhs.1.start()..rhs.1.end(); - (Expr::Binary(op, Box::new(lhs), Box::new(rhs)), s) - }) - .boxed(); - - let comparison = sum - .clone() - .then( - choice(( - just(Token::Eq).to(BinaryOp::Eq), - just(Token::Ne).to(BinaryOp::Ne), - just(Token::Lt).to(BinaryOp::Lt), - just(Token::Le).to(BinaryOp::Le), - just(Token::Gt).to(BinaryOp::Gt), - just(Token::Ge).to(BinaryOp::Ge), - )) - .map_with_span(|op, s| (op, s)) - .then(sum) - .repeated(), - ) - .foldl(|lhs, (op, rhs)| { - let s = lhs.1.start()..rhs.1.end(); - (Expr::Binary(op, Box::new(lhs), Box::new(rhs)), s) - }) - .boxed(); - - let logical = comparison - .clone() - .then( - choice(( - just(Token::And).to(BinaryOp::And), - just(Token::Or).to(BinaryOp::Or), - )) - .map_with_span(|op, s| (op, s)) - .then(comparison) - .repeated(), - ) - .foldl(|lhs, (op, rhs)| { - let s = lhs.1.start()..rhs.1.end(); - (Expr::Binary(op, Box::new(lhs), Box::new(rhs)), s) - }) - .boxed(); - - logical - .clone() - .then( - just(Token::Pipe) - .to(BinaryOp::Pipe) - .map_with_span(|op, s| (op, s)) - .then(logical) - .repeated(), - ) - .foldl(|lhs, (op, rhs)| { - let s = lhs.1.start()..rhs.1.end(); - (Expr::Binary(op, Box::new(lhs), Box::new(rhs)), s) - }) - .boxed() - }) -} - -pub fn stmt_parser() -> impl P> { - let fun = just(Token::Fun) - .ignore_then(symbol_parser()) - .then(symbol_parser().repeated()) - .then_ignore(just(Token::Assign)) - .then(expr_parser()) - .map(|((name, args), body)| Stmt::Fun(name, args, body)); - - fun.map_with_span(|e, s| (e, s)) -} - -pub fn stmts_parser() -> impl P>> { - stmt_parser().repeated() -} - -pub fn parse( - tokens: Vec>, - len: usize, -) -> (Option>>, Vec>) { - let (ast, parse_error) = stmts_parser() - .then_ignore(end()) - .parse_recovery(Stream::from_iter(len..len + 1, tokens.into_iter())); - - (ast, parse_error) -} - -pub fn parse_expr( - tokens: Vec>, - len: usize, -) -> (Option>, Vec>) { - let (ast, parse_error) = expr_parser() - .then_ignore(end()) - .parse_recovery(Stream::from_iter(len..len + 1, tokens.into_iter())); - - (ast, parse_error) -} - -pub fn report(e: Simple, src: &str) { - let report = Report::build(ReportKind::Error, (), e.span().start()); - - let report = match e.reason() { - error::SimpleReason::Unclosed { span, delimiter } => report - .with_message("Unclosed delimiter") - .with_label( - Label::new(span.clone()) - .with_message(format!("Unclosed {}", delimiter.fg(Color::Yellow))) - .with_color(Color::Yellow), - ) - .with_label( - Label::new(e.span()) - .with_message(format!( - "Delimiter must be closed before {}", - e.found() - .unwrap_or(&"end of file".to_string()) - .fg(Color::Red) - )) - .with_color(Color::Red), - ), - - error::SimpleReason::Unexpected => report - .with_message(format!( - "Unexpected {}, expected {}", - if e.found().is_some() { - "token in input" - } else { - "end of input" - }, - if e.expected().len() == 0 { - "something else".to_string() - } else { - e.expected() - .map(|expected| match expected { - Some(expected) => expected.to_string(), - None => "end of input".to_string(), - }) - .collect::>() - .join(", ") - } - )) - .with_label( - Label::new(e.span()) - .with_message(format!( - "Unexpected token {}", - e.found() - .unwrap_or(&"end of file".to_string()) - .fg(Color::Red) - )) - .with_color(Color::Red), - ), - chumsky::error::SimpleReason::Custom(msg) => report.with_message(msg).with_label( - Label::new(e.span()) - .with_message(format!("{}", msg.fg(Color::Red))) - .with_color(Color::Red), - ), - }; - - report.finish().eprint(Source::from(&src)).unwrap(); -} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..271800c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml deleted file mode 100644 index d4d3d50..0000000 --- a/rustfmt.toml +++ /dev/null @@ -1 +0,0 @@ -version = "Two" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..12308f2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,55 @@ +#![feature(trait_alias)] +pub mod parse; +pub mod trans; + +use parse::parse::lex; + +fn main() { + let input = r#" + println((\x: int -> x + 1)(1)); + "#; + + let tokens = lex(input.to_owned()); + println!("{:?}", tokens); + + // use parse::past::*; + // use trans::ty::Type; + // use trans::low::*; + + // let exprs = vec![ + // PExpr::Call(Box::new(PExpr::Sym("println".to_string())), vec![ + // PExpr::Str("Hello, world!".to_string()), + // ]), + // PExpr::Let { + // vars: vec![ + // ("x".to_string(), Type::Num, PExpr::Num(1)), + // ], + // body: Box::new(PExpr::Sym("x".to_string())), + // }, + // PExpr::Let { + // vars: vec![ + // ("x".to_string(), Type::Num, PExpr::Num(34)), + // ("y".to_string(), Type::Num, PExpr::Num(35)), + // ], + // body: Box::new(PExpr::BinaryOp( + // PBinaryOp::Add, + // Box::new(PExpr::Sym("x".to_string())), + // Box::new(PExpr::Sym("y".to_string())), + // )), + // }, + // ]; + + // let nexprs = exprs.into_iter().map(translate_expr).collect::>(); + + // for expr in &nexprs { + // println!("{}", expr); + // } + + // println!("──────────────────────────────────────────────────"); + + // let jsexprs = nexprs.into_iter().map(translate_js).collect::>(); + + // for expr in &jsexprs { + // println!("{}", expr); + // } +} diff --git a/src/parse/mod.rs b/src/parse/mod.rs new file mode 100644 index 0000000..0c737d8 --- /dev/null +++ b/src/parse/mod.rs @@ -0,0 +1,2 @@ +pub mod parse; +pub mod past; \ No newline at end of file diff --git a/src/parse/parse.rs b/src/parse/parse.rs new file mode 100644 index 0000000..4210668 --- /dev/null +++ b/src/parse/parse.rs @@ -0,0 +1,204 @@ +#![allow(clippy::type_complexity)] +use chumsky::{error, prelude::*, Stream}; +use std::fmt::{Display, Formatter, Result as FmtResult}; +use super::past::*; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +pub enum Delim { Paren, Brack, Brace } + +#[derive(Clone, Debug, Eq, Hash, PartialEq)] +pub enum Token { + Num(i64), Str(String), Bool(bool), Sym(String), + + Add, Sub, Mul, Div, Mod, + Eq, Neq, Lt, Gt, Lte, Gte, + And, Or, Not, + + Assign, Comma, Colon, Semicolon, + Open(Delim), Close(Delim), + Lambda, Arrow, + + Let, Func, +} + +impl Display for Token { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self { + Token::Num(n) => write!(f, "{}", n), + Token::Str(s) => write!(f, "\"{}\"", s), + Token::Bool(b) => write!(f, "{}", b), + Token::Sym(s) => write!(f, "{}", s), + + Token::Add => write!(f, "+"), + Token::Sub => write!(f, "-"), + Token::Mul => write!(f, "*"), + Token::Div => write!(f, "/"), + Token::Mod => write!(f, "%"), + Token::Eq => write!(f, "=="), + Token::Neq => write!(f, "!="), + Token::Lt => write!(f, "<"), + Token::Gt => write!(f, ">"), + Token::Lte => write!(f, "<="), + Token::Gte => write!(f, ">="), + Token::And => write!(f, "&&"), + Token::Or => write!(f, "||"), + Token::Not => write!(f, "!"), + + Token::Assign => write!(f, "="), + Token::Comma => write!(f, ","), + Token::Colon => write!(f, ":"), + Token::Semicolon => write!(f, ";"), + Token::Open(d) => write!(f, "{}", match d { + Delim::Paren => "(", + Delim::Brack => "[", + Delim::Brace => "{", + }), + Token::Close(d) => write!(f, "{}", match d { + Delim::Paren => ")", + Delim::Brack => "]", + Delim::Brace => "}", + }), + Token::Lambda => write!(f, "\\"), + Token::Arrow => write!(f, "->"), + + Token::Let => write!(f, "let"), + Token::Func => write!(f, "func"), + } + } +} + +pub type Span = std::ops::Range; +pub type Spanned = (T, Span); + +pub fn lexer() -> impl Parser, Error = Simple> { + let num = text::int(10) + .map(|s: String| Token::Num(s.parse().unwrap())); + + let string = just('"') + .ignore_then(filter(|c| *c != '"').repeated()) + .then_ignore(just('"')) + .collect::() + .map(Token::Str); + + let symbol = choice(( + just("->").to(Token::Arrow), + + just('+').to(Token::Add), + just('-').to(Token::Sub), + just('*').to(Token::Mul), + just('/').to(Token::Div), + just('%').to(Token::Mod), + just("==").to(Token::Eq), + just("!=").to(Token::Neq), + just("<=").to(Token::Lte), + just(">=").to(Token::Gte), + just('<').to(Token::Lt), + just('>').to(Token::Gt), + just("&&").to(Token::And), + just("||").to(Token::Or), + just('!').to(Token::Not), + + just('=').to(Token::Assign), + just(',').to(Token::Comma), + just(':').to(Token::Colon), + just(';').to(Token::Semicolon), + just('\\').to(Token::Lambda), + )); + + let delim = choice(( + just('(').to(Token::Open(Delim::Paren)), + just(')').to(Token::Close(Delim::Paren)), + just('[').to(Token::Open(Delim::Brack)), + just(']').to(Token::Close(Delim::Brack)), + just('{').to(Token::Open(Delim::Brace)), + just('}').to(Token::Close(Delim::Brace)), + )); + + let kw = text::ident() + .map(|s: String| match s.as_str() { + "true" => Token::Bool(true), + "false" => Token::Bool(false), + "let" => Token::Let, + "func" => Token::Func, + _ => Token::Sym(s), + }); + + let token = num + .or(string) + .or(symbol) + .or(delim) + .or(kw) + .map_with_span(move |token, span| (token, span)) + .padded() + .recover_with(skip_then_retry_until([])); + + let comments = just('/') + .then_ignore( + just('*') + .ignore_then(take_until(just("*/")).ignored()) + .or(just('/').ignore_then(none_of('\n').ignored().repeated().ignored())), + ) + .padded() + .ignored() + .repeated(); + + token + .padded_by(comments) + .repeated() + .padded() + .then_ignore(end()) +} + +pub fn lex(src: String) -> (Option>, Vec>) { + let (tokens, lex_error) = lexer().parse_recovery(src.as_str()); + (tokens, lex_error) +} + +pub trait P = chumsky::Parser> + Clone; + +pub fn literal_parser() -> impl P { + filter_map(|span, token| match token { + Token::Num(i) => Ok(PLiteral::Num(i)), + Token::Bool(b) => Ok(PLiteral::Bool(b)), + Token::Str(s) => Ok(PLiteral::Str(s)), + _ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))), + }) + .labelled("literal") +} + +pub fn symbol_parser() -> impl P { + filter_map(|span, token| match token { + Token::Sym(s) => Ok(s), + _ => Err(Simple::expected_input_found(span, Vec::new(), Some(token))), + }) + .labelled("symbol") +} + +pub fn nested_parser<'a, T: 'a>( + parser: impl P + 'a, + delim: Delim, + f: impl Fn(Span) -> T + Clone + 'a, +) -> impl P + 'a { + parser + .delimited_by(just(Token::Open(delim)), just(Token::Close(delim))) + .recover_with(nested_delimiters( + Token::Open(delim), + Token::Close(delim), + [ + ( + Token::Open(Delim::Paren), + Token::Close(Delim::Paren), + ), + ( + Token::Open(Delim::Brack), + Token::Close(Delim::Brack), + ), + ( + Token::Open(Delim::Brace), + Token::Close(Delim::Brace), + ), + ], + f, + )) + .boxed() +} diff --git a/src/parse/past.rs b/src/parse/past.rs new file mode 100644 index 0000000..4c02d9c --- /dev/null +++ b/src/parse/past.rs @@ -0,0 +1,40 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; +use crate::trans::ty::*; + +#[derive(Clone, Debug)] +pub enum PUnaryOp { + Neg, + Not, +} + +#[derive(Clone, Debug)] +pub enum PBinaryOp { + Add, Sub, Mul, Div, Mod, + Eq, Neq, Lt, Gt, Lte, Gte, + And, Or, +} + +#[derive(Clone, Debug)] +pub enum PLiteral { Num(i64), Str(String), Bool(bool) } + +/// Enum to represent a parsed expression +#[derive(Clone, Debug)] +pub enum PExpr { + Lit(PLiteral), + Sym(String), + + Vec(Vec), + + UnaryOp(PUnaryOp, Box), + BinaryOp(PBinaryOp, Box, Box), + + Call(Box, Vec), + Lambda { + args: Vec<(String, Type)>, + body: Box, + }, + Let { + vars: Vec<(String, Type, Self)>, + body: Box, + } +} \ No newline at end of file diff --git a/src/trans/ast.rs b/src/trans/ast.rs new file mode 100644 index 0000000..f3485bc --- /dev/null +++ b/src/trans/ast.rs @@ -0,0 +1,67 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; +use super::ty::Type; + +#[derive(Clone, Debug)] +pub enum UnaryOp { + Neg, + Not, +} + +#[derive(Clone, Debug)] +pub enum BinaryOp { + Add, Sub, Mul, Div, Mod, + Eq, Neq, Lt, Gt, Lte, Gte, + And, Or, +} + +#[derive(Clone, Debug)] +pub enum Literal { + Num(i64), Str(String), Bool(bool), +} + +/// Enum to represent internal expression +#[derive(Clone, Debug)] +pub enum Expr { + Lit(Literal), + Sym(String), + + UnaryOp(UnaryOp, Box), + BinaryOp(BinaryOp, Box, Box), + + Call(Box, Vec), + Lambda { + args: Vec<(String, Type)>, + body: Box, + }, +} + +impl Display for Expr { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self { + Expr::Lit(l) => match l { + Literal::Num(n) => write!(f, "{}", n), + Literal::Str(s) => write!(f, "\"{}\"", s), + Literal::Bool(b) => write!(f, "{}", b), + }, + Expr::Sym(s) => write!(f, "{}", s), + + Expr::UnaryOp(op, e) => write!(f, "({:?} {})", op, e), + Expr::BinaryOp(op, e1, e2) => write!(f, "({:?} {} {})", op, e1, e2), + + Expr::Call(c, args) => { + write!(f, "({}", c)?; + for arg in args { + write!(f, " {}", arg)?; + } + write!(f, ")") + }, + Expr::Lambda { args, body } => { + write!(f, "(lambda ")?; + for (name, ty) in args { + write!(f, "[{} {}]", name, ty)?; + } + write!(f, " {})", body) + }, + } + } +} \ No newline at end of file diff --git a/src/trans/js.rs b/src/trans/js.rs new file mode 100644 index 0000000..06c654f --- /dev/null +++ b/src/trans/js.rs @@ -0,0 +1,72 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; +use super::ty::Type; + +#[derive(Clone, Debug)] +pub enum JSLiteral { Num(i64), Str(String), Bool(bool) } + +/// Enum to represent javascript expression +#[derive(Clone, Debug)] +pub enum JSExpr { + Lit(JSLiteral), + Sym(String), + + Op(&'static str, Box, Option>), + + Call(Box, Vec), + Method(Box, String, Vec), + Lambda { + args: Vec<(String, Type)>, + body: Box, + }, +} + +impl Display for JSExpr { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self { + JSExpr::Lit(l) => match l { + JSLiteral::Num(n) => write!(f, "{}", n), + JSLiteral::Str(s) => write!(f, "'{}'", s), + JSLiteral::Bool(b) => write!(f, "{}", b), + }, + JSExpr::Sym(s) => write!(f, "{}", s), + + JSExpr::Op(op, lhs, rhs) => { + match rhs { + Some(rhs) => write!(f, "({} {} {})", lhs, op, rhs), + None => write!(f, "({} {})", op, lhs), + } + } + + JSExpr::Call(c, args) => { + write!(f, "{}(", c)?; + for (i, arg) in args.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", arg)?; + } + write!(f, ")") + }, + JSExpr::Method(c, m, args) => { + write!(f, "{}.{}(", c, m)?; + for (i, arg) in args.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", arg)?; + } + write!(f, ")") + }, + JSExpr::Lambda { args, body } => { + write!(f, "((")?; + for (i, (name, _ty)) in args.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", name)?; + } + write!(f, ") => {})", body) + }, + } + } +} \ No newline at end of file diff --git a/src/trans/low.rs b/src/trans/low.rs new file mode 100644 index 0000000..224b591 --- /dev/null +++ b/src/trans/low.rs @@ -0,0 +1,129 @@ +use crate::parse::past::{PExpr, PLiteral, PBinaryOp, PUnaryOp}; +use super::{ + ast::{Expr, Literal, BinaryOp, UnaryOp}, + js::{JSExpr, JSLiteral}, +}; + +pub fn translate_expr(expr: PExpr) -> Expr { + match expr { + PExpr::Lit(l) => Expr::Lit(match l { + PLiteral::Num(n) => Literal::Num(n), + PLiteral::Str(s) => Literal::Str(s), + PLiteral::Bool(b) => Literal::Bool(b), + }), + PExpr::Sym(s) => Expr::Sym(s), + + PExpr::UnaryOp(op, e) => Expr::UnaryOp(match op { + PUnaryOp::Neg => UnaryOp::Neg, + PUnaryOp::Not => UnaryOp::Not, + }, Box::new(translate_expr(*e))), + PExpr::BinaryOp(op, e1, e2) => Expr::BinaryOp( + match op { + PBinaryOp::Add => BinaryOp::Add, + PBinaryOp::Sub => BinaryOp::Sub, + PBinaryOp::Mul => BinaryOp::Mul, + PBinaryOp::Div => BinaryOp::Div, + PBinaryOp::Mod => BinaryOp::Mod, + + PBinaryOp::Eq => BinaryOp::Eq, + PBinaryOp::Neq => BinaryOp::Neq, + + PBinaryOp::Lt => BinaryOp::Lt, + PBinaryOp::Gt => BinaryOp::Gt, + PBinaryOp::Lte => BinaryOp::Lte, + PBinaryOp::Gte => BinaryOp::Gte, + + PBinaryOp::And => BinaryOp::And, + PBinaryOp::Or => BinaryOp::Or, + }, + Box::new(translate_expr(*e1)), + Box::new(translate_expr(*e2)), + ), + + PExpr::Call(f, args) => Expr::Call( + Box::new(translate_expr(*f)), + args.into_iter().map(translate_expr).collect(), + ), + PExpr::Lambda { args, body } => Expr::Lambda { + args, + body: Box::new(translate_expr(*body)), + }, + PExpr::Let { vars, body } => { + let mut expr = *body; // The expression we're building up + for (name, ty, val) in vars.into_iter().rev() { // Reverse so we can build up the lambda + // e.g.: let x : t = e1 in e2 end => (lambda (x : t) = e2)(e1) + + // Build up the lambda + expr = PExpr::Lambda { + args: vec![(name, ty)], + body: Box::new(expr), + }; + // Call the lambda with the value + expr = PExpr::Call(Box::new(expr), vec![val]); + } + + translate_expr(expr) + } + } +} + +pub fn translate_js(expr: Expr) -> JSExpr { + match expr { + Expr::Lit(l) => match l { + Literal::Num(n) => JSExpr::Lit(JSLiteral::Num(n)), + Literal::Str(s) => JSExpr::Lit(JSLiteral::Str(s)), + Literal::Bool(b) => JSExpr::Lit(JSLiteral::Bool(b)), + }, + Expr::Sym(s) => JSExpr::Sym(s), + + Expr::UnaryOp(op, e) => JSExpr::Op(match op { + UnaryOp::Neg => "-", + UnaryOp::Not => "!", + }, Box::new(translate_js(*e)), None), + Expr::BinaryOp(op, e1, e2) => JSExpr::Op(match op { + BinaryOp::Add => "+", + BinaryOp::Sub => "-", + BinaryOp::Mul => "*", + BinaryOp::Div => "/", + BinaryOp::Mod => "%", + + BinaryOp::Eq => "==", + BinaryOp::Neq => "!=", + BinaryOp::Lt => "<", + BinaryOp::Gt => ">", + BinaryOp::Lte => "<=", + BinaryOp::Gte => ">=", + + BinaryOp::And => "&&", + BinaryOp::Or => "||", + }, Box::new(translate_js(*e1)), Some(Box::new(translate_js(*e2)))), + + Expr::Call(f, args) => { + match *f { + Expr::Sym(ref s) => { + match s.as_str() { + "println" => { + JSExpr::Method( + Box::new(JSExpr::Sym("console".to_string())), + "log".to_string(), + args.into_iter().map(translate_js).collect(), + ) + }, + _ => JSExpr::Call( + Box::new(translate_js(*f)), + args.into_iter().map(translate_js).collect(), + ), + } + }, + _ => JSExpr::Call( + Box::new(translate_js(*f)), + args.into_iter().map(translate_js).collect(), + ), + } + } + Expr::Lambda { args, body } => JSExpr::Lambda { + args, + body: Box::new(translate_js(*body)), + }, + } +} \ No newline at end of file diff --git a/src/trans/mod.rs b/src/trans/mod.rs new file mode 100644 index 0000000..e0d5a1b --- /dev/null +++ b/src/trans/mod.rs @@ -0,0 +1,4 @@ +pub mod ty; +pub mod ast; +pub mod js; +pub mod low; \ No newline at end of file diff --git a/src/trans/ty.rs b/src/trans/ty.rs new file mode 100644 index 0000000..74988da --- /dev/null +++ b/src/trans/ty.rs @@ -0,0 +1,29 @@ +use std::fmt::{Display, Formatter, Result as FmtResult}; + +#[derive(Clone, Debug)] +pub enum Type { + Num, Str, Bool, + Fun(Vec, Box), + Unknown, +} + +impl Display for Type { + fn fmt(&self, f: &mut Formatter) -> FmtResult { + match self { + Type::Num => write!(f, "num"), + Type::Str => write!(f, "str"), + Type::Bool => write!(f, "bool"), + Type::Fun(args, ret) => { + write!(f, "(")?; + for (i, arg) in args.iter().enumerate() { + if i > 0 { + write!(f, ", ")?; + } + write!(f, "{}", arg)?; + } + write!(f, ") -> {}", ret) + }, + Type::Unknown => write!(f, "unknown"), + } + } +} \ No newline at end of file diff --git a/vm/Cargo.lock b/vm/Cargo.lock deleted file mode 100644 index 40bc055..0000000 --- a/vm/Cargo.lock +++ /dev/null @@ -1,16 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "mecha" -version = "0.1.0" -dependencies = [ - "fnv", -] diff --git a/vm/Cargo.toml b/vm/Cargo.toml deleted file mode 100644 index 9f74c7c..0000000 --- a/vm/Cargo.toml +++ /dev/null @@ -1,7 +0,0 @@ -[package] -name = "vm" -version = "0.1.0" -edition = "2021" - -[dependencies] -fnv = "1.0.7" diff --git a/vm/src/exec.rs b/vm/src/exec.rs deleted file mode 100644 index cc2a318..0000000 --- a/vm/src/exec.rs +++ /dev/null @@ -1,342 +0,0 @@ -use crate::model::*; -use std::{cell::RefCell, rc::Rc}; - -#[derive(Clone, Debug)] -pub struct Executor { - pub stack: Vec, - pub env: Rc>, - pub outer_env: Option>>, - pub instrs: Vec, - pub ip: usize, -} - -#[derive(Debug)] -pub struct Error(String, usize); - -impl Error { - pub fn make>(msg: S, ip: usize) -> Self { - Self(msg.into(), ip) - } -} - -impl Executor { - pub fn new(instrs: Vec) -> Self { - Self { - stack: Vec::new(), - env: Rc::new(RefCell::new(Env::new())), - outer_env: None, - instrs, - ip: 0, - } - } - - pub fn run(&mut self) -> Result<(), Error> { - while self.ip < self.instrs.len() { - self.step()?; - self.ip += 1; - } - Ok(()) - } - - pub fn run_with Result<(), Error>>(&mut self, f: F) -> Result<(), Error> { - while self.ip < self.instrs.len() { - self.step()?; - self.ip += 1; - f(self)?; - } - Ok(()) - } - - fn err(&self, msg: &str) -> Error { - Error::make(msg, self.ip) - } - - fn push(&mut self, v: Value) -> Result<(), Error> { - self.stack.push(v); - Ok(()) - } - - fn pop(&mut self) -> Result { - self.stack.pop().ok_or_else(|| self.err("stack underflow")) - } - - fn get(&self, name: &str) -> Result { - self.env - .borrow() - .get(name) - .ok_or_else(|| self.err(format!("unbound variable: {}", name).as_str())) - } - - fn set(&mut self, name: &str, v: Value) -> Result<(), Error> { - // Set the variable in the current environment if it is defined - if self.env.borrow().binds.contains_key(name) { - self.env.borrow_mut().binds.insert(name.to_string(), v); - // If it is not defined in the current environment then try the outer environment - } else if let Some(env) = &self.outer_env { - if env.borrow().binds.contains_key(name) { - env.borrow_mut().binds.insert(name.to_string(), v); - } else { - // If not then define it in the current environment - self.env.borrow_mut().binds.insert(name.to_string(), v); - } - } else { - self.env.borrow_mut().binds.insert(name.to_string(), v); - } - Ok(()) - } - - fn step(&mut self) -> Result<(), Error> { - let instr = self.instrs.clone(); // TODO: maybe don't clone here - let instr = instr - .get(self.ip) - .ok_or_else(|| self.err("invalid instruction pointer"))?; - - macro_rules! impl_binop { - ($op:tt, $inp:ident, $ret:ident) => { - match (self.pop()?, self.pop()?) { - (Value::$inp(a), Value::$inp(b)) => { - self.stack.push(Value::$ret(a $op b)); - } - _ => return Err(Error::make(format!("can't apply operator to non-{}", stringify!($inp)).as_str(), self.ip)), - } - }; - } - - match instr { - Instr::NumPush(x) => { - self.push(Value::Num(*x))?; - } - Instr::NumAdd => impl_binop!(+, Num, Num), - Instr::NumSub => impl_binop!(-, Num, Num), - Instr::NumMul => impl_binop!(*, Num, Num), - Instr::NumDiv => impl_binop!(/, Num, Num), - Instr::NumMod => impl_binop!(%, Num, Num), - Instr::NumEq => impl_binop!(==, Num, Bool), - Instr::NumNe => impl_binop!(!=, Num, Bool), - Instr::NumLt => impl_binop!(<, Num, Bool), - Instr::NumGt => impl_binop!(>, Num, Bool), - Instr::NumLe => impl_binop!(<=, Num, Bool), - Instr::NumGe => impl_binop!(>=, Num, Bool), - - Instr::BoolPush(x) => { - self.push(Value::Bool(*x))?; - } - Instr::BoolAnd => impl_binop!(&&, Bool, Bool), - Instr::BoolOr => impl_binop!(||, Bool, Bool), - Instr::BoolNot => { - if let Value::Bool(b) = self.pop()? { - self.push(Value::Bool(!b))?; - } else { - return Err(Error::make("can't apply `not` to non-boolean", self.ip)); - } - } - - Instr::StrPush(x) => { - self.push(Value::Str(x.clone()))?; - } - Instr::StrConcat => { - if let (Value::Str(a), Value::Str(b)) = (self.pop()?, self.pop()?) { - self.push(Value::Str(a + &b))?; - } else { - return Err(Error::make("can't concatenate non-strings", self.ip)); - } - } - Instr::Pop => { - self.pop()?; - } - Instr::Dup => { - let v = self.pop()?; - self.push(v.clone())?; - self.push(v)?; - } - - Instr::ListMake(len) => { - let mut list = Vec::new(); - for _ in 0..*len { - list.push( - self.pop() - .map_err(|_| self.err("not enough arguments to make List"))?, - ); - } - list.reverse(); - self.push(Value::List(list))?; - } - Instr::ListGet(index) => { - if let Value::List(list) = self.pop()? { - let v = list - .get(*index) - .cloned() - .ok_or_else(|| self.err("index out of bounds"))?; - self.push(v)?; - } else { - return Err(Error::make("can't get from non-List", self.ip)); - } - } - Instr::ListSet(index) => { - if let Value::List(mut list) = self.pop()? { - let v = self.pop()?; - list.get_mut(*index) - .ok_or_else(|| self.err("index out of bounds"))? - .clone_from(&v); - self.push(Value::List(list))?; - } else { - return Err(Error::make("can't set in non-List", self.ip)); - } - } - Instr::ListLen => { - if let Value::List(list) = self.pop()? { - self.push(Value::Num(list.len() as i64))?; - } else { - return Err(Error::make("can't get length of non-List", self.ip)); - } - } - Instr::ListJoin => { - if let (Value::List(mut list1), Value::List(list2)) = (self.pop()?, self.pop()?) { - list1.extend(list2); - self.push(Value::List(list1))?; - } else { - return Err(Error::make("can't join non-Lists", self.ip)); - } - } - - Instr::FuncMake(args, instrs) => { - let closure = Func::new(args.to_vec(), Rc::clone(&self.env), instrs.clone()); - self.push(Value::Func(closure))?; - } - Instr::FuncApply => { - let v = self.pop()?; - if let Value::Func(closure) = v { - // Pop the arguments - let mut args = Vec::new(); - for _ in 0..closure.args.len() { - args.push( - self.pop() - .map_err(|_| self.err("not enough arguments to apply Function"))?, - ); - } - args.reverse(); - - self.stack.append(&mut closure.run(args)?); - } else { - return Err(Error::make( - format!("can't apply non-Function, got {:?}", v), - self.ip, - )); - } - } - Instr::FuncCall(name) => { - if let Value::Func(closure) = self.get(name)? { - let mut args = Vec::new(); - for _ in 0..closure.args.len() { - args.push( - self.pop() - .map_err(|_| self.err("not enough arguments to call Function"))?, - ); - } - args.reverse(); - - self.stack.append(&mut closure.run(args)?); - } else { - return Err(Error::make("can't call non-Function", self.ip)); - } - } - - Instr::Get(name) => { - let v = self.get(name)?; - self.push(v)?; - } - Instr::Set(name) => { - let v = self.pop()?; - self.set(name, v)?; - } - - Instr::Jump(n) => { - self.ip += n; - } - Instr::JumpIfFalse(n) => { - if let Value::Bool(b) = self.pop()? { - if !b { - self.ip += n; - } - } else { - return Err(Error::make("can't apply `if` to non-boolean", self.ip)); - } - } - - Instr::Print => { - let v = self.pop()?; - print!("{}", v); - } - Instr::PrintLn => { - let v = self.pop()?; - println!("{}", v); - } - } - - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn exec_expect(executor: &mut Executor, expected: Vec) { - match executor.run() { - Ok(_) => { - assert_eq!(executor.stack, expected); - } - Err(e) => panic!("{:?}", e), - } - } - - #[test] - fn test_sanity() { - let mut executor = Executor::new(vec![Instr::NumPush(1), Instr::NumPush(2), Instr::NumAdd]); - exec_expect(&mut executor, vec![Value::Num(3)]); - } - - #[test] - #[should_panic] - fn test_pop_underflow() { - let mut executor = Executor::new(vec![Instr::NumAdd]); - executor.run().unwrap(); - } - - #[test] - fn test_closure() { - let mut executor = Executor::new(vec![ - Instr::FuncMake( - vec![], - vec![ - Instr::NumPush(0), - Instr::Set("total".to_string()), - Instr::FuncMake( - vec![], - vec![ - Instr::Get("total".to_string()), - Instr::NumPush(1), - Instr::NumAdd, - Instr::Set("total".to_string()), - Instr::Get("total".to_string()), - ], - ), - Instr::Set("counter".to_string()), - Instr::Get("counter".to_string()), - ], - ), - Instr::FuncApply, - Instr::Set("tally".to_string()), - Instr::Get("tally".to_string()), - Instr::FuncApply, - Instr::Get("tally".to_string()), - Instr::FuncApply, - Instr::Get("tally".to_string()), - Instr::FuncApply, - ]); - exec_expect( - &mut executor, - vec![Value::Num(1), Value::Num(2), Value::Num(3)], - ); - } -} diff --git a/vm/src/lib.rs b/vm/src/lib.rs deleted file mode 100644 index ddb85ba..0000000 --- a/vm/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![allow(clippy::new_without_default)] -pub mod exec; -pub mod model; - -// fn _main() { -// let instrs = vec![ -// Instr::NumPush(34), -// Instr::NumPush(34), -// Instr::FuncMake( -// vec!["abc".to_string()], -// vec![ -// Instr::Get("abc".to_string()), -// Instr::NumPush(1), -// Instr::NumAdd, -// ], -// ), -// Instr::FuncApply, -// Instr::NumAdd, -// Instr::Print, -// ]; - -// // instrs.iter().for_each(|instr| { -// // println!( -// // "{}", -// // instr -// // .to_bytes() -// // .iter() -// // .map(|b| format!("{:02x}", b)) -// // .collect::>() -// // .join(" ") -// // ) -// // }); - -// let mut executor = Executor::new(instrs); -// match executor.run() { -// Ok(()) => (), -// Err(e) => println!("{:?}", e), -// } -// } diff --git a/vm/src/model.rs b/vm/src/model.rs deleted file mode 100644 index c90ae49..0000000 --- a/vm/src/model.rs +++ /dev/null @@ -1,359 +0,0 @@ -use crate::exec::{Error, Executor}; -use fnv::FnvHashMap; -use std::{ - cell::{Cell, RefCell}, - fmt::{Debug, Display}, - rc::Rc, -}; - -#[derive(Clone, Eq, PartialEq)] -pub enum Value { - Num(i64), - Bool(bool), - Str(String), - List(Vec), - Func(Func), -} - -impl Debug for Value { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Value::Num(n) => write!(f, "Num({})", n), - Value::Bool(b) => write!(f, "Bool({})", b), - Value::Str(s) => write!(f, "Str({})", s), - Value::List(xs) => write!(f, "List({:?})", xs), - Value::Func(c) => write!(f, "Func({})", c.args.len()), - } - } -} - -impl Display for Value { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - Value::Num(n) => write!(f, "{}", n), - Value::Bool(b) => write!(f, "{}", b), - Value::Str(s) => write!(f, "{}", s), - Value::List(xs) => write!( - f, - "[{}]", - xs.iter() - .map(|x| x.to_string()) - .collect::>() - .join(", ") - ), - Value::Func(_) => write!(f, ""), - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Env { - pub binds: FnvHashMap, - pub parent: Option>, -} - -impl Env { - pub fn new() -> Self { - Self { - binds: FnvHashMap::default(), - parent: None, - } - } - - pub fn new_with_parent(parent: Rc) -> Self { - Self { - binds: FnvHashMap::default(), - parent: Some(parent), - } - } - - pub fn get(&self, name: &str) -> Option { - // Get the value from the current environment first - // and then from the parent environment recursively - self.binds - .get(name) - .cloned() - .or_else(|| self.parent.as_ref().and_then(|p| p.get(name)).or(None)) - } - - pub fn set(&mut self, name: String, value: Value) { - // Set the value in the current environment - // The handling of deciding whether to create a new binding - // is done in the Executor - self.binds.insert(name, value); - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Func { - pub args: Vec, - pub env: Rc>, - pub instrs: Vec, -} - -impl Func { - pub fn new(args: Vec, env: Rc>, instrs: Vec) -> Self { - Self { args, env, instrs } - } - - pub fn run(self, args: Vec) -> Result, Error> { - // Create a new environment for the closure - let mut closure_env = Env::new(); - for (arg, val) in self.args.iter().zip(args) { - closure_env.binds.insert(arg.clone(), val); - } - // Set the parent to the current environment - closure_env.parent = Some(Rc::new(self.env.borrow().clone())); - - // Execute the closure - let mut new_executor = Executor { - stack: Vec::new(), - env: Rc::new(RefCell::new(closure_env)), - outer_env: Some(Rc::clone(&self.env)), - instrs: self.instrs, - ip: 0, - }; - new_executor.run()?; - Ok(new_executor.stack) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum Instr { - // Example: NumPush -34, NumPush 103, NumAdd - // 00 de ff ff ff ff ff ff ff - // 00 67 00 00 00 00 00 00 00 - // 01 - NumPush(i64), // 9 bytes: 1 byte for the enum, 8 bytes for the i64 - NumAdd, // ┐ 1 byte - NumSub, // │ - NumMul, // │ - NumDiv, // │ - NumMod, // │ - NumEq, // │ - NumNe, // │ - NumLt, // │ - NumGt, // │ - NumLe, // │ - NumGe, // ┘ - - BoolPush(bool), // 2 bytes: 1 byte for the enum, 1 byte for the bool - BoolAnd, // ┐ 1 byte - BoolOr, // │ - BoolNot, // ┘ - - // StrPush: - // ┌─┬───╶╶╶┐ - // │x│ s... [00] - // └─┴───╶╶╶┘ - // where x is the enum (1 byte) - // s is the string (n bytes) - // Example: StrPush "Hello, World!" - // [XX] [48 65 6c 6c 6f 2c 20 57 6f 72 6c 64 21] [00] - // └────┼────────────────────────────────────────┼─╼ enum - // └────────────────────────────────────────┼─╼ string - // └─╼ null delimiter - // Total of 15 bytes (1 + 13 + 1) - StrPush(String), // 1 + string.len() + 1 bytes - StrConcat, // 1 byte - - Pop, // ┐ 1 byte - Dup, // ┘ - - ListMake(usize), // ┐ 9 bytes: 1 byte for the enum, 8 bytes for the usize (64-bit) - ListGet(usize), // │ - ListSet(usize), // ┘ - ListLen, // ┐ 1 byte - ListJoin, // ┘ - - // FuncMake: - // ┌─┬───┬───┬─────╶╶╶┬──────╶╶╶╶╶ - // │x│ n │ m │ a... │ i... - // └─┴───┴───┴─────╶╶╶┴──────╶╶╶╶╶ - // where x is the enum (1 byte) - // n is the number of arguments (8 bytes) - // m is the number of instructions (8 bytes) - // a is the arguments (n bytes, null delimited) - // ╴╴┬──────┬────┬╶╶ - // │ s... │ 00 │ // For example: "a", "bc" -> [61 00 62 63 00] - // ╴╴┴──────┴────┴╶╶ - // i is the instructions (m bytes) - // Example: FuncMake ["x", "y"] [Get "x", Get "yz", NumAdd] - // [XX] [02 ..] [03 ..] [78 00 79 7a 00] [16 78 00 16 79 7a 00 01] - // └────┼───────┼───────┼────────────────┼─╼ enum - // └───────┼───────┼────────────────┼─╼ number of arguments - // └───────┼────────────────┼─╼ number of instructions - // └────────────────┼─╼ arguments (null delimited) - // └─╼ instructions - FuncMake(Vec, Vec), // 1 + 8 + 8 + args.len() + instrs.len() bytes - FuncApply, // 1 byte - FuncCall(String), // 1 + string.len() + 1 bytes - - Get(String), // ┐ 1 + string.len() + 1 bytes - Set(String), // ┘ - - Jump(usize), // ┐ 9 bytes: 1 byte for the enum, 8 bytes for the usize (64-bit) - JumpIfFalse(usize), // ┘ - - Print, // ┐ 1 byte - PrintLn, // ┘ -} - -static mut INSTR_INDEX: Cell = Cell::new(0); - -impl Instr { - pub fn size(&self) -> usize { - match self { - Instr::NumPush(_) => 1 + std::mem::size_of::(), - Instr::NumAdd - | Instr::NumSub - | Instr::NumMul - | Instr::NumDiv - | Instr::NumMod - | Instr::NumEq - | Instr::NumNe - | Instr::NumLt - | Instr::NumGt - | Instr::NumLe - | Instr::NumGe => 1, - - Instr::BoolPush(_) => 1 + std::mem::size_of::(), - Instr::BoolAnd | Instr::BoolOr | Instr::BoolNot => 1, - - Instr::StrPush(s) => 1 + s.len() + 1, - Instr::StrConcat => 1, - - Instr::Pop | Instr::Dup => 1, - - Instr::ListMake(_) | Instr::ListGet(_) | Instr::ListSet(_) => { - 1 + std::mem::size_of::() - } - Instr::ListLen | Instr::ListJoin => 1, - - Instr::FuncMake(args, instrs) => { - 1 + 8 - + 8 - + args.iter().map(|s| s.len() + 1).sum::() - + instrs.iter().map(|i| i.size()).sum::() - } - Instr::FuncApply => 1, - Instr::FuncCall(s) => 1 + s.len() + 1, - - Instr::Get(s) | Instr::Set(s) => 1 + s.len() + 1, - - Instr::Jump(_) | Instr::JumpIfFalse(_) => 1 + std::mem::size_of::(), - - Instr::Print | Instr::PrintLn => 1, - } - } - - pub fn to_bytes(&self) -> Vec { - // A macro that will return the next index and increment it - // so we don't have to rewrite all the first bytes again when - // we changes the order or add new instructions - macro_rules! index { - () => { - unsafe { - let i = INSTR_INDEX.get(); - INSTR_INDEX.set(i + 1); - i - } - }; - } - - let mut bytes = Vec::new(); - match self { - Instr::NumPush(n) => { - bytes.push(index!()); - bytes.extend(n.to_le_bytes()); - } - Instr::NumAdd - | Instr::NumSub - | Instr::NumMul - | Instr::NumDiv - | Instr::NumMod - | Instr::NumEq - | Instr::NumNe - | Instr::NumLt - | Instr::NumGt - | Instr::NumLe - | Instr::NumGe => bytes.push(index!()), - - Instr::BoolPush(b) => { - bytes.push(index!()); - bytes.push(*b as u8); - } - Instr::BoolAnd => bytes.push(index!()), - Instr::BoolOr => bytes.push(index!()), - Instr::BoolNot => bytes.push(index!()), - - Instr::StrPush(s) => { - bytes.push(index!()); - bytes.extend(s.as_bytes()); - bytes.push(0x00); - } - Instr::StrConcat => bytes.push(index!()), - - Instr::Pop => bytes.push(index!()), - Instr::Dup => bytes.push(index!()), - - Instr::ListMake(n) => { - bytes.push(index!()); - bytes.extend(n.to_le_bytes()); - } - Instr::ListGet(n) => { - bytes.push(index!()); - bytes.extend(n.to_le_bytes()); - } - Instr::ListSet(n) => { - bytes.push(index!()); - bytes.extend(n.to_le_bytes()); - } - Instr::ListLen => bytes.push(index!()), - Instr::ListJoin => bytes.push(index!()), - - Instr::FuncMake(args, instrs) => { - bytes.push(index!()); - bytes.extend((args.len() as u64).to_le_bytes()); - bytes.extend((instrs.len() as u64).to_le_bytes()); - for arg in args { - bytes.extend(arg.as_bytes()); - bytes.push(0x00); - } - for instr in instrs { - bytes.extend(instr.to_bytes()); - } - } - Instr::FuncApply => bytes.push(index!()), - Instr::FuncCall(s) => { - bytes.push(index!()); - bytes.extend(s.as_bytes()); - bytes.push(0x00); - } - - Instr::Get(s) => { - bytes.push(index!()); - bytes.extend(s.as_bytes()); - bytes.push(0x00); - } - Instr::Set(s) => { - bytes.push(index!()); - bytes.extend(s.as_bytes()); - bytes.push(0x00); - } - - Instr::Jump(n) => { - bytes.push(index!()); - bytes.extend(n.to_le_bytes()); - } - Instr::JumpIfFalse(n) => { - bytes.push(index!()); - bytes.extend(n.to_le_bytes()); - } - - Instr::Print => bytes.push(index!()), - Instr::PrintLn => bytes.push(index!()), - } - bytes - } -}