From fb3f384c844efbedd4a8bcbab71328bd59a2285d Mon Sep 17 00:00:00 2001 From: me Date: Sat, 13 Dec 2025 16:08:41 +0200 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 551 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 27 ++ LICENSE | 202 ++++++++++++++ Makefile | 11 + assets/fonts/monogram.ttf | Bin 0 -> 10468 bytes readme.md | 48 ++++ rustfmt.toml | 1 + src/ast/helpers.rs | 47 ++++ src/ast/mod.rs | 6 + src/ast/types.rs | 232 ++++++++++++++++ src/interpret/mod.rs | 313 ++++++++++++++++++++++ src/interpret/types.rs | 168 ++++++++++++ src/lib.rs | 3 + src/main.rs | 27 ++ src/runtime/mod.rs | 5 + src/runtime/runtime.rs | 32 +++ src/runtime/types.rs | 19 ++ 18 files changed, 1693 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 assets/fonts/monogram.ttf create mode 100644 readme.md create mode 100644 rustfmt.toml create mode 100644 src/ast/helpers.rs create mode 100644 src/ast/mod.rs create mode 100644 src/ast/types.rs create mode 100644 src/interpret/mod.rs create mode 100644 src/interpret/types.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/runtime/mod.rs create mode 100644 src/runtime/runtime.rs create mode 100644 src/runtime/types.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9975e2a --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,551 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "ayin-lang" +version = "0.1.0" +dependencies = [ + "env_logger", + "log", + "macroquad", + "thiserror", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "fontdue" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e57e16b3fe8ff4364c0661fdaac543fb38b29ea9bc9c2f45612d90adf931d2b" +dependencies = [ + "hashbrown", + "ttf-parser", +] + +[[package]] +name = "glam" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e05e7e6723e3455f4818c7b26e855439f7546cf617ef669d1adedb8669e5cb9" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", + "png", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "jiff" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49cce2b81f2098e7e3efc35bc2e0a6b7abec9d34128283d7a26fa8f32a6dbb35" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "macroquad" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2befbae373456143ef55aa93a73594d080adfb111dc32ec96a1123a3e4ff4ae" +dependencies = [ + "fontdue", + "glam", + "image", + "macroquad_macro", + "miniquad", + "quad-rand", +] + +[[package]] +name = "macroquad_macro" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64b1d96218903768c1ce078b657c0d5965465c95a60d2682fd97443c9d2483dd" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "miniquad" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb3e758e46dbc45716a8a49ca9edc54b15bcca826277e80b1f690708f67f9e3" +dependencies = [ + "libc", + "ndk-sys", + "objc-rs", + "winapi", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "ndk-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "objc-rs" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a1e7069a2525126bf12a9f1f7916835fafade384fb27cabf698e745e2a1eb8" +dependencies = [ + "malloc_buf", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "portable-atomic" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quad-rand" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a651516ddc9168ebd67b24afd085a718be02f8858fe406591b013d101ce2f40" + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "ttf-parser" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c591d83f69777866b9126b24c6dd9a18351f177e49d625920d19f989fd31cf8" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +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" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d5ab67a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "ayin-lang" +version = "0.1.0" +edition = "2024" +authors = ["alloca "] +description = "Programming language for live-coding games" +license = "Apache-2.0" +readme = "readme.md" +default-run = "ayin" + +[lib] +name = "ayin" +path = "src/lib.rs" + +[[bin]] +name = "ayin" +path = "src/main.rs" + +[dependencies] +thiserror = "1" +env_logger = "0.11.8" +log = "0.4.27" +macroquad = "0.4.14" + +[workspace.lints.clippy] +all = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c274041 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 alloca + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e559ab7 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY: watch +watch: + cargo watch --clear -x 'clippy' -x 'test --all' + +.PHONY: run +run: + cargo run + +.PHONY: wasm +wasm: + cargo build --target wasm32-unknown-unknown diff --git a/assets/fonts/monogram.ttf b/assets/fonts/monogram.ttf new file mode 100644 index 0000000000000000000000000000000000000000..aceaebab76d4ccb69d59241d154216f6f53d7e8d GIT binary patch literal 10468 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 Name { + Name(str.into()) +} + +pub fn label(str: &str) -> Label { + Label(str.into()) +} + +pub fn var(str: &str) -> Expr { + Expr::Var(name(str)) +} + +pub fn record(rec: Vec<(&str, Value)>) -> Value { + Value::Record(Record({ + rec.into_iter() + .map(|(lbl, val)| (Label(lbl.into()), val)) + .collect() + })) +} + +pub fn record_expr(rec: Vec<(&str, Value)>) -> Expr { + Expr::Value(record(rec)) +} + +pub fn assign(name: &str, expr: Expr) -> Statement { + Statement::Assign { + name: name.into(), + expr, + } +} + +pub fn stmt_expr(expr: Expr) -> Statement { + Statement::Expr(expr) +} + +pub fn define_expr(name: &str, expr: Expr) -> Definition { + Definition::Expr { + name: name.into(), + expr, + } +} + +pub fn func(func: Fn) -> Expr { + Expr::Func(Box::new(func)) +} diff --git a/src/ast/mod.rs b/src/ast/mod.rs new file mode 100644 index 0000000..aaabf38 --- /dev/null +++ b/src/ast/mod.rs @@ -0,0 +1,6 @@ +//! Ast definition for Ayin. + +pub mod helpers; +mod types; + +pub use types::*; diff --git a/src/ast/types.rs b/src/ast/types.rs new file mode 100644 index 0000000..2c5e03b --- /dev/null +++ b/src/ast/types.rs @@ -0,0 +1,232 @@ +//! Ast definition for Ayin. + +use std::collections::BTreeMap; + +/// An expression. +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub enum Expr { + Value(Value), + Var(Name), + Access { + expr: Box, + field: Label, + }, + MethodCall { + expr: Box, + method: MethodName, + args: Vec, + }, + Func(Box), + FunCall { + func: Box, + args: Vec, + }, + Block(Vec), + If { + condition: Box, + then: Box, + r#else: Box, + }, +} + +/// A statement. +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub enum Statement { + Expr(Expr), + Assign { name: Name, expr: Expr }, +} + +/// A reduced value. +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub enum Value { + Int(i64), + Float(f64), + String(String), + Boolean(bool), + Record(Record), + Closure { func: Box, env: EnvName }, + Deferred { expr: Box, env: EnvName }, +} + +/// An anonymous function. +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub struct Fn { + pub args: Vec, + pub body: Expr, +} + +/// A function argument. +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub struct Arg { + pub name: Name, + pub r#type: Type, +} + +/// A type. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] +pub enum Type { + Named(TypeName), + App { typefun: Name, args: Vec }, +} + +/// A symbol. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] +pub struct Name(pub String); + +/// A type name. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] +pub struct TypeName(pub String); + +/// A method name. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] +pub struct MethodName(pub String); + +/// A field. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] +pub struct Label(pub String); + +/// A reference to the environment. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] +pub struct EnvName(pub String); + +#[derive(PartialEq, PartialOrd, Debug, Clone)] +pub struct Record(pub BTreeMap); + +/// A Program. +#[derive(PartialEq, PartialOrd, Debug)] +pub struct Program(pub Vec); + +/// A definition. +#[derive(PartialEq, PartialOrd, Debug)] +pub enum Definition { + Function { name: Name, func: Fn }, + Expr { name: Name, expr: Expr }, +} + +// ----- // +// Impls // +// ----- // + +impl Definition { + pub fn name(&self) -> &Name { + match self { + Definition::Function { name, .. } => name, + Definition::Expr { name, .. } => name, + } + } +} + +impl From<&str> for Name { + fn from(value: &str) -> Name { + Name(value.into()) + } +} + +impl From<&str> for TypeName { + fn from(value: &str) -> TypeName { + TypeName(value.into()) + } +} + +impl From<&str> for MethodName { + fn from(value: &str) -> MethodName { + MethodName(value.into()) + } +} + +impl From<&str> for Label { + fn from(value: &str) -> Label { + Label(value.into()) + } +} + +impl From<&str> for Expr { + fn from(value: &str) -> Expr { + Expr::Var(value.into()) + } +} + +impl From> for Value { + fn from(rec: Vec<(&str, Value)>) -> Value { + Value::Record(Record({ + rec.into_iter() + .map(|(lbl, val)| (Label(lbl.into()), val)) + .collect() + })) + } +} + +impl From> for Expr { + fn from(rec: Vec<(&str, Value)>) -> Expr { + Expr::Value(rec.into()) + } +} + +impl Expr { + pub fn get_type(&self) -> &Type { + todo!() + } + pub fn field(self, label: &str) -> Expr { + Expr::Access { + expr: Box::new(self), + field: label.into(), + } + } + pub fn call(self, args: Vec) -> Expr { + Expr::FunCall { + func: Box::new(self), + args, + } + } +} + +impl From for Value { + fn from(i: i64) -> Value { + Value::Int(i) + } +} + +impl From for Expr { + fn from(i: i64) -> Expr { + Expr::Value(i.into()) + } +} + +impl From for Value { + fn from(b: bool) -> Value { + Value::Boolean(b) + } +} + +impl From for Expr { + fn from(b: bool) -> Expr { + Expr::Value(b.into()) + } +} + +impl From> for Program { + fn from(defs: Vec) -> Program { + Program(defs) + } +} + +impl From> for Expr { + fn from(stmts: Vec) -> Expr { + Expr::Block(stmts) + } +} + +impl Record { + pub fn get(&self, label: &Label) -> Result<&Value, Error> { + match self.0.get(label) { + Some(v) => Ok(v), + None => Err(Error::LabelNotFound(label.clone())), + } + } +} + +#[derive(PartialEq, Debug, thiserror::Error)] +pub enum Error { + #[error("Label not found: {0:?}")] + LabelNotFound(Label), +} diff --git a/src/interpret/mod.rs b/src/interpret/mod.rs new file mode 100644 index 0000000..fa635a8 --- /dev/null +++ b/src/interpret/mod.rs @@ -0,0 +1,313 @@ +//! Interpreter for Z. + +pub mod types; + +use types::*; + +use crate::ast; + +pub fn run(program: ast::Program) -> Result { + let env_name = ast::EnvName("global".to_string()); + let mut state = State::new("global".into()); + + defs_to_env(program.0, &env_name, &mut state.envs)?; + + let main = ast::Expr::Value(state.envs.get(&env_name)?.get(&"main".into())?.clone()); + + let env: Env = state.envs.get(&env_name)?.clone(); + let result = eval_expr(&env, &mut state, &main)?; + println!("{:?}", result); + + Ok(result) +} + +fn eval_statement( + expr_env: &mut Env, + state: &mut State, + statement: &ast::Statement, +) -> Result { + match statement { + ast::Statement::Expr(expr) => eval_expr(expr_env, state, expr), + ast::Statement::Assign { name, expr } => { + let result = eval_expr(expr_env, state, expr)?; + expr_env.insert(name.clone(), result.clone()); + Ok(result) + } + } +} + +fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result { + match expr { + ast::Expr::If { + condition, + then, + r#else, + } => match eval_expr(expr_env, state, condition)? { + ast::Value::Boolean(condition) => { + if condition { + eval_expr(expr_env, state, then) + } else { + eval_expr(expr_env, state, r#else) + } + } + v => Err(Error::NotABoolean(v)), + }, + ast::Expr::Func(func) => { + let env_name = state.generate_env(expr_env.clone())?; + Ok(ast::Value::Closure { + func: func.clone(), + env: env_name, + }) + } + ast::Expr::Block(statements) => { + let mut block_env = expr_env.clone(); + if let Some(last) = statements.iter().try_fold(None, |_, statement| { + let result = eval_statement(&mut block_env, state, statement)?; + Ok::, Error>(Some(result)) + })? { + Ok(last) + } else { + Err(Error::LastStatementNotAnExpr) + } + } + ast::Expr::Access { expr, field } => match eval_expr(expr_env, state, expr)? { + ast::Value::Record(record) => eval_expr( + expr_env, + state, + &ast::Expr::Value(record.get(field).map_err(Error::from)?.clone()), + ), + v => Err(Error::NotARecord(v)), + }, + ast::Expr::Var(var) => { + let value = expr_env.get(var)?.clone(); + eval_expr(expr_env, state, &ast::Expr::Value(value)) + } + ast::Expr::MethodCall { expr, method, args } => { + let r#type = expr.get_type(); + let method = state.method_env.get(r#type, method)?; + match eval_expr(expr_env, state, &ast::helpers::func(method.clone()))? { + ast::Value::Closure { func, env } => { + let mut closure_env: Env = state.envs.get(&env)?.clone(); + + if func.args.len() != args.len() + 1 { + Err(Error::ArgumentsMismatch)?; + } + + let mut args = args.clone(); + args.push(*expr.clone()); + + for (arg, e) in func.args.into_iter().zip(args.iter()) { + let evalled = eval_expr(&closure_env, state, e)?; + closure_env.0.insert(arg.name, evalled); + } + eval_expr(&closure_env, state, &func.body) + } + v => Err(Error::NotAFunction(v)), + } + } + + ast::Expr::FunCall { func, args } => match eval_expr(expr_env, state, func)? { + ast::Value::Closure { func, env } => { + let mut closure_env: Env = state.envs.get(&env)?.clone(); + + if func.args.len() != args.len() { + Err(Error::ArgumentsMismatch)?; + } + + for (arg, e) in func.args.into_iter().zip(args.iter()) { + let evalled = eval_expr(&closure_env, state, e)?; + closure_env.0.insert(arg.name, evalled); + } + eval_expr(&closure_env, state, &func.body) + } + v => Err(Error::NotAFunction(v)), + }, + ast::Expr::Value(v) => match v { + ast::Value::Deferred { expr, env } => { + let closure_env: Env = state.envs.get(env)?.clone(); + eval_expr(&closure_env, state, expr) + } + _ => Ok(v.clone()), + }, + } +} + +fn defs_to_env( + defs: Vec, + env_name: &ast::EnvName, + envs: &mut Envs, +) -> Result<(), Error> { + let mut env = Env::new(); + + for def in defs { + let (name, closure) = match def { + ast::Definition::Function { func, name } => ( + name, + ast::Value::Closure { + func: Box::new(func), + env: env_name.clone(), + }, + ), + ast::Definition::Expr { expr, name } => ( + name, + ast::Value::Deferred { + expr: Box::new(expr.clone()), + env: env_name.clone(), + }, + ), + }; + env.insert_nodup(&name, closure)?; + } + + envs.0.insert(env_name.clone(), env); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::helpers; + + #[test] + fn main_0() { + let program = vec![helpers::define_expr("main", 0.into())].into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + #[test] + fn var_lookup() { + let program = vec![ + helpers::define_expr("main", "lit".into()), + helpers::define_expr("lit", 0.into()), + ] + .into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + #[test] + fn var_assign_and_lookup() { + let program = vec![helpers::define_expr( + "main", + vec![ + helpers::assign("zero", 0.into()), + helpers::stmt_expr("zero".into()), + ] + .into(), + )] + .into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + + #[test] + fn field_access() { + let program = vec![ + helpers::define_expr("main", "record".into()), + helpers::define_expr( + "record", + ast::Expr::from(vec![("my_field", 0.into())]).field("my_field"), + ), + ] + .into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + + #[test] + fn fun_call() { + let program = vec![ + helpers::define_expr("main", "zero".into()), + helpers::define_expr( + "zero", + helpers::func(ast::Fn { + args: vec![], + body: 0.into(), + }) + .call(vec![]), + ), + ] + .into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + + #[test] + fn if_then_else() { + let program = vec![helpers::define_expr( + "main", + ast::Expr::If { + condition: Box::new(false.into()), + then: Box::new(0.into()), + r#else: Box::new(ast::Expr::If { + condition: Box::new(true.into()), + then: Box::new(1.into()), + r#else: Box::new(2.into()), + }), + }, + )] + .into(); + let result = run(program); + assert_eq!(result, Ok(1.into())); + } + + #[test] + fn fun_call_args() { + let program = vec![ + helpers::define_expr("main", "zero".into()), + helpers::define_expr( + "zero", + helpers::func(ast::Fn { + args: vec![ + ast::Arg { + name: "a".into(), + r#type: ast::Type::Named("i64".into()), + }, + ast::Arg { + name: "b".into(), + r#type: ast::Type::Named("i64".into()), + }, + ], + body: "b".into(), + }) + .call(vec![1.into(), 0.into()]), + ), + ] + .into(); + let result = run(program); + assert_eq!(result, Ok(0.into())); + } + + // Errors + + #[test] + fn duplicate_toplevel_defs() { + let program = vec![ + helpers::define_expr("main", "record".into()), + helpers::define_expr("main", 0.into()), + ] + .into(); + let result = run(program); + assert_eq!(result, Err(Error::DuplicateNames("main".into()))); + } + + #[test] + fn field_access_not_a_record() { + let program = vec![ + helpers::define_expr("main", "record".into()), + helpers::define_expr("record", ast::Expr::from(0).field("my_field")), + ] + .into(); + let result = run(program); + assert_eq!(result, Err(Error::NotARecord(0.into()))); + } + #[test] + fn fun_call_not_a_function() { + let program = vec![ + helpers::define_expr("main", "zero".into()), + helpers::define_expr("zero", ast::Expr::from(0).call(vec![1.into()])), + ] + .into(); + let result = run(program); + assert_eq!(result, Err(Error::NotAFunction(0.into()))); + } +} diff --git a/src/interpret/types.rs b/src/interpret/types.rs new file mode 100644 index 0000000..769bcdf --- /dev/null +++ b/src/interpret/types.rs @@ -0,0 +1,168 @@ +//! Types used in the interpreter. + +use crate::ast; +use std::collections::BTreeMap; + +#[derive(PartialEq, Debug, thiserror::Error)] +pub enum Error { + #[error("Closure generation failed. Duplicate env names: {0:?}")] + DuplicateEnvNames(ast::EnvName), + #[error("Duplicate names: {0:?}")] + DuplicateNames(ast::Name), + #[error("Duplicate method names: {0:?}")] + DuplicateMethodNames(ast::MethodName), + #[error("Name not found: {0:?}")] + NameNotFound(ast::Name), + #[error("Type not found: {0:?}")] + TypeNotFound(ast::Type), + #[error("Method name not found: {0:?}")] + MethodNameNotFound(ast::MethodName), + #[error("Field not found: {0:?}")] + FieldNotFound(ast::Label), + #[error("Env not found: {0:?}")] + EnvNotFound(ast::EnvName), + #[error("Last statement is not an expression")] + LastStatementNotAnExpr, + #[error("Not a function {0:?}")] + NotAFunction(ast::Value), + #[error("Not a record {0:?}")] + NotARecord(ast::Value), + #[error("Not a boolean {0:?}")] + NotABoolean(ast::Value), + #[error("Arguments mismatch")] + ArgumentsMismatch, +} + +impl From for Error { + fn from(error: ast::Error) -> Error { + match error { + ast::Error::LabelNotFound(l) => Error::FieldNotFound(l), + } + } +} + +#[derive(PartialEq, Debug, Clone)] +struct Namer { + counter: u64, +} +impl Namer { + fn new() -> Namer { + Namer { counter: 0 } + } + pub fn next(&mut self) -> u64 { + self.counter += 1; + self.counter + } +} + +pub struct State { + pub name: String, + namer: Namer, + pub envs: Envs, + pub method_env: MethodEnv, +} + +impl State { + pub fn new(name: String) -> State { + State { + name, + namer: Namer::new(), + envs: Envs(BTreeMap::new()), + method_env: MethodEnv::new(), + } + } + pub fn generate_env(&mut self, env: Env) -> Result { + let env_name = ast::EnvName(format!("{}_{}", self.name, self.namer.next())); + self.envs.insert(&env_name, env)?; + Ok(env_name) + } +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Env(pub BTreeMap); +impl Default for Env { + fn default() -> Env { + Env::new() + } +} +impl Env { + pub fn new() -> Env { + Env(BTreeMap::new()) + } + pub fn get(&self, name: &ast::Name) -> Result<&ast::Value, Error> { + self.0.get(name).ok_or(Error::NameNotFound(name.clone())) + } + pub fn insert(&mut self, name: ast::Name, value: ast::Value) { + self.0.insert(name.clone(), value); + } + pub fn insert_nodup(&mut self, name: &ast::Name, value: ast::Value) -> Result<(), Error> { + if self.0.insert(name.clone(), value).is_some() { + Err(Error::DuplicateNames(name.clone())) + } else { + Ok(()) + } + } +} + +#[derive(PartialEq, Debug, Clone)] +pub struct MethodEnv(pub BTreeMap>); + +impl MethodEnv { + pub fn new() -> MethodEnv { + MethodEnv(BTreeMap::new()) + } + pub fn get( + &self, + r#type: &ast::Type, + method_name: &ast::MethodName, + ) -> Result<&ast::Fn, Error> { + self.0 + .get(r#type) + .ok_or(Error::TypeNotFound(r#type.clone()))? + .get(method_name) + .ok_or(Error::MethodNameNotFound(method_name.clone())) + } + // todo: check for dups + pub fn insert( + &mut self, + r#type: ast::Type, + method_name: ast::MethodName, + func: ast::Fn, + ) -> Result<(), Error> { + self.0 + .entry(r#type.clone()) + .and_modify(|map| { + map.insert(method_name.clone(), func.clone()); + }) + .or_insert({ + let mut map = BTreeMap::new(); + map.insert(method_name.clone(), func); + map + }); + Ok(()) + } +} + +impl Default for MethodEnv { + fn default() -> Self { + Self::new() + } +} + +#[derive(PartialEq, Debug, Clone)] +pub struct Envs(pub BTreeMap); + +impl Envs { + pub fn get(&self, env_name: &ast::EnvName) -> Result<&Env, Error> { + self.0 + .get(env_name) + .ok_or(Error::EnvNotFound(env_name.clone())) + } + pub fn insert(&mut self, name: &ast::EnvName, env: Env) -> Result<(), Error> { + if self.0.insert(name.clone(), env).is_some() { + Err(Error::DuplicateEnvNames(name.clone())) + } else { + Ok(()) + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..df42f86 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,3 @@ +pub mod ast; +pub mod interpret; +pub mod runtime; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2f03ce0 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,27 @@ +use ayin::runtime::*; +use macroquad::prelude::*; + +fn window_conf() -> Conf { + Conf { + window_title: "Ayin".to_owned(), + fullscreen: false, + window_width: SCREEN_WIDTH, + window_height: SCREEN_HEIGHT, + ..Default::default() + } +} + +#[macroquad::main(window_conf)] +async fn main() { + env_logger::init(); + info!("Hello, 👁️‍🗨️!"); + + let mut state = setup().await; + + loop { + let events = fetch_events(); + update(&mut state, events); + draw(&state); + next_frame().await; + } +} diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs new file mode 100644 index 0000000..16df112 --- /dev/null +++ b/src/runtime/mod.rs @@ -0,0 +1,5 @@ +pub mod runtime; +pub use runtime::*; + +pub mod types; +pub use types::*; diff --git a/src/runtime/runtime.rs b/src/runtime/runtime.rs new file mode 100644 index 0000000..a78284d --- /dev/null +++ b/src/runtime/runtime.rs @@ -0,0 +1,32 @@ +use super::types::*; +use macroquad::prelude::*; + +pub async fn setup() -> State { + let font = load_ttf_font("./assets/fonts/monogram.ttf").await.unwrap(); + + State { + assets: Assets { font }, + } +} + +pub fn update(_state: &mut State, events: Events) {} + +pub fn fetch_events() -> Events { + Events(vec![]) +} + +pub fn draw(state: &State) { + clear_background(Color::from_hex(0x081829)); + + set_default_camera(); + let (w, h) = (SCREEN_WIDTH as f32 / 2., 400.0); + + set_camera(&Camera2D { + zoom: vec2(2.2 / w, 2.2 / h), + target: Vec2 { x: w / 2.2, y: 0.0 }, + viewport: Some((0, 0, w as i32, h as i32)), + ..Default::default() + }); + + set_default_camera(); +} diff --git a/src/runtime/types.rs b/src/runtime/types.rs new file mode 100644 index 0000000..deceaba --- /dev/null +++ b/src/runtime/types.rs @@ -0,0 +1,19 @@ +use crate::ast; +use macroquad::prelude::*; + +pub struct State { + pub assets: Assets, + code: ast::Program, +} + +pub struct Assets { + pub font: Font, +} + +pub struct Event {} +pub struct Events(pub Vec); + +pub const SCALE: i32 = 4; + +pub const SCREEN_WIDTH: i32 = 360 * SCALE; +pub const SCREEN_HEIGHT: i32 = 360 * SCALE;