diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 79b3053b..7708e110 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -1,42 +1,10 @@ -name: Python package +name: Build on: [push, pull_request] env: CARGO_TERM_COLOR: always jobs: - build-python: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Python 3.12.1 - uses: actions/setup-python@v2 - with: - python-version: "3.12.1" - - uses: pdm-project/setup-pdm@v3 - name: Setup PDM - with: - python-version: "3.12.1" # Version range or exact version of a Python version to use, the same as actions/setup-python - architecture: x64 # The target architecture (x86, x64) of the Python interpreter. the same as actions/setup-python - version: head - prerelease: false # Allow prerelease versions of PDM to be installed - enable-pep582: false # Enable PEP 582 package loading globally - allow-python-prereleases: false # Allow prerelease versions of Python to be installed. For example if only 3.12-dev is available, 3.12 will fallback to 3.12-dev - - name: Install dependencies - run: | - cd python - pdm install - - name: Run linters - run: | - cd python - pdm add pre-commit - pdm run pre-commit run --all-files - - name: Run Python tests - run: | - cd python - pdm install --production - export AAA_STDLIB_PATH=$(pwd)/../stdlib - pdm run pytest --color=yes --cov=aaa --cov-report=term-missing -x -vv build-rust: runs-on: ubuntu-latest steps: @@ -44,6 +12,10 @@ jobs: uses: actions/checkout@v4 - name: Install Rust uses: actions-rust-lang/setup-rust-toolchain@v1 + - name: Run linters + run: | + pip install pre-commit + pre-commit run --all-files - name: Build run: | export AAA_STDLIB_PATH=$(pwd)/stdlib diff --git a/.gitignore b/.gitignore index b1dd771e..296bcc03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,3 @@ -.python-version -__pycache__ .coverage *.code-workspace .envrc diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6adeb54d..6cdb2528 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,6 @@ repos: rev: v4.0.1 hooks: - id: check-merge-conflict - - id: debug-statements - id: check-yaml - id: check-json exclude: "aaa-vscode-extension/.*" @@ -14,48 +13,33 @@ repos: - repo: local hooks: - - id: mypy - name: run mypy + - id: cargo-check-aaa + name: aaa cargo check language: system - entry: mypy --strict - types: [python] - - - repo: https://github.com/lk16/detect-missing-init - rev: v0.1.6 - hooks: - - id: detect-missing-init - args: - ["--create", "--track", "--python-folders", "python/aaa,python/tests"] - - - repo: https://github.com/doublify/pre-commit-rust - rev: v1.0 - hooks: - - id: fmt + entry: cargo check --manifest-path aaa/Cargo.toml pass_filenames: false - args: [--manifest-path, aaa-stdlib/Cargo.toml] - - id: cargo-check + - id: cargo-fmt-aaa + name: aaa cargo fmt + language: system + entry: cargo check --manifest-path aaa/Cargo.toml pass_filenames: false - args: [--manifest-path, aaa-stdlib/Cargo.toml] - - id: clippy + - id: cargo-clippy-aaa + name: aaa cargo clippy + language: system + entry: cargo clippy --manifest-path aaa/Cargo.toml -- -Dwarnings pass_filenames: false - args: [--manifest-path, aaa-stdlib/Cargo.toml] - - - repo: https://github.com/doublify/pre-commit-rust - rev: v1.0 - hooks: - - id: fmt + - id: cargo-check-aaa-stdlib + name: aaa-stdlib cargo check + language: system + entry: cargo check --manifest-path aaa-stdlib/Cargo.toml pass_filenames: false - args: [--manifest-path, aaa/Cargo.toml] - - id: cargo-check + - id: cargo-fmt-aaa-stdlib + name: aaa-stdlib cargo fmt + language: system + entry: cargo check --manifest-path aaa-stdlib/Cargo.toml pass_filenames: false - args: [--manifest-path, aaa/Cargo.toml] - - id: clippy + - id: cargo-clippy-aaa-stdlib + name: aaa-stdlib cargo clippy + language: system + entry: cargo clippy --manifest-path aaa-stdlib/Cargo.toml -- -Dwarnings pass_filenames: false - args: [--manifest-path, aaa/Cargo.toml] - - - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.8 - hooks: - - id: ruff - args: [--fix] - - id: ruff-format diff --git a/README.md b/README.md index 8643b224..bd7784cf 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,30 @@ # Aaa -Stack-based language, like forth. -### Contents -The following tools for the Aaa language can be found is this repo -* A [parser](./python/aaa/parser/) for Aaa. -* A [type checker](./python/aaa/type_checker/) -* A [transpiler to Rust](./python/aaa/transpiler/) -* A [VS Code extension](./aaa-vscode-extension/README.md) for the Aaa language. -* A lot of tests, written both in python and Aaa +This project implements tools for working with the Aaa programming language. -### Examples +The language is stack-based and is inspired by [Forth](https://en.wikipedia.org/wiki/Forth_(programming_language)) and [Porth](https://gitlab.com/tsoding/porth). + + +### Installation ```sh -cd python +# Install aaa transpiler and runner tool. Run from repository root. +cargo install --path aaa +``` + + +### Examples + + +```sh # Run the obligatory hello world example -pdm run ./manage.py run 'fn main { "Hello world\n" . }' +aaa run 'fn main { "Hello world\n" . }' # Run code from a file. Implements the famous fizzbuzz interview question. -pdm run ./manage.py run examples/fizzbuzz.aaa +aaa run examples/fizzbuzz.aaa # Run bare-bones HTTP server in Aaa -pdm run ./manage.py run examples/http_server.aaa +aaa run examples/http_server.aaa # Send request from different shell curl http://localhost:8080 @@ -28,15 +32,16 @@ curl http://localhost:8080 ### Running tests + + ```sh # Run tests written in Aaa -pdm run ./manage.py test . +aaa test . -# Run tests written in Python -pdm run pytest +# Run tests written in Rust +cargo test --manifest-path aaa/Cargo.toml ``` - ### Aaa features - elementary types (`int`, `bool`, `str`) - container types (`vec`, `map`) @@ -48,45 +53,5 @@ pdm run pytest ### Name The name of this language is just the first letter of the Latin alphabet repeated three times. When code in this language doesn't work, its meaning becomes an [abbreviation](https://en.uncyclopedia.co/wiki/AAAAAAAAA!). -### Setup -All these commands should be run from the root of this repository. - -This project requires python 3.12.1 or newer. Consider using [pyenv](https://github.com/pyenv/pyenv). - -```sh -# Download python 3.12.1 -pyenv install 3.12.1 - -# Use it in this project -pyenv local 3.12.1 -``` - -This project also requires rust, see instructions [here](https://www.rust-lang.org/tools/install) on how to install. - -After you setup rust and python, run the following commands. - -```sh -# Install dependencies -pdm install - -# Tell Aaa where the standard library lives -export AAA_STDLIB_PATH=$(pwd)/stdlib - -# Run hello world program -pdm run ./manage.py run 'fn main { "Hello world\n" . }' - -# Run tests -pdm run pytest -pdm run ./manage.py test . - -# Setup pre-commit hooks -pdm run pre-commit install -``` - -Now you can start running code in Aaa or develop the language! - -To enable syntax highlighting for VS Code, enable the [Aaa language extension](./aaa-vscode-extension/README.md) - - ### Aaa and porth After watching part of the [Youtube series](https://www.youtube.com/playlist?list=PLpM-Dvs8t0VbMZA7wW9aR3EtBqe2kinu4) on [porth](https://gitlab.com/tsoding/porth), I wanted to make my own stack-based language. Aaa and porth have some similarities, but obviously are not compatible with each other. No code was copied over from the porth repo. diff --git a/aaa-stdlib/Cargo.lock b/aaa-stdlib/Cargo.lock index e674b57b..b2865018 100644 --- a/aaa-stdlib/Cargo.lock +++ b/aaa-stdlib/Cargo.lock @@ -6,6 +6,7 @@ version = 3 name = "aaa-stdlib" version = "0.1.0" dependencies = [ + "lazy_static", "nix", "regex", ] @@ -37,6 +38,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.141" diff --git a/aaa-stdlib/Cargo.toml b/aaa-stdlib/Cargo.toml index 6f4da1c1..365983cd 100644 --- a/aaa-stdlib/Cargo.toml +++ b/aaa-stdlib/Cargo.toml @@ -6,5 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +lazy_static = "1.5.0" nix = "0.26.2" regex = "1.8.4" diff --git a/aaa-stdlib/src/interface.rs b/aaa-stdlib/src/interface.rs new file mode 100644 index 00000000..e23920fd --- /dev/null +++ b/aaa-stdlib/src/interface.rs @@ -0,0 +1,22 @@ +use crate::stack::InterfaceMappingType; + +fn call_interface_function( + interface_mapping: &InterfaceMappingType, + interface_name: &str, + function_name: &str, +) { + let top = self.top(); + let top_type_id = top.type_id(); + + let interface_name = format!("builtins:{}", interface_name); + + let first_key = &(interface_name.as_str(), top_type_id.as_str()); + + let function = interface_mapping + .get(first_key) + .unwrap() + .get(function_name) + .unwrap(); + + function(self); +} diff --git a/aaa-stdlib/src/map.rs b/aaa-stdlib/src/map.rs index 03eba7d5..4b399875 100644 --- a/aaa-stdlib/src/map.rs +++ b/aaa-stdlib/src/map.rs @@ -1,7 +1,6 @@ use std::{ cell::RefCell, collections::hash_map::DefaultHasher, - fmt::{Display, Formatter, Result}, hash::{Hash, Hasher}, rc::Rc, }; @@ -149,16 +148,6 @@ where ); } } - - pub fn fmt_as_set(&self) -> String { - let mut parts = vec![]; - for bucket in self.buckets.borrow().iter() { - for (k, _) in bucket.iter() { - parts.push(format!("{k:?}")); - } - } - format!("{{{}}}", parts.join(",")) - } } impl PartialEq for Map @@ -193,22 +182,6 @@ where { } -impl Display for Map -where - K: UserType, - V: UserType, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - let mut parts = vec![]; - for bucket in self.buckets.borrow().iter() { - for (k, v) in bucket.iter() { - parts.push(format!("{k:?}: {v:?}")); - } - } - write!(f, "{{{}}}", parts.join(", ")) - } -} - impl Clone for Map where K: UserType, diff --git a/aaa-stdlib/src/set.rs b/aaa-stdlib/src/set.rs index b06b7288..5ce9ddcd 100644 --- a/aaa-stdlib/src/set.rs +++ b/aaa-stdlib/src/set.rs @@ -19,7 +19,7 @@ impl Display for SetValue { } impl UserType for SetValue { - fn kind(&self) -> String { + fn type_id(&self) -> String { String::from("SetValue") } diff --git a/aaa-stdlib/src/stack.rs b/aaa-stdlib/src/stack.rs index ac00ad12..0f8fdb6c 100644 --- a/aaa-stdlib/src/stack.rs +++ b/aaa-stdlib/src/stack.rs @@ -1,8 +1,8 @@ use std::{ cell::RefCell, + collections::HashMap, env, ffi::CString, - fmt::{Display, Formatter, Result}, fs, io::{stdout, Write}, net::{Ipv4Addr, ToSocketAddrs}, @@ -10,6 +10,7 @@ use std::{ process, rc::Rc, str::FromStr, + sync::Arc, thread::sleep, time::{Duration, SystemTime, UNIX_EPOCH}, vec, @@ -39,46 +40,30 @@ use crate::{ vector::Vector, }; +pub type InterfaceMappingType = + HashMap<(&'static str, &'static str, &'static str), fn(&mut Stack)>; + pub struct Stack where T: UserType, { items: Vec>, -} - -impl Display for Stack -where - T: UserType, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - write!(f, "Stack ({}):", self.len())?; - for item in self.items.iter() { - write!(f, " ")?; - write!(f, "{}", item)?; - } - Ok(()) - } -} - -impl Default for Stack -where - T: UserType, -{ - fn default() -> Self { - Self::new() - } + interface_mapping: Arc>, } impl Stack where T: UserType, { - pub fn new() -> Self { - Self { items: Vec::new() } + pub fn new(interface_mapping: Arc>) -> Self { + Self { + items: Vec::new(), + interface_mapping, + } } - pub fn from_argv() -> Self { - let mut stack = Self::new(); + pub fn from_argv(interface_mapping: Arc>) -> Self { + let mut stack = Self::new(interface_mapping); let mut arg_vector = Vector::new(); for arg in env::args() { arg_vector.push(Variable::String(Rc::new(RefCell::new(arg)))); @@ -101,13 +86,13 @@ where } fn pop_type_error(&self, expected_type: &str, found: &Variable) -> ! { - let found = found.kind(); + let found = found.type_id(); let msg = format!("pop failed, expected {expected_type}, found {found}"); self.type_error(&msg); } fn type_error_vec_str(&self, v: &Variable) { - let found = v.kind(); + let found = v.type_id(); let msg = format!("expected vec[str], but found {found} in vec"); self.type_error(&msg); } @@ -283,14 +268,42 @@ where } } + pub fn pop_option(&mut self) -> Rc>>> { + match self.pop() { + Variable::Option(v) => v, + v => self.pop_type_error("Option", &v), + } + } + + pub fn pop_result(&mut self) -> Rc, Variable>>> { + match self.pop() { + Variable::Result(v) => v, + v => self.pop_type_error("Result", &v), + } + } + pub fn pop_function_pointer_and_call(&mut self) { let func = self.pop_function_pointer(); func(self); } + pub fn call_interface_function(&mut self, interface_name: &str, function_name: &str) { + let top = self.top(); + let top_type_id = top.type_id(); + + let key = &(interface_name, top_type_id.as_str(), function_name); + + let function = self.interface_mapping.get(key).unwrap(); + + function(self); + } + pub fn print(&mut self) { - let top = self.pop(); - print!("{top}"); + self.call_interface_function("builtins:Show", "show"); + + let printed = self.pop_str(); + print!("{}", printed.borrow()); + _ = stdout().flush(); // TODO remove when #67 `fflush` is added } @@ -429,11 +442,10 @@ where } pub fn repr(&mut self) { - let top = self.pop(); - let repr = format!("{top:?}"); - self.push_str(&repr); + self.call_interface_function("builtins:Debug", "debug"); } + #[allow(clippy::should_implement_trait)] // TODO #238 Rename drop function pub fn drop(&mut self) { self.pop(); } @@ -601,7 +613,7 @@ where let result = bind(fd as i32, &addr); - if !result.is_ok() { + if result.is_err() { self.push_bool(false); } @@ -1034,7 +1046,7 @@ where let string_rc = self.pop_str(); let string = &*string_rc.borrow(); - match string.char_indices().skip(offset as usize).next() { + match string.char_indices().nth(offset as usize) { None => { self.push_char('\0'); self.push_bool(false); @@ -1054,7 +1066,7 @@ where string.push(char); - self.push_str(&string); + self.push_str(string); } pub fn vec_copy(&mut self) { @@ -1103,7 +1115,10 @@ where let mut env: Vec = vec![]; for (key, value) in popped_env.iter() { - env.push(CString::new(format!("{key:}={value:}")).unwrap()) + let key = key.get_string(); + let value = value.get_string(); + + env.push(CString::new(format!("{}={}", key.borrow(), value.borrow())).unwrap()) } let argv_rc = self.pop_vec(); @@ -1521,7 +1536,7 @@ where let found_stack_top = self .items .iter() - .map(|i| i.kind()) + .map(|i| i.type_id()) .collect::>() .join(" "); @@ -1534,7 +1549,7 @@ where let start_index = self.items.len() - expected_top.len(); for (actual, expected) in self.items.iter().skip(start_index).zip(expected_top.iter()) { - if actual.kind() != *expected && actual.kind() != "none" { + if actual.type_id() != *expected && actual.type_id() != "none" { // #33 Remove check for none ok = false; } @@ -1545,7 +1560,7 @@ where .items .iter() .skip(start_index) - .map(|i| i.kind()) + .map(|i| i.type_id()) .collect::>() .join(" "); @@ -1568,7 +1583,7 @@ where pub fn char_is_digit(&mut self) { let char = self.pop_char(); - self.push_bool(char.is_digit(10)); + self.push_bool(char.is_ascii_digit()); } pub fn char_equals(&mut self) { @@ -1605,4 +1620,191 @@ where let rhs = self.pop(); self.push_bool(lhs == rhs); } + + pub fn option_some(&mut self) { + let popped = self.pop(); + self.push(Variable::Option(Rc::new(RefCell::new(Some(popped))))); + } + + pub fn option_none(&mut self) { + self.push(Variable::Option(Rc::new(RefCell::new(None)))); + } + + pub fn result_ok(&mut self) { + let popped = self.pop(); + self.push(Variable::Result(Rc::new(RefCell::new(Ok(popped))))); + } + + pub fn result_error(&mut self) { + let popped = self.pop(); + self.push(Variable::Result(Rc::new(RefCell::new(Err(popped))))); + } + + pub fn option_unwrap(&mut self) { + let option = self.pop_option(); + let option = &*option.borrow(); + + match option { + Some(v) => self.push(v.clone()), + None => panic!("option:unwrap failed!"), + } + } + + pub fn option_unwrap_or(&mut self) { + let default = self.pop(); + + let option = self.pop_option(); + let option = &*option.borrow(); + + match option { + Some(v) => self.push(v.clone()), + None => self.push(default), + } + } + + pub fn option_is_some(&mut self) { + let option = self.pop_option(); + self.push_bool(option.borrow().is_some()); + } + + pub fn option_is_none(&mut self) { + let option = self.pop_option(); + self.push_bool(option.borrow().is_none()); + } + + pub fn result_unwrap(&mut self) { + let result = self.pop_result(); + let result = &*result.borrow(); + + match result { + Ok(v) => self.push(v.clone()), + Err(_) => panic!("result:unwrap failed"), + } + } + + pub fn result_unwrap_error(&mut self) { + let result = self.pop_result(); + let result = &*result.borrow(); + + match result { + Ok(_) => panic!("result:unwrap_error failed"), + Err(v) => self.push(v.clone()), + } + } + + pub fn result_is_ok(&mut self) { + let result = self.pop_result(); + self.push_bool(result.borrow().is_ok()); + } + + pub fn result_is_error(&mut self) { + let result = self.pop_result(); + self.push_bool(result.borrow().is_err()); + } + + pub fn bool_show(&mut self) { + let popped = self.pop_bool(); + self.push_str(format!("{}", popped).as_str()); + } + + pub fn int_show(&mut self) { + let popped = self.pop_int(); + self.push_str(format!("{}", popped).as_str()); + } + + pub fn str_show(&mut self) { + self.dup(); + } + + pub fn char_show(&mut self) { + let popped = self.pop_char(); + self.push_str(format!("{}", popped).as_str()); + } + + pub fn map_show(&mut self) { + let popped = self.pop_map(); + + let mut parts: Vec = vec![]; + + for (key, value) in popped.borrow().iter() { + self.push(key); + self.call_interface_function("builtins:Debug", "debug"); + let key_str = self.pop_str(); + + self.push(value); + self.call_interface_function("builtins:Debug", "debug"); + let value_str = self.pop_str(); + + parts.push(format!( + "{}: {}", + (*key_str.borrow()).clone(), + (*value_str.borrow()).clone() + )); + } + + let combined = format!("{{{}}}", parts.join(", ")); + self.push_str(combined.as_str()); + } + + pub fn set_show(&mut self) { + let popped = self.pop_set(); + + let mut parts: Vec = vec![]; + + for (key, _) in popped.borrow().iter() { + self.push(key); + self.call_interface_function("builtins:Debug", "debug"); + let key_str = self.pop_str(); + + parts.push((*key_str.borrow()).clone()); + } + + let combined = format!("{{{}}}", parts.join(", ")); + self.push_str(combined.as_str()); + } + + pub fn vec_show(&mut self) { + let popped = self.pop_vec(); + + let mut parts: Vec = vec![]; + + for item in popped.borrow().iter() { + self.push(item); + self.call_interface_function("builtins:Debug", "debug"); + let part = self.pop_str(); + parts.push((*part.borrow()).clone()); + } + + let combined = format!("[{}]", parts.join(", ")); + self.push_str(combined.as_str()); + } + + pub fn bool_debug(&mut self) { + self.bool_show(); + } + + pub fn int_debug(&mut self) { + self.int_show(); + } + + pub fn str_debug(&mut self) { + let popped = self.pop_str(); + self.push_str(format!("{:?}", popped.borrow()).as_str()); + } + + pub fn char_debug(&mut self) { + self.char_show(); + } + + pub fn map_debug(&mut self) { + self.map_show(); + } + + pub fn set_debug(&mut self) { + self.set_show(); + } + + pub fn vec_debug(&mut self) { + self.vec_show(); + } } diff --git a/aaa-stdlib/src/var.rs b/aaa-stdlib/src/var.rs index db3f6ebd..d3e690bd 100644 --- a/aaa-stdlib/src/var.rs +++ b/aaa-stdlib/src/var.rs @@ -1,10 +1,4 @@ -use std::{ - cell::RefCell, - fmt::{Debug, Display, Formatter, Result}, - hash::Hash, - mem, - rc::Rc, -}; +use std::{cell::RefCell, hash::Hash, mem, rc::Rc}; use regex::Regex; @@ -15,8 +9,8 @@ use crate::{ vector::{Vector, VectorIterator}, }; -pub trait UserType: Clone + Debug + Display + Hash + PartialEq { - fn kind(&self) -> String; +pub trait UserType: Clone + Hash + PartialEq { + fn type_id(&self) -> String; fn clone_recursive(&self) -> Self; } @@ -38,6 +32,8 @@ where SetIterator(Rc>>>), Regex(Rc>), FunctionPointer(fn(&mut Stack)), + Option(Rc>>>), + Result(Rc, Variable>>>), UserType(Rc>), } @@ -96,6 +92,14 @@ where Variable::FunctionPointer(Stack::zero_function_pointer_value) } + pub fn option_zero_value() -> Variable { + Variable::Option(Rc::new(RefCell::new(None))) + } + + pub fn result_zero_value() -> Variable { + Variable::Result(Rc::new(RefCell::new(Ok(Variable::None)))) + } + pub fn get_integer(&self) -> isize { match self { Self::Integer(v) => *v, @@ -168,7 +172,7 @@ where pub fn get_function_pointer(&self) -> fn(&mut Stack) { match self { - Self::FunctionPointer(v) => v.clone(), + Self::FunctionPointer(v) => *v, _ => unreachable!(), } } @@ -185,22 +189,24 @@ impl UserType for Variable where T: UserType, { - fn kind(&self) -> String { + fn type_id(&self) -> String { match self { - Self::None => String::from("none"), - Self::Integer(_) => String::from("int"), - Self::Boolean(_) => String::from("bool"), - Self::Character(_) => String::from("char"), - Self::String(_) => String::from("str"), - Self::Vector(_) => String::from("vec"), - Self::Set(_) => String::from("set"), - Self::Map(_) => String::from("map"), - Self::VectorIterator(_) => String::from("vec_iter"), - Self::MapIterator(_) => String::from("map_iter"), - Self::SetIterator(_) => String::from("set_iter"), - Self::Regex(_) => String::from("regex"), - Self::FunctionPointer(_) => String::from("fn_ptr"), - Self::UserType(v) => (**v).borrow().kind(), + Self::Boolean(_) => String::from("builtins:bool"), + Self::Character(_) => String::from("builtins:char"), + Self::FunctionPointer(_) => String::from("builtins:fn_ptr"), + Self::Integer(_) => String::from("builtins:int"), + Self::Map(_) => String::from("builtins:map"), + Self::MapIterator(_) => String::from("builtins:map_iter"), + Self::None => String::from("builtins:none"), + Self::Option(_) => String::from("builtins:Option"), + Self::Regex(_) => String::from("builtins:regex"), + Self::Result(_) => String::from("builtins:Result"), + Self::Set(_) => String::from("builtins:set"), + Self::SetIterator(_) => String::from("builtins:set_iter"), + Self::String(_) => String::from("builtins:str"), + Self::Vector(_) => String::from("builtins:vec"), + Self::VectorIterator(_) => String::from("builtins:vec_iter"), + Self::UserType(v) => (**v).borrow().type_id(), } } @@ -244,47 +250,19 @@ where unreachable!(); // Cannot recursively clone an iterator } Self::Regex(regex) => Self::Regex(regex.clone()), - Self::FunctionPointer(v) => Self::FunctionPointer(v.clone()), + Self::FunctionPointer(v) => Self::FunctionPointer(*v), Self::UserType(v) => { let cloned = (**v).borrow().clone_recursive(); Self::UserType(Rc::new(RefCell::new(cloned))) } - } - } -} - -impl Display for Variable -where - T: UserType, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - match self { - Self::Boolean(v) => write!(f, "{}", v), - Self::Integer(v) => write!(f, "{}", v), - Self::String(v) => write!(f, "{}", (**v).borrow()), - Self::Character(v) => write!(f, "{}", v), - Self::Vector(v) => write!(f, "{}", (**v).borrow()), - Self::Set(v) => write!(f, "{}", (**v).borrow().fmt_as_set()), - Self::Map(v) => write!(f, "{}", (**v).borrow()), - Self::VectorIterator(_) => write!(f, "vec_iter"), - Self::MapIterator(_) => write!(f, "map_iter"), - Self::SetIterator(_) => write!(f, "set_iter"), - Self::None => write!(f, "None"), - Self::Regex(_) => write!(f, "regex"), - Self::FunctionPointer(_) => write!(f, "func_ptr"), - Self::UserType(v) => write!(f, "{}", (**v).borrow()), - } - } -} - -impl Debug for Variable -where - T: UserType, -{ - fn fmt(&self, f: &mut Formatter<'_>) -> Result { - match self { - Self::String(v) => write!(f, "{:?}", (**v).borrow()), - _ => write!(f, "{}", self), + Self::Option(v) => { + let cloned = (**v).borrow().clone(); + Self::Option(Rc::new(RefCell::new(cloned))) + } + Self::Result(v) => { + let cloned = (**v).borrow().clone(); + Self::Result(Rc::new(RefCell::new(cloned))) + } } } } @@ -309,6 +287,8 @@ where } (Self::FunctionPointer(lhs), Self::FunctionPointer(rhs)) => lhs == rhs, (Self::UserType(lhs), Self::UserType(rhs)) => lhs == rhs, + (Self::Option(lhs), Self::Option(rhs)) => lhs == rhs, + (Self::Result(lhs), Self::Result(rhs)) => lhs == rhs, _ => unreachable!(), // Can't compare variables of different types } } @@ -329,9 +309,7 @@ where let string = (**v).borrow().clone(); Hash::hash(&string, state) } - _ => { - unreachable!() // Can't hash variables of different types - } + _ => unreachable!(), // Can't hash variables of different types } } } diff --git a/aaa-stdlib/src/vector.rs b/aaa-stdlib/src/vector.rs index 03a45b65..9a0aff57 100644 --- a/aaa-stdlib/src/vector.rs +++ b/aaa-stdlib/src/vector.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, fmt::Display, hash::Hash, rc::Rc}; +use std::{cell::RefCell, hash::Hash, rc::Rc}; use crate::var::UserType; @@ -82,19 +82,6 @@ where } } -impl Display for Vector -where - T: UserType, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let mut reprs: Vec = vec![]; - for item in self.iter() { - reprs.push(format!("{item:?}")) - } - write!(f, "[{}]", reprs.join(", ")) - } -} - impl PartialEq for Vector where T: UserType, diff --git a/aaa-vscode-extension/syntaxes/aaa.tmLanguage.json b/aaa-vscode-extension/syntaxes/aaa.tmLanguage.json index 404e61e2..485a6843 100644 --- a/aaa-vscode-extension/syntaxes/aaa.tmLanguage.json +++ b/aaa-vscode-extension/syntaxes/aaa.tmLanguage.json @@ -51,10 +51,12 @@ ], "repository": { "keywords": { - "patterns": [{ - "name": "keyword.control.aaa", - "match": "\\b(if|else|while|fn|as|args|return|import|from|struct|foreach|use|const|enum|match|case|default|call|builtin)\\b" - }] + "patterns": [ + { + "name": "keyword.control.aaa", + "match": "\\b(if|else|while|fn|as|args|return|import|from|struct|foreach|use|const|enum|match|case|default|call|builtin|interface)\\b" + } + ] }, "strings": { "name": "string.quoted.double.aaa", diff --git a/aaa/Cargo.lock b/aaa/Cargo.lock index 04336086..1fcfcf96 100644 --- a/aaa/Cargo.lock +++ b/aaa/Cargo.lock @@ -393,9 +393,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" diff --git a/aaa/Cargo.toml b/aaa/Cargo.toml index 6fe7062b..bc932ea2 100644 --- a/aaa/Cargo.toml +++ b/aaa/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] chrono = "0.4.38" clap = { version = "4.5.17", features = ["derive"] } -lazy_static = "1.4.0" +lazy_static = "1.5.0" rand = "0.8.5" regex = "1.10.4" rstest = "0.19.0" diff --git a/aaa/src/common/files.rs b/aaa/src/common/files.rs index f82cdd8c..c18762cb 100644 --- a/aaa/src/common/files.rs +++ b/aaa/src/common/files.rs @@ -8,15 +8,14 @@ pub fn repository_root() -> PathBuf { .canonicalize() .unwrap() .ancestors() - .skip(4) - .next() + .nth(4) .unwrap() .to_path_buf() } -pub fn normalize_path(path: &PathBuf, current_dir: &PathBuf) -> PathBuf { +pub fn normalize_path(path: &PathBuf, current_dir: &Path) -> PathBuf { let path = if path.is_relative() { - current_dir.join(&path) + current_dir.join(path) } else { path.clone() }; diff --git a/aaa/src/common/formatting.rs b/aaa/src/common/formatting.rs index 947484d2..50abbd2f 100644 --- a/aaa/src/common/formatting.rs +++ b/aaa/src/common/formatting.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -pub fn join_display(separator: &str, values: &Vec) -> String { +pub fn join_display(separator: &str, values: &[T]) -> String { values .iter() .map(|value| format!("{}", value)) @@ -8,7 +8,7 @@ pub fn join_display(separator: &str, values: &Vec) -> String { .join(separator) } -pub fn join_display_prefixed(prefix: &str, separator: &str, values: &Vec) -> String { +pub fn join_display_prefixed(prefix: &str, separator: &str, values: &[T]) -> String { let suffix = join_display(separator, values); format!("{}{}", prefix.to_owned(), suffix) diff --git a/aaa/src/common/hash.rs b/aaa/src/common/hash.rs new file mode 100644 index 00000000..91cc6ae6 --- /dev/null +++ b/aaa/src/common/hash.rs @@ -0,0 +1,26 @@ +use std::path::PathBuf; + +use sha2::{Digest, Sha256}; + +use super::position::Position; + +pub fn hash_key(key_tuple: (PathBuf, String)) -> String { + let mut hasher = Sha256::new(); + hasher.update(key_tuple.0.to_string_lossy().as_bytes()); + hasher.update(key_tuple.1.as_bytes()); + + let hash = hasher.finalize(); + let hash = format!("{:x}", hash); + + hash[..16].to_owned() +} + +pub fn hash_position(position: &Position) -> String { + let mut hasher = Sha256::new(); + hasher.update(format!("{}", position).as_bytes()); + + let hash = hasher.finalize(); + let hash = format!("{:x}", hash); + + hash[..16].to_owned() +} diff --git a/aaa/src/common/mod.rs b/aaa/src/common/mod.rs index c910ccd6..535e293c 100644 --- a/aaa/src/common/mod.rs +++ b/aaa/src/common/mod.rs @@ -1,4 +1,5 @@ pub mod files; pub mod formatting; +pub mod hash; pub mod position; pub mod traits; diff --git a/aaa/src/common/position.rs b/aaa/src/common/position.rs index acb5cee5..54fb1cd2 100644 --- a/aaa/src/common/position.rs +++ b/aaa/src/common/position.rs @@ -16,12 +16,8 @@ impl Position { } } - pub fn after(&self, string: &String) -> Self { - let newline_indices: Vec<_> = string - .match_indices("\n") - .into_iter() - .map(|(n, _)| n) - .collect(); + pub fn after(&self, string: &str) -> Self { + let newline_indices: Vec<_> = string.match_indices("\n").map(|(n, _)| n).collect(); let line = self.line + newline_indices.len(); diff --git a/aaa/src/cross_referencer/cross_referencer.rs b/aaa/src/cross_referencer/cross_referencer.rs index f964d24a..8b3b47b0 100644 --- a/aaa/src/cross_referencer/cross_referencer.rs +++ b/aaa/src/cross_referencer/cross_referencer.rs @@ -1,6 +1,6 @@ use std::{ cell::{Cell, RefCell}, - collections::{hash_map::Entry as HashMapEntry, HashMap, HashSet}, + collections::{HashMap, HashSet}, fmt::Debug, path::PathBuf, rc::Rc, @@ -14,20 +14,23 @@ use super::{ types::{ function_body::{ Assignment, Boolean, Branch, Call, CallArgument, CallEnum, CallEnumConstructor, - CallFunction, CallLocalVariable, CallStruct, CaseBlock, Char, DefaultBlock, Foreach, - FunctionBody, FunctionBodyItem, GetField, GetFunction, Integer, Match, ParsedString, - Return, SetField, Use, Variable, While, + CallFunction, CallInterfaceFunction, CallLocalVariable, CallStruct, CaseBlock, Char, + DefaultBlock, Foreach, FunctionBody, FunctionBodyItem, GetField, GetFunction, Integer, + Match, ParsedString, Return, SetField, Use, Variable, While, }, identifiable::{ Argument, Enum, EnumType, Function, FunctionPointerType, FunctionSignature, - Identifiable, Import, ResolvedEnum, ResolvedStruct, ReturnTypes, Struct, StructType, - Type, + Identifiable, Import, Interface, InterfaceType, ResolvedEnum, ResolvedInterface, + ResolvedInterfaceFunction, ResolvedStruct, ReturnTypes, Struct, StructType, Type, }, }, }; use crate::{ common::{position::Position, traits::HasPosition}, - cross_referencer::{errors::get_function_non_function, types::function_body::FunctionType}, + cross_referencer::{ + errors::{get_function_non_function, invalid_interface_function}, + types::function_body::FunctionType, + }, parser::types::{self as parsed, SourceFile}, type_checker::errors::NameCollision, }; @@ -93,7 +96,7 @@ impl CrossReferencer { self.errors.push(error); } - if self.errors.len() > 0 { + if !self.errors.is_empty() { return Err(self.errors); } @@ -108,7 +111,7 @@ impl CrossReferencer { &mut self, path: &PathBuf, ) -> Result<(), CrossReferencerError> { - if self.dependency_stack.contains(&path) { + if self.dependency_stack.contains(path) { let mut dependencies = self.dependency_stack.clone(); dependencies.push(path.clone()); @@ -160,36 +163,33 @@ impl CrossReferencer { } for identifiable in indentifiables.iter() { - let key = identifiable.key(); + if let Some(colliding) = self + .identifiables + .insert(identifiable.key(), identifiable.clone()) + { + let error = CrossReferencerError::NameCollision(NameCollision { + items: [Box::new(colliding.clone()), Box::new(identifiable.clone())], + }); - match self.identifiables.entry(key) { - HashMapEntry::Vacant(entry) => { - entry.insert(identifiable.clone()); - } - HashMapEntry::Occupied(entry) => { - let error = CrossReferencerError::NameCollision(NameCollision { - items: [ - Box::new(entry.get().clone()), - Box::new(identifiable.clone()), - ], - }); - - self.errors.push(error); - } - }; + self.errors.push(error); + } } let mut structs = vec![]; let mut enums = vec![]; let mut functions = vec![]; let mut imports = vec![]; + let mut interfaces = vec![]; + let mut interface_functions = vec![]; - for identifiable in indentifiables.iter() { + for identifiable in indentifiables.into_iter() { match identifiable { - Identifiable::Struct(struct_) => structs.push(struct_.clone()), - Identifiable::Enum(enum_) => enums.push(enum_.clone()), - Identifiable::Function(function) => functions.push(function.clone()), - Identifiable::Import(import) => imports.push(import.clone()), + Identifiable::Struct(struct_) => structs.push(struct_), + Identifiable::Enum(enum_) => enums.push(enum_), + Identifiable::Function(function) => functions.push(function), + Identifiable::Import(import) => imports.push(import), + Identifiable::Interface(interface) => interfaces.push(interface), + Identifiable::InterfaceFunction(function) => interface_functions.push(function), _ => (), } } @@ -223,6 +223,12 @@ impl CrossReferencer { self.errors.push(error); } } + + for interface in interfaces.iter().cloned() { + if let Err(error) = self.resolve_interface(interface) { + self.errors.push(error); + } + } } fn load_file(&self, source_file: &SourceFile) -> Vec { @@ -232,11 +238,12 @@ impl CrossReferencer { identifiables.extend(Self::load_enums(&source_file.enums)); identifiables.extend(Self::load_functions(&source_file.functions)); identifiables.extend(Self::load_imports(&source_file.imports)); + identifiables.extend(Self::load_interfaces(&source_file.interfaces)); identifiables } - fn load_structs(parsed_structs: &Vec) -> Vec { + fn load_structs(parsed_structs: &[parsed::Struct]) -> Vec { parsed_structs .iter() .cloned() @@ -244,7 +251,7 @@ impl CrossReferencer { .collect() } - fn load_enums(parsed_enums: &Vec) -> Vec { + fn load_enums(parsed_enums: &[parsed::Enum]) -> Vec { let mut identifiables = vec![]; for parsed_enum in parsed_enums.iter() { @@ -265,7 +272,7 @@ impl CrossReferencer { identifiables } - fn load_functions(parsed_functions: &Vec) -> Vec { + fn load_functions(parsed_functions: &[parsed::Function]) -> Vec { parsed_functions .iter() .cloned() @@ -273,7 +280,7 @@ impl CrossReferencer { .collect() } - fn load_imports(parsed_imports: &Vec) -> Vec { + fn load_imports(parsed_imports: &[parsed::Import]) -> Vec { let mut imports = vec![]; for parsed_import in parsed_imports.iter() { @@ -286,6 +293,26 @@ impl CrossReferencer { imports } + fn load_interfaces(parsed_interfaces: &[parsed::Interface]) -> Vec { + let mut identifiables = vec![]; + + for parsed_interface in parsed_interfaces { + let identifiable: Identifiable = parsed_interface.clone().into(); + + identifiables.push(identifiable.clone()); + + let Identifiable::Interface(interface) = identifiable else { + unreachable!(); + }; + + for parsed_interface_function in &parsed_interface.functions { + identifiables.push((interface.clone(), parsed_interface_function).into()); + } + } + + identifiables + } + fn resolve_import(&self, import_rc: Rc>) -> Result<(), CrossReferencerError> { let mut import = import_rc.borrow_mut(); let target_key = import.target_key(); @@ -356,12 +383,10 @@ impl CrossReferencer { let struct_ = struct_rc.borrow(); let mut fields = HashMap::new(); - if let Some(parsed_fields) = &struct_.parsed.fields { - for parsed_field in parsed_fields { - let name = parsed_field.name.value.clone(); - let field = self.resolve_type(&parsed_field.type_, &type_parameters)?; - fields.insert(name, field); - } + for parsed_field in &struct_.parsed.fields { + let name = parsed_field.name.value.clone(); + let field = self.resolve_type(&parsed_field.type_, type_parameters)?; + fields.insert(name, field); } Ok(fields) @@ -486,6 +511,7 @@ impl CrossReferencer { struct_, parameters, }), + Identifiable::Interface(interface) => Type::Interface(InterfaceType { interface }), _ => unreachable!(), }; @@ -500,9 +526,9 @@ impl CrossReferencer { let builtins_key = &(self.builtins_path.clone(), name.clone()); let key = (position.path.clone(), name.clone()); - let mut identifiable = self.identifiables.get(&builtins_key); + let mut identifiable = self.identifiables.get(builtins_key); - if let None = identifiable { + if identifiable.is_none() { identifiable = self.identifiables.get(&key); } @@ -605,7 +631,7 @@ impl CrossReferencer { let mut data = vec![]; for unresolved_data_item in parsed_variant.data.iter() { - let data_item = self.resolve_type(&unresolved_data_item, &type_parameters)?; + let data_item = self.resolve_type(unresolved_data_item, type_parameters)?; data.push(data_item); } variants.insert(name, data); @@ -619,9 +645,15 @@ impl CrossReferencer { function_rc: Rc>, ) -> Result<(), CrossReferencerError> { let type_parameters = self.resolve_function_parameters(function_rc.clone())?; - let arguments = self.resolve_function_arguments(function_rc.clone(), &type_parameters)?; - let return_types = - self.resolve_function_return_types(function_rc.clone(), &type_parameters)?; + let arguments = { + let parsed_arguments = &function_rc.borrow().parsed.arguments; + self.resolve_function_arguments(parsed_arguments, &type_parameters)? + }; + + let return_types = { + let parsed_return_types = &function_rc.borrow().parsed.return_types; + self.resolve_function_return_types(parsed_return_types, &type_parameters)? + }; (*function_rc).borrow_mut().resolved_signature = Some(FunctionSignature { type_parameters, @@ -669,14 +701,10 @@ impl CrossReferencer { fn resolve_function_arguments( &self, - function_rc: Rc>, + parsed_arguments: &[parsed::Argument], type_parameters: &HashMap, ) -> Result, CrossReferencerError> { - let function = function_rc.borrow(); - - function - .parsed - .arguments + parsed_arguments .iter() .map(|parsed| self.resolve_function_argument(parsed, type_parameters)) .collect() @@ -684,12 +712,10 @@ impl CrossReferencer { fn resolve_function_return_types( &self, - function_rc: Rc>, + parsed_return_types: &parsed::ReturnTypes, type_parameters: &HashMap, ) -> Result { - let function = function_rc.borrow(); - - let types = match &function.parsed.return_types { + let types = match &parsed_return_types { parsed::ReturnTypes::Never => return Ok(ReturnTypes::Never), parsed::ReturnTypes::Sometimes(types) => types, }; @@ -740,10 +766,10 @@ impl CrossReferencer { ), }; - return Ok(Type::FunctionPointer(FunctionPointerType { + Ok(Type::FunctionPointer(FunctionPointerType { argument_types, return_types, - })); + })) } fn resolve_function_return_type( @@ -776,6 +802,7 @@ impl CrossReferencer { struct_, parameters, }), + Identifiable::Interface(interface) => Type::Interface(InterfaceType { interface }), _ => unreachable!(), }; @@ -824,6 +851,9 @@ impl CrossReferencer { parameters, }), Identifiable::Enum(enum_) => Type::Enum(EnumType { enum_, parameters }), + Identifiable::Interface(interface) => { + Type::Interface(InterfaceType { interface }) + } _ => todo!(), // InvalidType } } @@ -833,6 +863,79 @@ impl CrossReferencer { Ok(type_) } + fn validate_interface_function_self_argument( + interface: &Interface, + interface_function: &parsed::InterfaceFunction, + ) -> Result<(), CrossReferencerError> { + // The parser enforces we get at least one argument + let first_argument = interface_function.arguments.first().unwrap(); + + let function_name = interface_function.name.func_name.value.clone(); + let interface_name = interface.name(); + let position = interface_function.position.clone(); + + if first_argument.name.value != "self" { + return invalid_interface_function(position, function_name, interface_name); + }; + + let parsed::Type::Regular(type_) = &first_argument.type_ else { + return invalid_interface_function(position, function_name, interface_name); + }; + + if type_.name.value != "Self" { + return invalid_interface_function(position, function_name, interface_name); + } + + // TODO ban usage of the "Self" identifier in any other place + + Ok(()) + } + + fn resolve_interface( + &self, + interface_rc: Rc>, + ) -> Result<(), CrossReferencerError> { + let mut resolved_functions = vec![]; + + { + let interface = &*interface_rc.borrow(); + + for function in &interface.parsed.functions { + Self::validate_interface_function_self_argument(interface, function)?; + + let mut resolved_arguments = vec![]; + + // TODO replace interface function arguments by interface when their type is Self + resolved_arguments.push(Argument { + name: function.arguments[0].name.value.clone(), + position: function.arguments[0].position(), + type_: Type::Interface(InterfaceType { + interface: interface_rc.clone(), + }), + }); + + resolved_arguments.extend( + self.resolve_function_arguments(&function.arguments[1..], &HashMap::new())?, + ); + + let resolved_function = ResolvedInterfaceFunction { + name: function.name.func_name.value.clone(), + arguments: resolved_arguments, + return_types: self + .resolve_function_return_types(&function.return_types, &HashMap::new())?, + }; + + resolved_functions.push(resolved_function); + } + } + + (*interface_rc).borrow_mut().resolved = Some(ResolvedInterface { + functions: resolved_functions, + }); + + Ok(()) + } + fn resolve_function( &self, function_rc: Rc>, @@ -939,7 +1042,7 @@ impl<'a> FunctionBodyResolver<'a> { .variables .iter() .cloned() - .map(|var| Variable::from(var)) + .map(Variable::from) .collect(); let assignment = Assignment { @@ -1029,7 +1132,7 @@ impl<'a> FunctionBodyResolver<'a> { })); } - if let Some(_) = self.local_variables.get(&name) { + if self.local_variables.contains(&name) { return Ok(FunctionBodyItem::CallLocalVariable(CallLocalVariable { name, position, @@ -1055,33 +1158,36 @@ impl<'a> FunctionBodyResolver<'a> { match identifiable { Identifiable::Import(_) => unreachable!(), Identifiable::Function(function_rc) => { - return Ok(FunctionBodyItem::CallFunction(CallFunction { + Ok(FunctionBodyItem::CallFunction(CallFunction { function: function_rc.clone(), type_parameters: parameters, position: parsed.position(), })) } - Identifiable::Enum(enum_rc) => { - return Ok(FunctionBodyItem::CallEnum(CallEnum { - enum_: enum_rc.clone(), - type_parameters: parameters, - position: parsed.position(), - })) - } - Identifiable::Struct(struct_rc) => { - return Ok(FunctionBodyItem::CallStruct(CallStruct { - struct_: struct_rc.clone(), - type_parameters: parameters, - position: parsed.position(), - })) - } + Identifiable::Enum(enum_rc) => Ok(FunctionBodyItem::CallEnum(CallEnum { + enum_: enum_rc.clone(), + type_parameters: parameters, + position: parsed.position(), + })), + Identifiable::Struct(struct_rc) => Ok(FunctionBodyItem::CallStruct(CallStruct { + struct_: struct_rc.clone(), + type_parameters: parameters, + position: parsed.position(), + })), Identifiable::EnumConstructor(enum_ctor) => { - return Ok(FunctionBodyItem::CallEnumConstructor(CallEnumConstructor { + Ok(FunctionBodyItem::CallEnumConstructor(CallEnumConstructor { enum_constructor: enum_ctor.clone(), type_parameters: parameters, position: parsed.position(), - })); + })) } + Identifiable::Interface(_) => todo!(), // TODO calling an interface should lead to an error + Identifiable::InterfaceFunction(function) => Ok( + FunctionBodyItem::CallInterfaceFunction(CallInterfaceFunction { + function, + position: parsed.position(), + }), + ), } } @@ -1190,7 +1296,7 @@ impl<'a> FunctionBodyResolver<'a> { .variables .iter() .cloned() - .map(|var| Variable::from(var)) + .map(Variable::from) .collect(); for variable in &variables { @@ -1260,7 +1366,7 @@ impl<'a> FunctionBodyResolver<'a> { .variables .iter() .cloned() - .map(|var| Variable::from(var)) + .map(Variable::from) .collect(); for variable in &variables { diff --git a/aaa/src/cross_referencer/errors.rs b/aaa/src/cross_referencer/errors.rs index 59e101df..c7c57861 100644 --- a/aaa/src/cross_referencer/errors.rs +++ b/aaa/src/cross_referencer/errors.rs @@ -16,6 +16,7 @@ pub enum CrossReferencerError { NameCollision(NameCollision), GetFunctionNotFound(GetFunctionNotFound), GetFunctionNonFunction(GetFunctionNonFunction), + InvalidInterfaceFunction(InvalidInterfaceFunction), } impl Display for CrossReferencerError { @@ -29,6 +30,7 @@ impl Display for CrossReferencerError { Self::NameCollision(error) => write!(f, "{}", error), Self::GetFunctionNotFound(error) => write!(f, "{}", error), Self::GetFunctionNonFunction(error) => write!(f, "{}", error), + Self::InvalidInterfaceFunction(error) => write!(f, "{}", error), } } } @@ -208,3 +210,37 @@ pub fn get_function_non_function( }, )) } + +pub struct InvalidInterfaceFunction { + pub position: Position, + pub function_name: String, + pub interface_name: String, +} + +impl Display for InvalidInterfaceFunction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!( + f, + "{} Invalid function {} in interface {}", + self.position, self.function_name, self.interface_name + )?; + writeln!( + f, + "The first argument must be named self and have type Self." + ) + } +} + +pub fn invalid_interface_function( + position: Position, + function_name: String, + interface_name: String, +) -> Result { + Err(CrossReferencerError::InvalidInterfaceFunction( + InvalidInterfaceFunction { + position, + function_name, + interface_name, + }, + )) +} diff --git a/aaa/src/cross_referencer/mod.rs b/aaa/src/cross_referencer/mod.rs index 0ac9cda8..b5637c08 100644 --- a/aaa/src/cross_referencer/mod.rs +++ b/aaa/src/cross_referencer/mod.rs @@ -1,3 +1,4 @@ +#[allow(clippy::module_inception)] pub mod cross_referencer; pub mod errors; pub mod types; diff --git a/aaa/src/cross_referencer/types/function_body.rs b/aaa/src/cross_referencer/types/function_body.rs index b505bb63..0ee7b796 100644 --- a/aaa/src/cross_referencer/types/function_body.rs +++ b/aaa/src/cross_referencer/types/function_body.rs @@ -5,7 +5,9 @@ use std::{cell::RefCell, fmt::Display}; use crate::common::position::Position; use crate::parser::types as parsed; -use super::identifiable::{Enum, EnumConstructor, Function, ReturnTypes, Struct, Type}; +use super::identifiable::{ + Enum, EnumConstructor, Function, InterfaceFunction, ReturnTypes, Struct, Type, +}; pub struct FunctionBody { pub position: Position, @@ -14,54 +16,56 @@ pub struct FunctionBody { pub enum FunctionBodyItem { Assignment(Assignment), - Branch(Branch), Boolean(Boolean), + Branch(Branch), Call(Call), CallArgument(CallArgument), - CallFunction(CallFunction), CallEnum(CallEnum), CallEnumConstructor(CallEnumConstructor), + CallFunction(CallFunction), + CallInterfaceFunction(CallInterfaceFunction), CallLocalVariable(CallLocalVariable), CallStruct(CallStruct), Char(Char), Foreach(Foreach), FunctionType(FunctionType), + GetField(GetField), GetFunction(GetFunction), Integer(Integer), Match(Match), Return(Return), - GetField(GetField), SetField(SetField), + String(ParsedString), Use(Use), While(While), - String(ParsedString), } impl Display for FunctionBodyItem { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Assignment(_) => write!(f, "Assignment"), - Self::Branch(_) => write!(f, "Branch"), Self::Boolean(_) => write!(f, "Boolean"), + Self::Branch(_) => write!(f, "Branch"), Self::Call(_) => write!(f, "Call"), Self::CallArgument(_) => write!(f, "CallArgument"), - Self::CallFunction(_) => write!(f, "CallFunction"), Self::CallEnum(_) => write!(f, "CallEnum"), Self::CallEnumConstructor(_) => write!(f, "CallEnumConstructor"), + Self::CallFunction(_) => write!(f, "CallFunction"), + Self::CallInterfaceFunction(_) => write!(f, "CallInterfaceFunction"), Self::CallLocalVariable(_) => write!(f, "CallLocalVariable"), Self::CallStruct(_) => write!(f, "CallStruct"), Self::Char(_) => write!(f, "Char"), Self::Foreach(_) => write!(f, "Foreach"), Self::FunctionType(_) => write!(f, "FunctionType"), + Self::GetField(_) => write!(f, "GetField"), Self::GetFunction(_) => write!(f, "CallByName"), Self::Integer(_) => write!(f, "Integer"), Self::Match(_) => write!(f, "Match"), Self::Return(_) => write!(f, "Return"), - Self::GetField(_) => write!(f, "GetField"), Self::SetField(_) => write!(f, "SetField"), + Self::String(_) => write!(f, "String"), Self::Use(_) => write!(f, "Use"), Self::While(_) => write!(f, "While"), - Self::String(_) => write!(f, "String"), } } } @@ -70,27 +74,28 @@ impl FunctionBodyItem { pub fn position(&self) -> Position { match self { Self::Assignment(item) => item.position.clone(), - Self::Branch(item) => item.position.clone(), Self::Boolean(item) => item.position.clone(), + Self::Branch(item) => item.position.clone(), Self::Call(item) => item.position.clone(), Self::CallArgument(item) => item.position.clone(), - Self::CallFunction(item) => item.position.clone(), Self::CallEnum(item) => item.position.clone(), Self::CallEnumConstructor(item) => item.position.clone(), + Self::CallFunction(item) => item.position.clone(), + Self::CallInterfaceFunction(item) => item.position.clone(), Self::CallLocalVariable(item) => item.position.clone(), Self::CallStruct(item) => item.position.clone(), Self::Char(item) => item.position.clone(), Self::Foreach(item) => item.position.clone(), Self::FunctionType(item) => item.position.clone(), + Self::GetField(item) => item.position.clone(), Self::GetFunction(item) => item.position.clone(), Self::Integer(item) => item.position.clone(), Self::Match(item) => item.position.clone(), Self::Return(item) => item.position.clone(), - Self::GetField(item) => item.position.clone(), Self::SetField(item) => item.position.clone(), + Self::String(item) => item.position.clone(), Self::Use(item) => item.position.clone(), Self::While(item) => item.position.clone(), - Self::String(item) => item.position.clone(), } } } @@ -136,7 +141,7 @@ pub struct Char { pub value: char, } -#[allow(dead_code)] // TODO #214 implement interfaces +#[allow(dead_code)] // TODO #243 Support foreach loops pub struct Foreach { pub position: Position, pub body: FunctionBody, @@ -249,3 +254,8 @@ pub struct ParsedString { pub position: Position, pub value: String, } + +pub struct CallInterfaceFunction { + pub position: Position, + pub function: InterfaceFunction, +} diff --git a/aaa/src/cross_referencer/types/identifiable.rs b/aaa/src/cross_referencer/types/identifiable.rs index 5f6edc30..9caea6c4 100644 --- a/aaa/src/cross_referencer/types/identifiable.rs +++ b/aaa/src/cross_referencer/types/identifiable.rs @@ -1,4 +1,11 @@ -use std::{cell::RefCell, collections::HashMap, fmt::Display, iter::zip, path::PathBuf, rc::Rc}; +use std::{ + cell::RefCell, + collections::HashMap, + fmt::{Debug, Display}, + iter::zip, + path::PathBuf, + rc::Rc, +}; use crate::{ common::{formatting::join_display, position::Position, traits::HasPosition}, @@ -8,7 +15,6 @@ use crate::{ use super::function_body::FunctionBody; pub struct Struct { - pub is_builtin: bool, pub parsed: parsed::Struct, pub resolved: Option, } @@ -22,7 +28,6 @@ impl PartialEq for Struct { impl From for Struct { fn from(parsed: parsed::Struct) -> Self { Self { - is_builtin: parsed.fields.is_none(), parsed, resolved: None, } @@ -84,6 +89,10 @@ impl Struct { mapping } + + pub fn is_builtin(&self) -> bool { + self.parsed.is_builtin + } } pub struct ResolvedStruct { @@ -133,6 +142,13 @@ pub enum Type { Struct(StructType), Enum(EnumType), Parameter(TypeParameter), + Interface(InterfaceType), +} + +impl Debug for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self) + } } impl Display for Type { @@ -142,6 +158,7 @@ impl Display for Type { Self::Struct(struct_) => write!(f, "{}", struct_), Self::Enum(enum_) => write!(f, "{}", enum_), Self::Parameter(param) => write!(f, "{}", param), + Self::Interface(interface) => write!(f, "{}", interface), } } } @@ -149,10 +166,11 @@ impl Display for Type { impl Type { pub fn kind(&self) -> &'static str { match &self { - &Self::FunctionPointer(_) => "function pointer", - &Self::Struct(_) => "struct", - &Self::Enum(_) => "enum", - &Self::Parameter(_) => "parameter", + Self::FunctionPointer(_) => "function pointer", + Self::Struct(_) => "struct", + Self::Enum(_) => "enum", + Self::Parameter(_) => "parameter", + Self::Interface(_) => "interface", } } } @@ -164,6 +182,7 @@ impl HasPosition for Type { &Self::Struct(struct_type) => struct_type.struct_.borrow().position(), &Self::Enum(enum_type) => enum_type.enum_.borrow().position(), &Self::Parameter(parameter) => parameter.position.clone(), + &Self::Interface(interface_type) => interface_type.interface.borrow().position(), } } } @@ -224,6 +243,17 @@ impl Display for FunctionPointerType { } } +#[derive(Clone, PartialEq)] +pub struct InterfaceType { + pub interface: Rc>, +} + +impl Display for InterfaceType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.interface.borrow().name()) + } +} + #[derive(Clone, PartialEq)] pub enum ReturnTypes { Sometimes(Vec), @@ -233,7 +263,7 @@ pub enum ReturnTypes { impl Display for ReturnTypes { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Self::Never => return write!(f, "never"), + Self::Never => write!(f, "never"), Self::Sometimes(types) => { let joined_types = join_display(", ", types); write!(f, "{}", joined_types) @@ -274,7 +304,6 @@ impl Display for Argument { pub struct Enum { pub parsed: parsed::Enum, pub resolved: Option, - pub is_builtin: bool, } impl PartialEq for Enum { @@ -286,7 +315,6 @@ impl PartialEq for Enum { impl From for Enum { fn from(parsed: parsed::Enum) -> Self { Self { - is_builtin: false, // TODO #224 add is_builtin to parsed Enum, Struct and Function parsed, resolved: None, } @@ -319,12 +347,12 @@ impl Enum { } pub fn zero_variant_name(&self) -> &String { - &self.parsed.variants.get(0).unwrap().name.value + &self.parsed.variants.first().unwrap().name.value } pub fn zero_variant_data(&self) -> &Vec { let name = self.zero_variant_name(); - &self.variants().get(name).unwrap() + self.variants().get(name).unwrap() } pub fn parameter_names(&self) -> Vec { @@ -347,6 +375,10 @@ impl Enum { mapping } + + pub fn is_builtin(&self) -> bool { + self.parsed.is_builtin + } } impl HasPosition for Enum { @@ -422,7 +454,7 @@ impl From for Function { fn from(parsed: parsed::Function) -> Self { Self { is_builtin: parsed.body.is_none(), - parsed: parsed, + parsed, resolved_signature: None, resolved_body: None, } @@ -438,8 +470,7 @@ impl Function { self.signature() .arguments .iter() - .filter(|arg| &arg.name == name) - .next() + .find(|arg| &arg.name == name) } pub fn has_argument(&self, name: &String) -> bool { @@ -519,6 +550,95 @@ impl HasPosition for Import { } } +pub struct Interface { + pub parsed: parsed::Interface, + pub resolved: Option, +} + +impl PartialEq for Interface { + fn eq(&self, other: &Self) -> bool { + self.name() == other.name() && self.position() == other.position() + } +} + +impl Interface { + pub fn name(&self) -> String { + self.parsed.name.value.clone() + } + + pub fn is_builtin(&self) -> bool { + self.parsed.is_builtin + } + + pub fn resolved(&self) -> &ResolvedInterface { + match &self.resolved { + Some(resolved) => resolved, + None => unreachable!(), + } + } + + pub fn key(&self) -> (PathBuf, String) { + (self.position().path, self.name()) + } +} + +impl HasPosition for Interface { + fn position(&self) -> Position { + self.parsed.position.clone() + } +} + +impl From for Interface { + fn from(parsed: parsed::Interface) -> Self { + Self { + parsed, + resolved: None, + } + } +} + +pub struct ResolvedInterface { + pub functions: Vec, +} + +#[derive(Clone)] +pub struct ResolvedInterfaceFunction { + pub name: String, + pub arguments: Vec, + pub return_types: ReturnTypes, +} + +#[derive(Clone)] +pub struct InterfaceFunction { + pub interface: Rc>, + pub function_name: String, + pub position: Position, +} + +impl InterfaceFunction { + pub fn name(&self) -> String { + format!("{}:{}", self.interface.borrow().name(), self.function_name) + } + + pub fn resolved_function(&self) -> ResolvedInterfaceFunction { + let interface = self.interface.borrow(); + + interface + .resolved() + .functions + .iter() + .find(|function| function.name == self.function_name) + .unwrap() + .clone() + } +} + +impl HasPosition for InterfaceFunction { + fn position(&self) -> Position { + self.position.clone() + } +} + #[derive(Clone)] pub enum Identifiable { Struct(Rc>), @@ -526,6 +646,8 @@ pub enum Identifiable { EnumConstructor(Rc>), Function(Rc>), Import(Rc>), + Interface(Rc>), + InterfaceFunction(InterfaceFunction), } impl From for Identifiable { @@ -558,15 +680,32 @@ impl From<(parsed::Import, parsed::ImportItem)> for Identifiable { } } +impl From for Identifiable { + fn from(parsed: parsed::Interface) -> Self { + Identifiable::Interface(Rc::new(RefCell::new(parsed.into()))) + } +} + +impl From<(Rc>, &parsed::InterfaceFunction)> for Identifiable { + fn from(tuple: (Rc>, &parsed::InterfaceFunction)) -> Self { + Identifiable::InterfaceFunction(InterfaceFunction { + interface: tuple.0, + function_name: tuple.1.name.func_name.value.clone(), + position: tuple.1.position.clone(), + }) + } +} + impl Identifiable { pub fn is_builtin(&self) -> bool { match self { Identifiable::Function(function) => function.borrow().is_builtin, - Identifiable::Struct(struct_) => struct_.borrow().is_builtin, - Identifiable::Enum(enum_) => enum_.borrow().is_builtin, + Identifiable::Struct(struct_) => struct_.borrow().is_builtin(), + Identifiable::Enum(enum_) => enum_.borrow().is_builtin(), Identifiable::EnumConstructor(enum_ctor) => { - enum_ctor.borrow().enum_.borrow().is_builtin + enum_ctor.borrow().enum_.borrow().is_builtin() } + Identifiable::Interface(interface) => interface.borrow().is_builtin(), _ => false, } } @@ -578,6 +717,8 @@ impl Identifiable { Identifiable::Function(function) => function.borrow().name(), Identifiable::Import(import) => import.borrow().name(), Identifiable::Struct(struct_) => struct_.borrow().name(), + Identifiable::Interface(interface) => interface.borrow().name(), + Identifiable::InterfaceFunction(interface_function) => interface_function.name(), } } @@ -594,6 +735,8 @@ impl HasPosition for Identifiable { Identifiable::Function(function) => function.borrow().position(), Identifiable::Import(import) => import.borrow().position(), Identifiable::Struct(struct_) => struct_.borrow().position(), + Identifiable::Interface(interface) => interface.borrow().position(), + Identifiable::InterfaceFunction(interface_function) => interface_function.position(), } } } @@ -601,11 +744,13 @@ impl HasPosition for Identifiable { impl Display for Identifiable { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let prefix = match self { - Identifiable::Enum(_) => "enum ", + Identifiable::Enum(_) => "enum", Identifiable::EnumConstructor(_) => "enum constructor", Identifiable::Function(_) => "function", Identifiable::Import(_) => "import", Identifiable::Struct(_) => "struct", + Identifiable::Interface(_) => "interface", + Identifiable::InterfaceFunction(_) => "interface function", }; write!(f, "{} {}", prefix, self.name()) diff --git a/aaa/src/parser/mod.rs b/aaa/src/parser/mod.rs index 07295a2b..038f6ceb 100644 --- a/aaa/src/parser/mod.rs +++ b/aaa/src/parser/mod.rs @@ -1,2 +1,3 @@ +#[allow(clippy::module_inception)] pub mod parser; pub mod types; diff --git a/aaa/src/parser/parser.rs b/aaa/src/parser/parser.rs index 4e8fa5c2..6e3d8e86 100644 --- a/aaa/src/parser/parser.rs +++ b/aaa/src/parser/parser.rs @@ -10,8 +10,8 @@ use super::types::{ Argument, Assignment, Boolean, Branch, CallByPointer, CaseBlock, CaseLabel, Char, DefaultBlock, Enum, EnumVariant, Foreach, FreeFunctionCall, FreeFunctionName, Function, FunctionBody, FunctionBodyItem, FunctionCall, FunctionName, FunctionType, Identifier, Import, ImportItem, - Integer, Match, MemberFunctionCall, MemberFunctionName, RegularType, Return, ReturnTypes, - SourceFile, Struct, StructField, Type, While, + Integer, Interface, InterfaceFunction, Match, MemberFunctionCall, MemberFunctionName, + RegularType, Return, ReturnTypes, SourceFile, Struct, StructField, Type, While, }; pub enum ParseError { @@ -50,7 +50,7 @@ struct Parser { impl Parser { fn new(tokens: Vec) -> Self { - return Self { tokens }; + Self { tokens } } fn parse(&self) -> Result { @@ -89,17 +89,14 @@ impl Parser { let mut items = vec![]; let item; - (item, offset) = parse_func(&self, offset)?; + (item, offset) = parse_func(self, offset)?; items.push(item); - loop { - match self.peek_token_type(offset) { - Some(TokenType::Comma) => offset += 1, - _ => break, - } + while let Some(TokenType::Comma) = self.peek_token_type(offset) { + offset += 1; let item; - (item, offset) = match parse_func(&self, offset) { + (item, offset) = match parse_func(self, offset) { Ok((item, offset)) => (item, offset), Err(_) => break, }; @@ -134,6 +131,11 @@ impl Parser { offset = child_offset; source_file.functions.push(function); } + TokenType::Interface => { + let (interface, child_offset) = self.parse_interface(offset)?; + offset = child_offset; + source_file.interfaces.push(interface); + } TokenType::Builtin => { let next_token = self.peek_token(offset + 1)?; match next_token.type_ { @@ -142,11 +144,21 @@ impl Parser { (struct_, offset) = self.parse_struct(offset)?; source_file.structs.push(struct_); } + TokenType::Enum => { + let enum_; + (enum_, offset) = self.parse_enum(offset)?; + source_file.enums.push(enum_); + } TokenType::Fn => { - let (function, child_offset) = self.parse_function(offset)?; - offset = child_offset; + let function; + (function, offset) = self.parse_function(offset)?; source_file.functions.push(function); } + TokenType::Interface => { + let interface; + (interface, offset) = self.parse_interface(offset)?; + source_file.interfaces.push(interface); + } _ => return Err(ParseError::UnexpectedToken(token)), } } @@ -159,14 +171,13 @@ impl Parser { fn parse_struct(&self, mut offset: usize) -> ParseResult { let mut struct_ = Struct::default(); - let mut is_builtin = false; let mut builtin_token = None; if self.peek_token_type(offset) == Some(TokenType::Builtin) { let first_token; (first_token, offset) = self.parse_token(offset, TokenType::Builtin)?; builtin_token = Some(first_token); - is_builtin = true; + struct_.is_builtin = true; } let struct_token; @@ -183,11 +194,7 @@ impl Parser { (struct_.parameters, offset) = self.parse_parameter_list(offset)?; } - if !is_builtin { - let fields; - (fields, offset) = self.parse_struct_fields(offset)?; - struct_.fields = Some(fields); - } + (struct_.fields, offset) = self.parse_struct_fields(offset)?; Ok((struct_, offset)) } @@ -881,9 +888,22 @@ impl Parser { fn parse_enum(&self, mut offset: usize) -> ParseResult { let mut enum_ = Enum::default(); - let first_token; - (first_token, offset) = self.parse_token(offset, TokenType::Enum)?; - enum_.position = first_token.position(); + let mut builtin_token = None; + + if self.peek_token_type(offset) == Some(TokenType::Builtin) { + let first_token; + (first_token, offset) = self.parse_token(offset, TokenType::Builtin)?; + builtin_token = Some(first_token); + enum_.is_builtin = true; + } + + let enum_token; + (enum_token, offset) = self.parse_token(offset, TokenType::Enum)?; + + enum_.position = match builtin_token { + Some(builtin_token) => builtin_token.position(), + None => enum_token.position(), + }; (enum_.name, offset) = self.parse_identifier(offset)?; @@ -959,6 +979,59 @@ impl Parser { Ok((item, offset)) } + + fn parse_interface(&self, mut offset: usize) -> ParseResult { + let mut interface = Interface::default(); + + let mut builtin_token = None; + + if self.peek_token_type(offset) == Some(TokenType::Builtin) { + let first_token; + (first_token, offset) = self.parse_token(offset, TokenType::Builtin)?; + builtin_token = Some(first_token); + interface.is_builtin = true; + } + + let interface_token; + (interface_token, offset) = self.parse_token(offset, TokenType::Interface)?; + + interface.position = match builtin_token { + Some(builtin_token) => builtin_token.position(), + None => interface_token.position(), + }; + + (interface.name, offset) = self.parse_identifier(offset)?; + + (_, offset) = self.parse_token(offset, TokenType::Start)?; + (interface.functions, offset) = + self.parse_comma_separated(offset, Parser::parse_interface_function)?; + (_, offset) = self.parse_token(offset, TokenType::End)?; + + Ok((interface, offset)) + } + + fn parse_interface_function(&self, mut offset: usize) -> ParseResult { + let mut function = InterfaceFunction::default(); + + let fn_token; + + (fn_token, offset) = self.parse_token(offset, TokenType::Fn)?; + function.position = fn_token.position(); + + (function.name, offset) = self.parse_member_function_name(offset)?; + (function.arguments, offset) = self.parse_arguments(offset)?; + + if self.peek_token_type(offset) == Some(TokenType::Return) { + (function.return_types, offset) = self.parse_function_return_types(offset)?; + } + + if self.tokens[offset - 1].type_ == TokenType::Comma { + // Prevent consuming trailing comma + offset -= 1; + } + + Ok((function, offset)) + } } #[cfg(test)] @@ -1581,13 +1654,14 @@ mod tests { #[rstest] #[case("", false)] - #[case("builtin struct foo", true)] - #[case("builtin struct foo { }", false)] - #[case("builtin struct foo[A]", true)] - #[case("builtin struct foo[A,]", true)] - #[case("builtin struct foo[A,B]", true)] - #[case("builtin struct foo[A,B,]", true)] - #[case("builtin struct foo[A,B,]", true)] + #[case("builtin struct foo", false)] + #[case("builtin struct foo { }", true)] + #[case("builtin struct foo[A]", false)] + #[case("builtin struct foo[A] { }", true)] + #[case("builtin struct foo[A,] { }", true)] + #[case("builtin struct foo[A,B] { }", true)] + #[case("builtin struct foo[A,B,] { }", true)] + #[case("builtin struct foo[A,B,] { }", true)] #[case("struct foo { }", true)] #[case("struct foo", false)] #[case("struct foo[A] { }", true)] @@ -1655,6 +1729,34 @@ mod tests { check_parse(code, expected_parsed, Parser::parse_import); } + #[rstest] + #[case("", false)] + #[case("fn Self:foo args self as Self return int", true)] + #[case("fn Self:foo args self as Self", true)] + #[case("fn Self:foo args self as Self return int, bool", true)] + #[case("fn Self:foo args self as Self return never", true)] + #[case("fn Self:foo args self as Self, bar as Bar return int", true)] + #[case("fn foo args self as Self, bar as Bar return int", false)] + fn test_parse_interface_function(#[case] code: &str, #[case] expected_parsed: bool) { + check_parse(code, expected_parsed, Parser::parse_interface_function); + } + + #[rstest] + #[case("", false)] + #[case("interface Fooable {}", false)] + #[case("interface Fooable { fn Self:foo args self as Self }", true)] + #[case("interface Fooable { fn Self:foo args self as Self, }", true)] + #[case( + "interface Fooable { + fn Self:foo args self as Self, + fn Self:foo args self as Self, + }", + true + )] + fn test_parse_interface(#[case] code: &str, #[case] expected_parsed: bool) { + check_parse(code, expected_parsed, Parser::parse_interface); + } + #[rstest] #[case("", true)] #[case("from \"file\" import foo", true)] diff --git a/aaa/src/parser/types.rs b/aaa/src/parser/types.rs index fce9d3fc..86618a65 100644 --- a/aaa/src/parser/types.rs +++ b/aaa/src/parser/types.rs @@ -3,7 +3,7 @@ use lazy_static::lazy_static; use std::{ collections::HashMap, fmt::Display, - path::{PathBuf, MAIN_SEPARATOR}, + path::{Path, PathBuf, MAIN_SEPARATOR_STR}, }; use crate::{ @@ -17,10 +17,11 @@ pub struct SourceFile { pub functions: Vec, pub imports: Vec, pub structs: Vec, + pub interfaces: Vec, } impl SourceFile { - pub fn dependencies(&self, current_dir: &PathBuf) -> Vec { + pub fn dependencies(&self, current_dir: &Path) -> Vec { self.imports .iter() .map(|import| import.get_source_path(current_dir)) @@ -34,6 +35,7 @@ pub struct Enum { pub name: Identifier, pub parameters: Vec, pub variants: Vec, + pub is_builtin: bool, } #[derive(Clone, Default)] @@ -242,7 +244,7 @@ pub enum FunctionName { impl Default for FunctionName { fn default() -> Self { - return Self::Free(FreeFunctionName::default()); + Self::Free(FreeFunctionName::default()) } } @@ -286,7 +288,7 @@ pub struct Import { } impl Import { - pub fn get_source_path(&self, current_dir: &PathBuf) -> PathBuf { + pub fn get_source_path(&self, current_dir: &Path) -> PathBuf { let source = &self.source.value; if source.ends_with(".aaa") { @@ -306,7 +308,7 @@ impl Import { .path .parent() .unwrap() - .join(source.replace(".", &MAIN_SEPARATOR.to_string())); + .join(source.replace(".", MAIN_SEPARATOR_STR)); path.set_extension("aaa"); @@ -345,7 +347,7 @@ pub enum ReturnTypes { impl Default for ReturnTypes { fn default() -> Self { - return ReturnTypes::Sometimes(vec![]); + ReturnTypes::Sometimes(vec![]) } } @@ -354,7 +356,8 @@ pub struct Struct { pub position: Position, pub name: Identifier, pub parameters: Vec, - pub fields: Option>, + pub fields: Vec, + pub is_builtin: bool, } #[derive(Clone)] @@ -434,7 +437,7 @@ impl HasPosition for Type { impl Default for Type { fn default() -> Self { - return Self::Regular(RegularType::default()); + Self::Regular(RegularType::default()) } } @@ -514,6 +517,22 @@ impl ParsedString { } } +#[derive(Default, Clone)] +pub struct InterfaceFunction { + pub position: Position, + pub name: MemberFunctionName, + pub arguments: Vec, + pub return_types: ReturnTypes, +} + +#[derive(Default, Clone)] +pub struct Interface { + pub position: Position, + pub name: Identifier, + pub functions: Vec, + pub is_builtin: bool, +} + lazy_static! { static ref ESCAPE_SEQUENCES: HashMap = { let mut escape_sequences = HashMap::new(); diff --git a/aaa/src/runner/mod.rs b/aaa/src/runner/mod.rs index 972e31d0..15eafcef 100644 --- a/aaa/src/runner/mod.rs +++ b/aaa/src/runner/mod.rs @@ -1,2 +1,3 @@ pub mod errors; +#[allow(clippy::module_inception)] pub mod runner; diff --git a/aaa/src/runner/runner.rs b/aaa/src/runner/runner.rs index a9aac0f3..bd85acd1 100644 --- a/aaa/src/runner/runner.rs +++ b/aaa/src/runner/runner.rs @@ -3,7 +3,7 @@ use std::{ env, fs::{self, read_to_string}, io, - path::PathBuf, + path::{Path, PathBuf}, process::Command, }; @@ -93,9 +93,10 @@ impl Runner { let runner_error: RunnerError = error.into(); eprint!("{}", runner_error); } - eprintln!(""); + eprintln!(); eprintln!("Found {} errors", error_count); - return 1; + + 1 } fn parse_entrypoint(&self) -> Result { @@ -103,8 +104,8 @@ impl Runner { Ok(parsed) } - fn parse_file(&self, code: &String, path: &PathBuf) -> Result { - let tokens = tokenize_filtered(&code, Some(path.clone()))?; + fn parse_file(&self, code: &str, path: &Path) -> Result { + let tokens = tokenize_filtered(code, Some(path.to_path_buf()))?; let parsed = parse(tokens)?; Ok(parsed) @@ -158,7 +159,7 @@ impl Runner { .join(random_folder_name()) } - fn compile(&self, transpiler_root_path: &PathBuf) -> Result { + fn compile(&self, transpiler_root_path: &Path) -> Result { // Use shared target dir between executables, // because every Aaa compilation would otherwise take 120 MB disk, // due to Rust dependencies. @@ -171,7 +172,7 @@ impl Runner { let stdlib_impl_path = repository_root().join("aaa-stdlib"); // Join strings in this ugly fashion to prevent leading whitespace in generated Cargo.toml - let cargo_toml_content = vec![ + let cargo_toml_content = [ "[package]", "name = \"aaa-stdlib-user\"", "version = \"0.1.0\"", @@ -184,6 +185,7 @@ impl Runner { ) .as_str(), "regex = \"1.8.4\"", + "lazy_static = \"1.4.0\"", "", ] .join("\n"); @@ -216,7 +218,7 @@ impl Runner { let binary_path = cargo_target_dir.join("release/aaa-stdlib-user"); if let Some(requested_binary_path) = &self.options.output_binary { - fs::rename(binary_path, &requested_binary_path).unwrap(); + fs::rename(binary_path, requested_binary_path).unwrap(); return Ok(requested_binary_path.clone()); } @@ -266,10 +268,8 @@ impl Runner { .expect("could not run binary") .code() .unwrap(); - } else { - if self.options.output_binary.is_none() { - println!("Generated binary in {}", binary_path.display()); - } + } else if self.options.output_binary.is_none() { + println!("Generated binary in {}", binary_path.display()); } } diff --git a/aaa/src/tests/doctests.rs b/aaa/src/tests/doctests.rs index 2572d6e4..95bae424 100644 --- a/aaa/src/tests/doctests.rs +++ b/aaa/src/tests/doctests.rs @@ -3,7 +3,7 @@ use std::{ env, fmt::Display, fs::{self, read_to_string}, - path::PathBuf, + path::{Path, PathBuf}, process::Command, }; @@ -15,7 +15,15 @@ enum CommentMode { Stderr, } -#[derive(Default, Clone, Debug)] +#[derive(Clone, PartialEq, Default)] +enum Action { + Skip, + Check, + #[default] + Run, +} + +#[derive(Default, Clone)] struct DocTest { name: String, path: PathBuf, @@ -24,7 +32,7 @@ struct DocTest { expected_stdout: String, expected_stderr: String, source_path: PathBuf, - skipped: bool, + action: Action, } impl DocTest { @@ -156,8 +164,6 @@ impl DocTestRunner { } pub fn run(&mut self) -> i32 { - print!("\n\n"); - for path in self.paths.clone() { self.parse_doctest_file(path); } @@ -168,7 +174,7 @@ impl DocTestRunner { let mut errors = vec![]; for doc_test in self.doc_tests.values() { - match self.run_doc_test(&doc_test) { + match self.run_doc_test(doc_test) { DocTestResult::Ok => (), DocTestResult::Skipped => skipped_tests += 1, DocTestResult::Err(error) => errors.push(error), @@ -235,12 +241,13 @@ impl DocTestRunner { sections } - fn parse_doc_test(path: &PathBuf, lines: Vec) -> DocTest { + fn parse_doc_test(path: &Path, lines: Vec) -> DocTest { + use Action::*; use CommentMode::*; let mut comment_mode = Default; let mut doc_test = DocTest { - path: path.clone(), + path: path.to_path_buf(), source_path: env::temp_dir() .join("aaa-doctests") .join(random_folder_name()), @@ -265,7 +272,7 @@ impl DocTestRunner { to_do ); } - doc_test.skipped = true; + doc_test.action = Skip; } if let Some(suffix) = line.strip_prefix("/// name:") { @@ -273,6 +280,17 @@ impl DocTestRunner { continue; } + if let Some(suffix) = line.strip_prefix("/// action:") { + if doc_test.action != Skip { + match suffix.trim() { + "check" => doc_test.action = Check, + "run" => doc_test.action = Run, + "skip" => doc_test.action = Skip, + action => panic!("Found doctest with invalid action {}", action), + } + } + } + if let Some(suffix) = line.strip_prefix("/// file:") { file_name = suffix.trim().to_owned(); continue; @@ -317,7 +335,7 @@ impl DocTestRunner { fn run_doc_test(&self, doc_test: &DocTest) -> DocTestResult { print!("{} ... ", doc_test.pretty_name()); - if doc_test.skipped { + if doc_test.action == Action::Skip { println!("SKIPPED"); return DocTestResult::Skipped; } @@ -336,9 +354,15 @@ impl DocTestRunner { .to_string_lossy() .into_owned(); + let command = match doc_test.action { + Action::Check => ["run", "-q", "--release", "check", &main_file], + Action::Run => ["run", "-q", "--release", "run", &main_file], + Action::Skip => unreachable!(), + }; + // Enable optimizations with `--release` to speed up running doctests. let output = Command::new("cargo") - .args(["run", "-q", "--release", "check", &main_file]) + .args(command) .output() .expect("Failed to execute command"); @@ -383,7 +407,7 @@ impl DocTestRunner { } println!("OK"); - return DocTestResult::Ok; + DocTestResult::Ok } } diff --git a/aaa/src/tests/src/assign.aaa b/aaa/src/tests/src/assign.aaa index 1c6037f3..2fb4fbd9 100644 --- a/aaa/src/tests/src/assign.aaa +++ b/aaa/src/tests/src/assign.aaa @@ -1,5 +1,7 @@ /// name: assign local variable ok +/// action: check + fn main { 3 use x { @@ -11,6 +13,8 @@ fn main { /// name: assign local variable fail too many values +/// action: check + fn main { 3 use x { @@ -29,6 +33,8 @@ fn main { /// name: assign local variable fail not enough values +/// action: check + fn main { 3 use x { @@ -47,6 +53,8 @@ fn main { /// name: assign local variable fail wrong type +/// action: check + fn main { 3 use x { @@ -67,6 +75,8 @@ fn main { /// name: assign local variable fail non existing variable +/// action: check + fn main { 3 use x { @@ -85,6 +95,8 @@ fn main { /// name: assign argument ok +/// action: check + fn main { nop } diff --git a/aaa/src/tests/src/branch.aaa b/aaa/src/tests/src/branch.aaa index 5371728a..65795736 100644 --- a/aaa/src/tests/src/branch.aaa +++ b/aaa/src/tests/src/branch.aaa @@ -1,5 +1,7 @@ /// name: branch without else ok +/// action: check + fn main { if true { nop @@ -10,6 +12,8 @@ fn main { /// name: branch with else ok +/// action: check + fn main { if true { nop @@ -22,6 +26,8 @@ fn main { /// name: branch without else fail +/// action: check + fn main { if true { 3 @@ -42,6 +48,8 @@ fn main { /// name: branch with else fail +/// action: check + fn main { if true { 3 @@ -64,6 +72,8 @@ fn main { /// name: branch with invalid condition +/// action: check + fn main { if 3 { nop @@ -84,6 +94,8 @@ fn main { /// name: else with type never +/// action: check + fn main { if true { nop @@ -98,6 +110,8 @@ fn main { /// name: if with type never +/// action: check + fn main { if false { unreachable @@ -112,6 +126,8 @@ fn main { /// name: if with type never without else +/// action: check + fn main { if false { unreachable @@ -124,6 +140,8 @@ fn main { /// name: if and else with type never +/// action: check + fn main { if true { unreachable diff --git a/aaa/src/tests/src/builtin.aaa b/aaa/src/tests/src/builtin.aaa index 0bd1f01c..164cca80 100644 --- a/aaa/src/tests/src/builtin.aaa +++ b/aaa/src/tests/src/builtin.aaa @@ -1,5 +1,7 @@ /// name: unexpected builtin function +/// action: check + fn main { nop } @@ -17,11 +19,13 @@ builtin fn foo /// name: unexpected builtin struct +/// action: check + fn main { nop } -builtin struct bar +builtin struct bar {} /// status: 1 diff --git a/aaa/src/tests/src/call_enum.aaa b/aaa/src/tests/src/call_enum.aaa index d5ef69d8..0e73265e 100644 --- a/aaa/src/tests/src/call_enum.aaa +++ b/aaa/src/tests/src/call_enum.aaa @@ -1,5 +1,7 @@ /// name: call enum ok +/// action: check + enum Enum[T] { value } @@ -12,6 +14,8 @@ fn main { /// name: call enum with too many type parameters +/// action: check + enum Enum[T] { value } @@ -33,6 +37,8 @@ fn main { /// name: call enum with too few type parameters +/// action: check + enum Enum[T] { value } diff --git a/aaa/src/tests/src/call_keyword.aaa b/aaa/src/tests/src/call_keyword.aaa index 424f1467..99bac792 100644 --- a/aaa/src/tests/src/call_keyword.aaa +++ b/aaa/src/tests/src/call_keyword.aaa @@ -1,5 +1,7 @@ /// name: call ok +/// action: check + fn is_nice args x as int return bool { x 69 = } @@ -13,6 +15,8 @@ fn main { /// name: call fail wrong argument +/// action: check + fn is_nice args x as int return bool { x 69 = } @@ -35,6 +39,8 @@ fn main { /// name: call fail use return type incorrectly +/// action: check + fn is_nice args x as int return bool { x 69 = } @@ -58,6 +64,8 @@ fn main { /// name: call fail empty stack +/// action: check + fn main { call } @@ -75,6 +83,8 @@ fn main { /// name: call fail non-function +/// action: check + fn main { 3 call } diff --git a/aaa/src/tests/src/call_struct.aaa b/aaa/src/tests/src/call_struct.aaa index 40471e92..8a7a0349 100644 --- a/aaa/src/tests/src/call_struct.aaa +++ b/aaa/src/tests/src/call_struct.aaa @@ -1,5 +1,7 @@ /// name: call struct ok +/// action: check + fn main { vec[int] drop } @@ -8,6 +10,8 @@ fn main { /// name: call struct with too many type parameters +/// action: check + fn main { vec[int, str] } @@ -25,6 +29,8 @@ fn main { /// name: call struct with too few type parameters +/// action: check + fn main { vec } diff --git a/aaa/src/tests/src/enum_constructor.aaa b/aaa/src/tests/src/enum_constructor.aaa index 0c14284a..72f1c6d0 100644 --- a/aaa/src/tests/src/enum_constructor.aaa +++ b/aaa/src/tests/src/enum_constructor.aaa @@ -1,5 +1,7 @@ /// name: enum with type parameter and make function +/// action: check + enum Foo[F] { foo as F, } @@ -16,6 +18,8 @@ fn main { /// name: enum constructor call with missing parameters +/// action: check + enum Foo[int] { foo } diff --git a/aaa/src/tests/src/get_field.aaa b/aaa/src/tests/src/get_field.aaa index 9bb6c233..6ea01525 100644 --- a/aaa/src/tests/src/get_field.aaa +++ b/aaa/src/tests/src/get_field.aaa @@ -1,5 +1,7 @@ /// name: get field ok +/// action: check + struct Foo { x as int } @@ -12,6 +14,8 @@ fn main { /// name: get field fail stack underflow +/// action: check + fn main { "x" ? } @@ -27,6 +31,8 @@ fn main { /// name: get field fail enum +/// action: check + enum Foo { x as int } @@ -46,6 +52,8 @@ fn main { /// name: get field fail field not found +/// action: check + struct Foo { x as int } @@ -65,6 +73,8 @@ fn main { /// name: get struct field with type param ok +/// action: check + struct Foo[T] { foo as T } @@ -81,6 +91,8 @@ fn main { /// name: get struct field with type param fail +/// action: check + struct Foo[T] { foo as T } diff --git a/aaa/src/tests/src/get_function.aaa b/aaa/src/tests/src/get_function.aaa index 135d3bb4..075f6365 100644 --- a/aaa/src/tests/src/get_function.aaa +++ b/aaa/src/tests/src/get_function.aaa @@ -1,5 +1,7 @@ /// name: get function ok custom non-returning +/// action: check + fn never_returns return never { 1 exit } @@ -12,6 +14,8 @@ fn main { /// name: get function fail non existent +/// action: check + fn main { "foo" fn drop } @@ -27,6 +31,8 @@ fn main { /// name: get function fail not a function +/// action: check + fn main { "int" fn drop } @@ -42,6 +48,8 @@ fn main { /// name: get function ok builtin +/// action: check + fn main { "+" fn drop } @@ -50,6 +58,8 @@ fn main { /// name: get function ok custom returning +/// action: check + fn main { "main" fn drop } diff --git a/aaa/src/tests/src/import.aaa b/aaa/src/tests/src/import.aaa index 499a024b..edf8db02 100644 --- a/aaa/src/tests/src/import.aaa +++ b/aaa/src/tests/src/import.aaa @@ -1,5 +1,7 @@ /// name: cyclic import +/// action: check + from "foo" import bar struct baz {} @@ -28,6 +30,8 @@ struct bar {} /// name: missing import +/// action: check + from "foo" import bar fn main { @@ -49,6 +53,8 @@ struct baz {} /// name: import from non-existent file +/// action: check + from "foo" import bar fn main { @@ -66,6 +72,8 @@ fn main { /// name: indirect import +/// action: check + from "foo" import bar fn main { diff --git a/aaa/src/tests/src/interface.aaa b/aaa/src/tests/src/interface.aaa new file mode 100644 index 00000000..49bab6a4 --- /dev/null +++ b/aaa/src/tests/src/interface.aaa @@ -0,0 +1,318 @@ + +/// name: use builtin interface + +fn main { + "hello\n" . + 69 . + "\n" . +} + +/// stdout: +/// hello +/// 69 + +/// ---------------------------------------------------------------------------- + +/// name: use interface function as regular function + +fn main { + "hello\n" str:show . + 69 int:show . + "\n" . +} + +/// stdout: +/// hello +/// 69 + +/// ---------------------------------------------------------------------------- + +/// name: use interface function explicitly + +fn main { + "hello\n" Show:show . + 69 Show:show . + "\n" . +} + +/// stdout: +/// hello +/// 69 + +/// ---------------------------------------------------------------------------- + +/// name: use builtin interface on custom type + +/// skip TODO this fails on CI + +struct Foo {} + +fn Foo:show args foo as Foo return str { + "I am foo!" +} + +fn main { + Foo . + "\n" . +} + +/// stdout: +/// I am foo! + +/// ---------------------------------------------------------------------------- + +/// name: use interface function explicitly on custom type + +/// skip TODO this fails on CI + +struct Foo {} + +fn Foo:show args foo as Foo return str { + "I am foo!" +} + +fn main { + Foo Show:show . + "\n" . +} + +/// stdout: +/// I am foo! + +/// ---------------------------------------------------------------------------- + +/// name: use custom interface on custom type + +/// skip TODO this fails on CI + +interface Bar { + fn Self:bar args self as Self return int +} + +struct Foo {} + +fn Foo:bar args foo as Foo return int { + 69 +} + +fn main { + Foo Foo:bar . + "\n" . +} + +/// stdout: +/// 69 + +/// ---------------------------------------------------------------------------- + +/// name: use custom interface explicitly on custom type + +/// skip TODO this fails on CI + +interface Bar { + fn Self:bar args self as Self return int +} + +struct Foo {} + +fn Foo:bar args foo as Foo return int { + 69 +} + +fn main { + Foo Bar:bar . + "\n" . +} + +/// stdout: +/// 69 + +/// ---------------------------------------------------------------------------- + +/// name: use interface as argument type + +fn show_twice args s as Show return str, str { + s Show:show + s Show:show +} + +fn main { + 69 show_twice . . + "\n" . +} + +/// stdout: +/// 6969 + +/// ---------------------------------------------------------------------------- + +/// name: use interface as type param + +fn main { + vec[Show] + dup 69 vec:push + dup false vec:push + . + "\n" . +} + +/// stdout: +/// [69, false] + +/// ---------------------------------------------------------------------------- + +/// name: use vec int as vec show + +fn show_vec args v as vec[Show] { + v . +} + +fn main { + vec[int] + dup 69 vec:push + show_vec + "\n" . +} + +/// stdout: +/// [69] + +/// ---------------------------------------------------------------------------- + +/// name: use type as interface it does not implement + +/// action: check + +struct Foo {} + +fn main { + Foo . +} + +/// status: 1 + +/// stderr: +/// $SOURCE_PATH/main.aaa:3:9: Invalid stack when calling . +/// stack: Foo +/// expected top: Show +/// +/// Found 1 errors + +/// ---------------------------------------------------------------------------- + +/// name: use vec type as vec of interface it does not implement + +/// action: check + +struct Foo {} + +fn show_vec args v as vec[Show] { + v . +} + +fn main { + vec[Foo] + dup Foo vec:push + show_vec + "\n" . +} + +/// status: 1 + +/// stderr: +/// $SOURCE_PATH/main.aaa:8:5: Invalid stack when calling show_vec +/// stack: vec[Foo] +/// expected top: vec[Show] +/// +/// Found 1 errors + +/// ---------------------------------------------------------------------------- + +/// name: first interface function argument is not named self + +/// action: check + + +interface Foo { + fn Self:foo args x as Self +} + +fn main { + nop +} + +/// status: 1 + +/// stderr: +/// $SOURCE_PATH/main.aaa:2:5 Invalid function foo in interface Foo +/// The first argument must be named self and have type Self. +/// +/// Found 1 errors + +/// ---------------------------------------------------------------------------- + +/// name: first interface function argument is function type + +/// action: check + + +interface Foo { + fn Self:foo args self as fn[][] +} + +fn main { + nop +} + +/// status: 1 + +/// stderr: +/// $SOURCE_PATH/main.aaa:2:5 Invalid function foo in interface Foo +/// The first argument must be named self and have type Self. +/// +/// Found 1 errors + + +/// ---------------------------------------------------------------------------- + +/// name: first interface function argument does not have type Self + +/// action: check + + +interface Foo { + fn Self:foo args self as int +} + +fn main { + nop +} + +/// status: 1 + +/// stderr: +/// $SOURCE_PATH/main.aaa:2:5 Invalid function foo in interface Foo +/// The first argument must be named self and have type Self. +/// +/// Found 1 errors + +/// ---------------------------------------------------------------------------- + +/// name: interface with extra argument + +/// skip TODO interface with extra argument this fails + +interface Foo { + fn Self:foo args self as Self, x as int return int +} + +struct Bar {} + +fn Bar:foo args bar as Bar, x as int return int { + x +} + +fn main { + Bar 69 Foo:foo . + "\n" . +} diff --git a/aaa/src/tests/src/main_function.aaa b/aaa/src/tests/src/main_function.aaa index 1f43c16f..ec4fb800 100644 --- a/aaa/src/tests/src/main_function.aaa +++ b/aaa/src/tests/src/main_function.aaa @@ -1,5 +1,7 @@ /// name: main signature ok basic +/// action: check + fn main { nop } @@ -8,6 +10,8 @@ fn main { /// name: main signature ok with argument +/// action: check + fn main args argv as vec[str] { nop } @@ -16,6 +20,8 @@ fn main args argv as vec[str] { /// name: main signature ok with arguments and return type +/// action: check + fn main args argv as vec[str] return int { 0 } @@ -24,6 +30,8 @@ fn main args argv as vec[str] return int { /// name: main signature with return type +/// action: check + fn main return int { 0 } @@ -32,6 +40,8 @@ fn main return int { /// name: main signature fail with argument +/// action: check + fn main args argv as int { nop } @@ -50,6 +60,8 @@ fn main args argv as int { /// name: main signature fail with argument parameter +/// action: check + fn main args argv as vec[int] { nop } @@ -68,6 +80,8 @@ fn main args argv as vec[int] { /// name: main signature fail with argument amount +/// action: check + fn main args argv as vec[str], extra as int { nop } @@ -86,6 +100,8 @@ fn main args argv as vec[str], extra as int { /// name: main signature fail with return type +/// action: check + fn main return str { "" } @@ -104,6 +120,8 @@ fn main return str { /// name: main signature fail with return type amount +/// action: check + fn main return int, str { 0 "" } @@ -122,6 +140,8 @@ fn main return int, str { /// name: main signature fail no main function +/// action: check + // If we leave this out no file is being created by doctest framework struct dummy {} @@ -136,6 +156,8 @@ struct dummy {} /// name: main not a function +/// action: check + struct main {} /// status: 1 diff --git a/aaa/src/tests/src/match.aaa b/aaa/src/tests/src/match.aaa index 7313307c..47560262 100644 --- a/aaa/src/tests/src/match.aaa +++ b/aaa/src/tests/src/match.aaa @@ -1,5 +1,7 @@ /// name: match fail empty stack +/// action: check + enum Foo { foo, } @@ -23,6 +25,8 @@ fn main { /// name: match fail non-enum +/// action: check + fn main { 3 match { @@ -43,6 +47,8 @@ fn main { /// name: match fail unexpected enum +/// action: check + enum Foo { foo, } @@ -73,6 +79,8 @@ fn main { /// name: match fail repeated case +/// action: check + enum Foo { foo, } @@ -102,6 +110,8 @@ fn main { /// name: match fail repeated default +/// action: check + enum Foo { foo, } @@ -131,6 +141,8 @@ fn main { /// name: match fail missing case +/// action: check + enum FooBar { foo, bar, @@ -157,6 +169,8 @@ fn main { /// name: match fail unreachable default +/// action: check + enum Foo { foo, } @@ -184,6 +198,8 @@ fn main { /// name: match fail inconsistent child stacks +/// action: check + enum FooBar { foo, bar, @@ -215,6 +231,8 @@ fn main { /// name: match fail unexpected variable expecting multiple +/// action: check + enum Foo { foo as { int, int }, } @@ -242,6 +260,8 @@ fn main { /// name: match fail unexpected variable expecting none +/// action: check + enum Foo { foo, } @@ -269,6 +289,8 @@ fn main { /// name: match fail var name collision +/// action: check + enum Foo { foo as int, } @@ -298,6 +320,8 @@ fn main { /// name: match fail unreachable code afterwards +/// action: check + enum Foo { foo, } @@ -323,6 +347,8 @@ fn main { /// name: match ok simple +/// action: check + enum FooBar { foo as int, bar, @@ -345,6 +371,8 @@ fn main { /// name: match ok with never +/// action: check + enum FooBar { foo as int, bar, diff --git a/aaa/src/tests/src/member_function.aaa b/aaa/src/tests/src/member_function.aaa index f1dc6a3d..95c0dcb7 100644 --- a/aaa/src/tests/src/member_function.aaa +++ b/aaa/src/tests/src/member_function.aaa @@ -1,5 +1,7 @@ /// name: member function ok +/// action: check + struct Foo {} fn Foo:bar args foo as Foo { @@ -14,6 +16,8 @@ fn main { /// name: member function fail missing argument +/// action: check + struct Foo {} fn Foo:bar { @@ -35,6 +39,8 @@ fn main { /// name: member function fail invalid first argument +/// action: check + struct Foo {} fn Foo:bar args baz as fn[][] { @@ -58,6 +64,8 @@ fn main { /// name: member function fail unexpected first argument type +/// action: check + struct Foo {} fn Foo:bar args baz as int { diff --git a/aaa/src/tests/src/naming.aaa b/aaa/src/tests/src/naming.aaa index 7e5d0fe8..3574624d 100644 --- a/aaa/src/tests/src/naming.aaa +++ b/aaa/src/tests/src/naming.aaa @@ -1,5 +1,7 @@ /// name: colliding struct and function +/// action: check + fn main { nop } @@ -19,6 +21,8 @@ struct main {} /// name: argument name collision with function +/// action: check + fn main { nop } @@ -40,6 +44,8 @@ fn bar args main as int { /// name: invalid argument type +/// action: check + fn main { nop } @@ -59,6 +65,8 @@ fn bar args x as foo { /// name: invalid argument param type +/// action: check + fn main { nop } @@ -78,6 +86,8 @@ fn bar args x as vec[Foo] { /// name: invalid return type +/// action: check + fn main { nop } @@ -97,6 +107,8 @@ fn bar return foo { /// name: invalid return param type +/// action: check + fn main { nop } diff --git a/aaa/src/tests/src/set_field.aaa b/aaa/src/tests/src/set_field.aaa index e295004c..fec470a4 100644 --- a/aaa/src/tests/src/set_field.aaa +++ b/aaa/src/tests/src/set_field.aaa @@ -1,5 +1,7 @@ /// name: set field ok +/// action: check + struct Foo { x as int } @@ -12,6 +14,8 @@ fn main { /// name: set field fail stack underflow +/// action: check + fn main { "x" { 3 } ! } @@ -27,6 +31,8 @@ fn main { /// name: set field fail enum +/// action: check + enum Foo { x as int } @@ -46,6 +52,8 @@ fn main { /// name: set field fail field not found +/// action: check + struct Foo { x as int } @@ -65,6 +73,8 @@ fn main { /// name: set field fail expression is empty stack +/// action: check + struct Foo { x as int } @@ -86,6 +96,8 @@ fn main { /// name: set field fail expression is wrong type +/// action: check + struct Foo { x as int } @@ -107,6 +119,8 @@ fn main { /// name: set field fail expression is multiple values +/// action: check + struct Foo { x as int } @@ -128,6 +142,8 @@ fn main { /// name: set struct field with type param ok +/// action: check + struct Foo[T] { foo as T } @@ -141,6 +157,8 @@ fn main { /// name: set struct field with type param fail +/// action: check + struct Foo[T] { foo as T } diff --git a/aaa/src/tests/src/stack.aaa b/aaa/src/tests/src/stack.aaa index 67bfc01c..5ef2d316 100644 --- a/aaa/src/tests/src/stack.aaa +++ b/aaa/src/tests/src/stack.aaa @@ -1,5 +1,7 @@ /// name: nop +/// action: check + fn main { nop } @@ -8,6 +10,8 @@ fn main { /// name: add ok +/// action: check + fn main { 3 3 + drop @@ -17,6 +21,8 @@ fn main { /// name: add stack underflow +/// action: check + fn main { 3 + } @@ -34,6 +40,8 @@ fn main { /// name: call function fail +/// action: check + fn main { 3 "hello" + } @@ -51,6 +59,8 @@ fn main { /// name: signature error +/// action: check + fn foo return bool { 3 } @@ -72,6 +82,8 @@ fn main { /// name: invalid stack on return +/// action: check + fn foo return bool { 3 return } @@ -93,6 +105,8 @@ fn main { /// name: correct stack on return +/// action: check + fn foo return int, str { if true { 69 "hello" return diff --git a/aaa/src/tests/src/use.aaa b/aaa/src/tests/src/use.aaa index 9e690ca7..ae2ccdb6 100644 --- a/aaa/src/tests/src/use.aaa +++ b/aaa/src/tests/src/use.aaa @@ -1,5 +1,7 @@ /// name: use simple +/// action: check + fn main { 3 use x { @@ -11,6 +13,8 @@ fn main { /// name: use variable too early +/// action: check + fn main { 3 use x { @@ -30,6 +34,8 @@ fn main { /// name: use fail variable too late +/// action: check + fn main { x 3 @@ -49,6 +55,8 @@ fn main { /// name: use fail stack underflow +/// action: check + fn main { use x { nop @@ -67,6 +75,8 @@ fn main { /// name: use fail same variable reused with overlap +/// action: check + fn main { 3 use x { @@ -90,6 +100,8 @@ fn main { /// name: use ok same variable reused without overlap +/// action: check + fn main { 3 use x { @@ -106,6 +118,8 @@ fn main { /// name: use fail argument name collision +/// action: check + fn main { nop } @@ -130,6 +144,8 @@ fn foo args x as int { /// name: use fail builtin function name collision +/// action: check + fn main { 3 use exit { @@ -141,7 +157,7 @@ fn main { /// stderr: /// Found name collision: -/// $AAA_STDLIB_PATH/builtins.aaa:161:1: function exit +/// $AAA_STDLIB_PATH/builtins.aaa:201:1: function exit /// $SOURCE_PATH/main.aaa:3:9: local variable exit /// /// Found 1 errors @@ -150,6 +166,8 @@ fn main { /// name: use fail builtin type name collision +/// action: check + fn main { 3 use vec { @@ -161,7 +179,7 @@ fn main { /// stderr: /// Found name collision: -/// $AAA_STDLIB_PATH/builtins.aaa:11:1: struct vec +/// $AAA_STDLIB_PATH/builtins.aaa:24:1: struct vec /// $SOURCE_PATH/main.aaa:3:9: local variable vec /// /// Found 1 errors diff --git a/aaa/src/tests/src/while.aaa b/aaa/src/tests/src/while.aaa index 45b58722..917822e9 100644 --- a/aaa/src/tests/src/while.aaa +++ b/aaa/src/tests/src/while.aaa @@ -1,5 +1,7 @@ /// name: while loop ok +/// action: check + fn main { while false { nop @@ -10,6 +12,8 @@ fn main { /// name: while loop fail condition +/// action: check + fn main { while 3 { nop @@ -30,6 +34,8 @@ fn main { /// name: while loop fail body +/// action: check + fn main { while true { 3 diff --git a/aaa/src/tokenizer/mod.rs b/aaa/src/tokenizer/mod.rs index 0dd054e1..3a758628 100644 --- a/aaa/src/tokenizer/mod.rs +++ b/aaa/src/tokenizer/mod.rs @@ -1,2 +1,3 @@ +#[allow(clippy::module_inception)] pub mod tokenizer; pub mod types; diff --git a/aaa/src/tokenizer/tokenizer.rs b/aaa/src/tokenizer/tokenizer.rs index 1f4d3a03..ab08cd66 100644 --- a/aaa/src/tokenizer/tokenizer.rs +++ b/aaa/src/tokenizer/tokenizer.rs @@ -7,7 +7,7 @@ use regex::{Captures, Regex}; use crate::common::position::Position; -use super::types::{Token, TokenType, ENUM_REGEX_PAIRS}; +use super::types::{Token, TokenType, TOKEN_REGEX_TUPLES}; pub struct TokenizerError { pub position: Position, @@ -32,7 +32,7 @@ impl TokenizerError { } pub fn tokenize(code: &str, path: Option) -> Result, TokenizerError> { - let path = path.or(Some(PathBuf::from("/unknown/path.aaa"))).unwrap(); + let path = path.unwrap_or(PathBuf::from("/unknown/path.aaa")); let mut tokens = vec![]; let mut position = Position::new(path.clone(), 1, 1); @@ -84,7 +84,7 @@ fn captures_at_offset<'a>(s: &'a str, re: &Regex, offset: usize) -> Option Option<(TokenType, String)> { - for (token_type, regex, group) in ENUM_REGEX_PAIRS.iter() { + for (token_type, regex, group) in TOKEN_REGEX_TUPLES.iter() { if let Some(captures) = captures_at_offset(line, regex, offset) { let value = String::from(captures.get(*group).unwrap().as_str()); return Some((*token_type, value)); diff --git a/aaa/src/tokenizer/types.rs b/aaa/src/tokenizer/types.rs index cd0dd0dd..add08a77 100644 --- a/aaa/src/tokenizer/types.rs +++ b/aaa/src/tokenizer/types.rs @@ -30,7 +30,7 @@ impl Token { impl HasPosition for Token { fn position(&self) -> Position { - return self.position.clone(); + self.position.clone() } } @@ -53,6 +53,7 @@ pub enum TokenType { Fn, If, Import, + Interface, Match, Never, Return, @@ -99,6 +100,7 @@ impl TokenType { | TokenType::Fn | TokenType::If | TokenType::Import + | TokenType::Interface | TokenType::Match | TokenType::Never | TokenType::Return @@ -111,14 +113,11 @@ impl TokenType { } pub fn is_filtered(&self) -> bool { - match self { - TokenType::Comment | TokenType::Whitespace => true, - _ => false, - } + matches!(self, TokenType::Comment | TokenType::Whitespace) } } -const TOKEN_TYPE_REGEXES: &[(TokenType, &'static str, usize)] = &[ +const TOKEN_TYPE_REGEXES: &[(TokenType, &str, usize)] = &[ (TokenType::Args, "(args)([^_a-zA-Z]|$)", 1), (TokenType::As, "(as)([^_a-zA-Z]|$)", 1), (TokenType::Builtin, "(builtin)([^_a-zA-Z]|$)", 1), @@ -134,6 +133,7 @@ const TOKEN_TYPE_REGEXES: &[(TokenType, &'static str, usize)] = &[ (TokenType::Fn, "(fn)([^_a-zA-Z]|$)", 1), (TokenType::If, "(if)([^_a-zA-Z]|$)", 1), (TokenType::Import, "(import)([^_a-zA-Z]|$)", 1), + (TokenType::Interface, "(interface)([^_a-zA-Z]|$)", 1), (TokenType::Match, "(match)([^_a-zA-Z]|$)", 1), (TokenType::Never, "(never)([^_a-zA-Z]|$)", 1), (TokenType::Return, "(return)([^_a-zA-Z]|$)", 1), @@ -175,7 +175,7 @@ const TOKEN_TYPE_REGEXES: &[(TokenType, &'static str, usize)] = &[ ]; lazy_static! { - pub static ref ENUM_REGEX_PAIRS: Vec<(TokenType, Regex, usize)> = { + pub static ref TOKEN_REGEX_TUPLES: Vec<(TokenType, Regex, usize)> = { let mut pairs = Vec::new(); for (token_type, pattern, group) in TOKEN_TYPE_REGEXES.iter() { let regex = Regex::new(pattern).expect("Failed to compile regex pattern"); @@ -261,6 +261,9 @@ mod tests { #[case("import", Some(TokenType::Import))] #[case("import_", Some(TokenType::Identifier))] #[case("importx", Some(TokenType::Identifier))] + #[case("interface", Some(TokenType::Interface))] + #[case("interface_", Some(TokenType::Identifier))] + #[case("interfacex", Some(TokenType::Identifier))] #[case("match", Some(TokenType::Match))] #[case("match_", Some(TokenType::Identifier))] #[case("matchx", Some(TokenType::Identifier))] diff --git a/aaa/src/transpiler/code.rs b/aaa/src/transpiler/code.rs index 0cf777cb..8f140dcf 100644 --- a/aaa/src/transpiler/code.rs +++ b/aaa/src/transpiler/code.rs @@ -1,4 +1,4 @@ -const INDENTATION: &'static str = " "; +const INDENTATION: &str = " "; pub struct Code { lines: Vec, @@ -27,11 +27,7 @@ impl Code { } if string.ends_with('}') || string.ends_with("},") { - if self.indent_level == 0 { - panic!("Cannot indent below level 0."); - } - - self.indent_level -= 1; + self.unindent(); } let prefix = INDENTATION.repeat(self.indent_level); @@ -40,7 +36,7 @@ impl Code { self.lines.push(line); if string.ends_with('{') { - self.indent_level += 1; + self.indent(); } } @@ -52,6 +48,18 @@ impl Code { } } + pub fn indent(&mut self) { + self.indent_level += 1; + } + + pub fn unindent(&mut self) { + if self.indent_level == 0 { + panic!("Cannot indent below level 0."); + } + + self.indent_level -= 1; + } + pub fn get(&self) -> String { self.lines.join("\n") + "\n" } diff --git a/aaa/src/transpiler/mod.rs b/aaa/src/transpiler/mod.rs index ebbbd7e2..7dfa8c8c 100644 --- a/aaa/src/transpiler/mod.rs +++ b/aaa/src/transpiler/mod.rs @@ -1,2 +1,3 @@ pub mod code; +#[allow(clippy::module_inception)] pub mod transpiler; diff --git a/aaa/src/transpiler/transpiler.rs b/aaa/src/transpiler/transpiler.rs index cf4511d8..257a25ac 100644 --- a/aaa/src/transpiler/transpiler.rs +++ b/aaa/src/transpiler/transpiler.rs @@ -1,19 +1,21 @@ use chrono::Local; -use sha2::{Digest, Sha256}; use crate::{ - common::{position::Position, traits::HasPosition}, + common::{ + hash::{hash_key, hash_position}, + traits::HasPosition, + }, cross_referencer::types::{ function_body::{ Assignment, Boolean, Branch, CallArgument, CallEnum, CallEnumConstructor, CallFunction, - CallLocalVariable, CallStruct, CaseBlock, Char, DefaultBlock, FunctionBody, - FunctionBodyItem, FunctionType, GetField, GetFunction, Integer, Match, ParsedString, - Return, SetField, Use, While, + CallInterfaceFunction, CallLocalVariable, CallStruct, CaseBlock, Char, DefaultBlock, + FunctionBody, FunctionBodyItem, FunctionType, GetField, GetFunction, Integer, Match, + ParsedString, Return, SetField, Use, While, }, identifiable::{Enum, Function, Identifiable, ReturnTypes, Struct, Type}, }, transpiler::code::Code, - type_checker::type_checker, + type_checker::type_checker::{self, InterfacesTable}, }; use lazy_static::lazy_static; use std::{cell::RefCell, collections::HashMap, fs, iter::zip, path::PathBuf, rc::Rc}; @@ -49,6 +51,8 @@ lazy_static! { ("map", "Variable::map_zero_value()"), ("set", "Variable::set_zero_value()"), ("regex", "Variable::regex_zero_value()"), + ("Option", "Variable::option_zero_value()"), + ("Result", "Variable::result_zero_value()"), ]; HashMap::from(array) @@ -60,6 +64,7 @@ pub struct Transpiler { pub structs: HashMap<(PathBuf, String), Rc>>, pub enums: HashMap<(PathBuf, String), Rc>>, pub functions: HashMap<(PathBuf, String), Rc>>, + pub interfaces_table: InterfacesTable, pub main_function: Rc>, pub verbose: bool, } @@ -100,15 +105,14 @@ impl Transpiler { functions, main_function: type_checked.main_function, verbose, + interfaces_table: type_checked.interfaces_table, } } pub fn run(&self) { - // TODO #225 Support runtime type checking - let code = self.generate_file(); - fs::create_dir_all(&self.transpiler_root_path.join("src")).unwrap(); + fs::create_dir_all(self.transpiler_root_path.join("src")).unwrap(); let main_path = self.transpiler_root_path.join("src/main.rs"); if self.verbose { @@ -125,6 +129,8 @@ impl Transpiler { code.add_code(self.generate_warning_silencing_macros()); code.add_code(self.generate_imports()); + code.add_code(self.generate_interface_mapping()); + code.add_code(self.generate_UserTypeEnum()); for enum_ in self.enums.values() { @@ -179,17 +185,79 @@ impl Transpiler { let mut code = Code::new(); code.add_line("use aaa_stdlib::map::Map;"); - code.add_line("use aaa_stdlib::stack::Stack;"); code.add_line("use aaa_stdlib::set::{Set, SetValue};"); + code.add_line("use aaa_stdlib::stack::Stack;"); code.add_line("use aaa_stdlib::var::{UserType, Variable};"); code.add_line("use aaa_stdlib::vector::Vector;"); + code.add_line("use lazy_static::lazy_static;"); code.add_line("use regex::Regex;"); code.add_line("use std::cell::RefCell;"); code.add_line("use std::collections::HashMap;"); - code.add_line("use std::fmt::{Debug, Display, Formatter, Result};"); code.add_line("use std::hash::Hash;"); code.add_line("use std::process;"); code.add_line("use std::rc::Rc;"); + code.add_line("use std::sync::Arc;"); + code.add_line(""); + + code + } + + fn generate_interface_mapping(&self) -> Code { + let mut code = Code::new(); + + code.add_line("type InterfaceMapPointer = fn(&mut Stack);"); + code.add_line(""); + + code.add_line("lazy_static! {"); + code.add_line("static ref INTERFACE_MAPPING: Arc> = {"); + + code.add_line("let hash_map = HashMap::from(["); + + code.indent(); + + for (interface_rc, implementor_rc, interface_mapping) in &self.interfaces_table { + let interface = &*interface_rc.borrow(); + let implementor = &*implementor_rc.borrow(); + + let interface_hash = if interface.is_builtin() { + format!("builtins:{}", interface.name()) + } else { + hash_key(interface.key()) + }; + + let implementor_hash = if implementor.is_builtin() { + format!("builtins:{}", implementor.name()) + } else { + hash_key(implementor.key()) + }; + + // TODO generate comments for future debugging + + for (function_name, function) in interface_mapping { + let function = &*function.borrow(); + + let function_ptr_expr = if function.is_builtin { + format!("Stack::{}", self.generate_builtin_function_name(function)) + } else { + let hash = Self::hash_name(function.position().path, function.name()); + format!("user_func_{}", hash) + }; + + code.add_line(format!( + "((\"{}\", \"{}\", \"{}\"), {} as InterfaceMapPointer),", + interface_hash, implementor_hash, function_name, function_ptr_expr + )); + } + } + + code.unindent(); + code.add_line("]);"); + + code.add_line("Arc::new(hash_map)"); + + code.add_line("};"); + code.add_line("}"); + code.add_line(""); code @@ -202,8 +270,6 @@ impl Transpiler { code.add_code(self.generate_UserTypeEnum_definition()); code.add_code(self.generate_UserTypeEnum_impl()); code.add_code(self.generate_UserTypeEnum_UserType_impl()); - code.add_code(self.generate_UserTypeEnum_Display_impl()); - code.add_code(self.generate_UserTypeEnum_Debug_impl()); code } @@ -215,8 +281,6 @@ impl Transpiler { code.add_code(self.generate_enum_constructors(enum_)); code.add_code(self.generate_enum_impl(enum_)); code.add_code(self.generate_enum_UserType_impl(enum_)); - code.add_code(self.generate_enum_Display_impl(enum_)); - code.add_code(self.generate_enum_Debug_impl(enum_)); code.add_code(self.generate_enum_Hash_impl(enum_)); code.add_code(self.generate_enum_PartialEq_impl(enum_)); @@ -229,8 +293,6 @@ impl Transpiler { code.add_code(self.generate_struct_definition(struct_)); code.add_code(self.generate_struct_impl(struct_)); code.add_code(self.generate_struct_UserType_impl(struct_)); - code.add_code(self.generate_struct_Display_impl(struct_)); - code.add_code(self.generate_struct_Debug_impl(struct_)); code.add_code(self.generate_struct_Hash_impl(struct_)); code.add_code(self.generate_struct_PartialEq_impl(struct_)); @@ -259,7 +321,7 @@ impl Transpiler { fn generate_UserTypeEnum_definition(&self) -> Code { let mut code = Code::new(); code.add_line("#[derive(Clone, Hash, PartialEq)]"); - code.add_line("enum UserTypeEnum {"); + code.add_line("pub enum UserTypeEnum {"); for ((path, name), struct_) in &self.structs { let struct_ = &*struct_.borrow(); @@ -308,7 +370,8 @@ impl Transpiler { code.add_line("}"); code.add_line(""); - return code; + + code } #[allow(non_snake_case)] @@ -318,14 +381,14 @@ impl Transpiler { let mut code = Code::new(); code.add_line("impl UserType for UserTypeEnum {"); - code.add_line("fn kind(&self) -> String {"); + code.add_line("fn type_id(&self) -> String {"); if names.is_empty() { code.add_line("unreachable!();"); } else { code.add_line("match self {"); for name in &names { - code.add_line(format!("Self::{name}(v) => v.kind(),")); + code.add_line(format!("Self::{name}(v) => v.type_id(),")); } code.add_line("}"); } @@ -356,65 +419,6 @@ impl Transpiler { code } - #[allow(non_snake_case)] - fn generate_UserTypeEnum_Display_impl(&self) -> Code { - let mut code = Code::new(); - - code.add_line("impl Display for UserTypeEnum {"); - - code.add_line("fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {"); - - let names = self.user_type_names(); - - if names.is_empty() { - code.add_line("unreachable!();"); - } else { - code.add_line("match self {"); - - for name in names { - code.add_line(format!("Self::{}(v) => write!(f, \"{{}}\", v),", name)); - } - - code.add_line("}"); - } - - code.add_line("}"); - - code.add_line("}"); - code.add_line(""); - - code - } - - #[allow(non_snake_case)] - fn generate_UserTypeEnum_Debug_impl(&self) -> Code { - let mut code = Code::new(); - code.add_line("impl Debug for UserTypeEnum {"); - - code.add_line("fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {"); - - let names = self.user_type_names(); - - if names.is_empty() { - code.add_line("unreachable!();"); - } else { - code.add_line("match self {"); - - for name in names { - code.add_line(format!("Self::{}(v) => write!(f, \"{{}}\", v),", name)); - } - - code.add_line("}"); - } - - code.add_line("}"); - - code.add_line("}"); - code.add_line(""); - - code - } - fn generate_builtin_function_name(&self, function: &Function) -> String { let function_name = function.name(); @@ -422,28 +426,11 @@ impl Transpiler { return name.to_string(); } - function_name.replace(":", "_") + function_name.replace(":", "_").to_lowercase() } fn hash_name(path: PathBuf, name: String) -> String { - let mut hasher = Sha256::new(); - hasher.update(path.to_string_lossy().as_bytes()); - hasher.update(name.as_bytes()); - - let hash = hasher.finalize(); - let hash = format!("{:x}", hash); - - hash[..16].to_owned() - } - - fn hash_position(position: &Position) -> String { - let mut hasher = Sha256::new(); - hasher.update(format!("{}", position).as_bytes()); - - let hash = hasher.finalize(); - let hash = format!("{:x}", hash); - - hash[..16].to_owned() + hash_key((path, name)) } fn generate_function_name(&self, function: &Function) -> String { @@ -473,10 +460,14 @@ impl Transpiler { let mut code = Code::new(); code.add_line("fn main() {"); + code.add_line("let interface_mapping = Arc::clone(&INTERFACE_MAPPING);"); + if main_function.arguments().is_empty() { - code.add_line("let mut stack: Stack = Stack::new();"); + code.add_line("let mut stack: Stack = Stack::new(interface_mapping);"); } else { - code.add_line("let mut stack:Stack = Stack::from_argv();"); + code.add_line( + "let mut stack:Stack = Stack::from_argv(interface_mapping);", + ); } code.add_line(format!("{}(&mut stack);", main_func_name)); @@ -537,27 +528,28 @@ impl Transpiler { match item { Assignment(assignment) => self.generate_assignment(assignment), - Branch(branch) => self.generate_branch(branch), Boolean(bool) => self.generate_boolean(bool), + Branch(branch) => self.generate_branch(branch), Call(_) => self.generate_call(), CallArgument(call) => self.generate_call_argument(call), - CallFunction(call) => self.generate_call_function(call), CallEnum(call) => self.generate_call_enum(call), CallEnumConstructor(call) => self.generate_call_enum_constructor(call), + CallFunction(call) => self.generate_call_function(call), + CallInterfaceFunction(call) => self.generate_call_interface_function(call), CallLocalVariable(call) => self.generate_call_local_variabiable(call), CallStruct(call) => self.generate_call_struct(call), Char(char) => self.generate_char(char), - Foreach(_) => unreachable!(), // TODO #214 support interfaces + Foreach(_) => unreachable!(), // TODO #243 Support foreach loops FunctionType(function_type) => self.generate_function_type(function_type), + GetField(get_field) => self.generate_get_field(get_field), GetFunction(get_function) => self.generate_get_function(get_function), Integer(integer) => self.generate_integer(integer), Match(match_) => self.generate_match(match_), Return(return_) => self.generate_return(return_), - GetField(get_field) => self.generate_get_field(get_field), SetField(set_field) => self.generate_set_field(set_field), + String(string) => self.generate_string(string), Use(use_) => self.generate_use(use_), While(while_) => self.generate_while(while_), - String(string) => self.generate_string(string), } } @@ -704,6 +696,7 @@ impl Transpiler { "Variable::FunctionPointer(Stack::zero_function_pointer_value)".to_owned() } Type::Parameter(_) => "Variable::None".to_owned(), + Type::Interface(_) => unreachable!(), } } @@ -757,7 +750,7 @@ impl Transpiler { let enum_ = &*enum_rc.borrow(); let enum_name = self.generate_enum_name(enum_); - let match_var = format!("match_var_{}", Self::hash_position(&match_.position)); + let match_var = format!("match_var_{}", hash_position(&match_.position)); let mut code = Code::new(); @@ -792,7 +785,7 @@ impl Transpiler { let data_items = enum_.variants().get(&block.variant_name).unwrap().len(); // We add the hash of location to prevent collisions with nested case blocks. - let var_prefix = format!("case_var_{}", Self::hash_position(&block.position)); + let var_prefix = format!("case_var_{}", hash_position(&block.position)); let mut line = format!("{}::variant_{}(", enum_name, block.variant_name); @@ -980,8 +973,8 @@ impl Transpiler { code.add_line(format!("impl UserType for {enum_name} {{")); - code.add_line("fn kind(&self) -> String {"); - code.add_line(format!("String::from(\"{}\")", enum_.name())); + code.add_line("fn type_id(&self) -> String {"); + code.add_line(format!("String::from(\"{}\")", enum_name)); code.add_line("}"); code.add_line(""); @@ -1017,98 +1010,6 @@ impl Transpiler { code } - #[allow(non_snake_case)] - fn generate_enum_Display_impl(&self, enum_: &Enum) -> Code { - let enum_name = self.generate_enum_name(enum_); - - let mut code = Code::new(); - code.add_line(format!("impl Display for {} {{", enum_name)); - - code.add_line("fn fmt(&self, f: &mut Formatter<'_>) -> Result {"); - code.add_line("match self {"); - - for (variant_name, associated_data) in enum_.variants() { - let mut line = format!("Self::variant_{}(", variant_name); - line += (0..associated_data.len()) - .map(|i| format!("arg{}", i)) - .collect::>() - .join(", ") - .as_str(); - - line += ") => {"; - - code.add_line(line); - - code.add_line(format!( - "write!(f, \"{}:{}{{{{\")?;", - enum_.name(), - variant_name - )); - - for (offset, _) in associated_data.iter().enumerate() { - if offset != 0 { - code.add_line("write!(f, \", \")?;"); - } - code.add_line(format!("write!(f, \"{{:?}}\", arg{offset})?;")); - } - - code.add_line("write!(f, \"}}\")"); - code.add_line("}"); - } - code.add_line("}"); - code.add_line("}"); - code.add_line("}"); - code.add_line(""); - - code - } - - #[allow(non_snake_case)] - fn generate_enum_Debug_impl(&self, enum_: &Enum) -> Code { - let enum_name = self.generate_enum_name(enum_); - - let mut code = Code::new(); - code.add_line(format!("impl Debug for {} {{", enum_name)); - - code.add_line("fn fmt(&self, f: &mut Formatter<'_>) -> Result {"); - code.add_line("match self {"); - - for (variant_name, associated_data) in enum_.variants() { - let mut line = format!("Self::variant_{}(", variant_name); - line += (0..associated_data.len()) - .map(|i| format!("arg{}", i)) - .collect::>() - .join(", ") - .as_str(); - - line += ") => {"; - - code.add_line(line); - - code.add_line(format!( - "write!(f, \"{}:{}{{{{\")?;", - enum_.name(), - variant_name - )); - - for (offset, _) in associated_data.iter().enumerate() { - if offset != 0 { - code.add_line("write!(f, \", \")?;"); - } - code.add_line(format!("write!(f, \"{{:?}}\", arg{offset})?;")); - } - - code.add_line("write!(f, \"}}\")"); - code.add_line("}"); - } - code.add_line("}"); - code.add_line("}"); - code.add_line("}"); - code.add_line(""); - - code - } - #[allow(non_snake_case)] fn generate_enum_Hash_impl(&self, enum_: &Enum) -> Code { let name = self.generate_enum_name(enum_); @@ -1119,14 +1020,14 @@ impl Transpiler { code.add_line("fn hash(&self, state: &mut H) {"); - code.add_line("todo!();"); // TODO #125 Implement hash for structs and enums + code.add_line("todo!();"); // TODO #244 Support type param interface bounds: remove this from generated code and use interface Hash code.add_line("}"); code.add_line("}"); code.add_line(""); - return code; + code } #[allow(non_snake_case)] @@ -1139,7 +1040,7 @@ impl Transpiler { code.add_line("fn eq(&self, other: &Self) -> bool {"); - code.add_line("todo!();"); // TODO #214 Implement interfaces + code.add_line("todo!();"); // TODO #244 Support type param interface bounds: remove this from generated code and use interface Equals code.add_line("}"); @@ -1214,8 +1115,8 @@ impl Transpiler { code.add_line(format!("impl UserType for {} {{", name)); - code.add_line("fn kind(&self) -> String {"); - code.add_line(format!("String::from(\"{}\")", struct_.name())); + code.add_line("fn type_id(&self) -> String {"); + code.add_line(format!("String::from(\"{}\")", name)); code.add_line("}"); code.add_line(""); @@ -1240,72 +1141,6 @@ impl Transpiler { code } - #[allow(non_snake_case)] - fn generate_struct_Display_impl(&self, struct_: &Struct) -> Code { - let name = self.generate_struct_name(struct_); - - let mut code = Code::new(); - - code.add_line(format!("impl Display for {} {{", name)); - - code.add_line("fn fmt(&self, f: &mut Formatter<'_>) -> Result {"); - - code.add_line(format!("write!(f, \"{}{{{{\")?;", name)); - - for (offset, field_name) in struct_.fields().keys().enumerate() { - if offset != 0 { - code.add_line("write!(f, \", \")?;"); - } - - code.add_line(format!( - "write!(f, \"{}: {{}}\", self.{})?;", - field_name, field_name - )); - } - - code.add_line("write!(f, \"}}\")"); - - code.add_line("}"); - - code.add_line("}"); - code.add_line(""); - - return code; - } - - #[allow(non_snake_case)] - fn generate_struct_Debug_impl(&self, struct_: &Struct) -> Code { - let name = self.generate_struct_name(struct_); - - let mut code = Code::new(); - - code.add_line(format!("impl Debug for {} {{", name)); - - code.add_line("fn fmt(&self, f: &mut Formatter<'_>) -> Result {"); - - code.add_line(format!("write!(f, \"{}{{{{\")?;", name)); - - for (offset, field_name) in struct_.fields().keys().enumerate() { - if offset != 0 { - code.add_line("write!(f, \", \")?;"); - } - - code.add_line(format!( - "write!(f, \"{}: {{:?}}\", self.{})?;", - field_name, field_name - )); - } - - code.add_line("write!(f, \"}}\")"); - - code.add_line("}"); - - code.add_line("}"); - code.add_line(""); - - return code; - } - #[allow(non_snake_case)] fn generate_struct_Hash_impl(&self, struct_: &Struct) -> Code { let name = self.generate_struct_name(struct_); @@ -1316,14 +1151,14 @@ impl Transpiler { code.add_line("fn hash(&self, state: &mut H) {"); - code.add_line("todo!();"); // TODO #125 Support hash for structs and enums + code.add_line("todo!();"); // TODO #244 Support type param interface bounds: remove this from generated code and use interface Hash code.add_line("}"); code.add_line("}"); code.add_line(""); - return code; + code } #[allow(non_snake_case)] @@ -1336,7 +1171,8 @@ impl Transpiler { code.add_line("fn eq(&self, other: &Self) -> bool {"); - code.add_line("todo!();"); // TODO #214 Implement interfaces + code.add_line("todo!();"); + // TODO #244 Support type param interface bounds: remove this from generated code and use interface Equals code.add_line("}"); @@ -1347,6 +1183,10 @@ impl Transpiler { } fn generate_enum_constructor_name(&self, enum_: &Enum, variant_name: String) -> String { + if enum_.is_builtin() { + return format!("{}_{}", enum_.name(), variant_name).to_lowercase(); + } + let function_name = format!("{}:{}", enum_.name(), variant_name); let hash = Self::hash_name(enum_.position().path, function_name); format!("enum_ctor_{}", hash) @@ -1357,10 +1197,36 @@ impl Transpiler { let variant_name = enum_ctor.variant_name(); let enum_ = &*enum_ctor.enum_.borrow(); let enum_ctor_name = self.generate_enum_constructor_name(enum_, variant_name); - Code::from_string(format!("{}(stack);", enum_ctor_name)) + + if enum_.is_builtin() { + Code::from_string(format!("stack.{}();", enum_ctor_name)) + } else { + Code::from_string(format!("{}(stack);", enum_ctor_name)) + } } fn generate_call(&self) -> Code { Code::from_string("stack.pop_function_pointer_and_call();") } + + fn generate_call_interface_function(&self, call: &CallInterfaceFunction) -> Code { + let mut code = Code::new(); + + let interface = &call.function.interface.borrow(); + + let interface_hash = if interface.is_builtin() { + format!("builtins:{}", interface.name()) + } else { + format!("user_type_{}", hash_key(interface.key())) + }; + + let function_name = &call.function.function_name; + + code.add_line(format!( + "stack.call_interface_function(\"{}\", \"{}\");", + interface_hash, function_name, + )); + + code + } } diff --git a/aaa/src/type_checker/call_checker.rs b/aaa/src/type_checker/call_checker.rs index dfdcea53..ff9afdfa 100644 --- a/aaa/src/type_checker/call_checker.rs +++ b/aaa/src/type_checker/call_checker.rs @@ -1,22 +1,28 @@ -use std::{collections::HashMap, iter::zip}; +use std::{collections::HashMap, iter::zip, path::PathBuf}; use super::errors::{does_not_return, stack_error, TypeResult}; use crate::{ - common::position::Position, - cross_referencer::types::identifiable::{EnumType, ReturnTypes, StructType, Type}, + common::{position::Position, traits::HasPosition}, + cross_referencer::types::identifiable::{ + EnumType, Identifiable, InterfaceType, ResolvedInterfaceFunction, ReturnTypes, Struct, + StructType, Type, + }, type_checker::errors::stack_underflow, }; -pub struct CallChecker { +pub struct CallChecker<'a> { pub type_params: HashMap, pub argument_types: Vec, pub return_types: ReturnTypes, pub name: String, pub position: Position, pub stack: Vec, + + pub identifiables: &'a HashMap<(PathBuf, String), Identifiable>, } -impl CallChecker { +impl<'a> CallChecker<'a> { + #[allow(clippy::result_large_err)] // TODO #239 Handle linter error for large error result pub fn check(self) -> TypeResult { let stack_before = self.stack.clone(); @@ -33,7 +39,7 @@ impl CallChecker { let mut type_params = self.type_params.clone(); for (func_arg, stack_arg) in zip(&self.argument_types, &stack_arg_types) { - if !Self::types_match(func_arg, stack_arg, &mut type_params) { + if !self.types_match(func_arg, stack_arg, &mut type_params) { let func_name = self.name; return stack_error(self.position, func_name, stack_before, self.argument_types); } @@ -55,17 +61,18 @@ impl CallChecker { } // Returns whether rhs matches lhs, updating `type_params` in the process. - fn types_match(lhs: &Type, rhs: &Type, type_params: &mut HashMap) -> bool { + fn types_match(&self, lhs: &Type, rhs: &Type, type_params: &mut HashMap) -> bool { use Type::*; match (lhs, rhs) { (FunctionPointer(lhs), FunctionPointer(rhs)) => lhs == rhs, - (Struct(lhs), Struct(rhs)) => Self::struct_types_match(lhs, rhs, type_params), - (Enum(lhs), Enum(rhs)) => Self::enum_types_match(lhs, rhs, type_params), + (Struct(lhs), Struct(rhs)) => self.struct_types_match(lhs, rhs, type_params), + (Enum(lhs), Enum(rhs)) => self.enum_types_match(lhs, rhs, type_params), (Parameter(lhs), _) => { if let Some(lhs) = type_params.get(&lhs.name) { match lhs { Parameter(_) => (), + Interface(lhs) => return self.implements_interface(lhs, rhs), _ => { if lhs != rhs { return false; @@ -77,11 +84,15 @@ impl CallChecker { type_params.insert(lhs.name.clone(), rhs.clone()); true } + (Interface(lhs), Struct(rhs)) => self.struct_implements_interface(lhs, rhs), + (Interface(_lhs), Enum(_rhs)) => todo!(), // TODO + (Interface(lhs), Interface(rhs)) => self.interface_implements_interface(lhs, rhs), _ => false, } } fn struct_types_match( + &self, lhs: &StructType, rhs: &StructType, type_params: &mut HashMap, @@ -96,11 +107,12 @@ impl CallChecker { lhs.parameters .iter() .zip(&rhs.parameters) - .map(|(lhs, rhs)| Self::types_match(lhs, rhs, type_params)) + .map(|(lhs, rhs)| self.types_match(lhs, rhs, type_params)) .all(|x| x) } fn enum_types_match( + &self, lhs: &EnumType, rhs: &EnumType, type_params: &mut HashMap, @@ -115,10 +127,98 @@ impl CallChecker { lhs.parameters .iter() .zip(&rhs.parameters) - .map(|(lhs, rhs)| Self::types_match(lhs, rhs, type_params)) + .map(|(lhs, rhs)| self.types_match(lhs, rhs, type_params)) .all(|x| x) } + pub fn implements_interface(&self, interface_type: &InterfaceType, type_: &Type) -> bool { + match type_ { + Type::Struct(struct_type) => { + self.struct_implements_interface(interface_type, struct_type) + } + _ => todo!(), // TODO + } + } + + pub fn struct_implements_interface( + &self, + interface_type: &InterfaceType, + struct_type: &StructType, + ) -> bool { + let interface = &*interface_type.interface.borrow(); + let struct_ = &*struct_type.struct_.borrow(); + + for required_function in &interface.resolved().functions { + if !self.struct_implements_interface_function(required_function, struct_) { + return false; + } + } + + true + } + + fn struct_implements_interface_function( + &self, + interface_function: &ResolvedInterfaceFunction, + struct_: &Struct, + ) -> bool { + // TODO lookup in TypeChecker + + let key = ( + struct_.position().path.clone(), + format!("{}:{}", struct_.name(), &interface_function.name), + ); + + let Some(identifiable) = self.identifiables.get(&key) else { + return false; + }; + + let Identifiable::Function(function) = identifiable else { + return false; + }; + + let function = &*function.borrow(); + + if function.arguments().len() != interface_function.arguments.len() { + return false; + } + + for (interface_function_arg, function_arg) in + zip(&interface_function.arguments, &function.arguments()[1..]) + { + if !self.types_match( + &interface_function_arg.type_, + &function_arg.type_, + &mut HashMap::new(), + ) { + return false; + } + } + + use ReturnTypes::*; + + match (&interface_function.return_types, function.return_types()) { + (_, Never) => (), + (Never, Sometimes(_)) => return false, + (Sometimes(lhs), Sometimes(rhs)) => { + if lhs != rhs { + return false; + } + } + } + + true + } + + fn interface_implements_interface(&self, lhs: &InterfaceType, rhs: &InterfaceType) -> bool { + if lhs.interface.borrow().key() == rhs.interface.borrow().key() { + return true; + } + + // TODO find out if rhs is subset of rhs + false + } + pub fn apply_type_params(type_: &Type, type_params: &HashMap) -> Type { match type_ { Type::Parameter(type_param) => { diff --git a/aaa/src/type_checker/errors.rs b/aaa/src/type_checker/errors.rs index aa88fbef..6f170aa4 100644 --- a/aaa/src/type_checker/errors.rs +++ b/aaa/src/type_checker/errors.rs @@ -1,3 +1,5 @@ +#![allow(clippy::result_large_err)] // TODO #239 Handle linter error for large error result + use std::{collections::HashSet, fmt::Display, path::PathBuf}; use crate::{ @@ -5,6 +7,7 @@ use crate::{ cross_referencer::types::identifiable::{Identifiable, ReturnTypes, Type}, }; +#[allow(clippy::enum_variant_names)] pub enum TypeError { BranchError(BranchError), CondtionError(ConditionError), @@ -1077,6 +1080,7 @@ impl Display for MemberFunctionUnexpectedTarget { } } +// TODO find ways to run these checks for the builtins file pub fn member_function_unexpected_target( position: Position, function_name: String, diff --git a/aaa/src/type_checker/mod.rs b/aaa/src/type_checker/mod.rs index 56c6ce3f..cc2ceecb 100644 --- a/aaa/src/type_checker/mod.rs +++ b/aaa/src/type_checker/mod.rs @@ -1,3 +1,4 @@ pub mod call_checker; pub mod errors; +#[allow(clippy::module_inception)] pub mod type_checker; diff --git a/aaa/src/type_checker/type_checker.rs b/aaa/src/type_checker/type_checker.rs index 4a058534..b23bd632 100644 --- a/aaa/src/type_checker/type_checker.rs +++ b/aaa/src/type_checker/type_checker.rs @@ -1,3 +1,5 @@ +#![allow(clippy::result_large_err)] // TODO #239 Handle linter error for large error result + use std::{ cell::RefCell, collections::{HashMap, HashSet}, @@ -14,13 +16,13 @@ use crate::{ types::{ function_body::{ Assignment, Branch, Call, CallArgument, CallEnum, CallEnumConstructor, - CallFunction, CallLocalVariable, CallStruct, CaseBlock, Foreach, FunctionBody, - FunctionBodyItem, FunctionType, GetField, GetFunction, Match, Return, SetField, - Use, While, + CallFunction, CallInterfaceFunction, CallLocalVariable, CallStruct, CaseBlock, + Foreach, FunctionBody, FunctionBodyItem, FunctionType, GetField, GetFunction, + Match, Return, SetField, Use, While, }, identifiable::{ - Argument, EnumType, Function, FunctionPointerType, Identifiable, ReturnTypes, - StructType, Type, + Argument, EnumType, Function, FunctionPointerType, Identifiable, Interface, + ResolvedInterfaceFunction, ReturnTypes, StructType, Type, }, }, }, @@ -46,16 +48,25 @@ use super::{ }, }; +pub type InterfaceMapping = HashMap>>; + +pub type InterfacesTable = Vec<( + Rc>, + Rc>, + InterfaceMapping, +)>; pub struct TypeChecker { pub identifiables: HashMap<(PathBuf, String), Identifiable>, pub builtins_path: PathBuf, pub entrypoint_path: PathBuf, pub verbose: bool, + pub interfaces_table: InterfacesTable, } pub struct Output { pub main_function: Rc>, pub identifiables: HashMap<(PathBuf, String), Identifiable>, + pub interfaces_table: InterfacesTable, } pub fn type_check( @@ -72,6 +83,7 @@ impl TypeChecker { builtins_path: input.builtins_path, entrypoint_path: input.entrypoint_path, verbose, + interfaces_table: vec![], } } @@ -90,9 +102,11 @@ impl TypeChecker { functions } - fn run(self) -> Result> { + fn run(mut self) -> Result> { let mut errors = vec![]; + self.interfaces_table = self.build_interfaces_table(); + for function_rc in self.functions() { let function = &*(*function_rc).borrow(); let checker = FunctionTypeChecker::new(function, &self); @@ -116,6 +130,7 @@ impl TypeChecker { let output = Output { main_function: main_function.unwrap(), identifiables: self.identifiables, + interfaces_table: self.interfaces_table, }; Ok(output) @@ -139,7 +154,7 @@ impl TypeChecker { match arguments.len() { 0 => (), 1 => { - let argument_type = &arguments.get(0).unwrap().type_; + let argument_type = &arguments.first().unwrap().type_; if !self.is_valid_main_argument_type(argument_type) { return invalid_main_signature(function.position()); } @@ -152,7 +167,7 @@ impl TypeChecker { ReturnTypes::Sometimes(return_types) => match return_types.len() { 0 => (), 1 => { - let return_type = return_types.get(0).unwrap(); + let return_type = return_types.first().unwrap(); if !self.is_valid_main_return_type(return_type) { return invalid_main_signature(function.position()); } @@ -181,7 +196,7 @@ impl TypeChecker { return false; } - let parameter = parameters.get(0).unwrap(); + let parameter = parameters.first().unwrap(); let Type::Struct(parameter_struct_type) = parameter else { return false; @@ -209,6 +224,99 @@ impl TypeChecker { true } + + fn build_interfaces_table(&self) -> InterfacesTable { + let mut interfaces_table = vec![]; + + let mut interfaces = vec![]; + + for identifiable in self.identifiables.values() { + if let Identifiable::Interface(interface) = identifiable { + interfaces.push(interface.clone()); + } + } + + for identifiable in self.identifiables.values() { + if matches!( + identifiable, + Identifiable::EnumConstructor(_) | Identifiable::Function(_), + ) { + continue; + } + + for interface in &interfaces { + if let Some(mapping) = self.get_interface_mapping(identifiable, interface) { + let identifiable = Rc::new(RefCell::new(identifiable.clone())); + + interfaces_table.push((interface.clone(), identifiable, mapping)); + } + } + } + + interfaces_table + } + + fn get_interface_mapping( + &self, + identifiable: &Identifiable, + interface: &Rc>, + ) -> Option { + let interface = &*interface.borrow(); + + let mut mapping = HashMap::new(); + + for required_function in &interface.resolved().functions { + let target = self.get_interface_function_target(required_function, identifiable)?; + mapping.insert(required_function.name.clone(), target); + } + + Some(mapping) + } + + fn get_interface_function_target( + &self, + interface_function: &ResolvedInterfaceFunction, + identifiable: &Identifiable, + ) -> Option>> { + let key = ( + identifiable.key().0, + format!("{}:{}", identifiable.key().1, &interface_function.name), + ); + + let identifiable = self.identifiables.get(&key)?; + + let Identifiable::Function(function_rc) = identifiable else { + return None; + }; + + let function = &*function_rc.borrow(); + + if function.arguments().len() != interface_function.arguments.len() { + return None; + } + + for (interface_function_arg, function_arg) in + zip(&interface_function.arguments, &function.arguments()[1..]) + { + if interface_function_arg.type_ != function_arg.type_ { + return None; + } + } + + use ReturnTypes::*; + + match (&interface_function.return_types, function.return_types()) { + (_, Never) => (), + (Never, Sometimes(_)) => return None, + (Sometimes(lhs), Sometimes(rhs)) => { + if lhs != rhs { + return None; + } + } + } + + Some(function_rc.clone()) + } } #[derive(Clone)] @@ -321,15 +429,15 @@ impl<'a> FunctionTypeChecker<'a> { } println!("returns: {}", &self.function.signature().return_types); - println!(""); + println!(); } - fn print_position_stack(&mut self, position: Position, stack: &Vec) { + fn print_position_stack(&mut self, position: Position, stack: &[Type]) { if !self.type_checker.verbose { return; } - let stack_types = join_display(" ", &stack); + let stack_types = join_display(" ", stack); println!("{} {}", position, stack_types); } @@ -362,7 +470,7 @@ impl<'a> FunctionTypeChecker<'a> { let position = self.function.position(); let name = self.function.name(); - let Some(first_argument) = self.function.arguments().get(0) else { + let Some(first_argument) = self.function.arguments().first() else { return member_function_without_arguments(position, name); }; @@ -412,28 +520,29 @@ impl<'a> FunctionTypeChecker<'a> { use FunctionBodyItem::*; match item { - Integer(_) => self.check_integer(stack), - String(_) => self.check_string(stack), + Assignment(assignment) => self.check_assignment(stack, assignment), Boolean(_) => self.check_boolean(stack), - Char(_) => self.check_character(stack), Branch(branch) => self.check_branch(stack, branch), - While(while_) => self.check_while(stack, while_), + Call(call) => self.check_call(stack, call), + CallArgument(call) => self.check_call_argument(stack, call), + CallEnum(call) => self.check_call_enum(stack, call), + CallEnumConstructor(call) => self.check_call_enum_constructor(stack, call), CallFunction(call) => self.check_call_function(stack, call), + CallInterfaceFunction(function) => self.check_call_interface_function(stack, function), + CallLocalVariable(call) => self.check_call_local_variable(stack, call), CallStruct(call) => self.check_call_struct(stack, call), + Char(_) => self.check_character(stack), + Foreach(foreach) => self.check_foreach(stack, foreach), FunctionType(func_type) => self.check_function_type(stack, func_type), - CallEnum(call) => self.check_call_enum(stack, call), - CallArgument(call) => self.check_call_argument(stack, call), - Return(return_) => self.check_return(stack, return_), - Use(use_) => self.check_use(stack, use_), - CallLocalVariable(call) => self.check_call_local_variable(stack, call), GetField(get_field) => self.check_get_field(stack, get_field), - SetField(set_field) => self.check_set_field(stack, set_field), - Assignment(assignment) => self.check_assignment(stack, assignment), GetFunction(get_function) => self.check_get_function(stack, get_function), - Foreach(foreach) => self.check_foreach(stack, foreach), + Integer(_) => self.check_integer(stack), Match(match_) => self.check_match(stack, match_), - CallEnumConstructor(call) => self.check_call_enum_constructor(stack, call), - Call(call) => self.check_call(stack, call), + Return(return_) => self.check_return(stack, return_), + SetField(set_field) => self.check_set_field(stack, set_field), + String(_) => self.check_string(stack), + Use(use_) => self.check_use(stack, use_), + While(while_) => self.check_while(stack, while_), } } @@ -458,7 +567,7 @@ impl<'a> FunctionTypeChecker<'a> { } fn check_condition_body(&mut self, stack: Vec, body: &FunctionBody) -> TypeResult { - let mut stack_after = self.check_function_body(stack.clone(), &body)?; + let mut stack_after = self.check_function_body(stack.clone(), body)?; let mut expected_stack_after = stack.clone(); expected_stack_after.push(self.builtin_type("bool")); @@ -531,6 +640,7 @@ impl<'a> FunctionTypeChecker<'a> { name: function.name(), position: call.position.clone(), stack, + identifiables: &self.type_checker.identifiables, }; checker.check() @@ -597,8 +707,7 @@ impl<'a> FunctionTypeChecker<'a> { .signature() .arguments .iter() - .filter(|arg| arg.name == call_arg.name) - .next() + .find(|arg| arg.name == call_arg.name) .unwrap(); let argument_type = argument.type_.clone(); @@ -784,7 +893,7 @@ impl<'a> FunctionTypeChecker<'a> { } fn check_foreach(&self, _stack: Vec, _for_each: &Foreach) -> TypeResult { - todo!() // TODO #214 Implement interfaces in Rust-based transpiler + todo!() // TODO #243 Support foreach loops } fn check_assignment(&mut self, stack: Vec, assignment: &Assignment) -> TypeResult { @@ -845,10 +954,10 @@ impl<'a> FunctionTypeChecker<'a> { // Update target, such that we can use this in transpiler match_.target.set(Some(enum_type.enum_.clone())); - Self::check_match_is_expected_enum(&enum_type, &match_)?; - Self::check_match_is_full_enumeration(&enum_type, &match_)?; + Self::check_match_is_expected_enum(&enum_type, match_)?; + Self::check_match_is_full_enumeration(&enum_type, match_)?; - stack = self.check_match_child_stacks(&stack, &enum_type, &match_)?; + stack = self.check_match_child_stacks(&stack, &enum_type, match_)?; Ok(stack) } @@ -891,7 +1000,7 @@ impl<'a> FunctionTypeChecker<'a> { if match_.default_blocks.len() > 1 { return colliding_default_blocks([ - match_.default_blocks.get(0).unwrap().position.clone(), + match_.default_blocks.first().unwrap().position.clone(), match_.default_blocks.get(1).unwrap().position.clone(), ]); } @@ -899,9 +1008,9 @@ impl<'a> FunctionTypeChecker<'a> { let missing_cases: HashSet<_> = enum_ .resolved() .variants - .iter() - .map(|(variant_name, _)| variant_name.clone()) - .filter(|variant_name| !found_cases.contains_key(variant_name)) + .keys() + .filter(|variant_name| !found_cases.contains_key(*variant_name)) + .cloned() .collect(); if !missing_cases.is_empty() && match_.default_blocks.is_empty() { @@ -909,7 +1018,7 @@ impl<'a> FunctionTypeChecker<'a> { } if missing_cases.is_empty() && !match_.default_blocks.is_empty() { - let default_position = match_.default_blocks.get(0).unwrap().position.clone(); + let default_position = match_.default_blocks.first().unwrap().position.clone(); return unreachable_default(default_position); } @@ -918,7 +1027,7 @@ impl<'a> FunctionTypeChecker<'a> { fn check_match_child_stacks( &mut self, - stack: &Vec, + stack: &[Type], enum_type: &EnumType, match_: &Match, ) -> TypeResult { @@ -935,7 +1044,8 @@ impl<'a> FunctionTypeChecker<'a> { .get(&case_block.variant_name) .unwrap(); - let case_stack = match self.check_case_block(stack.clone(), variant_data, &case_block) { + let case_stack = match self.check_case_block(stack.to_owned(), variant_data, case_block) + { Ok(case_stack) => ReturnTypes::Sometimes(case_stack), Err(TypeError::DoesNotReturn) => ReturnTypes::Never, Err(err) => return Err(err), @@ -948,11 +1058,12 @@ impl<'a> FunctionTypeChecker<'a> { let name = "default".to_owned(); let position = default_block.position.clone(); - let default_stack = match self.check_function_body(stack.clone(), &default_block.body) { - Ok(case_stack) => ReturnTypes::Sometimes(case_stack), - Err(TypeError::DoesNotReturn) => ReturnTypes::Never, - Err(err) => return Err(err), - }; + let default_stack = + match self.check_function_body(stack.to_owned(), &default_block.body) { + Ok(case_stack) => ReturnTypes::Sometimes(case_stack), + Err(TypeError::DoesNotReturn) => ReturnTypes::Never, + Err(err) => return Err(err), + }; child_return_types.push((name, position, default_stack)); } @@ -961,8 +1072,7 @@ impl<'a> FunctionTypeChecker<'a> { let returning_type = child_return_types .iter() .map(|(_, _, return_types)| return_types) - .filter(|return_types| matches!(return_types, ReturnTypes::Sometimes(_))) - .next(); + .find(|return_types| matches!(return_types, ReturnTypes::Sometimes(_))); let Some(returning_type) = returning_type else { return does_not_return(); // All cases diverge @@ -1070,6 +1180,7 @@ impl<'a> FunctionTypeChecker<'a> { name: enum_ctor.name(), position: call.position.clone(), stack, + identifiables: &self.type_checker.identifiables, }; checker.check() @@ -1089,10 +1200,38 @@ impl<'a> FunctionTypeChecker<'a> { position: call.position.clone(), argument_types: function_pointer.argument_types, return_types: function_pointer.return_types, - stack: stack, + stack, type_params: HashMap::new(), + identifiables: &self.type_checker.identifiables, }; checker.check() } + + fn check_call_interface_function( + &self, + stack: Vec, + call: &CallInterfaceFunction, + ) -> TypeResult { + let function = call.function.resolved_function(); + + let argument_types = function + .arguments + .iter() + .cloned() + .map(|arg| arg.type_) + .collect(); + + let call_checker = CallChecker { + name: call.function.name(), + position: call.position.clone(), + argument_types, + return_types: function.return_types.clone(), + stack: stack.clone(), + type_params: HashMap::new(), + identifiables: &self.type_checker.identifiables, + }; + + call_checker.check() + } } diff --git a/python/.coveragerc b/python/.coveragerc deleted file mode 100644 index 232eec48..00000000 --- a/python/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] -omit = - aaa/run.py - aaa/run_tests.py diff --git a/python/aaa/__init__.py b/python/aaa/__init__.py deleted file mode 100644 index 4c7816dd..00000000 --- a/python/aaa/__init__.py +++ /dev/null @@ -1,57 +0,0 @@ -import os -import secrets -from pathlib import Path -from tempfile import gettempdir - - -class AaaModel: - def __repr__(self) -> str: # pragma: nocover - return ( - f"{type(self).__qualname__}(" - + ", ".join( - f"{field_name}: {repr(field)}" - for field_name, field in vars(self).items() - ) - + ")" - ) - - -class AaaException(Exception): - ... - - -class AaaEnvironmentError(AaaException): - ... - - -AAA_DEFAULT_OUTPUT_FOLDER_ROOT = Path(gettempdir()) / "aaa/transpiled" - - -def __create_output_folder(root: Path, name: str | None) -> Path: - if name is None: - name = "".join(secrets.choice("0123456789abcdef") for _ in range(16)) - - path = root / name - path.resolve().mkdir(exist_ok=True, parents=True) - return path - - -def create_output_folder(name: str | None = None) -> Path: - return __create_output_folder(AAA_DEFAULT_OUTPUT_FOLDER_ROOT, name) - - -def aaa_project_root() -> Path: - return (Path(__file__).parent / "..").resolve() - - -def get_builtins_path() -> Path: - return get_stdlib_path() / "builtins.aaa" - - -def get_stdlib_path() -> Path: - try: - return Path(os.environ["AAA_STDLIB_PATH"]) - except KeyError as e: - raise AaaEnvironmentError( - "Cannot find builtins, because env var AAA_STDLIB_PATH is not set.\n" - ) from e diff --git a/python/aaa/cross_referencer/__init__.py b/python/aaa/cross_referencer/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/aaa/cross_referencer/cross_referencer.py b/python/aaa/cross_referencer/cross_referencer.py deleted file mode 100644 index a3d075dd..00000000 --- a/python/aaa/cross_referencer/cross_referencer.py +++ /dev/null @@ -1,1054 +0,0 @@ -from collections.abc import Callable -from pathlib import Path - -from basil.models import Position - -from aaa.cross_referencer.exceptions import ( - CircularDependencyError, - CollidingEnumVariant, - CollidingIdentifier, - CrossReferenceBaseException, - FunctionPointerTargetNotFound, - ImportedItemNotFound, - IndirectImportException, - InvalidArgument, - InvalidEnumType, - InvalidEnumVariant, - InvalidFunctionPointerTarget, - InvalidReturnType, - InvalidType, - UnexpectedBuiltin, - UnexpectedTypeParameterCount, - UnknownIdentifier, -) -from aaa.cross_referencer.models import ( - Argument, - Assignment, - BooleanLiteral, - Branch, - CallEnumConstructor, - CallFunction, - CallFunctionByPointer, - CallType, - CallVariable, - CaseBlock, - CharacterLiteral, - CrossReferencerOutput, - DefaultBlock, - Enum, - EnumConstructor, - ForeachLoop, - Function, - FunctionBody, - FunctionBodyItem, - FunctionPointer, - GetFunctionPointer, - Identifiable, - IdentifiablesDict, - ImplicitEnumConstructorImport, - ImplicitFunctionImport, - Import, - IntegerLiteral, - MatchBlock, - Never, - Return, - StringLiteral, - Struct, - StructFieldQuery, - StructFieldUpdate, - UseBlock, - Variable, - VariableType, - WhileLoop, -) -from aaa.parser import models as parser -from aaa.runner.exceptions import AaaTranslationException - - -class CrossReferencer: - def __init__(self, parser_output: parser.ParserOutput, verbose: bool) -> None: - self.parsed_files = parser_output.parsed - self.builtins_path = parser_output.builtins_path - self.entrypoint = parser_output.entrypoint - self.identifiers: IdentifiablesDict = {} - self.exceptions: list[CrossReferenceBaseException] = [] - self.cross_referenced_files: set[Path] = set() - self.dependency_stack: list[Path] = [] - self.verbose = verbose - - def _save_identifier(self, identifiable: Identifiable) -> None: - key = identifiable.identify() - - try: - found = self.identifiers[key] - except KeyError: - self.identifiers[key] = identifiable - else: - if found.position == identifiable.position: - # Cannot collide with same item - return - - self.exceptions += [CollidingIdentifier([identifiable, found])] - - def run(self) -> CrossReferencerOutput: - try: - self._cross_reference_file(self.entrypoint) - except CrossReferenceBaseException as e: - self.exceptions.append(e) - - if self.exceptions: - raise AaaTranslationException(self.exceptions) - - self._print_values() - - output = CrossReferencerOutput( - functions={ - k: v for (k, v) in self.identifiers.items() if isinstance(v, Function) - }, - enums={k: v for (k, v) in self.identifiers.items() if isinstance(v, Enum)}, - structs={ - k: v for (k, v) in self.identifiers.items() if isinstance(v, Struct) - }, - imports={ - k: v for (k, v) in self.identifiers.items() if isinstance(v, Import) - }, - builtins_path=self.builtins_path, - entrypoint=self.entrypoint, - ) - - return output - - def _get_remaining_dependencies(self, file: Path) -> list[Path]: - deps: list[Path] = [] - - if file != self.builtins_path: - deps += [self.builtins_path] - - deps += self.parsed_files[file].get_dependencies() - - # NOTE: don't use set difference to maintain import order as in source file - remaining_deps: list[Path] = [] - - for dep in deps: - if dep not in self.cross_referenced_files and dep not in remaining_deps: - remaining_deps.append(dep) - - return remaining_deps - - def _get_implicit_function_imports(self, import_: Import) -> IdentifiablesDict: - if not import_.is_resolved(): - return {} - - source = import_.resolved().source - implicit_imports: dict[tuple[Path, str], Identifiable] = {} - - if isinstance(source, Struct): - for (file, name), identifier in self.identifiers.items(): - if source.position.file == file and name.startswith(source.name + ":"): - assert isinstance(identifier, Function) - implicit_imports[ - (import_.position.file, name) - ] = ImplicitFunctionImport(identifier) - - if isinstance(source, Enum): - for (file, name), identifier in self.identifiers.items(): - if source.position.file == file and name.startswith(source.name + ":"): - key = (import_.position.file, name) - if isinstance(identifier, EnumConstructor): - implicit_imports[key] = ImplicitEnumConstructorImport( - identifier - ) - elif isinstance(identifier, Function): - implicit_imports[key] = ImplicitFunctionImport(identifier) - - return implicit_imports - - def _cross_reference_file(self, file: Path) -> None: - if file in self.dependency_stack: - raise CircularDependencyError(self.dependency_stack + [file]) - - self.dependency_stack.append(file) - - for dependency in self._get_remaining_dependencies(file): - self._cross_reference_file(dependency) - - ( - imports, - structs, - enums, - enum_ctors, - functions, - ) = self._load_identifiers(file) - - for identifier in imports + structs + enums + enum_ctors + functions: - self._save_identifier(identifier) - - for import_ in imports: - self._resolve_import(import_) - implicit_imports = self._get_implicit_function_imports(import_) - self.identifiers.update(implicit_imports) - - for type in structs: - self._resolve_struct(type) - - for enum in enums: - self._resolve_enum(enum) - - for function in functions: - self._resolve_function_signature(function) - - for function in functions: - if not isinstance(function.state, Function.WithSignature): - continue - self._resolve_function_body(function) - - self.dependency_stack.pop() - self.cross_referenced_files.add(file) - - def _resolve_function_signature(self, function: Function) -> None: - try: - params = self._resolve_function_params(function) - arguments = self._resolve_function_arguments(function, params) - return_types = self._resolve_function_return_types(function, params) - parsed_body = function.get_unresolved().parsed.get_body() - - function.add_signature(parsed_body, params, arguments, return_types) - self._check_function_identifiers_collision(function) - except CrossReferenceBaseException as e: - self.exceptions.append(e) - - def _print_values(self) -> None: # pragma: nocover - if not self.verbose: - return - - for (_, identifier), identifiable in self.identifiers.items(): - if isinstance(identifiable, Function): - file = identifiable.position.file - prefix = f"cross_referencer | Function {file}:{identifier}" - - print(prefix) - - for arg in identifiable.arguments: - file = arg.type.position.file - name = arg.name - var_type = arg.type - - if isinstance(arg.type, FunctionPointer): - print(f"{prefix} | Argument {name} of type ptr to function") - elif arg.type.is_placeholder: - print(f"{prefix} | Argument {name} of type {var_type}") - else: - print(f"{prefix} | Argument {name} of type {file}:{var_type}") - - if isinstance(identifiable.return_types, Never): - print(f"{prefix} | Return type never") - else: - for return_type in identifiable.return_types: - if isinstance(return_type, FunctionPointer): - print(f"{prefix} | Return type ptr to function") - elif return_type.is_placeholder: - print(f"{prefix} | Return type {return_type}") - else: - file = return_type.type.position.file - print(f"{prefix} | Return type {file}:{return_type}") - - elif isinstance(identifiable, Struct): - file = identifiable.position.file - prefix = f"cross_referencer | Type {file}:{identifier}" - - print(prefix) - - for name, var_type in identifiable.fields.items(): - file = var_type.position.file - print(f"{prefix} | Field {name} of type {file}:{var_type}") - - elif isinstance(identifiable, Import): - file = identifiable.position.file - prefix = f"cross_referencer | Import {file}:{identifier}" - source_type = type(identifiable.source).__name__ - - print( - f"{prefix} | Import {source_type} from " - + f"{identifiable.source_file}:{identifiable.source_name}" - ) - - elif isinstance( - identifiable, - Enum - | EnumConstructor - | ImplicitFunctionImport - | ImplicitEnumConstructorImport, - ): - pass # TODO #171 Redo verbose mode for cross referencer - - else: # pragma: nocover - raise NotImplementedError - - def _load_identifiers( - self, file: Path - ) -> tuple[ - list[Import], - list[Struct], - list[Enum], - list[EnumConstructor], - list[Function], - ]: - parsed_file = self.parsed_files[file] - - structs = self._load_struct_types(parsed_file.structs) - enum_ctors, enums = self._load_enums(parsed_file.enums) - - functions = self._load_functions(parsed_file.functions) - imports = self._load_imports(parsed_file.imports) - return imports, structs, enums, enum_ctors, functions - - def _load_enums( - self, parsed_enums: list[parser.Enum] - ) -> tuple[list[EnumConstructor], list[Enum]]: - enum_ctors: list[EnumConstructor] = [] - enums: list[Enum] = [] - - for parsed_enum in parsed_enums: - enum = Enum.from_parsed_enum(parsed_enum) - enums.append(enum) - - variants: dict[str, parser.EnumVariant] = {} - for variant in parsed_enum.get_variants(): - try: - found = variants[variant.name.value] - except KeyError: - pass - else: - raise CollidingEnumVariant(parsed_enum, [variant, found]) - - variants[variant.name.value] = variant - - enum_ctors += [ - EnumConstructor(enum, variant.name.value) - for variant in parsed_enum.get_variants() - ] - - return enum_ctors, enums - - def _load_struct_types(self, parsed_structs: list[parser.Struct]) -> list[Struct]: - structs: list[Struct] = [] - - for parsed_struct in parsed_structs: - fields = parsed_struct.get_fields() - - if fields is None: - if parsed_struct.position.file != self.builtins_path: - raise UnexpectedBuiltin(parsed_struct.position) - fields = {} - - structs.append(Struct.from_parsed_struct(parsed_struct, fields)) - - return structs - - def _load_functions( - self, parsed_functions: list[parser.Function] - ) -> list[Function]: - return [Function(parsed_function) for parsed_function in parsed_functions] - - def _load_imports(self, parsed_imports: list[parser.Import]) -> list[Import]: - imports: list[Import] = [] - - for parsed_import in parsed_imports: - for imported_item in parsed_import.imported_items.value: - import_ = Import(imported_item, parsed_import) - imports.append(import_) - - return imports - - def _resolve_import(self, import_: Import) -> None: - key = (import_.source_file, import_.source_name) - - try: - source = self.identifiers[key] - except KeyError: - e: CrossReferenceBaseException = ImportedItemNotFound(import_) - self.exceptions.append(e) - return - - if isinstance(source, Import): - e = IndirectImportException(import_) - self.exceptions.append(e) - return - - return import_.resolve(source) - - def _get_identifiable(self, identifier: parser.Identifier) -> Identifiable: - return self._get_identifiable_generic(identifier.value, identifier.position) - - def _get_type(self, identifier: parser.Identifier) -> Struct | Enum: - type = self._get_identifiable(identifier) - assert isinstance(type, Struct | Enum) - return type - - def _get_identifiable_generic(self, name: str, position: Position) -> Identifiable: - builtins_key = (self.builtins_path, name) - key = (position.file, name) - - if builtins_key in self.identifiers: - found = self.identifiers[builtins_key] - elif key in self.identifiers: - found = self.identifiers[key] - else: - raise UnknownIdentifier(position, name) - - if isinstance(found, Import): - if not isinstance(found.state, Import.Resolved): - # Something went wrong resolving the import earlier. - raise UnknownIdentifier(position, name) - - assert not isinstance(found.source, Import) - return found.source - - return found - - def _resolve_type( - self, parsed: parser.TypeLiteral | parser.FunctionPointerTypeLiteral - ) -> VariableType | FunctionPointer: - if isinstance(parsed, parser.FunctionPointerTypeLiteral): - parsed_return_types = parsed.get_return_types() - if isinstance(parsed_return_types, parser.Never): - return_types: list[VariableType | FunctionPointer] | Never = Never() - else: - return_types = [ - self._resolve_type(type) for type in parsed_return_types - ] - - return FunctionPointer( - parsed.position, - argument_types=[ - self._resolve_type(type) for type in parsed.get_arguments() - ], - return_types=return_types, - ) - - type_identifier = parsed.identifier - field_type = self._get_type(type_identifier) - - params: list[VariableType | FunctionPointer] = [] - - for parsed_param in parsed.get_params(): - if isinstance(parsed_param, parser.TypeLiteral): - param_type = self._get_type(parsed_param.identifier) - assert len(parsed_param.params) == 0 - - params.append( - VariableType( - type=param_type, - params=[], - is_placeholder=False, - position=parsed_param.position, - is_const=False, - ) - ) - else: - assert isinstance(parsed_param, parser.FunctionPointerTypeLiteral) - params.append(self._resolve_type(parsed_param)) - - return VariableType( - type=field_type, - params=params, - is_placeholder=False, - position=parsed.position, - is_const=False, - ) - - def _resolve_struct_params(self, struct: Struct) -> dict[str, Struct]: - resolved_params: dict[str, Struct] = {} - - for parsed_type_param in struct.get_unresolved().parsed_params: - struct = Struct.from_identifier(parsed_type_param) - param_name = parsed_type_param.value - - try: - colliding = self.identifiers[(struct.position.file, param_name)] - except KeyError: - pass - else: - raise CollidingIdentifier([struct, colliding]) - - resolved_params[param_name] = struct - - return resolved_params - - def _resolve_field_type( - self, - type_params: dict[str, Struct], - parsed_field_type: parser.TypeLiteral | parser.FunctionPointerTypeLiteral, - ) -> VariableType | FunctionPointer: - if isinstance(parsed_field_type, parser.FunctionPointerTypeLiteral): - return self._resolve_type(parsed_field_type) - - assert isinstance(parsed_field_type, parser.TypeLiteral) - arg_type_name = parsed_field_type.identifier.value - - params: list[VariableType | FunctionPointer] = [] - - try: - field_type: Struct | Enum = type_params[arg_type_name] - except KeyError: - identifiable = self._get_identifiable(parsed_field_type.identifier) - - if not isinstance(identifiable, Enum | Struct): - raise InvalidArgument( - used=parsed_field_type, found=identifiable - ) from None - - field_type = identifiable - - if len(parsed_field_type.params) != field_type.param_count: - raise UnexpectedTypeParameterCount( - position=parsed_field_type.identifier.position, - expected_param_count=field_type.param_count, - found_param_count=len(parsed_field_type.params), - ) from None - - params = self._lookup_function_params(type_params, parsed_field_type) - - return VariableType( - type=field_type, - params=params, - is_placeholder=arg_type_name in type_params, - position=parsed_field_type.position, - is_const=parsed_field_type.const, - ) - - def _resolve_struct(self, struct: Struct) -> None: - parsed_field_types = struct.get_unresolved().parsed_field_types - resolved_params = self._resolve_struct_params(struct) - - fields = { - field_name: self._resolve_field_type(resolved_params, parsed_field) - for field_name, parsed_field in parsed_field_types.items() - } - - struct.resolve(resolved_params, fields) - - def _resolve_enum_params(self, enum: Enum) -> dict[str, Struct]: - resolved_params: dict[str, Struct] = {} - - for parsed_type_param in enum.get_unresolved().parsed_params: - struct = Struct.from_identifier(parsed_type_param) - param_name = parsed_type_param.value - - try: - colliding = self.identifiers[(struct.position.file, param_name)] - except KeyError: - pass - else: - raise CollidingIdentifier([struct, colliding]) - - resolved_params[param_name] = struct - - return resolved_params - - def _resolve_enum(self, enum: Enum) -> None: - parsed_variants = enum.get_unresolved().parsed_variants - resolved_params = self._resolve_enum_params(enum) - - enum_variants: dict[str, list[VariableType | FunctionPointer]] = {} - for variant_name, associated_data in parsed_variants.items(): - resolved_associated_data = [ - self._resolve_field_type(resolved_params, item) - for item in associated_data - ] - - enum_variants[variant_name] = resolved_associated_data - - enum.resolve(resolved_params, enum_variants) - - def _resolve_function_params(self, function: Function) -> dict[str, Struct]: - resolved_params: dict[str, Struct] = {} - - for parsed_type_param in function.get_unresolved().parsed.get_params(): - struct = Struct.from_identifier(parsed_type_param) - param_name = parsed_type_param.value - - try: - colliding = self.identifiers[(function.position.file, param_name)] - except KeyError: - pass - else: - raise CollidingIdentifier([struct, colliding]) - - resolved_params[param_name] = struct - - return resolved_params - - def _resolve_function_argument( - self, - type_params: dict[str, Struct], - parsed_arg: parser.Argument, - ) -> Argument: - parsed_type = parsed_arg.type.literal - arg_type: VariableType | FunctionPointer - - if isinstance(parsed_type, parser.FunctionPointerTypeLiteral): - arg_type = self._resolve_type(parsed_type) - return Argument(identifier=parsed_arg.identifier, type=arg_type) - - assert isinstance(parsed_type, parser.TypeLiteral) - arg_type_name = parsed_type.identifier.value - type: Identifiable - - params: list[VariableType | FunctionPointer] = [] - - try: - type = type_params[arg_type_name] - except KeyError: - type = self._get_identifiable(parsed_type.identifier) - - if not isinstance(type, Enum | Struct): - raise InvalidArgument(used=parsed_type, found=type) from None - - if len(parsed_type.params) != type.param_count: - raise UnexpectedTypeParameterCount( - position=parsed_arg.identifier.position, - expected_param_count=type.param_count, - found_param_count=len(parsed_type.params), - ) from None - - params = self._lookup_function_params(type_params, parsed_type) - - arg_type = VariableType( - type=type, - params=params, - is_placeholder=arg_type_name in type_params, - position=parsed_type.position, - is_const=parsed_type.const, - ) - - return Argument(identifier=parsed_arg.identifier, type=arg_type) - - def _resolve_function_arguments( - self, function: Function, type_params: dict[str, Struct] - ) -> list[Argument]: - return [ - self._resolve_function_argument(type_params, parsed_arg) - for parsed_arg in function.get_unresolved().parsed.get_arguments() - ] - - def _lookup_function_param( - self, - type_params: dict[str, Struct], - param: parser.TypeLiteral | parser.FunctionPointerTypeLiteral, - ) -> VariableType | FunctionPointer: - if isinstance(param, parser.FunctionPointerTypeLiteral): - return self._resolve_type(param) - - param_name = param.identifier.value - if param_name in type_params: - param_type: Struct | Enum = type_params[param_name] - is_placeholder = True - else: - is_placeholder = False - identifier = self._get_identifiable(param.identifier) - - if not isinstance(identifier, Struct | Enum): - raise InvalidType(identifier) - - param_type = identifier - - return VariableType( - type=param_type, - is_placeholder=is_placeholder, - params=self._lookup_function_params(type_params, param), - position=param.position, - is_const=param.const, - ) - - def _lookup_function_params( - self, - type_params: dict[str, Struct], - parsed_type: parser.TypeLiteral, - ) -> list[VariableType | FunctionPointer]: - looked_up_params: list[VariableType | FunctionPointer] = [] - - for param in parsed_type.params: - literal = param.literal - - looked_up_param = self._lookup_function_param(type_params, literal) - looked_up_params.append(looked_up_param) - - return looked_up_params - - def _check_function_identifiers_collision(self, function: Function) -> None: - for lhs_index, lhs_arg in enumerate(function.arguments): - for rhs_arg in function.arguments[lhs_index + 1 :]: - if lhs_arg.name == rhs_arg.name: - # Argument names collide - raise CollidingIdentifier([lhs_arg, rhs_arg]) - - for argument in function.arguments: - key = (function.position.file, argument.name) - if key in self.identifiers: - identifier = self.identifiers[key] - # Argument collides with file-scoped identifier - raise CollidingIdentifier([identifier, argument]) - - if function.name == argument.name: - # Argument collides with function - raise CollidingIdentifier([function, argument]) - - for param_name, param in function.type_params.items(): - # If a param name collides with file-scoped identifier, - # creation of the param fails, so it's not tested here. - - if function.name == param_name: - # Param name collides with function - raise CollidingIdentifier([function, param]) - - for argument in function.arguments: - # Param name collides with argument - if param_name == argument.name: - raise CollidingIdentifier([param, argument]) - - def _resolve_function_return_types( - self, function: Function, type_params: dict[str, Struct] - ) -> list[VariableType | FunctionPointer] | Never: - parsed_return_types = function.get_unresolved().parsed.get_return_types() - - if isinstance(parsed_return_types, parser.Never): - return Never() - - return_types: list[VariableType | FunctionPointer] = [] - - for parsed_return_type in parsed_return_types: - if isinstance(parsed_return_type, parser.FunctionPointerTypeLiteral): - return_types.append(self._resolve_type(parsed_return_type)) - continue - - return_type_name = parsed_return_type.identifier.value - - if return_type_name in type_params: - type: Struct | Enum = type_params[return_type_name] - params: list[VariableType | FunctionPointer] = [] - else: - loaded_type = self._get_identifiable(parsed_return_type.identifier) - - if not isinstance(loaded_type, Struct | Enum): - raise InvalidReturnType(loaded_type) - - type = loaded_type - - params = self._lookup_function_params(type_params, parsed_return_type) - - return_type = VariableType( - type=type, - params=params, - is_placeholder=return_type_name in type_params, - position=parsed_return_type.position, - is_const=parsed_return_type.const, - ) - - expected_param_count = return_type.type.param_count - found_param_count = len(return_type.params) - if expected_param_count != found_param_count: - raise UnexpectedTypeParameterCount( - return_type.position, - expected_param_count, - found_param_count, - ) - - return_types.append(return_type) - return return_types - - def _resolve_function_body(self, function: Function) -> None: - parsed_body = function.get_with_signature().parsed_body - - if not parsed_body: - if function.position.file != self.builtins_path: - raise UnexpectedBuiltin(function.position) - - function.resolve(None) - return - - resolver = FunctionBodyResolver(self, function, parsed_body) - - try: - body = resolver.run() - except CrossReferenceBaseException as e: - self.exceptions.append(e) - return - - function.resolve(body) - - -class FunctionBodyResolver: - def __init__( - self, - cross_referencer: CrossReferencer, - function: Function, - parsed: parser.FunctionBody, - ) -> None: - self.function = function - self.parsed = parsed - self.cross_referencer = cross_referencer - - def run(self) -> FunctionBody: - return self._resolve_function_body(self.parsed) - - def _resolve_function_body(self, parsed_body: parser.FunctionBody) -> FunctionBody: - return FunctionBody( - items=[ - self._resolve_function_body_item(item) for item in parsed_body.items - ], - parsed=parsed_body, - ) - - def _resolve_branch(self, branch: parser.Branch) -> Branch: - else_body = branch.get_else_body() - - if else_body: - resolved_else_body = self._resolve_function_body(else_body) - else: - resolved_else_body = None - - return Branch( - condition=self._resolve_function_body(branch.condition), - if_body=self._resolve_function_body(branch.get_if_body()), - else_body=resolved_else_body, - parsed=branch, - ) - - def _resolve_while_loop(self, while_loop: parser.WhileLoop) -> WhileLoop: - return WhileLoop( - condition=self._resolve_function_body(while_loop.condition), - body=self._resolve_function_body(while_loop.body.value), - parsed=while_loop, - ) - - def _resolve_call( - self, call: parser.FunctionCall - ) -> CallVariable | CallFunction | CallType | CallEnumConstructor: - try: - identifiable = self._get_identifiable_from_call(call) - except UnknownIdentifier: - return CallVariable( - call.name(), bool(call.get_type_params()), call.position - ) - - type_params = [ - self._lookup_function_param(self.function.type_params, param) - for param in call.get_type_params() - ] - - if isinstance(identifiable, Function): - return CallFunction(identifiable, type_params, call.position) - - if isinstance(identifiable, ImplicitFunctionImport): - return CallFunction(identifiable.source, type_params, call.position) - - if isinstance(identifiable, EnumConstructor): - var_type = VariableType( - identifiable.enum, type_params, False, call.position, False - ) - return CallEnumConstructor(identifiable, var_type, call.position) - - if isinstance(identifiable, ImplicitEnumConstructorImport): - var_type = VariableType( - identifiable.source.enum, - type_params, - False, - call.position, - False, - ) - return CallEnumConstructor(identifiable.source, var_type, call.position) - - if isinstance(identifiable, Import): - raise NotImplementedError # This should never happen - - else: - var_type = VariableType( - identifiable, - [ - self._lookup_function_param(self.function.type_params, param) - for param in call.get_type_params() - ], - False, - call.position, - False, - ) - - expected_param_count = var_type.type.param_count - found_param_count = len(var_type.params) - if expected_param_count != found_param_count: - raise UnexpectedTypeParameterCount( - call.position, expected_param_count, found_param_count - ) - - return CallType(var_type) - - def _resolve_struct_field_update( - self, update: parser.StructFieldUpdate - ) -> StructFieldUpdate: - return StructFieldUpdate( - parsed=update, - new_value_expr=self._resolve_function_body(update.new_value_expr.value), - ) - - def _resolve_function_body_item( - self, parsed_item: parser.FunctionBodyItem.types - ) -> FunctionBodyItem: - resolve_functions: dict[ - type[parser.FunctionBodyItem.types], - Callable[..., FunctionBodyItem], - ] = { - parser.Assignment: self._resolve_assignment, - parser.Boolean: BooleanLiteral, - parser.Branch: self._resolve_branch, - parser.Call: self._resolve_call_function_by_pointer, - parser.Char: CharacterLiteral, - parser.ForeachLoop: self._resolve_foreach_loop, - parser.FunctionCall: self._resolve_call, - parser.FunctionPointerTypeLiteral: self._resolve_function_pointer_literal, - parser.GetFunctionPointer: self._resolve_get_function_pointer, - parser.Integer: IntegerLiteral, - parser.MatchBlock: self._resolve_match_block, - parser.Return: self._resolve_return, - parser.String: StringLiteral, - parser.StructFieldQuery: StructFieldQuery, - parser.StructFieldUpdate: self._resolve_struct_field_update, - parser.UseBlock: self._resolve_use_block, - parser.WhileLoop: self._resolve_while_loop, - } - - assert set(resolve_functions.keys()) == set( - parser.FunctionBodyItem.types.__args__ # type: ignore - ) - return resolve_functions[type(parsed_item)](parsed_item) - - def _resolve_function_pointer_literal( - self, parsed: parser.FunctionPointerTypeLiteral - ) -> FunctionPointer: - parsed_return_types = parsed.get_return_types() - if isinstance(parsed_return_types, parser.Never): - return_types: list[VariableType | FunctionPointer] | Never = Never() - else: - return_types = [ - self.cross_referencer._resolve_type(type) - for type in parsed_return_types - ] - - return FunctionPointer( - parsed.position, - argument_types=[ - self.cross_referencer._resolve_type(type) - for type in parsed.get_arguments() - ], - return_types=return_types, - ) - - def _resolve_match_block(self, parsed: parser.MatchBlock) -> MatchBlock: - blocks: list[CaseBlock | DefaultBlock] = [] - - for block in parsed.blocks: - if isinstance(block, parser.CaseBlock): - blocks.append(self._resolve_case_block(block)) - else: - blocks.append(self._resolve_default_block(block)) - - return MatchBlock(parsed.position, blocks) - - def _resolve_case_block(self, parsed: parser.CaseBlock) -> CaseBlock: - enum_type_name = parsed.label.enum_name.value - variant_name = parsed.label.variant_name.value - assert enum_type_name # parser ensures this doesn't fail - - enum_type = self._get_identifiable_generic(enum_type_name, parsed.position) - - if not isinstance(enum_type, Enum): - raise InvalidEnumType(parsed.position, enum_type) - - if variant_name not in enum_type.variants: - raise InvalidEnumVariant(parsed.position, enum_type, variant_name) - - variables = [ - Variable(parsed_var, False) for parsed_var in parsed.label.get_variables() - ] - - resolved_body = self._resolve_function_body(parsed.body.value) - - return CaseBlock( - parsed.position, - enum_type=enum_type, - variant_name=variant_name, - variables=variables, - body=resolved_body, - ) - - def _resolve_default_block(self, parsed: parser.DefaultBlock) -> DefaultBlock: - return DefaultBlock( - parsed.position, - self._resolve_function_body(parsed.body_block.value), - ) - - def _resolve_return(self, parsed: parser.Return) -> Return: - return Return(parsed) - - def _resolve_call_function_by_pointer( - self, call: parser.Call - ) -> CallFunctionByPointer: - return CallFunctionByPointer(call.position) - - def _resolve_get_function_pointer( - self, parsed: parser.GetFunctionPointer - ) -> GetFunctionPointer: - builtins_key = ( - self.cross_referencer.builtins_path, - parsed.function_name.value, - ) - key = (parsed.position.file, parsed.function_name.value) - - if builtins_key in self.cross_referencer.identifiers: - target = self.cross_referencer.identifiers[builtins_key] - elif key in self.cross_referencer.identifiers: - target = self.cross_referencer.identifiers[key] - else: - raise FunctionPointerTargetNotFound( - parsed.position, parsed.function_name.value - ) - - if isinstance(target, Import): - target = target.resolved().source - - if isinstance(target, Function | EnumConstructor): - return GetFunctionPointer(parsed.position, target) - - if isinstance(target, ImplicitFunctionImport | ImplicitEnumConstructorImport): - return GetFunctionPointer(parsed.position, target.source) - - raise InvalidFunctionPointerTarget(parsed.position, target) - - def _resolve_assignment(self, parsed: parser.Assignment) -> Assignment: - variables = [Variable(var, False) for var in parsed.variables.value] - body = self._resolve_function_body(parsed.body_block.value) - return Assignment(parsed, variables, body) - - def _resolve_use_block(self, parsed: parser.UseBlock) -> UseBlock: - variables = [ - Variable(parsed_var, False) for parsed_var in parsed.variables.value - ] - body = self._resolve_function_body(parsed.body_block.value) - return UseBlock(parsed, variables, body) - - def _resolve_foreach_loop(self, parsed: parser.ForeachLoop) -> ForeachLoop: - body = self._resolve_function_body(parsed.body.value) - return ForeachLoop(parsed, body) - - def _get_identifiable_from_call(self, call: parser.FunctionCall) -> Identifiable: - return self._get_identifiable_generic(call.name(), call.position) - - def _get_identifiable_generic(self, name: str, position: Position) -> Identifiable: - return self.cross_referencer._get_identifiable_generic(name, position) - - def _lookup_function_param( - self, - type_params: dict[str, Struct], - param: parser.TypeLiteral | parser.FunctionPointerTypeLiteral, - ) -> VariableType | FunctionPointer: - return self.cross_referencer._lookup_function_param(type_params, param) diff --git a/python/aaa/cross_referencer/exceptions.py b/python/aaa/cross_referencer/exceptions.py deleted file mode 100644 index ec141e8a..00000000 --- a/python/aaa/cross_referencer/exceptions.py +++ /dev/null @@ -1,241 +0,0 @@ -from pathlib import Path - -from basil.models import Position - -import aaa.parser.models as parser -from aaa import AaaException -from aaa.cross_referencer.models import ( - AaaCrossReferenceModel, - Argument, - Enum, - EnumConstructor, - Function, - Identifiable, - ImplicitEnumConstructorImport, - ImplicitFunctionImport, - Import, - Struct, - Variable, -) -from aaa.parser.models import TypeLiteral - - -def describe(item: Identifiable | Argument | Variable) -> str: - if isinstance( - item, - Function - | ImplicitFunctionImport - | EnumConstructor - | ImplicitEnumConstructorImport, - ): - return f"function {item.name}" - elif isinstance(item, Import): - return f"imported identifier {item.name}" - elif isinstance(item, Struct): - return f"struct {item.name}" - elif isinstance(item, Enum): - return f"enum {item.name}" - elif isinstance(item, Argument): - return f"function argument {item.name}" - elif isinstance(item, Variable): - return f"local variable {item.name}" - else: - assert isinstance(item, Enum) - return f"enum {item.name}" - - -class CrossReferenceBaseException(AaaException): - ... - - -class ImportedItemNotFound(CrossReferenceBaseException): - def __init__(self, import_: Import) -> None: - self.import_ = import_ - - def __str__(self) -> str: - return ( - f"{self.import_.position}: Could not import " - + f"{self.import_.source_name} from {self.import_.source_file}" - ) - - -class IndirectImportException(CrossReferenceBaseException): - def __init__(self, import_: Import) -> None: - self.import_ = import_ - - def __str__(self) -> str: - return f"{self.import_.position}: Indirect imports are forbidden." - - -class CollidingIdentifier(CrossReferenceBaseException): - def __init__(self, colliding: list[Identifiable | Argument | Variable]) -> None: - assert len(colliding) == 2 - assert colliding[0].position.file == colliding[1].position.file - - def sort_key(item: AaaCrossReferenceModel) -> tuple[int, int]: - return (item.position.line, item.position.column) - - self.colliding = sorted(colliding, key=sort_key) - - def __str__(self) -> str: - msg = "Found name collision:\n" - - for item in self.colliding: - msg += f"{item.position}: {describe(item)}\n" - - return msg.removesuffix("\n") - - -class CollidingEnumVariant(CrossReferenceBaseException): - def __init__(self, enum: parser.Enum, variants: list[parser.EnumVariant]) -> None: - assert len(variants) == 2 - assert variants[0].position.file == variants[1].position.file - - def sort_key(item: parser.EnumVariant) -> tuple[int, int]: - return (item.position.line, item.position.column) - - self.colliding = sorted(variants, key=sort_key) - self.enum = enum - - def __str__(self) -> str: - msg = "Duplicate enum variant name collision:\n" - - for item in self.colliding: - msg += ( - f"{item.position}: enum variant " - + f"{self.enum.get_name()}:{item.name.value}\n" - ) - - return msg.removesuffix("\n") - - -class UnknownIdentifier(CrossReferenceBaseException): - def __init__(self, position: Position, name: str) -> None: - self.position = position - self.name = name - - def __str__(self) -> str: - return f"{self.position}: Usage of unknown identifier {self.name}" - - -class InvalidReturnType(CrossReferenceBaseException): - def __init__(self, identifiable: Identifiable) -> None: - self.identifiable = identifiable - - def __str__(self) -> str: - return ( - f"{self.identifiable.position}: Cannot use " - + describe(self.identifiable) - + " as return type" - ) - - -class InvalidArgument(CrossReferenceBaseException): - def __init__(self, used: TypeLiteral, found: Identifiable) -> None: - self.used = used - self.found = found - - def __str__(self) -> str: - return ( - f"{self.used.position}: Cannot use {self.used.identifier.value} " - + "as argument\n" - + f"{self.found.position}: {describe(self.found)} collides" - ) - - -class InvalidType(CrossReferenceBaseException): - def __init__(self, identifiable: Identifiable) -> None: - self.identifiable = identifiable - - def __str__(self) -> str: - return ( - f"{self.identifiable.position}: Cannot use " - + describe(self.identifiable) - + " as type" - ) - - -class UnexpectedTypeParameterCount(CrossReferenceBaseException): - def __init__( - self, - position: Position, - expected_param_count: int, - found_param_count: int, - ) -> None: - self.position = position - self.expected_param_count = expected_param_count - self.found_param_count = found_param_count - - def __str__(self) -> str: - return ( - f"{self.position}: Unexpected number of type parameters\n" - + f"Expected parameter count: {self.expected_param_count}\n" - + f" Found parameter count: {self.found_param_count}" - ) - - -class UnexpectedBuiltin(CrossReferenceBaseException): - def __init__( - self, - position: Position, - ) -> None: - self.position = position - - def __str__(self) -> str: - return f"{self.position}: Builtins are not allowed outside the builtins file." - - -class CircularDependencyError(CrossReferenceBaseException): - def __init__(self, dependencies: list[Path]) -> None: - self.dependencies = dependencies - - def __str__(self) -> str: - message = "Circular dependency detected:\n" - for dep in self.dependencies: - message += f"- {dep}\n" - return message.removesuffix("\n") - - -class InvalidEnumType(CrossReferenceBaseException): - def __init__(self, position: Position, identifiable: Identifiable) -> None: - self.identifiable = identifiable - self.position = position - - def __str__(self) -> str: - return f"{self.position}: Cannot use {describe(self.identifiable)} as enum type" - - -class InvalidEnumVariant(CrossReferenceBaseException): - def __init__(self, position: Position, enum: Enum, variant_name: str) -> None: - self.enum = enum - self.variant_name = variant_name - self.position = position - - def __str__(self) -> str: - return ( - f"{self.position}: Variant {self.variant_name} of enum " - + f"{self.enum.name} does not exist" - ) - - -class InvalidFunctionPointerTarget(CrossReferenceBaseException): - def __init__(self, position: Position, identifiable: Identifiable) -> None: - self.position = position - self.identifiable = identifiable - - def __str__(self) -> str: - return f"{self.position}: Cannot create function pointer to " + describe( - self.identifiable - ) - - -class FunctionPointerTargetNotFound(CrossReferenceBaseException): - def __init__(self, position: Position, target_name: str) -> None: - self.position = position - self.target_name = target_name - - def __str__(self) -> str: - return ( - f"{self.position}: Cannot create pointer to function " - + f"{self.target_name} which was not found" - ) diff --git a/python/aaa/cross_referencer/models.py b/python/aaa/cross_referencer/models.py deleted file mode 100644 index 288e8dff..00000000 --- a/python/aaa/cross_referencer/models.py +++ /dev/null @@ -1,746 +0,0 @@ -from __future__ import annotations - -from pathlib import Path -from typing import Any - -from basil.models import Position - -from aaa import AaaModel -from aaa.parser import models as parser - - -class AaaCrossReferenceModel(AaaModel): - def __init__(self, position: Position) -> None: - self.position = position - - -class EnumConstructor(AaaCrossReferenceModel): - def __init__(self, enum: Enum, variant_name: str) -> None: - position = Position(enum.position.file, -1, -1) - self.enum = enum - self.variant_name = variant_name - self.name = f"{enum.name}:{variant_name}" - super().__init__(position) - - def identify(self) -> tuple[Path, str]: - return (self.position.file, self.name) - - -class Function(AaaCrossReferenceModel): - class Unresolved: - def __init__(self, parsed: parser.Function) -> None: - self.parsed = parsed - - self.type_params = {param.value: param for param in parsed.get_params()} - - class WithSignature: - def __init__( - self, - parsed_body: parser.FunctionBody | None, - type_params: dict[str, Struct], - arguments: list[Argument], - return_types: list[VariableType | FunctionPointer] | Never, - ) -> None: - self.parsed_body = parsed_body - self.type_params = type_params - self.arguments = arguments - self.return_types = return_types - - class Resolved: - def __init__( - self, - type_params: dict[str, Struct], - arguments: list[Argument], - return_types: list[VariableType | FunctionPointer] | Never, - body: FunctionBody | None, - ) -> None: - self.type_params = type_params - self.arguments = arguments - self.return_types = return_types - self.body = body - - def __init__(self, parsed: parser.Function) -> None: - self.state: Function.Resolved | Function.Unresolved | Function.WithSignature = ( - Function.Unresolved(parsed) - ) - self.end_position = parsed.get_end_position() - self.func_name = parsed.get_func_name() - self.struct_name = parsed.get_type_name() - self.name = parsed.get_name() - - super().__init__(parsed.position) - - def get_unresolved(self) -> Function.Unresolved: - assert isinstance(self.state, Function.Unresolved) - return self.state - - def get_with_signature(self) -> Function.WithSignature: - assert isinstance(self.state, Function.WithSignature) - return self.state - - def is_member_function(self) -> bool: - return self.struct_name != "" - - def is_test(self) -> bool: - return ( - not self.is_member_function() - and self.func_name.startswith("test_") - and self.position.file.name.startswith("test_") - ) - - @property - def arguments(self) -> list[Argument]: - assert isinstance(self.state, Function.WithSignature | Function.Resolved) - return self.state.arguments - - @property - def return_types(self) -> list[VariableType | FunctionPointer] | Never: - assert isinstance(self.state, Function.WithSignature | Function.Resolved) - return self.state.return_types - - @property - def type_params(self) -> dict[str, Struct]: - assert isinstance(self.state, Function.WithSignature | Function.Resolved) - return self.state.type_params - - @property - def type_param_names(self) -> list[str]: - type_params = sorted(self.type_params.values(), key=lambda t: t.position) - return [struct.name for struct in type_params] - - @property - def body(self) -> FunctionBody: - assert isinstance(self.state, Function.Resolved) - assert self.state.body - return self.state.body - - def add_signature( - self, - parsed_body: parser.FunctionBody | None, - type_params: dict[str, Struct], - arguments: list[Argument], - return_types: list[VariableType | FunctionPointer] | Never, - ) -> None: - assert isinstance(self.state, Function.Unresolved) - self.state = Function.WithSignature( - parsed_body, type_params, arguments, return_types - ) - - def is_resolved(self) -> bool: - return isinstance(self.state, Function.Resolved) - - def resolve(self, body: FunctionBody | None) -> None: - assert isinstance(self.state, Function.WithSignature) - self.state = Function.Resolved( - self.state.type_params, - self.state.arguments, - self.state.return_types, - body, - ) - - def identify(self) -> tuple[Path, str]: - return (self.position.file, self.name) - - -class Argument(AaaCrossReferenceModel): - def __init__( - self, - type: VariableType | FunctionPointer, - identifier: parser.Identifier, - ) -> None: - self.type = type - self.name = identifier.value - super().__init__(identifier.position) - - -class FunctionBody(AaaCrossReferenceModel): - def __init__( - self, parsed: parser.FunctionBody, items: list[FunctionBodyItem] - ) -> None: - self.items = items - super().__init__(parsed.position) - - -class Import(AaaCrossReferenceModel): - class Unresolved: - ... - - class Resolved: - def __init__(self, source: Identifiable) -> None: - self.source = source - - def __init__(self, import_item: parser.ImportItem, import_: parser.Import) -> None: - self.state: Import.Resolved | Import.Unresolved = Import.Unresolved() - self.source_file = import_.get_source_file() - self.source_name = import_item.original.value - self.name = import_item.imported.value - super().__init__(import_item.position) - - @property - def source(self) -> Identifiable: - assert isinstance(self.state, Import.Resolved) - return self.state.source - - def resolve(self, source: Identifiable) -> None: - assert isinstance(self.state, Import.Unresolved) - self.state = Import.Resolved(source) - - def is_resolved(self) -> bool: - return isinstance(self.state, Import.Resolved) - - def resolved(self) -> Import.Resolved: - assert isinstance(self.state, Import.Resolved) - return self.state - - def identify(self) -> tuple[Path, str]: - return (self.position.file, self.name) - - -class ImplicitFunctionImport(AaaCrossReferenceModel): - def __init__(self, source: Function) -> None: - self.source = source - self.name = self.source.name - - def identify(self) -> tuple[Path, str]: - return (self.position.file, self.name) - - -class ImplicitEnumConstructorImport(AaaCrossReferenceModel): - def __init__(self, source: EnumConstructor) -> None: - self.source = source - self.name = self.source.name - - def identify(self) -> tuple[Path, str]: - return (self.position.file, self.name) - - -class Struct(AaaCrossReferenceModel): - class Unresolved: - def __init__( - self, - parsed_field_types: dict[ - str, - parser.TypeLiteral | parser.FunctionPointerTypeLiteral, - ], - parsed_params: list[parser.Identifier], - ) -> None: - self.parsed_field_types = parsed_field_types - self.parsed_params = parsed_params - - class Resolved: - def __init__( - self, - type_params: dict[str, Struct], - fields: dict[str, VariableType | FunctionPointer], - ) -> None: - self.type_params = type_params - self.fields = fields - - @classmethod - def from_parsed_struct( - cls, - struct: parser.Struct, - fields: dict[str, parser.TypeLiteral | parser.FunctionPointerTypeLiteral], - ) -> Struct: - return Struct(struct.position, struct.get_name(), struct.get_params(), fields) - - @classmethod - def from_identifier(cls, identifier: parser.Identifier) -> Struct: - return Struct(identifier.position, identifier.value, [], {}) - - def __init__( - self, - position: Position, - name: str, - params: list[parser.Identifier], - fields: dict[ - str, - parser.TypeLiteral | parser.FunctionPointerTypeLiteral, - ], - ) -> None: - self.state: Struct.Resolved | Struct.Unresolved = Struct.Unresolved( - fields, params - ) - self.param_count = len(params) - self.name = name - super().__init__(position) - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, Struct): - raise TypeError - - return self.name == other.name and self.position.file == other.position.file - - @property - def fields(self) -> dict[str, VariableType | FunctionPointer]: - assert isinstance(self.state, Struct.Resolved) - return self.state.fields - - @property - def type_params(self) -> dict[str, Struct]: - assert isinstance(self.state, Struct.Resolved) - return self.state.type_params - - def param_dict( - self, var_type: VariableType - ) -> dict[str, VariableType | FunctionPointer]: - struct_type_placeholders = [ - struct.name - for struct in sorted(self.type_params.values(), key=lambda s: s.position) - ] - - return dict(zip(struct_type_placeholders, var_type.params, strict=True)) - - def get_unresolved(self) -> Struct.Unresolved: - assert isinstance(self.state, Struct.Unresolved) - return self.state - - def resolve( - self, - type_params: dict[str, Struct], - fields: dict[str, VariableType | FunctionPointer], - ) -> None: - assert isinstance(self.state, Struct.Unresolved) - self.state = Struct.Resolved(type_params, fields) - - def is_resolved(self) -> bool: - return isinstance(self.state, Struct.Resolved) - - def identify(self) -> tuple[Path, str]: - return (self.position.file, self.name) - - -class Enum(AaaCrossReferenceModel): - class Unresolved: - def __init__( - self, - variants: list[parser.EnumVariant], - parsed_params: list[parser.Identifier], - ) -> None: - self.parsed_variants = { - variant.name.value: variant.get_data() for variant in variants - } - - self.parsed_params = parsed_params - - # This can't fail, an enum needs to have at least one variant - self.zero_variant = variants[0].name.value - - class Resolved: - def __init__( - self, - type_params: dict[str, Struct], - variants: dict[str, list[VariableType | FunctionPointer]], - zero_variant: str, - ) -> None: - self.type_params = type_params - - if variants and zero_variant not in variants: - raise ValueError("zero value not in variants") - - self.variants = variants - self.zero_variant = zero_variant - - def __init__( - self, - position: Position, - name: str, - params: list[parser.Identifier], - variants: list[parser.EnumVariant], - ) -> None: - self.state: Enum.Resolved | Enum.Unresolved = Enum.Unresolved(variants, params) - self.param_count = len(params) - self.name = name - super().__init__(position) - - @classmethod - def from_parsed_enum(cls, enum: parser.Enum) -> Enum: - return Enum( - enum.position, enum.get_name(), enum.get_params(), enum.get_variants() - ) - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, Enum): - raise TypeError - - return self.name == other.name and self.position == other.position - - @property - def variants(self) -> dict[str, list[VariableType | FunctionPointer]]: - assert isinstance(self.state, Enum.Resolved) - return self.state.variants - - @property - def zero_variant(self) -> str: - return self.state.zero_variant - - def get_unresolved(self) -> Enum.Unresolved: - assert isinstance(self.state, Enum.Unresolved) - return self.state - - def get_resolved(self) -> Enum.Resolved: - assert isinstance(self.state, Enum.Resolved) - return self.state - - def resolve( - self, - type_params: dict[str, Struct], - variants: dict[str, list[VariableType | FunctionPointer]], - ) -> None: - assert isinstance(self.state, Enum.Unresolved) - self.state = Enum.Resolved(type_params, variants, self.state.zero_variant) - - def param_dict( - self, var_type: VariableType - ) -> dict[str, VariableType | FunctionPointer]: - struct_type_placeholders = [ - enum.name - for enum in sorted( - self.get_resolved().type_params.values(), key=lambda s: s.position - ) - ] - - return dict(zip(struct_type_placeholders, var_type.params, strict=True)) - - def is_resolved(self) -> bool: - return isinstance(self.state, Enum.Resolved) - - def identify(self) -> tuple[Path, str]: - return (self.position.file, self.name) - - -class VariableType(AaaCrossReferenceModel): - def __init__( - self, - type: Struct | Enum, - params: list[VariableType | FunctionPointer], - is_placeholder: bool, - position: Position, - is_const: bool, - ) -> None: - self.type = type - self.params = params - self.is_placeholder = is_placeholder - self.name = self.type.name - self.is_const = is_const - super().__init__(position) - - def __repr__(self) -> str: # pragma: nocover - output = self.name - - if self.params: - output += "[" - output += ", ".join(repr(param) for param in self.params) - output += "]" - - if self.is_const: - return f"(const {output})" - - return output - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, VariableType): # pragma: nocover - raise TypeError - - return ( - self.type == other.type - and self.params == other.params - and self.is_const == other.is_const - ) - - -class FunctionPointer(AaaCrossReferenceModel): - def __init__( - self, - position: Position, - argument_types: list[VariableType | FunctionPointer], - return_types: list[VariableType | FunctionPointer] | Never, - ) -> None: - self.argument_types = argument_types - self.return_types = return_types - super().__init__(position) - - def __repr__(self) -> str: - args = ", ".join(repr(arg) for arg in self.argument_types) - - if isinstance(self.return_types, Never): - returns = "never" - else: - returns = ", ".join(repr(return_) for return_ in self.return_types) - - return f"fn[{args}][{returns}]" - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, FunctionPointer): - raise TypeError - - return ( - self.argument_types == other.argument_types - and self.return_types == other.return_types - ) - - -class Never: - """ - Indicator that a FunctionBodyItem never returns. - Examples for which this is useful: return, continue, break, exit - """ - - def __eq__(self, other: Any) -> bool: - if not isinstance(other, Never): - raise TypeError - - return True - - -class IntegerLiteral(AaaCrossReferenceModel): - def __init__(self, parsed: parser.Integer) -> None: - self.value = parsed.value - super().__init__(parsed.position) - - -class StringLiteral(AaaCrossReferenceModel): - def __init__(self, parsed: parser.String) -> None: - self.value = parsed.value - super().__init__(parsed.position) - - -class CharacterLiteral(AaaCrossReferenceModel): - def __init__(self, parsed: parser.Char) -> None: - self.value = parsed.value - super().__init__(parsed.position) - - -class BooleanLiteral(AaaCrossReferenceModel): - def __init__(self, parsed: parser.Boolean) -> None: - self.value = parsed.value - super().__init__(parsed.position) - - -class WhileLoop(AaaCrossReferenceModel): - def __init__( - self, - condition: FunctionBody, - body: FunctionBody, - parsed: parser.WhileLoop, - ) -> None: - self.condition = condition - self.body = body - super().__init__(parsed.position) - - -class CallVariable(AaaCrossReferenceModel): - def __init__(self, name: str, has_type_params: bool, position: Position) -> None: - self.has_type_params = has_type_params - self.name = name - super().__init__(position) - - -class CallFunction(AaaCrossReferenceModel): - def __init__( - self, - function: Function, - type_params: list[VariableType | FunctionPointer], - position: Position, - ) -> None: - self.function = function - self.type_params = type_params - super().__init__(position) - - -class CallEnumConstructor(AaaCrossReferenceModel): - def __init__( - self, - enum_ctor: EnumConstructor, - enum_var_type: VariableType, - position: Position, - ) -> None: - self.enum_var_type = enum_var_type - self.enum_ctor = enum_ctor - super().__init__(position) - - -class CallType(AaaCrossReferenceModel): - def __init__(self, var_type: VariableType) -> None: - self.var_type = var_type - super().__init__(var_type.position) - - -class Branch(AaaCrossReferenceModel): - def __init__( - self, - condition: FunctionBody, - if_body: FunctionBody, - else_body: FunctionBody | None, - parsed: parser.Branch, - ) -> None: - self.condition = condition - self.if_body = if_body - self.else_body = else_body - super().__init__(parsed.position) - - -class StructFieldQuery(AaaCrossReferenceModel): - def __init__(self, parsed: parser.StructFieldQuery) -> None: - self.field_name = parsed.field_name - self.operator_position = parsed.operator_position - super().__init__(parsed.position) - - -class StructFieldUpdate(AaaCrossReferenceModel): - def __init__( - self, parsed: parser.StructFieldUpdate, new_value_expr: FunctionBody - ) -> None: - self.field_name = parsed.field_name - self.new_value_expr = new_value_expr - self.operator_position = parsed.operator_position - super().__init__(parsed.position) - - -class ForeachLoop(AaaCrossReferenceModel): - def __init__(self, parsed: parser.ForeachLoop, body: FunctionBody) -> None: - self.body = body - super().__init__(parsed.position) - - -class Variable(AaaCrossReferenceModel): - def __init__(self, parsed: parser.Identifier, is_func_arg: bool) -> None: - self.name = parsed.value - self.is_func_arg = is_func_arg - super().__init__(parsed.position) - - -class Assignment(AaaCrossReferenceModel): - def __init__( - self, - parsed: parser.Assignment, - variables: list[Variable], - body: FunctionBody, - ) -> None: - self.variables = variables - self.body = body - super().__init__(parsed.position) - - -class UseBlock(AaaCrossReferenceModel): - def __init__( - self, - parsed: parser.UseBlock, - variables: list[Variable], - body: FunctionBody, - ) -> None: - self.variables = variables - self.body = body - super().__init__(parsed.position) - - -class MatchBlock(AaaCrossReferenceModel): - def __init__( - self, position: Position, blocks: list[CaseBlock | DefaultBlock] - ) -> None: - self.blocks = blocks - super().__init__(position) - - -class CaseBlock(AaaCrossReferenceModel): # NOTE: This is NOT a FunctionBodyItem - def __init__( - self, - position: Position, - enum_type: Enum, - variant_name: str, - variables: list[Variable], - body: FunctionBody, - ) -> None: - self.enum_type = enum_type - self.variant_name = variant_name - self.variables = variables - self.body = body - super().__init__(position) - - -class DefaultBlock(AaaCrossReferenceModel): # NOTE: This is NOT a FunctionBodyItem - def __init__(self, position: Position, body: FunctionBody) -> None: - self.body = body - super().__init__(position) - - -class Return(AaaCrossReferenceModel): - def __init__(self, parsed: parser.Return) -> None: - super().__init__(parsed.position) - - -class GetFunctionPointer(AaaCrossReferenceModel): - def __init__(self, position: Position, target: Function | EnumConstructor) -> None: - self.target = target - super().__init__(position) - - -class CallFunctionByPointer(AaaCrossReferenceModel): - ... - - -Identifiable = ( - EnumConstructor - | Enum - | Function - | ImplicitEnumConstructorImport - | ImplicitFunctionImport - | Import - | Struct -) - - -FunctionBodyItem = ( - Assignment - | BooleanLiteral - | Branch - | CallEnumConstructor - | CallFunction - | CallFunctionByPointer - | CallType - | CallVariable - | CharacterLiteral - | ForeachLoop - | FunctionPointer - | GetFunctionPointer - | IntegerLiteral - | MatchBlock - | Return - | StringLiteral - | StructFieldQuery - | StructFieldUpdate - | UseBlock - | WhileLoop -) - -IdentifiablesDict = dict[tuple[Path, str], Identifiable] - - -class CrossReferencerOutput(AaaModel): - def __init__( - self, - structs: dict[tuple[Path, str], Struct], - enums: dict[tuple[Path, str], Enum], - functions: dict[tuple[Path, str], Function], - imports: dict[tuple[Path, str], Import], - builtins_path: Path, - entrypoint: Path, - ) -> None: - for struct in structs.values(): - assert struct.is_resolved() - - for enum in enums.values(): - assert enum.is_resolved() - - for function in functions.values(): - assert function.is_resolved() - - for import_ in imports.values(): - assert import_.is_resolved() - - self.structs = structs - self.enums = enums - self.functions = functions - self.imports = imports - self.builtins_path = builtins_path - self.entrypoint = entrypoint diff --git a/python/aaa/parser/__init__.py b/python/aaa/parser/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/aaa/parser/exceptions.py b/python/aaa/parser/exceptions.py deleted file mode 100644 index eeb01e53..00000000 --- a/python/aaa/parser/exceptions.py +++ /dev/null @@ -1,21 +0,0 @@ -from pathlib import Path - -from basil.exceptions import ParseError, TokenizerException - -from aaa import AaaException - - -class AaaParserBaseException(AaaException): - def __init__(self, child: ParseError | TokenizerException): - self.child = child - - def __str__(self) -> str: - return str(self.child) - - -class FileReadError(AaaParserBaseException): - def __init__(self, file: Path) -> None: - self.file = file - - def __str__(self) -> str: - return f"{self.file}: Could not read file. It may not exist." diff --git a/python/aaa/parser/models.py b/python/aaa/parser/models.py deleted file mode 100644 index 391d7389..00000000 --- a/python/aaa/parser/models.py +++ /dev/null @@ -1,1358 +0,0 @@ -from __future__ import annotations - -import os -from pathlib import Path - -from basil.models import Position, Token - -from aaa import AaaModel - - -class AaaParseModel(AaaModel): - def __init__(self, position: Position) -> None: - self.position = position - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> AaaParseModel: - raise NotImplementedError(f"for {cls.__name__}") - - -class Integer(AaaParseModel): - def __init__(self, position: Position, value: int) -> None: - self.value = value - super().__init__(position) - - -class String(AaaParseModel): - def __init__(self, position: Position, value: str) -> None: - self.value = value - super().__init__(position) - - -class Boolean(AaaParseModel): - def __init__(self, position: Position, value: bool) -> None: - self.value = value - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> Boolean: - assert len(children) == 1 - child = children[0] - assert isinstance(child, Token) - return Boolean(child.position, child.value == "true") - - -class Char(AaaParseModel): - def __init__(self, position: Position, value: str) -> None: - self.value = value - super().__init__(position) - - -class WhileLoop(AaaParseModel): - def __init__( - self, - position: Position, - condition: FunctionBody, - body: FunctionBodyBlock, - ) -> None: - self.condition = condition - self.body = body - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> WhileLoop: - assert len(children) == 3 - while_token, condition, body_block = children - assert isinstance(while_token, Token) - assert isinstance(condition, FunctionBody) - assert isinstance(body_block, FunctionBodyBlock) - - return WhileLoop(while_token.position, condition, body_block) - - -class Identifier(AaaParseModel): - def __init__(self, position: Position, name: str) -> None: - self.value = name - super().__init__(position) - - -class Branch(AaaParseModel): - def __init__( - self, - position: Position, - condition: FunctionBody, - if_body_block: FunctionBodyBlock, - else_body_block: FunctionBodyBlock | None, - ) -> None: - self.condition = condition - self.if_body_block = if_body_block - self.else_body_block = else_body_block - super().__init__(position) - - def get_if_body(self) -> FunctionBody: - return self.if_body_block.value - - def get_else_body(self) -> FunctionBody | None: - block = self.else_body_block - if block: - return block.value - return None - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> Branch: - assert len(children) in [3, 5] - - if_token, condition, if_body_block = children[:3] - assert isinstance(if_token, Token) - assert isinstance(condition, FunctionBody) - assert isinstance(if_body_block, FunctionBodyBlock) - - if len(children) == 5: - else_body_block = children[4] - assert isinstance(else_body_block, FunctionBodyBlock) - else: - else_body_block = None - - return Branch(if_token.position, condition, if_body_block, else_body_block) - - -class FunctionBody(AaaParseModel): - def __init__(self, position: Position, items: list[FunctionBodyItem]) -> None: - self.items = [item.value for item in items] - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FunctionBody: - items: list[FunctionBodyItem] = [] - - for child in children: - assert isinstance(child, FunctionBodyItem) - items.append(child) - - return FunctionBody(children[0].position, items) - - -class StructFieldQuery(AaaParseModel): - def __init__(self, field_name: String, operator_position: Position) -> None: - self.field_name = field_name - self.operator_position = operator_position - super().__init__(field_name.position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> StructFieldQuery: - assert len(children) == 2 - field_name, get_field_token = children - assert isinstance(field_name, String) - assert isinstance(get_field_token, Token) - - return StructFieldQuery(field_name, get_field_token.position) - - -class StructFieldUpdate(AaaParseModel): - def __init__( - self, - field_name: String, - new_value_block: FunctionBodyBlock, - operator_position: Position, - ) -> None: - self.field_name = field_name - self.new_value_expr = new_value_block - self.operator_position = operator_position - super().__init__(field_name.position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> StructFieldUpdate: - assert len(children) == 3 - field_name, new_value_block, set_field_token = children - assert isinstance(field_name, String) - assert isinstance(new_value_block, FunctionBodyBlock) - assert isinstance(set_field_token, Token) - - return StructFieldUpdate(field_name, new_value_block, set_field_token.position) - - -class GetFunctionPointer(AaaParseModel): - def __init__(self, position: Position, function_name: String): - self.function_name = function_name - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> GetFunctionPointer: - function_name = children[0] - assert isinstance(function_name, String) - - return GetFunctionPointer(function_name.position, function_name) - - -class Return(AaaParseModel): - ... - - -class Call(AaaParseModel): - ... - - -class Argument(AaaParseModel): - def __init__( - self, identifier: Identifier, type: TypeOrFunctionPointerLiteral - ) -> None: - self.identifier = identifier - self.type = type - super().__init__(identifier.position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> Argument: - assert len(children) == 3 - identifier, _, type_or_func_ptr_literal = children - - assert isinstance(identifier, Identifier) - assert isinstance(type_or_func_ptr_literal, TypeOrFunctionPointerLiteral) - - return Argument(identifier, type_or_func_ptr_literal) - - -class Function(AaaParseModel): - def __init__( - self, - is_builtin: bool, - declaration: FunctionDeclaration, - body_block: FunctionBodyBlock | None, - ) -> None: - self.is_builtin = is_builtin - self.declaration = declaration - self.body_block = body_block - super().__init__(declaration.position) - - def is_test(self) -> bool: # pragma: nocover - return not self.get_type_name() and self.get_func_name().startswith("test_") - - def get_params(self) -> list[Identifier]: - return self.declaration.name.get_params() - - def get_func_name(self) -> str: - return self.declaration.name.get_func_name() - - def get_type_name(self) -> str: - return self.declaration.name.get_type_name() - - def get_name(self) -> str: - type_name = self.get_type_name() - func_name = self.get_func_name() - if type_name: - return f"{type_name}:{func_name}" - return func_name - - def get_end_position(self) -> Position | None: - if not self.body_block: - return None - return self.body_block.end_token_position - - def get_body(self) -> FunctionBody | None: - if not self.body_block: - return None - return self.body_block.value - - def get_arguments(self) -> list[Argument]: - arguments = self.declaration.arguments - if not arguments: - return [] - return arguments.value - - def get_return_types( - self, - ) -> list[TypeLiteral | FunctionPointerTypeLiteral] | Never: - return_types = self.declaration.return_types - if not return_types: - return [] - if isinstance(return_types.value, Never): - return return_types.value - return [item.literal for item in return_types.value] - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> Function: - assert len(children) == 2 - - if isinstance(children[0], Token): - assert children[0].type == "builtin" - - declaration = children[1] - assert isinstance(declaration, FunctionDeclaration) - return Function(True, declaration, None) - - declaration = children[0] - assert isinstance(declaration, FunctionDeclaration) - - body_block = children[1] - assert isinstance(body_block, FunctionBodyBlock) - - return Function(False, declaration, body_block) - - -class ImportItem(AaaParseModel): - def __init__(self, original: Identifier, imported: Identifier) -> None: - self.original = original - self.imported = imported - super().__init__(original.position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> ImportItem: - original = children[0] - assert isinstance(original, Identifier) - - if len(children) == 3: - imported = children[2] - assert isinstance(imported, Identifier) - else: - imported = original - - return ImportItem(original, imported) - - -class ImportItems(AaaParseModel): - def __init__(self, items: list[ImportItem]) -> None: - assert len(items) >= 1 - - self.value = items - super().__init__(items[0].position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> ImportItems: - items: list[ImportItem] = [] - - for child in children: - if isinstance(child, ImportItem): - items.append(child) - elif isinstance(child, Token): - pass # ignore token - else: - raise NotImplementedError - - return ImportItems(items) - - -class Import(AaaParseModel): - def __init__( - self, position: Position, source: String, imported_items: ImportItems - ) -> None: - self.source = source - self.imported_items = imported_items - super().__init__(position) - - def get_source_file(self) -> Path: - source_path = Path(self.source.value) - - if self.source.value.endswith(".aaa"): - if source_path.is_absolute(): - return source_path - return (self.position.file.parent / source_path).resolve() - else: - return self.position.file.parent / ( - self.source.value.replace(".", os.sep) + ".aaa" - ) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> Import: - assert len(children) == 4 - from_token, source, _, import_items = children - assert isinstance(from_token, Token) - assert isinstance(source, String) - assert isinstance(import_items, ImportItems) - - return Import(from_token.position, source, import_items) - - -class Struct(AaaParseModel): - def __init__( - self, - is_builtin: bool, - declaration: StructDeclaration, - fields: StructFields | None, - ) -> None: - self.is_builtin = is_builtin - self.declaration = declaration - self.fields = fields - super().__init__(declaration.position) - - def get_name(self) -> str: - return self.declaration.flat_type_literal.identifier.value - - def get_params(self) -> list[Identifier]: - return self.declaration.flat_type_literal.params - - def get_fields( - self, - ) -> dict[str, TypeLiteral | FunctionPointerTypeLiteral] | None: - if self.is_builtin: - return None - - if not self.fields: - return {} - - return {field.name.value: field.type.literal for field in self.fields.value} - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> Struct: - if isinstance(children[0], Token): - assert len(children) == 2 - - assert children[0].type == "builtin" - - declaration = children[1] - assert isinstance(declaration, StructDeclaration) - - return Struct(True, declaration, None) - - declaration = children[0] - assert isinstance(declaration, StructDeclaration) - - fields: StructFields | None = None - if isinstance(children[2], StructFields): - fields = children[2] - - return Struct(False, declaration, fields) - - -class StructDeclaration(AaaParseModel): - def __init__(self, position: Position, flat_type_literal: FlatTypeLiteral) -> None: - self.flat_type_literal = flat_type_literal - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> StructDeclaration: - assert len(children) == 2 - - struct_token, flat_type_literal = children - assert isinstance(struct_token, Token) - assert isinstance(flat_type_literal, FlatTypeLiteral) - - return StructDeclaration(struct_token.position, flat_type_literal) - - -class SourceFile(AaaParseModel): - def __init__( - self, - position: Position, - functions: list[Function], - imports: list[Import], - structs: list[Struct], - enums: list[Enum], - ) -> None: - self.functions = functions - self.imports = imports - self.structs = structs - self.enums = enums - super().__init__(position) - - def get_dependencies(self) -> set[Path]: - return {import_.get_source_file() for import_ in self.imports} - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> SourceFile: - if children: - file = children[0].position.file - else: - file = Path("/dev/unknown") - - position = Position(file, 1, 1) - functions: list[Function] = [] - imports: list[Import] = [] - structs: list[Struct] = [] - enums: list[Enum] = [] - - for child in children: - if isinstance(child, Function): - functions.append(child) - elif isinstance(child, Import): - imports.append(child) - elif isinstance(child, Struct): - structs.append(child) - elif isinstance(child, Enum): - enums.append(child) - else: - raise NotImplementedError - - return SourceFile(position, functions, imports, structs, enums) - - -class EnumDeclaration(AaaParseModel): - def __init__(self, position: Position, flat_type_literal: FlatTypeLiteral) -> None: - self.flat_type_literal = flat_type_literal - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> EnumDeclaration: - assert len(children) == 2 - enum_token, flat_type_literal = children - - assert isinstance(enum_token, Token) - assert isinstance(flat_type_literal, FlatTypeLiteral) - return EnumDeclaration(enum_token.position, flat_type_literal) - - -class FlatTypeParams(AaaParseModel): - def __init__(self, position: Position, type_params: list[Identifier]) -> None: - self.value = type_params - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FlatTypeParams: - sq_start_token = children[0] - type_params: list[Identifier] = [] - - for child in children[1:]: - if isinstance(child, Identifier): - type_params.append(child) - elif isinstance(child, Token): - pass # Ignore sq brackets and comma - else: - raise NotImplementedError # Unexpected other model - - return FlatTypeParams(sq_start_token.position, type_params) - - -class TypeLiteral(AaaParseModel): - def __init__( - self, - position: Position, - identifier: Identifier, - params: list[TypeOrFunctionPointerLiteral], - const: bool, - ) -> None: - self.identifier = identifier - self.params = params - self.const = const - super().__init__(position) - - def get_params(self) -> list[TypeLiteral | FunctionPointerTypeLiteral]: - return [item.literal for item in self.params] - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> TypeLiteral: - assert len(children) <= 3 - - const = False - identifier: Identifier | None = None - params: list[TypeOrFunctionPointerLiteral] = [] - - for child in children: - if isinstance(child, Token): - if child.type == "const": - const = True - else: - raise NotImplementedError - - elif isinstance(child, Identifier): - identifier = child - - elif isinstance(child, TypeParams): - params = child.value - - assert identifier # required by parser - - return TypeLiteral(children[0].position, identifier, params, const) - - -class StructField(AaaParseModel): - def __init__(self, name: Identifier, type: TypeOrFunctionPointerLiteral): - self.name = name - self.type = type - super().__init__(name.position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> StructField: - assert len(children) == 3 - name, _, type = children - assert isinstance(name, Identifier) - assert isinstance(type, TypeOrFunctionPointerLiteral) - - return StructField(name, type) - - -class StructFields(AaaParseModel): - def __init__(self, fields: list[StructField]) -> None: - assert len(fields) >= 1 - - self.value = fields - super().__init__(fields[0].position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> StructFields: - fields: list[StructField] = [] - - for child in children: - if isinstance(child, StructField): - fields.append(child) - elif isinstance(child, Token): - pass # ignore commas - else: - raise NotImplementedError - - return StructFields(fields) - - -class FlatTypeLiteral(AaaParseModel): - def __init__( - self, - position: Position, - identifier: Identifier, - params: list[Identifier], - ) -> None: - self.identifier = identifier - self.params = params - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FlatTypeLiteral: - assert len(children) <= 2 - - identifier = children[0] - assert isinstance(identifier, Identifier) - - params: list[Identifier] = [] - if len(children) == 2: - flat_type_params = children[1] - assert isinstance(flat_type_params, FlatTypeParams) - params = flat_type_params.value - - return FlatTypeLiteral(identifier.position, identifier, params) - - -class FunctionPointerTypeLiteral(AaaParseModel): - def __init__( - self, - position: Position, - argument_types: CommaSeparatedTypeList | None, - return_types: CommaSeparatedTypeList | Never | None, - ) -> None: - self.argument_types = argument_types - self.return_types = return_types - super().__init__(position) - - def get_arguments(self) -> list[TypeLiteral | FunctionPointerTypeLiteral]: - if self.argument_types is None: - return [] - return [item.literal for item in self.argument_types.value] - - def get_return_types( - self, - ) -> list[TypeLiteral | FunctionPointerTypeLiteral] | Never: - if self.return_types is None: - return [] - if isinstance(self.return_types, Never): - return self.return_types - return [item.literal for item in self.return_types.value] - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FunctionPointerTypeLiteral: - fn_token = children[0] - assert isinstance(fn_token, Token) - - if isinstance(children[2], CommaSeparatedTypeList): - arg_list = children[2] - else: - arg_list = None - - return_type_list: CommaSeparatedTypeList | Never | None = None - - for child in children[4:]: - if isinstance(child, Token) and child.value == "never": - return_type_list = Never(child.position) - break - - if isinstance(child, CommaSeparatedTypeList): - return_type_list = child - break - - return FunctionPointerTypeLiteral(fn_token.position, arg_list, return_type_list) - - -class CommaSeparatedTypeList(AaaParseModel): - def __init__(self, literals: list[TypeOrFunctionPointerLiteral]) -> None: - assert len(literals) >= 1 - - self.value = literals - super().__init__(literals[0].position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> CommaSeparatedTypeList: - literals: list[TypeOrFunctionPointerLiteral] = [] - - for child in children: - if isinstance(child, TypeOrFunctionPointerLiteral): - literals.append(child) - elif isinstance(child, Token): - pass # ignore commas - else: - raise NotImplementedError - - return CommaSeparatedTypeList(literals) - - -class FunctionName(AaaParseModel): - def __init__(self, name: FreeFunctionName | MemberFunctionName) -> None: - self.name = name - super().__init__(name.position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FunctionName: - name = children[0] - assert isinstance(name, FreeFunctionName | MemberFunctionName) - - return FunctionName(name) - - def get_params(self) -> list[Identifier]: - return self.name.get_params() - - def get_func_name(self) -> str: - return self.name.get_func_name() - - def get_type_name(self) -> str: - return self.name.get_type_name() - - -class FunctionDeclaration(AaaParseModel): - def __init__( - self, - position: Position, - name: FunctionName, - arguments: Arguments | None, - return_types: ReturnTypes | None, - ) -> None: - self.name = name - self.arguments = arguments - self.return_types = return_types - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FunctionDeclaration: - fn_token = children[0] - assert isinstance(fn_token, Token) - - name = children[1] - assert isinstance(name, FunctionName) - - arguments: Arguments | None = None - return_types: ReturnTypes | None = None - - for child in children[2:]: - if isinstance(child, Arguments): - arguments = child - elif isinstance(child, ReturnTypes): - return_types = child - elif isinstance(child, Token | Return): - pass - else: - raise NotImplementedError # Unexpected child - - return FunctionDeclaration(fn_token.position, name, arguments, return_types) - - -class Arguments(AaaParseModel): - def __init__(self, position: Position, arguments: list[Argument]) -> None: - assert len(arguments) >= 1 - - self.value = arguments - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> Arguments: - arg_token = children[0] - assert isinstance(arg_token, Token) - - arguments: list[Argument] = [] - - for child in children[1:]: - if isinstance(child, Argument): - arguments.append(child) - elif isinstance(child, Token): - pass # ignore commas - else: - raise NotImplementedError - - return Arguments(arg_token.position, arguments) - - -class ReturnTypes(AaaParseModel): - def __init__( - self, - position: Position, - return_types: list[TypeOrFunctionPointerLiteral] | Never, - ) -> None: - self.value = return_types - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> ReturnTypes: - return_token = children[0] - assert isinstance(return_token, Return) - - return_types: list[TypeOrFunctionPointerLiteral] | Never - - if isinstance(children[1], Token) and children[1].value == "never": - return_types = Never(children[1].position) - - else: - return_types = [] - - for child in children[1:]: - if isinstance(child, TypeOrFunctionPointerLiteral): - return_types.append(child) - elif isinstance(child, Token): - pass # Ignore commas - else: - raise NotImplementedError - - return ReturnTypes(return_token.position, return_types) - - -class TypeOrFunctionPointerLiteral(AaaParseModel): - def __init__(self, literal: TypeLiteral | FunctionPointerTypeLiteral) -> None: - self.literal = literal - super().__init__(literal.position) - - @classmethod - def load( - cls, children: list[AaaParseModel | Token] - ) -> TypeOrFunctionPointerLiteral: - assert len(children) == 1 - child = children[0] - - assert isinstance(child, TypeLiteral | FunctionPointerTypeLiteral) - return TypeOrFunctionPointerLiteral(child) - - -class FreeFunctionCall(AaaParseModel): - def __init__(self, func_name: Identifier, params: TypeParams | None) -> None: - self.func_name = func_name - self.params = params - super().__init__(func_name.position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FreeFunctionCall: - func_name = children[0] - assert isinstance(func_name, Identifier) - - if len(children) > 1: - params = children[1] - assert isinstance(params, TypeParams) - else: - params = None - - return FreeFunctionCall(func_name, params) - - def name(self) -> str: - return self.func_name.value - - def get_type_params( - self, - ) -> list[TypeLiteral | FunctionPointerTypeLiteral]: - if not self.params: - return [] - return [item.literal for item in self.params.value] - - -class MemberFunctionCall(AaaParseModel): - def __init__( - self, - type_name: Identifier, - func_name: Identifier, - params: TypeParams | None, - ) -> None: - self.type_name = type_name - self.func_name = func_name - self.params = params - super().__init__(func_name.position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> MemberFunctionCall: - type_name = children[0] - assert isinstance(type_name, Identifier) - - if isinstance(children[1], Token): - assert children[1].type == "colon" - - func_name = children[2] - assert isinstance(func_name, Identifier) - - if len(children) > 3: - params = children[3] - assert isinstance(params, TypeParams) - else: - params = None - else: - params = children[1] - assert isinstance(params, TypeParams) - - func_name = children[3] - assert isinstance(func_name, Identifier) - - return MemberFunctionCall(type_name, func_name, params) - - def name(self) -> str: - return f"{self.type_name.value}:{self.func_name.value}" - - def get_type_params( - self, - ) -> list[TypeLiteral | FunctionPointerTypeLiteral]: - if not self.params: - return [] - return [item.literal for item in self.params.value] - - -class FreeFunctionName(AaaParseModel): - def __init__(self, func_name: Identifier, params: FlatTypeParams | None) -> None: - self.func_name = func_name - self.params = params - super().__init__(func_name.position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FreeFunctionName: - func_name = children[0] - assert isinstance(func_name, Identifier) - - if len(children) > 1: - params = children[1] - assert isinstance(params, FlatTypeParams) - else: - params = None - - return FreeFunctionName(func_name, params) - - def get_params(self) -> list[Identifier]: - if not self.params: - return [] - return self.params.value - - def get_func_name(self) -> str: - return self.func_name.value - - def get_type_name(self) -> str: - return "" - - -class MemberFunctionName(AaaParseModel): - def __init__( - self, - type_name: Identifier, - params: FlatTypeParams | None, - func_name: Identifier, - ) -> None: - self.type_name = type_name - self.params = params - self.func_name = func_name - super().__init__(type_name.position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> MemberFunctionName: - type_name = children[0] - assert isinstance(type_name, Identifier) - - if isinstance(children[1], FlatTypeParams): - params = children[1] - else: - params = None - - func_name = children[-1] - assert isinstance(func_name, Identifier) - - return MemberFunctionName(type_name, params, func_name) - - def get_params(self) -> list[Identifier]: - if not self.params: - return [] - return self.params.value - - def get_func_name(self) -> str: - return self.func_name.value - - def get_type_name(self) -> str: - return self.type_name.value - - -class FunctionCall(AaaParseModel): - def __init__(self, call: FreeFunctionCall | MemberFunctionCall) -> None: - self.call = call - super().__init__(call.position) - - def name(self) -> str: - return self.call.name() - - def get_type_params( - self, - ) -> list[TypeLiteral | FunctionPointerTypeLiteral]: - return self.call.get_type_params() - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FunctionCall: - call = children[0] - assert isinstance(call, FreeFunctionCall | MemberFunctionCall) - - return FunctionCall(call) - - -class TypeParams(AaaParseModel): - def __init__(self, value: list[TypeOrFunctionPointerLiteral]) -> None: - assert len(value) >= 1 - - self.value = value - super().__init__(value[0].position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> TypeParams: - type_params: list[TypeOrFunctionPointerLiteral] = [] - - for child in children: - if isinstance(child, TypeOrFunctionPointerLiteral): - type_params.append(child) - elif isinstance(child, Token): - pass # Ignore square brackets and commas - else: - raise NotImplementedError - - return TypeParams(type_params) - - -class Variables(AaaParseModel): - def __init__(self, value: list[Identifier]) -> None: - assert len(value) >= 1 - - self.value = value - super().__init__(value[0].position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> Variables: - variables: list[Identifier] = [] - - for child in children: - if isinstance(child, Identifier): - variables.append(child) - elif isinstance(child, Token): - pass # Ignore square brackets and commas - else: - raise NotImplementedError - - return Variables(variables) - - -class CaseLabel(AaaParseModel): - def __init__( - self, - position: Position, - enum_name: Identifier, - variant_name: Identifier, - variables: Variables | None, - ) -> None: - self.enum_name = enum_name - self.variant_name = variant_name - self.variables = variables - super().__init__(position) - - def get_variables(self) -> list[Identifier]: - if not self.variables: - return [] - return self.variables.value - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> CaseLabel: - enum_name, _, variant_name = children[:3] - assert isinstance(enum_name, Identifier) - assert isinstance(variant_name, Identifier) - - if len(children) > 3: - variables = children[4] - assert isinstance(variables, Variables) - else: - variables = None - - return CaseLabel(enum_name.position, enum_name, variant_name, variables) - - -class ForeachLoop(AaaParseModel): - def __init__(self, position: Position, body_block: FunctionBodyBlock) -> None: - self.body = body_block - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> ForeachLoop: - assert len(children) == 2 - foreach_token, body_block = children - assert isinstance(foreach_token, Token) - assert isinstance(body_block, FunctionBodyBlock) - - return ForeachLoop(foreach_token.position, body_block) - - -class UseBlock(AaaParseModel): - def __init__( - self, - position: Position, - variables: Variables, - body_block: FunctionBodyBlock, - ) -> None: - self.variables = variables - self.body_block = body_block - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> UseBlock: - assert len(children) == 3 - use_token, variables, body_block = children - assert isinstance(use_token, Token) - assert isinstance(variables, Variables) - assert isinstance(body_block, FunctionBodyBlock) - - return UseBlock(use_token.position, variables, body_block) - - -class Assignment(AaaParseModel): - def __init__( - self, - position: Position, - variables: Variables, - body_block: FunctionBodyBlock, - ) -> None: - self.variables = variables - self.body_block = body_block - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> Assignment: - assert len(children) == 3 - variables, _, body_block = children - assert isinstance(variables, Variables) - assert isinstance(body_block, FunctionBodyBlock) - - return Assignment(variables.position, variables, body_block) - - -class CaseBlock(AaaParseModel): - def __init__( - self, - position: Position, - label: CaseLabel, - body_block: FunctionBodyBlock, - ) -> None: - self.label = label - self.body = body_block - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> CaseBlock: - assert len(children) == 3 - case_token, case_label, body_block = children - assert isinstance(case_token, Token) - assert isinstance(case_label, CaseLabel) - assert isinstance(body_block, FunctionBodyBlock) - - return CaseBlock(case_token.position, case_label, body_block) - - -class DefaultBlock(AaaParseModel): - def __init__(self, position: Position, body_block: FunctionBodyBlock) -> None: - self.body_block = body_block - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> DefaultBlock: - assert len(children) == 2 - default_token, body_block = children - - assert isinstance(default_token, Token) - assert isinstance(body_block, FunctionBodyBlock) - - return DefaultBlock(default_token.position, body_block) - - -class MatchBlock(AaaParseModel): - def __init__( - self, position: Position, blocks: list[CaseBlock | DefaultBlock] - ) -> None: - self.blocks = blocks - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> MatchBlock: - blocks: list[CaseBlock | DefaultBlock] = [] - match_token = children[0] - assert isinstance(match_token, Token) - - for child in children[2:-1]: - assert isinstance(child, CaseBlock | DefaultBlock) - blocks.append(child) - - return MatchBlock(match_token.position, blocks) - - -class EnumVariant(AaaParseModel): - def __init__( - self, - name: Identifier, - associated_data: EnumVariantAssociatedData | None, - ) -> None: - self.name = name - self.associated_data = associated_data - super().__init__(name.position) - - def get_data(self) -> list[TypeLiteral | FunctionPointerTypeLiteral]: - if self.associated_data is None: - return [] - data = self.associated_data.value - if isinstance(data, TypeOrFunctionPointerLiteral): - return [data.literal] - return [item.literal for item in data.value] - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> EnumVariant: - name = children[0] - assert isinstance(name, Identifier) - - if len(children) == 3: - data = children[2] - assert isinstance(data, EnumVariantAssociatedData) - else: - data = None - - return EnumVariant(name, data) - - -class EnumVariants(AaaParseModel): - def __init__(self, variants: list[EnumVariant]) -> None: - assert len(variants) >= 1 - - self.value = variants - super().__init__(variants[0].position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> EnumVariants: - variants: list[EnumVariant] = [] - - for child in children: - if isinstance(child, EnumVariant): - variants.append(child) - elif isinstance(child, Token): - pass # ignore commas - else: - raise NotImplementedError - - return EnumVariants(variants) - - -class Enum(AaaParseModel): - def __init__(self, declaration: EnumDeclaration, variants: EnumVariants) -> None: - self.declaration = declaration - self.variants = variants - super().__init__(declaration.position) - - def get_variants(self) -> list[EnumVariant]: - return self.variants.value - - def get_name(self) -> str: - return self.declaration.flat_type_literal.identifier.value - - def get_params(self) -> list[Identifier]: - return self.declaration.flat_type_literal.params - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> Enum: - assert len(children) == 4 - declaration, _, variants, _ = children - - assert isinstance(declaration, EnumDeclaration) - assert isinstance(variants, EnumVariants) - - return Enum(declaration, variants) - - -class EnumVariantAssociatedData(AaaParseModel): - def __init__( - self, - position: Position, - data: TypeOrFunctionPointerLiteral | CommaSeparatedTypeList, - ) -> None: - self.value = data - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> EnumVariantAssociatedData: - position = children[0].position - - data: TypeOrFunctionPointerLiteral | CommaSeparatedTypeList - if isinstance(children[0], TypeOrFunctionPointerLiteral): - data = children[0] - else: - assert isinstance(children[1], CommaSeparatedTypeList) - data = children[1] - - return EnumVariantAssociatedData(position, data) - - -class Never(AaaParseModel): - ... - - -class FunctionBodyItem(AaaParseModel): - types = ( - Assignment - | Boolean - | Branch - | Call - | Char - | ForeachLoop - | FunctionCall - | FunctionPointerTypeLiteral - | GetFunctionPointer - | Integer - | MatchBlock - | Return - | String - | StructFieldQuery - | StructFieldUpdate - | UseBlock - | WhileLoop - ) - - def __init__(self, position: Position, value: types) -> None: - self.value = value - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FunctionBodyItem: - assert len(children) == 1 - child = children[0] - - assert isinstance(child, FunctionBodyItem.types.__args__) # type:ignore - return FunctionBodyItem(child.position, child) - - -class FunctionBodyBlock(AaaParseModel): - def __init__( - self, - position: Position, - body: FunctionBody, - end_token_position: Position, - ) -> None: - self.value = body - self.end_token_position = end_token_position - super().__init__(position) - - @classmethod - def load(cls, children: list[AaaParseModel | Token]) -> FunctionBodyBlock: - assert len(children) == 3 - start_token, body, end_token = children - assert isinstance(start_token, Token) - assert isinstance(body, FunctionBody) - assert isinstance(end_token, Token) - - return FunctionBodyBlock(start_token.position, body, end_token.position) - - -class ParserOutput(AaaModel): - def __init__( - self, - parsed: dict[Path, SourceFile], - entrypoint: Path, - builtins_path: Path, - ) -> None: - self.parsed = parsed - self.entrypoint = entrypoint - self.builtins_path = builtins_path diff --git a/python/aaa/parser/parser.py b/python/aaa/parser/parser.py deleted file mode 100644 index a0bef580..00000000 --- a/python/aaa/parser/parser.py +++ /dev/null @@ -1,320 +0,0 @@ -from pathlib import Path -from queue import Queue - -from basil.exceptions import ParseError, TokenizerException -from basil.file_parser import FileParser -from basil.models import Token - -from aaa import aaa_project_root, get_stdlib_path -from aaa.parser.exceptions import AaaParserBaseException, FileReadError -from aaa.parser.models import ( - AaaParseModel, - Argument, - Arguments, - Assignment, - Boolean, - Branch, - Call, - CaseBlock, - CaseLabel, - Char, - CommaSeparatedTypeList, - DefaultBlock, - Enum, - EnumDeclaration, - EnumVariant, - EnumVariantAssociatedData, - EnumVariants, - FlatTypeLiteral, - FlatTypeParams, - ForeachLoop, - FreeFunctionCall, - FreeFunctionName, - Function, - FunctionBody, - FunctionBodyBlock, - FunctionBodyItem, - FunctionCall, - FunctionDeclaration, - FunctionName, - FunctionPointerTypeLiteral, - GetFunctionPointer, - Identifier, - Import, - ImportItem, - ImportItems, - Integer, - MatchBlock, - MemberFunctionCall, - MemberFunctionName, - ParserOutput, - Return, - ReturnTypes, - SourceFile, - String, - Struct, - StructDeclaration, - StructField, - StructFieldQuery, - StructFields, - StructFieldUpdate, - TypeLiteral, - TypeOrFunctionPointerLiteral, - TypeParams, - UseBlock, - Variables, - WhileLoop, -) -from aaa.runner.exceptions import AaaTranslationException - -SYNTAX_JSON_PATH = aaa_project_root() / "syntax.json" - -NODE_TYPE_TO_MODEL: dict[str, type[AaaParseModel]] = { - "ARGUMENT": Argument, - "ARGUMENTS": Arguments, - "ASSIGNMENT": Assignment, - "BOOLEAN": Boolean, - "BRANCH": Branch, - "CASE_BLOCK": CaseBlock, - "CASE_LABEL": CaseLabel, - "COMMA_SEPARATED_TYPE_LIST": CommaSeparatedTypeList, - "DEFAULT_BLOCK": DefaultBlock, - "ENUM_DECLARATION": EnumDeclaration, - "ENUM_DEFINITION": Enum, - "ENUM_VARIANT_ASSOCIATED_DATA": EnumVariantAssociatedData, - "ENUM_VARIANT": EnumVariant, - "ENUM_VARIANTS": EnumVariants, - "FLAT_TYPE_LITERAL": FlatTypeLiteral, - "FLAT_TYPE_PARAMS": FlatTypeParams, - "FOREACH_LOOP": ForeachLoop, - "FREE_FUNCTION_CALL": FreeFunctionCall, - "FREE_FUNCTION_NAME": FreeFunctionName, - "FUNCTION_BODY_BLOCK": FunctionBodyBlock, - "FUNCTION_BODY_ITEM": FunctionBodyItem, - "FUNCTION_BODY": FunctionBody, - "FUNCTION_CALL": FunctionCall, - "FUNCTION_DECLARATION": FunctionDeclaration, - "FUNCTION_DEFINITION": Function, - "FUNCTION_NAME": FunctionName, - "FUNCTION_POINTER_TYPE_LITERAL": FunctionPointerTypeLiteral, - "GET_FUNCTION_POINTER": GetFunctionPointer, - "IMPORT_ITEM": ImportItem, - "IMPORT_ITEMS": ImportItems, - "IMPORT": Import, - "MATCH_BLOCK": MatchBlock, - "MEMBER_FUNCTION_CALL": MemberFunctionCall, - "MEMBER_FUNCTION_NAME": MemberFunctionName, - "RETURN_TYPES": ReturnTypes, - "SOURCE_FILE": SourceFile, - "STRUCT_DECLARATION": StructDeclaration, - "STRUCT_DEFINITION": Struct, - "STRUCT_FIELD_QUERY": StructFieldQuery, - "STRUCT_FIELD_UPDATE": StructFieldUpdate, - "STRUCT_FIELD": StructField, - "STRUCT_FIELDS": StructFields, - "TYPE_LITERAL": TypeLiteral, - "TYPE_OR_FUNCTION_POINTER_LITERAL": TypeOrFunctionPointerLiteral, - "TYPE_PARAMS": TypeParams, - "USE_BLOCK": UseBlock, - "VARIABLES": Variables, - "WHILE_LOOP": WhileLoop, -} - -UNTRANSFORMED_TOKEN_TYPES = { - "args", - "as", - "assign", - "builtin", - "case", - "colon", - "comma", - "const", - "default", - "else", - "end", - "enum", - "false", - "fn", - "foreach", - "from", - "get_field", - "if", - "import", - "match", - "never", - "set_field", - "sq_end", - "sq_start", - "start", - "struct", - "true", - "use", - "while", -} - -aaa_file_parser = FileParser(SYNTAX_JSON_PATH) - - -def transform_token(token: Token) -> AaaParseModel | Token: - if token.type in UNTRANSFORMED_TOKEN_TYPES: - return token - - if token.type == "identifier": - return Identifier(token.position, token.value) - - if token.type == "string": - return String(token.position, unescape_string(token.value[1:-1])) - - if token.type == "char": - return Char(token.position, unescape_string(token.value[1:-1])) - - if token.type == "integer": - return Integer(token.position, int(token.value)) - - if token.type == "return": - return Return(token.position) - - if token.type == "call": - return Call(token.position) - - raise NotImplementedError(f"for {token.type}") - - -def transform_node( - node_type: str, children: list[AaaParseModel | Token] -) -> AaaParseModel: - return NODE_TYPE_TO_MODEL[node_type].load(children) - - -def unescape_string(escaped: str) -> str: - simple_escape_sequences = { - '"': '"', - "'": "'", - "/": "/", - "\\": "\\", - "0": "\0", - "b": "\b", - "e": "\x1b", - "f": "\f", - "n": "\n", - "r": "\r", - "t": "\t", - } - - unescaped = "" - offset = 0 - - while offset < len(escaped): - backslash_offset = escaped.find("\\", offset) - - if backslash_offset == -1: - unescaped += escaped[offset:] - break - - unescaped += escaped[offset:backslash_offset] - - escape_determinant = escaped[backslash_offset + 1] - - if escape_determinant in simple_escape_sequences: - unescaped += simple_escape_sequences[escape_determinant] - offset = backslash_offset + 2 - continue - - if escape_determinant == "u": - unicode_hex = escaped[backslash_offset + 2 : backslash_offset + 6] - unicode_char = chr(int(unicode_hex, 16)) - unescaped += unicode_char - offset = backslash_offset + 6 - continue - - if escape_determinant == "U": - unicode_hex = escaped[backslash_offset + 2 : backslash_offset + 10] - unicode_char = chr(int(unicode_hex, 16)) - unescaped += unicode_char - offset = backslash_offset + 10 - continue - - # Unknown escape sequence - raise NotImplementedError - - return unescaped - - -class AaaParser: - def __init__(self, verbose: bool) -> None: - self.verbose = verbose - - self.file_parser = aaa_file_parser - self.builtins_path = get_stdlib_path() / "builtins.aaa" - - self.exceptions: list[AaaParserBaseException] = [] - self.parsed: dict[Path, SourceFile] = {} - - def run(self, entrypoint: Path, file_dict: dict[Path, str]) -> ParserOutput: - queue: Queue[Path] = Queue() - queue.put(self.builtins_path) - queue.put(entrypoint) - - while not queue.empty(): - file = queue.get() - - try: - source_file: AaaParseModel = self.parse_file(file, file_dict) - assert isinstance(source_file, SourceFile) - - except (TokenizerException, ParseError) as e: - self.exceptions.append(AaaParserBaseException(e)) - continue - - except AaaParserBaseException as e: - self.exceptions.append(e) - continue - - self.parsed[file] = source_file - - for dependency in source_file.get_dependencies(): - if dependency not in self.parsed: - queue.put(dependency) - - if self.exceptions: - raise AaaTranslationException(self.exceptions) - - return ParserOutput( - parsed=self.parsed, - builtins_path=self.builtins_path, - entrypoint=entrypoint, - ) - - def parse_file( - self, file: Path, file_dict: dict[Path, str] | None = None - ) -> SourceFile: - file_dict = file_dict or {} - - if file in file_dict: - text = file_dict[file] - else: - try: - text = file.read_text() - except OSError as e: - raise FileReadError(file) from e - - model = self.parse_text(text, self.file_parser.root_node_type, str(file)) - - assert isinstance(model, SourceFile) - return model - - def parse_text( - self, text: str, root_node_type: str, file_name: str | None = None - ) -> AaaParseModel: - return self.file_parser.parse_text_and_transform( - text, - file_name, - node_type=root_node_type, - verbose=self.verbose, - token_transformer=transform_token, - node_transformer=transform_node, - ) - - def tokenize_text(self, text: str) -> list[Token]: - # This is only supposed to be used for testing. - return self.file_parser.tokenize_text(text, filter_token_types=False) diff --git a/python/aaa/runner/__init__.py b/python/aaa/runner/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/aaa/runner/exceptions.py b/python/aaa/runner/exceptions.py deleted file mode 100644 index ae2a6145..00000000 --- a/python/aaa/runner/exceptions.py +++ /dev/null @@ -1,28 +0,0 @@ -from collections.abc import Sequence - -from aaa import AaaException - - -class RunnerBaseException(AaaException): - ... - - -class AaaTranslationException(RunnerBaseException): - """ - Indicates one or more errors happened while reading, parsingand type-checking Aaa - source. - """ - - def __init__(self, exceptions: Sequence[AaaException]) -> None: - self.exceptions = exceptions - - -class RustCompilerError(RunnerBaseException): - ... - - -class ExcecutableDidNotRun(RunnerBaseException): - """ - Indidates that excecutable was compiled but did not run. - The fact it did not run was intentional. This is here to satisfy the type-checker. - """ diff --git a/python/aaa/runner/run_tests.aaa b/python/aaa/runner/run_tests.aaa deleted file mode 100644 index fa5858a4..00000000 --- a/python/aaa/runner/run_tests.aaa +++ /dev/null @@ -1,53 +0,0 @@ -// [COMMENT]: this file is used as a template for the main file of the Aaa testing framework -// [IMPORTS] - -struct TestFunction { - function as fn[][], - name as str, -} - -fn make_test_function args function as fn[][], name as str return TestFunction { - TestFunction - dup "function" { function } ! - dup "name" { name } ! -} - -fn push_test_function args tests as vec[TestFunction], function as fn[][], name as str return vec[TestFunction] { - tests function name make_test_function vec:push - tests -} - -fn left_pad args string as str, length as int return str { - if string str:len length >= { - string return - } - - " " string str:append length left_pad -} - -fn TestFunction:format_name args test_function as TestFunction, offset as int, max as int return str { - "[" - offset repr max repr str:len left_pad str:append - "/" str:append - max repr str:append - "] " str:append - test_function "name" ? str:append -} - -fn main { - vec[TestFunction] - // [TEST FUNCTIONS] - use test_functions { - 1 test_functions vec:len - use offset, test_count { - test_functions - foreach { - use test_function { - test_function offset test_count TestFunction:format_name . "\n" . - test_function "function" ? call - } - offset <- { offset 1 + } - } - } - } -} diff --git a/python/aaa/runner/runner.py b/python/aaa/runner/runner.py deleted file mode 100644 index b01db413..00000000 --- a/python/aaa/runner/runner.py +++ /dev/null @@ -1,288 +0,0 @@ -import os -import subprocess -import sys -from collections.abc import Sequence -from pathlib import Path -from subprocess import CompletedProcess -from typing import Any - -from aaa import AaaException, create_output_folder -from aaa.cross_referencer.cross_referencer import CrossReferencer -from aaa.parser.models import SourceFile -from aaa.parser.parser import AaaParser -from aaa.runner.exceptions import ( - AaaTranslationException, - ExcecutableDidNotRun, - RustCompilerError, -) -from aaa.transpiler.transpiler import Transpiler -from aaa.type_checker.type_checker import TypeChecker - -CARGO_TOML_TEMPLATE = """ -[package] -name = "aaa-stdlib-user" -version = "0.1.0" -edition = "2021" - -[dependencies] -aaa-stdlib = {{ version = "0.1.0", path = "{stdlib_impl_path}" }} -regex = "1.8.4" -""" - -RUNNER_FILE_DICT_ROOT_PATH = Path("/aaa/runner/root") # This file should not exist - - -class Runner: - def __init__( - self, entrypoint: Path, file_dict: dict[Path, str] | None = None - ) -> None: - assert not RUNNER_FILE_DICT_ROOT_PATH.exists() - - self.file_dict: dict[Path, str] = {} - if file_dict: - assert not entrypoint.is_absolute() - assert entrypoint in file_dict - - for file, code in file_dict.items(): - assert not file.is_absolute() - self.file_dict[RUNNER_FILE_DICT_ROOT_PATH / file] = code - - self.entrypoint = RUNNER_FILE_DICT_ROOT_PATH / entrypoint - else: - self.entrypoint = Path(entrypoint).resolve() - - self.verbose = False - self.exceptions: Sequence[AaaException] = [] - self.parsed_files: dict[Path, SourceFile] = {} - self.verbose = False - - @staticmethod - def without_file(code: str) -> "Runner": - entry_point = Path("main.aaa") - files = {entry_point: code} - return Runner(entry_point, files) - - @staticmethod - def compile_command( - file_or_code: str, - verbose: bool, - binary_path: str, - runtime_type_checks: bool, - ) -> int: - if file_or_code.endswith(".aaa"): - runner = Runner(Path(file_or_code)) - else: - runner = Runner.without_file(file_or_code) - - runner.set_verbose(verbose) - - return runner.run( - compile=True, - binary_path=Path(binary_path).resolve(), - run=False, - runtime_type_checks=runtime_type_checks, - args=[], - ) - - @staticmethod - def run_command( - file_or_code: str, - verbose: bool, - runtime_type_checks: bool, - args: tuple[str], - ) -> int: - if file_or_code.endswith(".aaa"): - runner = Runner(Path(file_or_code)) - else: - runner = Runner.without_file(file_or_code) - - runner.set_verbose(verbose) - - return runner.run( - compile=True, - binary_path=None, - run=True, - runtime_type_checks=runtime_type_checks, - args=list(args), - ) - - def add_parsed_files(self, parsed_files: dict[Path, SourceFile]) -> None: - self.parsed_files.update(parsed_files) - - def set_verbose(self, verbose: bool) -> None: - self.verbose = verbose - - def _print_exceptions(self, runner_exception: AaaTranslationException) -> None: - for exception in runner_exception.exceptions: - print(exception, file=sys.stderr) - print(file=sys.stderr) - - print( - f"Found {len(runner_exception.exceptions)} error(s).", - file=sys.stderr, - ) - - def run( - self, - *, - compile: bool, - binary_path: Path | None, - run: bool, - runtime_type_checks: bool, - args: list[str], - **run_kwargs: Any, - ) -> int: - try: - return self._run_process( - compile=compile, - binary_path=binary_path, - run=run, - args=args, - runtime_type_checks=runtime_type_checks, - **run_kwargs, - ).returncode - except ExcecutableDidNotRun: - return 0 - except (AaaTranslationException, RustCompilerError, AaaException): - return 1 - - def _run_process( - self, - *, - compile: bool, - binary_path: Path | None, - run: bool, - runtime_type_checks: bool, - args: list[str], - **run_kwargs: Any, - ) -> CompletedProcess[bytes]: - transpiled = self.transpile(runtime_type_checks) - - if not compile: - raise ExcecutableDidNotRun - - compiled = transpiled.compile(binary_path, self.verbose) - - if not run: - raise ExcecutableDidNotRun - - return compiled.execute(args, **run_kwargs) - - def transpile(self, runtime_type_checks: bool) -> "Transpiled": - transpiler_root = create_output_folder() - - try: - parser = AaaParser(self.verbose) - parser_output = parser.run(self.entrypoint, self.file_dict) - cross_referencer_output = CrossReferencer(parser_output, self.verbose).run() - - type_checker = TypeChecker(cross_referencer_output, self.verbose) - type_checker_output = type_checker.run() - - transpiler = Transpiler( - cross_referencer_output=cross_referencer_output, - type_checker_output=type_checker_output, - transpiler_root=transpiler_root, - runtime_type_checks=runtime_type_checks, - verbose=self.verbose, - ) - - transpiler.run() - - except AaaTranslationException as e: - self.exceptions = e.exceptions - self._print_exceptions(e) - raise e - - except RustCompilerError as e: - raise e - - except AaaException as e: - self.exceptions = [e] - self._print_exceptions(AaaTranslationException([e])) - raise e - - return Transpiled(transpiler_root) - - -def compile_run( - entrypoint: Path, binary_path: Path | None = None, **run_kwargs: Any -) -> tuple[str, str, int]: - completed_process = Runner(entrypoint)._run_process( - compile=True, - binary_path=binary_path, - run=True, - args=[], - capture_output=True, - runtime_type_checks=True, - **run_kwargs, - ) - - return ( - completed_process.stdout.decode("utf-8"), - completed_process.stderr.decode("utf-8"), - completed_process.returncode, - ) - - -class Transpiled: - def __init__(self, transpiler_root: Path) -> None: - self.transpiler_root = transpiler_root - - def compile( - self, binary_path: Path | None = None, verbose: bool = False - ) -> "Compiled": - # Use shared target dir between executables, - # because every Aaa compilation would otherwise take 120 MB disk, - # due to Rust dependencies. - cargo_shared_target_dir = create_output_folder("") / "shared_target" - cargo_shared_target_dir.mkdir(exist_ok=True, parents=True) - - compiler_env = os.environ.copy() - compiler_env["CARGO_TARGET_DIR"] = str(cargo_shared_target_dir) - - cargo_toml = (self.transpiler_root / "Cargo.toml").resolve() - - stdlib_impl_path = (Path(__file__).parent / "../../../aaa-stdlib").resolve() - - cargo_toml.write_text( - CARGO_TOML_TEMPLATE.format(stdlib_impl_path=stdlib_impl_path) - ) - - command = [ - "cargo", - "build", - "--release", - "--quiet", - "--manifest-path", - str(cargo_toml), - ] - - completed_process = subprocess.run( - command, env=compiler_env, capture_output=True - ) - - exit_code = completed_process.returncode - - if verbose: - print(completed_process.stdout.decode("utf-8")) - print(completed_process.stderr.decode("utf-8"), file=sys.stderr) - - if exit_code != 0: - raise RustCompilerError() - - default_binary_path = cargo_shared_target_dir / "release/aaa-stdlib-user" - if binary_path: - binary_path.parent.mkdir(exist_ok=True) - binary_path = default_binary_path.rename(binary_path) - - return Compiled(binary_path or default_binary_path) - - -class Compiled: - def __init__(self, binary_file: Path) -> None: - self.binary_file = binary_file - - def execute(self, args: list[str], **run_kwargs: Any) -> CompletedProcess[bytes]: - command = [str(self.binary_file)] + args - return subprocess.run(command, **run_kwargs) diff --git a/python/aaa/runner/test_runner.py b/python/aaa/runner/test_runner.py deleted file mode 100644 index 46b51a34..00000000 --- a/python/aaa/runner/test_runner.py +++ /dev/null @@ -1,141 +0,0 @@ -import re -import sys -from glob import glob -from pathlib import Path - -from aaa import AaaException -from aaa.parser.exceptions import AaaParserBaseException -from aaa.parser.models import Function, SourceFile -from aaa.parser.parser import AaaParser -from aaa.runner.runner import Runner - - -class TestRunner: - # Tell pytest to ignore this class - __test__ = False - - def __init__(self, tests_root: str) -> None: - self.tests_root = Path(tests_root).resolve() - self.exceptions: list[AaaException] = [] - self.parsed_files: dict[Path, SourceFile] = {} - self.test_functions: list[Function] = [] - self.verbose = False - - @classmethod - def test_command( - cls, - path: str, - verbose: bool, - binary: str | None, - runtime_type_checks: bool, - ) -> int: - test_runner = TestRunner(path) - test_runner.set_verbose(verbose) - - if binary: - binary_path = Path(binary) - else: - binary_path = None - - return test_runner.run( - compile=True, - binary=binary_path, - run=True, - runtime_type_checks=runtime_type_checks, - ) - - def set_verbose(self, verbose: bool) -> None: - self.verbose = verbose - - def run( - self, - compile: bool, - binary: Path | None, - run: bool, - runtime_type_checks: bool, - ) -> int: - main_file_code = self._build_main_test_file() - - if self.exceptions: - for exception in self.exceptions: - print(str(exception), file=sys.stderr) - - print(f"Found {len(self.exceptions)} error(s).", file=sys.stderr) - return 1 - - runner = Runner.without_file(main_file_code) - runner.add_parsed_files(self.parsed_files) - runner.set_verbose(self.verbose) - return runner.run( - compile=compile, - binary_path=binary, - run=run, - runtime_type_checks=runtime_type_checks, - args=[], - ) - - def _get_parsed_test_files(self) -> dict[Path, SourceFile]: - glob_paths = glob("**/test_*.aaa", root_dir=self.tests_root, recursive=True) - test_files = {(self.tests_root / path).resolve() for path in glob_paths} - - parsed_files: dict[Path, SourceFile] = {} - - parser = AaaParser(self.verbose) - - for test_file in sorted(test_files): - try: - parsed_files[test_file] = parser.parse_file(test_file) - except AaaParserBaseException as e: - self.exceptions.append(e) - - return parsed_files - - def _get_test_functions(self) -> list[Function]: - test_functions: list[Function] = [] - - for parsed_file in self.parsed_files.values(): - for function in parsed_file.functions: - if function.is_test(): - test_functions.append(function) - - return test_functions - - def _get_test_func_alias(self, test_number: int) -> str: - alias = str(test_number) - - # Replace digits with letters, because digits - # can't be part of identifiers (such as aliases) - for i in range(10): - alias = alias.replace(str(i), chr(ord("a") + i)) - - return f"test_{alias}" - - def _build_main_test_file(self) -> str: - code = (Path(__file__).parent / "run_tests.aaa").read_text() - - self.parsed_files = self._get_parsed_test_files() - self.test_functions = self._get_test_functions() - - imports: list[str] = [] - pushed_test_funcs: list[str] = [] - - for test_number, test_function in enumerate(self.test_functions): - from_ = str(test_function.position.file) - func_name = test_function.get_func_name() - - # Functions are aliased to prevent naming collisions - alias = self._get_test_func_alias(test_number) - - imports.append(f'from "{from_}" import {func_name} as {alias}') - pushed_test_funcs.append( - f' "{alias}" fn "{from_}::{func_name}" push_test_function' - ) - - code = re.sub(" *// \\[COMMENT\\].*\n", "", code) - code = re.sub(" *// \\[IMPORTS\\].*\n", "\n".join(imports) + "\n", code) - code = re.sub( - " *// \\[TEST FUNCTIONS\\].*\n", - "\n".join(pushed_test_funcs) + "\n", - code, - ) - return code diff --git a/python/aaa/transpiler/__init__.py b/python/aaa/transpiler/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/aaa/transpiler/code.py b/python/aaa/transpiler/code.py deleted file mode 100644 index 55d2adc2..00000000 --- a/python/aaa/transpiler/code.py +++ /dev/null @@ -1,44 +0,0 @@ -class Code: - def __init__( - self, line: str | None = None, unindent: int = 0, indent: int = 0 - ) -> None: - self.lines: list[str] = [] - self.indent_level = 0 - - if line is not None: - self.__add_line(line, bool(unindent), bool(indent)) - - def __add_line(self, line: str, unindent: bool, indent: bool) -> None: - if line.endswith("\n"): - raise ValueError("Line should not end with newline.") - - if unindent: - self.indent_level -= 1 - - if self.indent_level < 0: - raise ValueError("Indentation can't be negative.") - - indented_line = " " * self.indent_level + line - self.lines.append(indented_line) - - if indent: - self.indent_level += 1 - - def __add_code(self, code: "Code") -> None: - for line in code.lines: - self.__add_line(line, False, False) - - def add(self, added: "str | Code", unindent: int = 0, indent: int = 0) -> None: - if isinstance(added, str): - self.__add_line(added, bool(unindent), bool(indent)) - elif isinstance(added, Code): - self.__add_code(added) - - def add_joined(self, join_line: str, joined: list["Code"]) -> None: - for offset, line in enumerate(joined): - self.add(line) - if offset != len(joined) - 1: - self.add(join_line) - - def get(self) -> str: - return "\n".join(self.lines) + "\n" diff --git a/python/aaa/transpiler/transpiler.py b/python/aaa/transpiler/transpiler.py deleted file mode 100644 index c9c21aeb..00000000 --- a/python/aaa/transpiler/transpiler.py +++ /dev/null @@ -1,1336 +0,0 @@ -from collections.abc import Callable -from datetime import datetime -from hashlib import sha256 -from pathlib import Path - -from basil.models import Position - -from aaa import create_output_folder -from aaa.cross_referencer.models import ( - Assignment, - BooleanLiteral, - Branch, - CallEnumConstructor, - CallFunction, - CallFunctionByPointer, - CallType, - CallVariable, - CaseBlock, - CharacterLiteral, - CrossReferencerOutput, - DefaultBlock, - Enum, - EnumConstructor, - ForeachLoop, - Function, - FunctionBody, - FunctionBodyItem, - FunctionPointer, - GetFunctionPointer, - IntegerLiteral, - MatchBlock, - Never, - Return, - StringLiteral, - Struct, - StructFieldQuery, - StructFieldUpdate, - UseBlock, - VariableType, - WhileLoop, -) -from aaa.transpiler.code import Code -from aaa.type_checker.models import TypeCheckerOutput - -AAA_RUST_BUILTIN_FUNCS = { - "-": "minus", - ".": "print", - "*": "multiply", - "/": "divide", - "%": "modulo", - "+": "plus", - "<": "less", - "<=": "less_equal", - ">": "greater", - ">=": "greater_equal", -} - - -VARIABLE_GET_FUNCTIONS = { - "int": "get_integer", - "bool": "get_boolean", - "char": "get_character", - "str": "get_string", - "vec": "get_vector", - "set": "get_set", - "map": "get_map", - "vec_iter": "get_vector_iterator", - "map_iter": "get_map_iterator", - "set_iter": "get_set_iterator", - "regex": "get_regex", -} - - -class Transpiler: - def __init__( - self, - *, - cross_referencer_output: CrossReferencerOutput, - type_checker_output: TypeCheckerOutput, - transpiler_root: Path | None, - runtime_type_checks: bool, - verbose: bool, - ) -> None: - self.functions = cross_referencer_output.functions - self.builtins_path = cross_referencer_output.builtins_path - self.entrypoint = cross_referencer_output.entrypoint - self.position_stacks = type_checker_output.position_stacks - self.transpiler_root = transpiler_root or create_output_folder() - - self.verbose = verbose - self.runtime_type_checks = runtime_type_checks - - self.structs: dict[tuple[Path, str], Struct] = {} - self.enums: dict[tuple[Path, str], Enum] = {} - - # Function currently being transpiled - self.current_function: Function | None = None - - for key, struct in cross_referencer_output.structs.items(): - if struct.position.file != self.builtins_path: - self.structs[key] = struct - - for key, type in cross_referencer_output.enums.items(): - if type.position.file != self.builtins_path: - self.enums[key] = type - - def run(self) -> None: - transpiled_file = self.transpiler_root / "src/main.rs" - transpiled_file.parent.mkdir(parents=True, exist_ok=True) - - code = self._generate_file() - transpiled_file.write_text(code.get()) - - if self.verbose: - print(f" transpiler | Saving transpiled file as {transpiled_file}") - - def _generate_file(self) -> Code: - code = self._generate_header_comment() - code.add(self._generate_warning_silencing_macros()) - code.add(self._generate_imports()) - - code.add(self._generate_UserTypeEnum()) - code.add(self._generate_UserTypeEnum_impl()) - code.add(self._generate_UserTypeEnum_UserType_impl()) - code.add(self._generate_UserTypeEnum_Display_impl()) - code.add(self._generate_UserTypeEnum_Debug_impl()) - - for enum in self.enums.values(): - code.add(self._generate_enum(enum)) - code.add(self._generate_enum_constructors(enum)) - code.add(self._generate_enum_impl(enum)) - code.add(self._generate_enum_UserType_impl(enum)) - code.add(self._generate_enum_Display_impl(enum)) - code.add(self._generate_enum_Debug_impl(enum)) - code.add(self._generate_enum_Hash_impl(enum)) - code.add(self._generate_enum_PartialEq_impl(enum)) - - for struct in self.structs.values(): - code.add(self._generate_struct(struct)) - code.add(self._generate_struct_impl(struct)) - code.add(self._generate_struct_UserType_impl(struct)) - code.add(self._generate_struct_Display_impl(struct)) - code.add(self._generate_struct_Debug_impl(struct)) - code.add(self._generate_struct_Hash_impl(struct)) - code.add(self._generate_struct_PartialEq_impl(struct)) - - for function in self.functions.values(): - code.add(self._generate_function(function)) - - code.add(self._generate_main_function()) - - return code - - def _generate_header_comment(self) -> Code: - now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - - code = Code("// #######################################################") - code.add("// # This file is machine generated. Do not edit. #") - code.add(f"// # Generated by Aaa Transpiler on {now}. #") - code.add("// # https://github.com/lk16/aaa #") - code.add("// #######################################################") - code.add("") - return code - - def _generate_warning_silencing_macros(self) -> Code: - code = Code("#![allow(unused_imports)]") - code.add("#![allow(unused_mut)]") - code.add("#![allow(unused_variables)]") - code.add("#![allow(dead_code)]") - code.add("#![allow(non_snake_case)]") - code.add("#![allow(non_camel_case_types)]") - code.add("") - return code - - def _generate_imports(self) -> Code: - code = Code("use aaa_stdlib::map::Map;") - code.add("use aaa_stdlib::stack::Stack;") - code.add("use aaa_stdlib::set::{Set, SetValue};") - code.add("use aaa_stdlib::var::{UserType, Variable};") - code.add("use aaa_stdlib::vector::Vector;") - code.add("use regex::Regex;") - code.add("use std::cell::RefCell;") - code.add("use std::collections::HashMap;") - code.add("use std::fmt::{Debug, Display, Formatter, Result};") - code.add("use std::hash::Hash;") - code.add("use std::process;") - code.add("use std::rc::Rc;") - code.add("") - return code - - def _generate_main_function(self) -> Code: - main_func = self.functions[(self.entrypoint, "main")] - main_func_name = self._generate_function_name(main_func) - - argv_used = len(main_func.arguments) != 0 - exit_code_returned = ( - isinstance(main_func.return_types, list) - and len(main_func.return_types) != 0 - ) - - code = Code() - code.add("fn main() {", indent=1) - - if argv_used: - code.add("let mut stack:Stack = Stack::from_argv();") - else: - code.add("let mut stack: Stack = Stack::new();") - - code.add(f"{main_func_name}(&mut stack);") - - if exit_code_returned: - code.add("stack.exit();") - - code.add("}", unindent=1) - - return code - - def _generate_builtin_function_name(self, function: Function) -> str: - if function.is_member_function() and function.func_name == "=": - name = f"{function.struct_name}_equals" - else: - name = function.name - - return self._generate_builtin_function_name_from_str(name) - - def _generate_builtin_function_name_from_str(self, name: str) -> str: - if name in AAA_RUST_BUILTIN_FUNCS: - return AAA_RUST_BUILTIN_FUNCS[name] - - return name.replace(":", "_") - - def _generate_function_name(self, function: Function) -> str: - if function.position.file == self.builtins_path: - return "stack." + self._generate_builtin_function_name(function) - - # TODO #37 Use modules in generated code so we don't have to hash at all - - # hash file and name to prevent naming collisions - hash_input = f"{function.position.file} {function.name}" - hash = sha256(hash_input.encode("utf-8")).hexdigest()[:16] - return f"user_func_{hash}" - - def _generate_enum_constructor_name(self, enum: Enum, variant_name: str) -> str: - # TODO #37 Use modules in generated code so we don't have to hash at all - - # hash file and name to prevent naming collisions - hash_input = f"{enum.position.file} {enum.name}:{variant_name}" - hash = sha256(hash_input.encode("utf-8")).hexdigest()[:16] - return f"enum_ctor_{hash}" - - def _get_member_function(self, var_type: VariableType, func_name: str) -> Function: - # NOTE It is required that member funcitions are - # defined in same file as the type they operate on. - file = var_type.type.position.file - name = f"{var_type.name}:{func_name}" - return self.functions[(file, name)] - - def _generate_function(self, function: Function) -> Code: - if function.position.file == self.builtins_path: - return Code() - - assert function.body - - self.current_function = function - - func_name = self._generate_function_name(function) - - code = Code(f"// Generated from: {function.position.file} {function.name}") - code.add(f"fn {func_name}(stack: &mut Stack) {{", indent=1) - - if function.arguments: - code.add("// load arguments") - for arg in reversed(function.arguments): - code.add(f"let mut var_{arg.name} = stack.pop();") - code.add("") - - code.add(self._generate_function_body(function.body)) - - code.add("}", unindent=1) - code.add("") - - self.current_function = None - return code - - def _generate_function_body(self, function_body: FunctionBody) -> Code: - code = Code() - - for item in function_body.items: - code.add(self._generate_runtime_type_checks(item.position)) - code.add(self._generate_function_body_item(item)) - - return code - - def _generate_function_body_item(self, item: FunctionBodyItem) -> Code: - generate_funcs: dict[type[FunctionBodyItem], Callable[..., Code]] = { - Assignment: self._generate_assignment_code, - BooleanLiteral: self._generate_boolean_literal, - Branch: self._generate_branch, - CallEnumConstructor: self._generate_call_enum_constructor_code, - CallFunction: self._generate_call_function_code, - CallFunctionByPointer: self._generate_call_by_function_pointer, - CallType: self._generate_call_type_code, - CallVariable: self._generate_call_variable_code, - CharacterLiteral: self._generate_character_literal, - ForeachLoop: self._generate_foreach_loop, - FunctionPointer: self._generate_function_pointer_literal, - GetFunctionPointer: self._generate_get_function_pointer, - IntegerLiteral: self._generate_integer_literal, - MatchBlock: self._generate_match_block_code, - Return: self._generate_return, - StringLiteral: self._generate_string_literal, - StructFieldQuery: self._generate_field_query_code, - StructFieldUpdate: self._generate_field_update_code, - UseBlock: self._generate_use_block_code, - WhileLoop: self._generate_while_loop, - } - - assert set(generate_funcs.keys()) == set(FunctionBodyItem.__args__) # type: ignore - - return generate_funcs[type(item)](item) - - def _generate_runtime_type_checks(self, position: Position) -> Code: - if not self.runtime_type_checks: - return Code() - - function = self.current_function - assert function - - # Runtime type checking doesn't work for functions with type parameters, - # because we don't know the types at transpile-time. - if function.type_params: - return Code() - - try: - type_stack = self.position_stacks[position] - except KeyError: - # If there is no type stack for this position, one of these is true: - # - the code is unreachable - # - there are bugs in generating the `position_stacks` (in TypeChecker) - # Either way should never, so we crash. - return Code( - "stack.unreachable_with_position(Some((" - + f'"{position.file}", {position.line}, {position.column}' - + ")));" - ) - - # We should not get here with unreachable code - assert not isinstance(type_stack, Never) - - variable_kinds: list[str] = [] - for item in type_stack: - if isinstance(item, FunctionPointer): - variable_kinds.append("fn_ptr") - else: - variable_kinds.append(item.name) - - return Code( - "stack.assert_stack_top_types(" - + f'"{position.file}", {position.line}, {position.column}, vec![' - + ", ".join(f'"{kind}"' for kind in variable_kinds) - + "]);" - ) - - def _generate_boolean_literal(self, bool_literal: BooleanLiteral) -> Code: - bool_value = "true" - if not bool_literal.value: - bool_value = "false" - - return Code(f"stack.push_bool({bool_value});") - - def _generate_integer_literal(self, int_literal: IntegerLiteral) -> Code: - return Code(f"stack.push_int({int_literal.value});") - - def _generate_call_by_function_pointer( - self, call_by_func_ptr: CallFunctionByPointer - ) -> Code: - return Code("stack.pop_function_pointer_and_call();") - - def _get_function_pointer_expression( - self, target: Function | EnumConstructor - ) -> str: - if isinstance(target, EnumConstructor): - return self._generate_enum_constructor_name( - target.enum, target.variant_name - ) - - if target.position.file == self.builtins_path: - return "Stack::" + self._generate_builtin_function_name(target) - - return self._generate_function_name(target) - - def _generate_get_function_pointer(self, get_func_ptr: GetFunctionPointer) -> Code: - ptr_expr = self._get_function_pointer_expression(get_func_ptr.target) - return Code(f"stack.push_function_pointer({ptr_expr});") - - def _generate_field_query_code(self, field_query: StructFieldQuery) -> Code: - field_name = field_query.field_name.value - - type_stack = self.position_stacks[field_query.position] - - # This is enforced by the type checker - assert not isinstance(type_stack, Never) - stack_top = type_stack[-1] - - # This is enforced by the type checker - assert isinstance(stack_top, VariableType) - struct_type = stack_top.type - - # This is enforced by the type checker - assert isinstance(struct_type, Struct) - - rust_struct_name = self._generate_type_name(struct_type) - - code = Code("{", indent=1) - code.add("let popped = stack.pop_user_type();") - code.add("let mut borrowed = (*popped).borrow_mut();") - - code.add(f"stack.push(borrowed.get_{rust_struct_name}().{field_name}.clone());") - - code.add("}", unindent=1) - return code - - def _generate_field_update_code(self, field_update: StructFieldUpdate) -> Code: - code = self._generate_function_body(field_update.new_value_expr) - field_name = field_update.field_name.value - - type_stack = self.position_stacks[field_update.position] - - # This is verified by the type checker - assert not isinstance(type_stack, Never) - stack_top = type_stack[-1] - - # This is verified by the type checker - assert isinstance(stack_top, VariableType) - struct_type = stack_top.type - - # This is verified by the type checker - assert isinstance(struct_type, Struct) - - rust_struct_name = self._generate_type_name(struct_type) - field_name = field_update.field_name.value - - code.add("{", indent=1) - - code.add("let value = stack.pop();") - - code.add("let popped = stack.pop_user_type();") - code.add("let mut borrowed = (*popped).borrow_mut();") - code.add(f"borrowed.get_{rust_struct_name}().{field_name} = value;") - code.add("}", unindent=1) - return code - - def _generate_match_block_code(self, match_block: MatchBlock) -> Code: - type_stack = self.position_stacks[match_block.position] - - # This is verified by the type checker - assert not isinstance(type_stack, Never) - stack_top = type_stack[-1] - - # This is verified by the type checker - assert isinstance(stack_top, VariableType) - enum_type = stack_top.type - - assert isinstance(enum_type, Enum) - - rust_enum_name = self._generate_type_name(enum_type) - - match_var = ( - "match_var_" + sha256(str(match_block.position).encode()).hexdigest()[:16] - ) - - code = Code( - f"let {match_var} = " - + f"stack.pop_user_type().borrow_mut().get_{rust_enum_name}().clone();" - ) - code.add(f"match {match_var} {{", indent=1) - - has_default = False - case_blocks = 0 - - for block in match_block.blocks: - if isinstance(block, CaseBlock): - case_blocks += 1 - code.add(self._generate_case_block_code(block)) - else: - assert isinstance(block, DefaultBlock) - has_default = True - code.add(self._generate_default_block_code(block)) - - if not has_default and case_blocks != len(enum_type.variants): - code.add("_ => {}") - - code.add("}", unindent=1) - - return code - - def _generate_case_block_body_with_variables( - self, case_block: CaseBlock, case_var_prefix: str - ) -> Code: - code = Code() - - enum = case_block.enum_type - variant_name = case_block.variant_name - associated_data = enum.variants[variant_name] - - for i, (_, var) in enumerate( - zip(associated_data, case_block.variables, strict=True) - ): - arg = f"{case_var_prefix}_{i}" - code.add(f"let mut var_{var.name}: Variable = {arg};") - - return code - - def _generate_case_block_body_without_variables( - self, case_block: CaseBlock, case_var_prefix: str - ) -> Code: - code = Code() - - enum = case_block.enum_type - variant_name = case_block.variant_name - associated_data = enum.variants[variant_name] - - for i, _item in enumerate(associated_data): - arg = f"{case_var_prefix}_{i}" - code.add(f"stack.push({arg});") - - return code - - def _generate_case_block_code(self, case_block: CaseBlock) -> Code: - enum = case_block.enum_type - variant_name = case_block.variant_name - associated_data = enum.variants[variant_name] - - rust_enum_name = self._generate_type_name(enum) - - assert len(case_block.variables) in [0, len(associated_data)] - - # NOTE we add the hash of location to prevent collisions with nested case blocks - case_var_prefix = ( - "case_var_" + sha256(str(case_block.position).encode()).hexdigest()[:16] - ) - - line = f"{rust_enum_name}::variant_{variant_name}(" - line += ", ".join(f"{case_var_prefix}_{i}" for i in range(len(associated_data))) - line += ") => {" - - code = Code(line, indent=1) - if case_block.variables: - code.add( - self._generate_case_block_body_with_variables( - case_block, case_var_prefix - ) - ) - else: - code.add( - self._generate_case_block_body_without_variables( - case_block, case_var_prefix - ) - ) - - code.add(self._generate_function_body(case_block.body)) - code.add("},", unindent=1) - - return code - - def _generate_default_block_code(self, default_block: DefaultBlock) -> Code: - code = Code("_ => {", indent=1) - code.add(self._generate_function_body(default_block.body)) - code.add("}", unindent=1) - - return code - - def _generate_return(self, return_: Return) -> Code: - return Code("return;") - - def _generate_string_literal(self, string_literal: StringLiteral) -> Code: - string_value = repr(string_literal.value)[1:-1].replace('"', '\\"') - return Code(f'stack.push_str("{string_value}");') - - def _generate_character_literal(self, char_literal: CharacterLiteral) -> Code: - string_value = repr(char_literal.value)[1:-1].replace('"', '\\"') - return Code(f"stack.push_char('{string_value}');") - - def _generate_while_loop(self, while_loop: WhileLoop) -> Code: - code = Code("loop {", indent=1) - code.add(self._generate_function_body(while_loop.condition)) - code.add("if !stack.pop_bool() {", indent=1) - code.add("break;") - code.add("}", unindent=1) - code.add(self._generate_function_body(while_loop.body)) - code.add("}", unindent=1) - return code - - def _generate_foreach_loop(self, foreach_loop: ForeachLoop) -> Code: - """ - iter - - while (true) { - dup iterator - next - if popped boolean is true { - drop everything `next` added - drop iterator - break - } - loop body - } - """ - - # If any of these next two lines fail, TypeChecker is broken. - stack = self.position_stacks[foreach_loop.position] - - # This is enforced by the type checker - assert not isinstance(stack, Never) - iterable_type = stack[-1] - - # This is enforced by the type checker - assert isinstance(iterable_type, VariableType) - - if iterable_type.is_const: - iter_func = self._get_member_function(iterable_type, "const_iter") - else: - iter_func = self._get_member_function(iterable_type, "iter") - - assert not isinstance(iter_func.return_types, Never) - - iterator_type = iter_func.return_types[0] - - # This is enforced by the type checker - assert isinstance(iterator_type, VariableType) - - next_func = self._get_member_function(iterator_type, "next") - assert not isinstance(next_func.return_types, Never) - - iter = self._generate_function_name(iter_func) - next = self._generate_function_name(next_func) - break_drop_count = len(next_func.return_types) - - code = Code() - - if iter_func.position.file == self.builtins_path: - code.add(f"{iter}();") - else: - code.add(f"{iter}(stack);") - - code.add("loop {", indent=1) - - code.add("stack.dup();") - if iter_func.position.file == self.builtins_path: - code.add(f"{next}();") - else: - code.add(f"{next}(stack);") - - code.add("if !stack.pop_bool() {", indent=1) - - # drop number of next items - 1 (boolean returned by `next` was popped earlier) - # and drop iterator (+ 1) - for _ in range(break_drop_count): - code.add("stack.drop();") - - code.add("break;") - - code.add("}", unindent=1) - - code.add(self._generate_function_body(foreach_loop.body)) - - code.add("}", unindent=1) - - return code - - def _generate_call_function_code(self, call_func: CallFunction) -> Code: - called = call_func.function - - if called.name == "make_const": - # This function doesn't do anything at runtime, so don't generate any code. - return Code() - - rust_func_name = self._generate_function_name(called) - - if called.position.file == self.builtins_path: - if called.name in ["assert", "todo", "unreachable"]: - position = call_func.position - rust_func_name = f"stack.{called.name}_with_position" - return Code( - f'{rust_func_name}(Some(("{position.file}", ' - + f"{position.line}, {position.column})));" - ) - return Code(f"{rust_func_name}();") - - return Code(f"{rust_func_name}(stack);") - - def _generate_call_type_code(self, call_type: CallType) -> Code: - var_type = call_type.var_type - zero_expr = self._generate_zero_expression(var_type) - return Code(f"stack.push({zero_expr});") - - def _generate_call_enum_constructor_code( - self, call_enum_ctor: CallEnumConstructor - ) -> Code: - enum = call_enum_ctor.enum_ctor.enum - variant_name = call_enum_ctor.enum_ctor.variant_name - - enum_ctor_func_name = self._generate_enum_constructor_name(enum, variant_name) - return Code(f"{enum_ctor_func_name}(stack);") - - def _generate_branch(self, branch: Branch) -> Code: - code = self._generate_function_body(branch.condition) - - code.add("if stack.pop_bool() {", indent=1) - code.add(self._generate_function_body(branch.if_body)) - - if branch.else_body: - code.add("} else {", unindent=1, indent=1) - code.add(self._generate_function_body(branch.else_body)) - - code.add("}", unindent=1) - - return code - - def _generate_type_name(self, type: Enum | Struct) -> str: - hash_input = f"{type.position.file} {type.name}" - hash = sha256(hash_input.encode("utf-8")).hexdigest()[:16] - - if isinstance(type, Enum): - return f"UserEnum{hash}" - return f"UserStruct{hash}" - - def _generate_enum(self, enum: Enum) -> Code: - rust_enum_name = self._generate_type_name(enum) - - code = Code(f"// Generated for: {enum.position.file} {enum.name}") - code.add("#[derive(Clone)]") - code.add(f"enum {rust_enum_name} {{", indent=1) - - for variant_name, variant_data in enum.variants.items(): - line = f"variant_{variant_name}(" - - line += ", ".join(["Variable"] * len(variant_data)) - - line += ")," - code.add(line) - code.add("}", unindent=1) - code.add("") - return code - - def _generate_enum_constructors(self, enum: Enum) -> Code: - rust_enum_name = self._generate_type_name(enum) - - code = Code() - - for variant_name, associated_data in enum.variants.items(): - enum_ctor_func_name = self._generate_enum_constructor_name( - enum, variant_name - ) - code.add( - f"fn {enum_ctor_func_name}(stack: &mut Stack) {{", - indent=1, - ) - for i, _ in reversed(list(enumerate(associated_data))): - code.add(f"let arg{i} = stack.pop();") - - line = f"let enum_ = {rust_enum_name}::variant_{variant_name}(" - line += ", ".join(f"arg{i}" for i in range(len(associated_data))) - line += ");" - - code.add(line) - code.add(f"stack.push_user_type(UserTypeEnum::{rust_enum_name}(enum_));") - code.add("}", unindent=1) - code.add("") - return code - - def _generate_enum_impl(self, enum: Enum) -> Code: - rust_enum_name = self._generate_type_name(enum) - - code = Code(f"impl {rust_enum_name} {{", indent=1) - code.add("fn new() -> Self {", indent=1) - code.add(f"Self::variant_{enum.zero_variant}(", indent=1) - - for variant_data_item in enum.variants[enum.zero_variant]: - if ( - isinstance(variant_data_item, VariableType) - and variant_data_item.is_placeholder - ): - code.add("Variable::None,") - continue - - zero_value = self._generate_zero_expression(variant_data_item) - code.add(f"{zero_value},") - code.add(")", unindent=1) - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("") - - return code - - def _generate_enum_PartialEq_impl(self, enum: Enum) -> Code: - if enum.position.file == self.builtins_path: - return Code() - - rust_enum_name = self._generate_type_name(enum) - - code = Code(f"impl PartialEq for {rust_enum_name} {{", indent=1) - code.add("fn eq(&self, other: &Self) -> bool {", indent=1) - - try: - equals_func = self.functions[(enum.position.file, f"{enum.name}:=")] - except KeyError: - code.add(f'panic!("{enum.name} does not support operator =")') - else: - equals_func_name = self._generate_function_name(equals_func) - - code.add("let mut stack: Stack = Stack::new();") - code.add( - f"stack.push_user_type(UserTypeEnum::{rust_enum_name}(self.clone()));" - ) - code.add( - f"stack.push_user_type(UserTypeEnum::{rust_enum_name}(other.clone()));" - ) - code.add(f"{equals_func_name}(&mut stack);") - code.add("stack.pop_bool()") - - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("") - return code - - def _generate_enum_Hash_impl(self, enum: Enum) -> Code: - if enum.position.file == self.builtins_path: - return Code() - - rust_enun_name = self._generate_type_name(enum) - - code = Code(f"impl Hash for {rust_enun_name} {{", indent=1) - - code.add("fn hash(&self, state: &mut H) {", indent=1) - code.add("todo!();") # TODO #125 Implement hash for structs and enums - code.add("}", unindent=1) - - code.add("}", unindent=1) - code.add("") - return code - - def _generate_enum_UserType_impl(self, enum: Enum) -> Code: - if enum.position.file == self.builtins_path: - return Code() - - rust_enum_name = self._generate_type_name(enum) - - code = Code(f"impl UserType for {rust_enum_name} {{", indent=1) - - code.add("fn kind(&self) -> String {", indent=1) - code.add(f'String::from("{enum.name}")') - code.add("}", unindent=1) - - code.add("") - - code.add("fn clone_recursive(&self) -> Self {", indent=1) - code.add("match self {", indent=1) - - for variant_name, associated_data in enum.variants.items(): - line = f"Self::variant_{variant_name}(" - - line += ", ".join([f"arg{i}" for i in range(len(associated_data))]) - line += ") => {" - - code.add(line, indent=1) - - code.add(f"Self::variant_{variant_name}(", indent=1) - for i, _ in enumerate(associated_data): - code.add(f"arg{i}.clone_recursive(),") - code.add(")", unindent=1) - - code.add("},", unindent=1) - - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("") - return code - - def _generate_enum_Display_impl(self, enum: Enum) -> Code: - if enum.position.file == self.builtins_path: - return Code() - - rust_enum_type = self._generate_type_name(enum) - - code = Code(f"impl Display for {rust_enum_type} {{", indent=1) - - code.add("fn fmt(&self, f: &mut Formatter<'_>) -> Result {", indent=1) - code.add("match self {", indent=1) - - for variant_name, associated_data in enum.variants.items(): - line = f"Self::variant_{variant_name}(" - line += ", ".join([f"arg{i}" for i in range(len(associated_data))]) - line += ") => {" - - code.add(line, indent=1) - - code.add(f'write!(f, "{enum.name}:{variant_name}{{{{")?;') - - for offset, _item in enumerate(associated_data): - if offset != 0: - code.add('write!(f, ", ")?;') - - code.add(f'write!(f, "{{:?}}", arg{offset})?;') - - code.add('write!(f, "}}")') - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("}", unindent=1) - - code.add("}", unindent=1) - code.add("") - return code - - def _generate_enum_Debug_impl(self, enum: Enum) -> Code: - rust_enum_type = self._generate_type_name(enum) - - code = Code(f"impl Debug for {rust_enum_type} {{", indent=1) - code.add("fn fmt(&self, f: &mut Formatter<'_>) -> Result {", indent=1) - code.add('write!(f, "{}", self)') - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("") - return code - - def _generate_zero_expression(self, type: VariableType | FunctionPointer) -> str: - if isinstance(type, FunctionPointer): - return "Variable::function_pointer_zero_value()" - - if type.name == "int": - return "Variable::integer_zero_value()" - elif type.name == "bool": - return "Variable::boolean_zero_value()" - elif type.name == "char": - return "Variable::character_zero_value()" - elif type.name == "str": - return "Variable::string_zero_value()" - elif type.name == "vec": - return "Variable::vector_zero_value()" - elif type.name == "map": - return "Variable::map_zero_value()" - elif type.name == "set": - return "Variable::set_zero_value()" - elif type.name == "regex": - return "Variable::regex_zero_value()" - rust_type_name = self._generate_type_name(type.type) - return ( - "Variable::UserType(Rc::new(RefCell::new(" - + f"UserTypeEnum::{rust_type_name}({rust_type_name}::new()))))" - ) - - def _generate_UserTypeEnum(self) -> Code: - code = Code("#[derive(Clone, Hash, PartialEq)]") - code.add("enum UserTypeEnum {", indent=1) - - for (file, name), struct in self.structs.items(): - rust_struct_name = self._generate_type_name(struct) - code.add( - f"{rust_struct_name}({rust_struct_name}), " - + f"// Generated for {file} {name}" - ) - - for (file, name), enum in self.enums.items(): - rust_struct_name = self._generate_type_name(enum) - code.add( - f"{rust_struct_name}({rust_struct_name}), " - + f"// Generated for {file} {name}" - ) - - code.add("}", unindent=1) - code.add("") - return code - - def _generate_UserTypeEnum_impl(self) -> Code: - code = Code("impl UserTypeEnum {", indent=1) - - func_code_list: list[Code] = [] - - user_types = self.structs | self.enums - - for type in user_types.values(): - rust_struct_name = self._generate_type_name(type) - - func_code = Code( - f"fn get_{rust_struct_name}(&mut self) -> &mut {rust_struct_name} {{", - indent=1, - ) - func_code.add("match self {", indent=1) - func_code.add(f"Self::{rust_struct_name}(v) => v,") - - if len(user_types) != 1: - func_code.add("_ => unreachable!(),") - - func_code.add("}", unindent=1) - func_code.add("}", unindent=1) - - func_code_list.append(func_code) - - code.add_joined("", func_code_list) - code.add("}", unindent=1) - code.add("") - return code - - def _generate_UserTypeEnum_UserType_impl(self) -> Code: - code = Code("impl UserType for UserTypeEnum {", indent=1) - code.add("fn kind(&self) -> String {", indent=1) - - user_types = self.structs | self.enums - - if user_types: - code.add("match self {", indent=1) - - for user_type in user_types.values(): - rust_struct_name = self._generate_type_name(user_type) - code.add(f"Self::{rust_struct_name}(v) => v.kind(),") - - code.add("}", unindent=1) - else: - code.add("unreachable!();") - - code.add("}", unindent=1) - code.add("") - - code.add("fn clone_recursive(&self) -> Self {", indent=1) - - if user_types: - code.add("match self {", indent=1) - - for user_type in user_types.values(): - rust_struct_name = self._generate_type_name(user_type) - code.add( - f"Self::{rust_struct_name}(v) => " - + f"Self::{rust_struct_name}(v.clone_recursive())," - ) - - code.add("}", unindent=1) - else: - code.add("unreachable!();") - - code.add("}", unindent=1) - - code.add("}", unindent=1) - code.add("") - return code - - def _generate_UserTypeEnum_Debug_impl(self) -> Code: - code = Code("impl Display for UserTypeEnum {", indent=1) - code.add("fn fmt(&self, f: &mut Formatter<'_>) -> Result {", indent=1) - - user_types = self.structs | self.enums - - if user_types: - code.add("match self {", indent=1) - - for type in user_types.values(): - rust_struct_name = self._generate_type_name(type) - code.add(f'Self::{rust_struct_name}(v) => write!(f, "{{}}", v),') - - code.add("}", unindent=1) - - else: - code.add("unreachable!();") - - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("") - return code - - def _generate_UserTypeEnum_Display_impl(self) -> Code: - code = Code("impl Debug for UserTypeEnum {", indent=1) - code.add("fn fmt(&self, f: &mut Formatter<'_>) -> Result {", indent=1) - - user_types = self.structs | self.enums - - if user_types: - code.add("match self {", indent=1) - - for type in user_types.values(): - rust_struct_name = self._generate_type_name(type) - code.add(f'Self::{rust_struct_name}(v) => write!(f, "{{}}", v),') - - code.add("}", unindent=1) - else: - code.add("unreachable!();") - - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("") - return code - - def _generate_struct(self, struct: Struct) -> Code: - if struct.position.file == self.builtins_path: - return Code() - - rust_struct_name = self._generate_type_name(struct) - - code = Code(f"// Generated for: {struct.position.file} {struct.name}") - code.add("#[derive(Clone)]") - code.add(f"struct {rust_struct_name} {{", indent=1) - - for field_name in struct.fields: - code.add(f"{field_name}: Variable,") - - code.add("}", unindent=1) - code.add("") - return code - - def _generate_struct_field_type(self, type: VariableType | FunctionPointer) -> str: - if isinstance(type, FunctionPointer): - return "fn (&mut Stack)" - if type.name == "int": - return "isize" - elif type.name == "bool": - return "bool" - elif type.name == "char": - return "char" - elif type.name == "str": - return "Rc>" - elif type.name == "vec": - return "Rc>>>" - elif type.name == "map": - return "Rc, Variable>>>" - elif type.name == "set": - return "Rc>>>" - elif type.name == "regex": - return "Rc>" - return "Rc>" - - def _generate_struct_impl(self, struct: Struct) -> Code: - if struct.position.file == self.builtins_path and not struct.fields: - return Code() - - rust_struct_name = self._generate_type_name(struct) - - code = Code(f"// Generated for: {struct.position.file} {struct.name}") - code.add(f"impl {rust_struct_name} {{", indent=1) - code.add("fn new() -> Self {", indent=1) - - code.add("Self {", indent=1) - - for field_name, field_var_type in struct.fields.items(): - if ( - isinstance(field_var_type, VariableType) - and field_var_type.name in struct.type_params - ): - code.add(f"{field_name}: Variable::None,") - continue - - zero_expression = self._generate_zero_expression(field_var_type) - code.add(f"{field_name}: {zero_expression},") - - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("") - return code - - def _generate_clone_recursive_expression( - self, source_expr: str, type: Struct | Enum - ) -> Code: - if type.name in ["bool", "int", "char"]: - code = Code(source_expr) - elif type.name == "regex": - code = Code(f"{source_expr}.clone()") - elif type.name == "str": - code = Code(f"let string = (*{source_expr}).borrow().clone();") - code.add("Rc::new(RefCell::new(string))") - elif type.name == "vec": - code = Code("let mut vector = Vector::new();") - code.add(f"let source = (*{source_expr}).borrow();") - code.add("for item in source.iter() {", indent=1) - code.add("vector.push(item.clone_recursive())") - code.add("}", unindent=1) - code.add("Rc::new(RefCell::new(vector))") - elif type.name == "set": - code = Code("let mut set = Map::new();") - code.add(f"let source = (*{source_expr}).borrow();") - code.add("for (item, _) in source.iter() {", indent=1) - code.add("set.insert(item.clone_recursive(), SetValue{});") - code.add("}", unindent=1) - code.add("Rc::new(RefCell::new(set))") - elif type.name == "map": - code = Code("let mut map = Map::new();") - code.add(f"let source = (*{source_expr}).borrow();") - code.add("for (key, value) in source.iter() {", indent=1) - code.add("map.insert(key.clone_recursive(), value.clone_recursive());") - code.add("}", unindent=1) - code.add("Rc::new(RefCell::new(map))") - else: - code = Code(f"let source = (*{source_expr}).borrow();") - code.add("Rc::new(RefCell::new(source.clone_recursive()))") - return code - - def _generate_struct_UserType_impl(self, struct: Struct) -> Code: - if struct.position.file == self.builtins_path: - return Code() - - rust_struct_name = self._generate_type_name(struct) - - code = Code(f"impl UserType for {rust_struct_name} {{", indent=1) - - code.add("fn kind(&self) -> String {", indent=1) - code.add(f'String::from("{struct.name}")') - code.add("}", unindent=1) - - code.add("") - - code.add("fn clone_recursive(&self) -> Self {", indent=1) - - code.add("Self {", indent=1) - - for field_name in struct.fields: - code.add(f"{field_name}: self.{field_name}.clone_recursive(),") - - code.add("}", unindent=1) - - code.add("}", unindent=1) - - code.add("}", unindent=1) - code.add("") - return code - - def _generate_struct_Display_impl(self, struct: Struct) -> Code: - if struct.position.file == self.builtins_path: - return Code() - - rust_struct_name = self._generate_type_name(struct) - - code = Code(f"impl Display for {rust_struct_name} {{", indent=1) - - code.add("fn fmt(&self, f: &mut Formatter<'_>) -> Result {", indent=1) - - code.add(f'write!(f, "{struct.name}{{{{")?;') - - for offset, field_name in enumerate(struct.fields): - if offset != 0: - code.add('write!(f, ", ")?;') - - code.add(f'write!(f, "{field_name}: {{:?}}", self.{field_name})?;') - - code.add('write!(f, "}}")') - - code.add("}", unindent=1) - - code.add("}", unindent=1) - code.add("") - return code - - def _generate_struct_Debug_impl(self, struct: Struct) -> Code: - if struct.position.file == self.builtins_path: - return Code() - - rust_struct_name = self._generate_type_name(struct) - - code = Code(f"impl Debug for {rust_struct_name} {{", indent=1) - code.add("fn fmt(&self, f: &mut Formatter<'_>) -> Result {", indent=1) - code.add('write!(f, "{}", self)') - code.add("}", unindent=1) - code.add("}", unindent=1) - - code.add("") - return code - - def _generate_struct_Hash_impl(self, struct: Struct) -> Code: - if struct.position.file == self.builtins_path: - return Code() - - rust_struct_name = self._generate_type_name(struct) - - code = Code(f"impl Hash for {rust_struct_name} {{", indent=1) - - code.add("fn hash(&self, state: &mut H) {", indent=1) - code.add("todo!();") # TODO #125 Implement hash for structs and enums - code.add("}", unindent=1) - - code.add("}", unindent=1) - code.add("") - return code - - def _generate_struct_PartialEq_impl(self, struct: Struct) -> Code: - if struct.position.file == self.builtins_path: - return Code() - - rust_struct_name = self._generate_type_name(struct) - - code = Code(f"impl PartialEq for {rust_struct_name} {{", indent=1) - code.add("fn eq(&self, other: &Self) -> bool {", indent=1) - - try: - equals_func = self.functions[(struct.position.file, f"{struct.name}:=")] - except KeyError: - code.add(f'panic!("{struct.name} does not support operator =")') - else: - equals_func_name = self._generate_function_name(equals_func) - - code.add("let mut stack: Stack = Stack::new();") - code.add( - f"stack.push_user_type(UserTypeEnum::{rust_struct_name}(self.clone()));" - ) - code.add( - f"stack.push_user_type(UserTypeEnum::{rust_struct_name}(other.clone()));" - ) - code.add(f"{equals_func_name}(&mut stack);") - code.add("stack.pop_bool()") - - code.add("}", unindent=1) - code.add("}", unindent=1) - code.add("") - return code - - def _generate_use_block_code(self, use_block: UseBlock) -> Code: - code = Code("{", indent=1) - - for var in reversed(use_block.variables): - code.add(f"let mut var_{var.name} = stack.pop();") - - code.add(self._generate_function_body(use_block.body)) - code.add("}", unindent=1) - - return code - - def _generate_call_variable_code(self, call_var: CallVariable) -> Code: - name = call_var.name - return Code(f"stack.push(var_{name}.clone());") - - def _generate_assignment_code(self, assignment: Assignment) -> Code: - code = self._generate_function_body(assignment.body) - - for var in reversed(assignment.variables): - code.add(f"stack.assign(&mut var_{var.name});") - - return code - - def _generate_function_pointer_literal(self, func_ptr: FunctionPointer) -> Code: - return Code("stack.push_function_pointer(Stack::zero_function_pointer_value);") diff --git a/python/aaa/type_checker/__init__.py b/python/aaa/type_checker/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/aaa/type_checker/exceptions.py b/python/aaa/type_checker/exceptions.py deleted file mode 100644 index 14c8e426..00000000 --- a/python/aaa/type_checker/exceptions.py +++ /dev/null @@ -1,752 +0,0 @@ -from pathlib import Path - -from basil.models import Position - -from aaa import AaaException -from aaa.cross_referencer.exceptions import describe -from aaa.cross_referencer.models import ( - Argument, - Assignment, - CallFunctionByPointer, - CallVariable, - CaseBlock, - DefaultBlock, - Enum, - EnumConstructor, - Function, - FunctionPointer, - MatchBlock, - Never, - Struct, - StructFieldQuery, - StructFieldUpdate, - UseBlock, - Variable, - VariableType, -) - - -def format_typestack( - type_stack: list[VariableType | FunctionPointer] | Never, -) -> str: - if isinstance(type_stack, Never): - return "never" - - return " ".join(repr(item) for item in type_stack) - - -class TypeCheckerException(AaaException): - def __init__(self, position: Position) -> None: - self.position = position - - -class FunctionTypeError(TypeCheckerException): - def __init__( - self, - function: Function, - computed_return_types: list[VariableType | FunctionPointer] | Never, - ) -> None: - self.computed_return_types = computed_return_types - self.function = function - super().__init__(function.position) - - def __str__(self) -> str: - expected = format_typestack(self.function.return_types) - found = format_typestack(self.computed_return_types) - - return ( - f"{self.position}: Function {self.function.name} returns wrong type(s)\n" - + f"expected return types: {expected}\n" - + f" found return types: {found}" - ) - - -class StackTypesError(TypeCheckerException): - def __init__( - self, - position: Position, - type_stack: list[VariableType | FunctionPointer], - func_like: Function - | EnumConstructor - | CallFunctionByPointer - | StructFieldUpdate - | StructFieldQuery, - type_params: list[VariableType | FunctionPointer] | None = None, - expected_stack_top_override: list[VariableType | FunctionPointer] | None = None, - ) -> None: - self.type_stack = type_stack - self.func_like = func_like - self.type_params = type_params or [] - self.expected_stack_top_override = expected_stack_top_override - super().__init__(position) - - def func_like_name(self) -> str: # pragma: nocover - if isinstance(self.func_like, Function | EnumConstructor): - name = self.func_like.name - - if self.type_params: - name += "[" + ",".join(repr(param) for param in self.type_params) + "]" - - return name - - elif isinstance(self.func_like, StructFieldQuery): - return "?" - elif isinstance(self.func_like, StructFieldUpdate): - return "!" - else: - assert isinstance(self.func_like, CallFunctionByPointer) - try: - return repr(self.type_stack[-1]) - except IndexError: - return "" - - def format_expected_typestack(self) -> str: # pragma: nocover - if self.expected_stack_top_override is not None: - return format_typestack(self.expected_stack_top_override) - - if isinstance(self.func_like, Function): - types = [arg.type for arg in self.func_like.arguments] - return format_typestack(types) - elif isinstance(self.func_like, StructFieldQuery): - return " str" - elif isinstance(self.func_like, StructFieldUpdate): - return " str " - elif isinstance(self.func_like, CallFunctionByPointer): - try: - func_ptr = self.type_stack[-1] - except IndexError: - return "... " - - if isinstance(func_ptr, FunctionPointer): - return " ".join( - repr(item) for item in func_ptr.argument_types + [func_ptr] - ) - return "... " - - else: - assert isinstance(self.func_like, EnumConstructor) - types = self.func_like.enum.variants[self.func_like.variant_name] - return format_typestack(types) - - def __str__(self) -> str: - return ( - f"{self.position}: Invalid stack types when calling " - + f"{self.func_like_name()}\n" - + "Expected stack top: " - + self.format_expected_typestack() - + "\n" - + " Found stack: " - + format_typestack(self.type_stack) - ) - - -class ConditionTypeError(TypeCheckerException): - def __init__( - self, - position: Position, - type_stack: list[VariableType | FunctionPointer], - condition_stack: list[VariableType | FunctionPointer], - ) -> None: - self.type_stack = type_stack - self.condition_stack = condition_stack - super().__init__(position) - - def __str__(self) -> str: - stack_before = format_typestack(self.type_stack) - stack_after = format_typestack(self.condition_stack) - - return ( - f"{self.position}: Condition type error\n" - + f"stack before: {stack_before}\n" - + f" stack after: {stack_after}" - ) - - -class BranchTypeError(TypeCheckerException): - def __init__( - self, - position: Position, - type_stack: list[VariableType | FunctionPointer], - if_stack: list[VariableType | FunctionPointer], - else_stack: list[VariableType | FunctionPointer], - ) -> None: - self.type_stack = type_stack - self.if_stack = if_stack - self.else_stack = else_stack - super().__init__(position) - - def __str__(self) -> str: - before_stack = format_typestack(self.type_stack) - if_stack = format_typestack(self.if_stack) - else_stack = format_typestack(self.else_stack) - - return ( - f"{self.position}: Inconsistent stacks for branches\n" - + f" before: {before_stack}\n" - + f" after if-branch: {if_stack}\n" - + f"after else-branch: {else_stack}" - ) - - -class WhileLoopTypeError(TypeCheckerException): - def __init__( - self, - position: Position, - type_stack: list[VariableType | FunctionPointer], - loop_stack: list[VariableType | FunctionPointer], - ) -> None: - self.type_stack = type_stack - self.loop_stack = loop_stack - super().__init__(position) - - def __str__(self) -> str: - before_stack = format_typestack(self.type_stack) - after_stack = format_typestack(self.loop_stack) - - return ( - f"{self.position}: Invalid stack modification inside while loop body\n" - + f"before while loop: {before_stack}\n" - + f" after while loop: {after_stack}" - ) - - -class InvalidMainSignuture(TypeCheckerException): - def __str__(self) -> str: - return ( - f"{self.position}: Main function has wrong signature, it should have:\n" - + "- no type parameters\n" - + "- either no arguments or one vec[str] argument\n" - + "- return either nothing or an int" - ) - - -class InvalidTestSignuture(TypeCheckerException): - def __init__(self, function: Function) -> None: - self.function = function - super().__init__(function.position) - - def __str__(self) -> str: - return ( - f"{self.position}: Test function {self.function.name} should have no " - + "arguments and no return types" - ) - - -class StructUpdateStackError(TypeCheckerException): - def __init__( - self, - position: Position, - type_stack: list[VariableType | FunctionPointer], - type_stack_before: list[VariableType | FunctionPointer], - ) -> None: - self.type_stack = type_stack - self.type_stack_before = type_stack_before - super().__init__(position) - - def __str__(self) -> str: - expected_stack = format_typestack(self.type_stack_before) - found_stack = format_typestack(self.type_stack) - - return ( - f"{self.position}: Incorrect stack modification when updating " - + "struct field\n" - + f" Expected: {expected_stack} \n" - + f" Found: {found_stack}" - ) - - -class StructUpdateTypeError(TypeCheckerException): - def __init__( - self, - position: Position, - type_stack: list[VariableType | FunctionPointer], - struct_type: Struct, - field_name: str, - expected_type: VariableType | FunctionPointer, - found_type: VariableType | FunctionPointer, - ) -> None: - self.type_stack = type_stack - self.struct_type = struct_type - self.field_name = field_name - self.expected_type = expected_type - self.found_type = found_type - super().__init__(position) - - def __str__(self) -> str: - return ( - f"{self.position}: Attempt to set field {self.field_name} of " - + f"{self.struct_type.name} to wrong type\n" - + f"Expected type: {self.expected_type}\n" - + f" Found type: {self.found_type}\n" - + "\n" - + "Type stack: " - + format_typestack(self.type_stack) - ) - - -class InvalidMemberFunctionSignature(TypeCheckerException): - def __init__(self, function: Function, type: Struct | Enum) -> None: - self.type = type - self.function = function - super().__init__(function.position) - - def __str__(self) -> str: - full_func_name = f"{self.function.struct_name}:{self.function.func_name}" - formatted = ( - f"{self.position}: Function {full_func_name} has invalid " - + "member-function signature\n\n" - ) - - arguments = [arg.type for arg in self.function.arguments] - - formatted += ( - f"Expected arg types: {self.type.name} ...\n" - + f" Found arg types: {' '.join(repr(arg) for arg in arguments)}" - ) - - return formatted - - -class UnknownField(TypeCheckerException): - def __init__( - self, position: Position, struct_type: Struct, field_name: str - ) -> None: - self.struct_type = struct_type - self.field_name = field_name - super().__init__(position) - - def __str__(self) -> str: - return ( - f"{self.position}: Usage of unknown field {self.field_name} of type " - + self.struct_type.name - ) - - -class MainFunctionNotFound(TypeCheckerException): - def __init__(self, file: Path) -> None: - self.file = file - - def __str__(self) -> str: - return f"{self.file}: No main function found" - - -class MissingIterable(TypeCheckerException): - def __str__(self) -> str: - return f"{self.position}: Cannot use foreach, function stack is empty." - - -class InvalidIterable(TypeCheckerException): - def __init__( - self, position: Position, iterable_type: VariableType | FunctionPointer - ) -> None: - self.iterable_type = iterable_type - super().__init__(position) - - def __str__(self) -> str: - return ( - f"{self.position}: Invalid iterable type {self.iterable_type}.\n" - + "Iterable types need to have a function named iter which:\n" - + "- takes one argument (the iterable)\n" - + "- returns one value (an iterator)" - ) - - -class InvalidIterator(TypeCheckerException): - def __init__( - self, - position: Position, - iterable_type: VariableType, - iterator_type: VariableType | FunctionPointer, - ) -> None: - self.iterable_type = iterable_type - self.iterator_type = iterator_type - super().__init__(position) - - def __str__(self) -> str: - return ( - f"{self.position}: Invalid iterator type {self.iterator_type} " - + f"to iterate over {self.iterable_type}.\n" - + "Iterator types need to have a function named next which:\n" - + "- takes one argument (the iterator)\n" - + "- returns at least 2 values, the last being a boolean\n" - + "- indicates if more data is present in the iterable with " - + "this last return value\n" - + "- for const iterators all return values of `next` except " - + "the last one must be const" - ) - - -class ForeachLoopTypeError(TypeCheckerException): - def __init__( - self, - position: Position, - expected_type_stack_after: list[VariableType | FunctionPointer], - type_stack_after: list[VariableType | FunctionPointer], - ) -> None: - self.type_stack_after = type_stack_after - self.expected_type_stack_after = expected_type_stack_after - super().__init__(position) - - def __str__(self) -> str: - stack_after = format_typestack(self.type_stack_after) - expected_stack_after = format_typestack(self.expected_type_stack_after) - - return ( - f"{self.position}: Invalid stack modification inside foreach loop body\n" - + f"stack at end of foreach loop: {stack_after}\n" - + f" expected stack: {expected_stack_after}" - ) - - -class UseBlockStackUnderflow(TypeCheckerException): - def __init__(self, stack_size: int, use_block: UseBlock) -> None: - self.stack_size = stack_size - self.use_block_vars = len(use_block.variables) - super().__init__(use_block.position) - - def __str__(self) -> str: - return ( - f"{self.position}: Use block consumes more values " - + "than can be found on the stack\n" - + f" stack size: {self.stack_size}\n" - + f"used variables: {self.use_block_vars}" - ) - - -class AssignmentTypeError(TypeCheckerException): - def __init__( - self, - expected_var_types: list[VariableType | FunctionPointer], - found_var_types: list[VariableType | FunctionPointer], - assignment: Assignment, - ) -> None: - self.expected_var_types = expected_var_types - self.found_var_types = found_var_types - super().__init__(assignment.position) - - def __str__(self) -> str: - return ( - f"{self.position}: Assignment with wrong number and/or type of values\n" - + "expected types: " - + " ".join(str(var_type) for var_type in self.expected_var_types) - + "\n" - + " found types: " - + " ".join(str(var_type) for var_type in self.found_var_types) - ) - - -class UpdateConstStructError(TypeCheckerException): - def __init__(self, field_update: StructFieldUpdate, struct_name: str) -> None: - self.field_name = field_update.field_name.value - self.struct_name = struct_name - super().__init__(field_update.position) - - def __str__(self) -> str: - return ( - f"{self.position}: Cannot update field {self.field_name} on " - + f"const struct {self.struct_name}" - ) - - -class AssignConstValueError(TypeCheckerException): - def __init__(self, var: Variable, type: VariableType) -> None: - self.var_name = var.name - self.type = type - super().__init__(var.position) - - def __str__(self) -> str: - return f"{self.position}: Cannot assign to {self.type} {self.var_name}" - - -class MemberFunctionTypeNotFound(TypeCheckerException): - def __init__(self, function: Function) -> None: - self.function = function - - def __str__(self) -> str: - struct_name = self.function.struct_name - return ( - f"{self.function.position}: Cannot find type {struct_name} in " - + "same file as member function definition." - ) - - -class InvalidEqualsFunctionSignature(TypeCheckerException): - def __init__(self, function: Function) -> None: - self.function = function - - def __str__(self) -> str: - return ( - f"{self.function.position}: Invalid equals function signature.\n" - + "An equals function should have:\n" - + "- 2 const arguments of same type\n" - + "- 1 return value, which is a boolean" - ) - - -class UnreachableCode(TypeCheckerException): - def __str__(self) -> str: - return f"{self.position}: Found unreachable code." - - -class ReturnTypesError(TypeCheckerException): - def __init__( - self, - position: Position, - type_stack: list[VariableType | FunctionPointer], - function: Function, - ) -> None: - self.type_stack = type_stack - self.function = function - super().__init__(position) - - def __str__(self) -> str: - return ( - f"{self.position}: Invalid stack types when returning.\n" - + f"function returns: {format_typestack(self.function.return_types)}\n" - + f" found stack: {format_typestack(self.type_stack)}" - ) - - -class MatchTypeError(TypeCheckerException): - def __init__( - self, - match_block: MatchBlock, - type_stack: list[VariableType | FunctionPointer], - ) -> None: - self.match_block = match_block - self.type_stack = type_stack - super().__init__(match_block.position) - - def __str__(self) -> str: - return ( - f"{self.position}: Cannot match on this stack:\n" - + "expected stack types: \n" - + f" found stack types: {format_typestack(self.type_stack)}" - ) - - -class CaseEnumTypeError(TypeCheckerException): - def __init__( - self, case_block: CaseBlock, expected_enum: Enum, found_enum: Enum - ) -> None: - self.match_block = case_block - self.expected_enum = expected_enum - self.found_enum = found_enum - super().__init__(case_block.position) - - def __str__(self) -> str: - return ( - f"{self.position}: Cannot use case for enum {self.found_enum.name} " - + f"when matching on enum {self.expected_enum.name}" - ) - - -def describe_block(block: CaseBlock | DefaultBlock) -> str: - if isinstance(block, CaseBlock): - return f"case {block.enum_type.name}:{block.variant_name}" - else: - return "default" - - -class CaseStackTypeError(TypeCheckerException): - def __init__( - self, - blocks: list[CaseBlock | DefaultBlock], - block_type_stacks: list[list[VariableType | FunctionPointer] | Never], - ) -> None: - self.blocks = blocks - self.block_type_stacks = block_type_stacks - super().__init__(blocks[0].position) - - def __str__(self) -> str: - message = "Inconsistent stack types for match cases:\n" - - for block, type_stack in zip(self.blocks, self.block_type_stacks, strict=True): - description = describe_block(block) - - message += ( - f"{block.position}: ({description}) {format_typestack(type_stack)}\n" - ) - - return message.removesuffix("\n") - - -class DuplicateCase(TypeCheckerException): - def __init__( - self, first: CaseBlock | DefaultBlock, second: CaseBlock | DefaultBlock - ) -> None: - self.first = first - self.second = second - - def __str__(self) -> str: - return ( - "Duplicate case found in match block:\n" - + f"{self.first.position}: {describe_block(self.first)}\n" - + f"{self.second.position}: {describe_block(self.second)}" - ) - - -class MissingEnumCases(TypeCheckerException): - def __init__( - self, - match_block: MatchBlock, - enum_type: Enum, - missing_variants: set[str], - ) -> None: - self.match_block = match_block - self.missing_variants = missing_variants - self.enum_type = enum_type - - def __str__(self) -> str: - return ( - f"{self.match_block.position}: Missing cases " - + f"for enum {self.enum_type.name}.\n" - + "\n".join( - [ - f"- {self.enum_type.name}:{variant}" - for variant in self.missing_variants - ] - ) - ) - - -class UnreachableDefaultBlock(TypeCheckerException): - def __init__(self, block: DefaultBlock) -> None: - self.block = block - - def __str__(self) -> str: - return f"{self.block.position}: Unreachable default block." - - -class CaseAsArgumentCountError(TypeCheckerException): - def __init__(self, case_block: CaseBlock, associated_items: int): - self.case_block = case_block - self.associated_items = associated_items - - def __str__(self) -> str: - expected_args = len(self.case_block.variables) - - return ( - f"{self.case_block.position}: Unexpected number of case-arguments.\n" - + f"Expected arguments: {expected_args}\n" - + f" Found arguments: {self.associated_items}" - ) - - -class UnknownVariableOrFunction(TypeCheckerException): - def __init__(self, var_name: str, position: Position) -> None: - self.name = var_name - super().__init__(position) - - def __str__(self) -> str: - return f"{self.position}: Usage of unknown variable or function {self.name}" - - -class UnsupportedOperator(TypeCheckerException): - def __init__(self, type_name: str, operator: str, position: Position) -> None: - self.type_name = type_name - self.operator = operator - super().__init__(position) - - def __str__(self) -> str: - return ( - f"{self.position}: Type {self.type_name} " - + f"does not support operator {self.operator}" - ) - - -class CollidingVariable(TypeCheckerException): - def __init__( - self, - lhs: Variable | Argument, - rhs: Variable | Argument | Struct | Enum | Function, - ) -> None: - def sort_key( - item: Variable | Argument | Struct | Function | Enum, - ) -> tuple[int, int]: - return (item.position.line, item.position.column) - - self.colliding = sorted([lhs, rhs], key=sort_key) - - def __str__(self) -> str: - msg = "Found name collision:\n" - - for item in self.colliding: - msg += f"{item.position}: {describe(item)}\n" - - return msg.removesuffix("\n") - - -class InvalidCallWithTypeParameters(TypeCheckerException): - def __init__(self, call_var: CallVariable, var: Variable | Argument) -> None: - self.call_var = call_var - self.var = var - super().__init__(call_var.position) - - def __str__(self) -> str: - if isinstance(self.var, Argument): - object = "argument" - else: - assert isinstance(self.var, Variable) - object = "variable" - - return ( - f"{self.position}: Cannot use {object} {self.call_var.name} " - + "with type parameters" - ) - - -class UseFieldOfEnumException(TypeCheckerException): - def __init__(self, node: StructFieldQuery | StructFieldUpdate) -> None: - self.node = node - super().__init__(node.position) - - def __str__(self) -> str: - if isinstance(self.node, StructFieldQuery): - get_set = "get" - else: - get_set = "set" - - return f"{self.position}: Cannot {get_set} field on Enum" - - -class UseFieldOfFunctionPointerException(TypeCheckerException): - def __init__(self, node: StructFieldQuery | StructFieldUpdate) -> None: - self.node = node - super().__init__(node.position) - - def __str__(self) -> str: - if isinstance(self.node, StructFieldQuery): - get_set = "get" - else: - get_set = "set" - - return f"{self.position}: Cannot {get_set} field on FunctionPointer" - - -class UnexpectedTypeParameterCount(TypeCheckerException): - def __init__( - self, - position: Position, - expected_param_count: int, - found_param_count: int, - ) -> None: - self.position = position - self.expected_param_count = expected_param_count - self.found_param_count = found_param_count - super().__init__(position) - - def __str__(self) -> str: - return ( - f"{self.position}: Unexpected number of type parameters\n" - + f"Expected parameter count: {self.expected_param_count}\n" - + f" Found parameter count: {self.found_param_count}" - ) - - -class SignatureItemMismatch(AaaException): - """ - Raised when stack doesn't match the signature of a function. - """ - - ... diff --git a/python/aaa/type_checker/models.py b/python/aaa/type_checker/models.py deleted file mode 100644 index 11074fa0..00000000 --- a/python/aaa/type_checker/models.py +++ /dev/null @@ -1,14 +0,0 @@ -from basil.models import Position - -from aaa import AaaModel -from aaa.cross_referencer.models import FunctionPointer, Never, VariableType - - -class TypeCheckerOutput(AaaModel): - def __init__( - self, - foreach_loop_stacks: dict[ - Position, list[VariableType | FunctionPointer] | Never - ], - ) -> None: - self.position_stacks = foreach_loop_stacks diff --git a/python/aaa/type_checker/type_checker.py b/python/aaa/type_checker/type_checker.py deleted file mode 100644 index e299118c..00000000 --- a/python/aaa/type_checker/type_checker.py +++ /dev/null @@ -1,1300 +0,0 @@ -from collections.abc import Callable, Mapping -from copy import copy, deepcopy -from pathlib import Path -from typing import Any - -from basil.models import Position - -from aaa.cross_referencer.exceptions import UnexpectedTypeParameterCount -from aaa.cross_referencer.models import ( - Argument, - Assignment, - BooleanLiteral, - Branch, - CallEnumConstructor, - CallFunction, - CallFunctionByPointer, - CallType, - CallVariable, - CaseBlock, - CharacterLiteral, - CrossReferencerOutput, - DefaultBlock, - Enum, - EnumConstructor, - ForeachLoop, - Function, - FunctionBody, - FunctionBodyItem, - FunctionPointer, - GetFunctionPointer, - IntegerLiteral, - MatchBlock, - Never, - Return, - StringLiteral, - Struct, - StructFieldQuery, - StructFieldUpdate, - UseBlock, - Variable, - VariableType, - WhileLoop, -) -from aaa.runner.exceptions import AaaTranslationException -from aaa.type_checker.exceptions import ( - AssignConstValueError, - AssignmentTypeError, - BranchTypeError, - CaseAsArgumentCountError, - CaseEnumTypeError, - CaseStackTypeError, - CollidingVariable, - ConditionTypeError, - DuplicateCase, - ForeachLoopTypeError, - FunctionTypeError, - InvalidCallWithTypeParameters, - InvalidEqualsFunctionSignature, - InvalidIterable, - InvalidIterator, - InvalidMainSignuture, - InvalidMemberFunctionSignature, - InvalidTestSignuture, - MainFunctionNotFound, - MatchTypeError, - MemberFunctionTypeNotFound, - MissingEnumCases, - MissingIterable, - ReturnTypesError, - SignatureItemMismatch, - StackTypesError, - StructUpdateStackError, - StructUpdateTypeError, - TypeCheckerException, - UnknownField, - UnknownVariableOrFunction, - UnreachableCode, - UnreachableDefaultBlock, - UnsupportedOperator, - UpdateConstStructError, - UseBlockStackUnderflow, - UseFieldOfEnumException, - UseFieldOfFunctionPointerException, - WhileLoopTypeError, - format_typestack, -) -from aaa.type_checker.models import TypeCheckerOutput - - -class TypeChecker: - def __init__( - self, cross_referencer_output: CrossReferencerOutput, verbose: bool - ) -> None: - self.functions = cross_referencer_output.functions - self.types: dict[tuple[Path, str], Struct | Enum] = ( - cross_referencer_output.structs | cross_referencer_output.enums - ) - self.imports = cross_referencer_output.imports - self.builtins_path = cross_referencer_output.builtins_path - self.entrypoint = cross_referencer_output.entrypoint - self.exceptions: list[TypeCheckerException] = [] - self.position_stacks: dict[ - Position, list[VariableType | FunctionPointer] | Never - ] = {} - self.verbose = verbose - - def run(self) -> TypeCheckerOutput: - for function in self.functions.values(): - if self._is_builtin(function): - # builtins can't be type-checked - continue - - checker = SingleFunctionTypeChecker(function, self) - - try: - checker.run() - except TypeCheckerException as e: - self.exceptions.append(e) - finally: - self.position_stacks.update(checker.position_stacks) - - self._print_position_stacks() - - try: - self._check_main_function() - except TypeCheckerException as e: - self.exceptions.append(e) - - if self.exceptions: - raise AaaTranslationException(self.exceptions) - - return TypeCheckerOutput(self.position_stacks) - - def _print_position_stacks(self) -> None: - if not self.verbose: - return - - for position in sorted(self.position_stacks.keys()): - type_stack: list[ - VariableType | FunctionPointer - ] | Never = self.position_stacks[position] - - formatted_position = str(position) - formatted_stack = format_typestack(type_stack) - - if len(formatted_position) > 40: - formatted_position = "…" + formatted_position[-39:] - - print(f"type checker | {formatted_position:>40} | {formatted_stack}") - - def _is_builtin(self, function: Function) -> bool: - return function.position.file == self.builtins_path - - def _check_main_function(self) -> None: - try: - function = self.functions[(self.entrypoint, "main")] - except KeyError as e: - raise MainFunctionNotFound(self.entrypoint) from e - - main_arguments_ok = False - - if len(function.arguments) == 0: - main_arguments_ok = True - - if ( - len(function.arguments) == 1 - and isinstance(function.arguments[0].type, VariableType) - and function.arguments[0].type.name == "vec" - and len(function.arguments[0].type.params) == 1 - and isinstance(function.arguments[0].type.params[0], VariableType) - and function.arguments[0].type.params[0].name == "str" - ): - main_arguments_ok = True - - main_return_type_ok = False - - if isinstance(function.return_types, Never): - # It's fine if main never returns. - main_return_type_ok = True - else: - if len(function.return_types) == 0: - main_return_type_ok = True - - if ( - len(function.return_types) == 1 - and isinstance(function.return_types[0], VariableType) - and function.return_types[0].name == "int" - ): - main_return_type_ok = True - - if not all( - [ - main_arguments_ok, - main_return_type_ok, - len(function.type_params) == 0, - ] - ): - raise InvalidMainSignuture(function.position) - - -class SingleFunctionTypeChecker: - def __init__(self, function: Function, type_checker: TypeChecker) -> None: - self.function = function - - self.types = type_checker.types - self.functions = type_checker.functions - self.builtins_path = type_checker.builtins_path - self.vars: dict[ - str, tuple[Variable | Argument, VariableType | FunctionPointer] - ] = {arg.name: (arg, arg.type) for arg in function.arguments} - self.verbose = type_checker.verbose - - # NOTE: we keep track of stacks per position. - # This is useful for the Transpiler and for debugging. - self.position_stacks: dict[ - Position, list[VariableType | FunctionPointer] | Never - ] = {} - - def run(self) -> None: - assert self.function.body - - if self.function.is_test(): # pragma: nocover - self._check_test_function() - - if self.function.is_member_function(): - self._check_member_function() - - computed_return_types = self._check_function_body(self.function.body, []) - - function_end_position = self.function.end_position - assert function_end_position - - # NOTE: add computed return types so we can log it for debugging - self.position_stacks[function_end_position] = computed_return_types - - if not self._confirm_return_types(computed_return_types): - raise FunctionTypeError(self.function, computed_return_types) - - def _confirm_return_types( - self, computed: list[VariableType | FunctionPointer] | Never - ) -> bool: - expected = self.function.return_types - - if isinstance(computed, Never): - # If we never return, it doesn't matter - # what the signature promised to return. - return True - - if isinstance(expected, Never): - return False - - if len(expected) != len(computed): - return False - - for expected_value, computed_value in zip(expected, computed, strict=True): - if isinstance(expected_value, VariableType): - if not isinstance(computed_value, VariableType): - return False - - if computed_value.type != expected_value.type: - return False - - if computed_value.params != expected_value.params: - return False - - if expected_value.is_const and not computed_value.is_const: - return False - else: - assert isinstance(expected_value, FunctionPointer) - if not isinstance(computed_value, FunctionPointer): - return False - - if computed_value.argument_types != expected_value.argument_types: - return False - - if computed_value.return_types != expected_value.return_types: - return False - return True - - def _match_signature_items( - self, - expected_var_type: VariableType | FunctionPointer, - var_type: VariableType | FunctionPointer, - placeholder_types: dict[str, VariableType | FunctionPointer], - ) -> dict[str, VariableType | FunctionPointer]: - if isinstance(expected_var_type, FunctionPointer): - if not isinstance(var_type, FunctionPointer): - raise SignatureItemMismatch - - if expected_var_type != var_type: - raise SignatureItemMismatch - - return placeholder_types - - if expected_var_type.is_placeholder: - if expected_var_type.name in placeholder_types: - if placeholder_types[expected_var_type.name] == var_type: - return placeholder_types - - if isinstance(var_type, VariableType): - non_const_var_type = copy(var_type) - non_const_var_type.is_const = False - - if placeholder_types[expected_var_type.name] == non_const_var_type: - return placeholder_types - - raise SignatureItemMismatch - - placeholder_types[expected_var_type.name] = var_type - return placeholder_types - - else: - if not isinstance(var_type, VariableType): - raise SignatureItemMismatch - - if expected_var_type.type != var_type.type: - raise SignatureItemMismatch - - if len(var_type.params) != len(expected_var_type.params): - raise SignatureItemMismatch - - for expected_param, param in zip( - expected_var_type.params, var_type.params, strict=True - ): - placeholder_types = self._match_signature_items( - expected_param, param, placeholder_types - ) - - if var_type.is_const and not expected_var_type.is_const: - # Cannot use const value as non-const argument - raise SignatureItemMismatch - - return placeholder_types - - def _apply_placeholders_in_type( - self, - type: VariableType | FunctionPointer, - placeholder_types: Mapping[str, VariableType | FunctionPointer], - ) -> VariableType | FunctionPointer: - if isinstance(type, FunctionPointer): - # TODO #154 Support using function parameters in function pointer values - return type - - type = deepcopy(type) - - if type.is_placeholder: - updated_return_type = copy(placeholder_types[type.name]) - - if isinstance(updated_return_type, VariableType) and type.is_const: - updated_return_type.is_const = True - - return updated_return_type - - for i, param in enumerate(type.params): - type.params[i] = self._apply_placeholders_in_type(param, placeholder_types) - - return type - - def _get_builtin_var_type(self, type_name: str) -> VariableType: - type = self.types[(self.builtins_path, type_name)] - return VariableType(type, [], False, type.position, False) - - def _get_bool_var_type(self) -> VariableType: - return self._get_builtin_var_type("bool") - - def _get_str_var_type(self) -> VariableType: - return self._get_builtin_var_type("str") - - def _get_char_var_type(self) -> VariableType: - return self._get_builtin_var_type("char") - - def _get_int_var_type(self) -> VariableType: - return self._get_builtin_var_type("int") - - def _check_integer_literal( - self, - literal: IntegerLiteral, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer]: - return type_stack + [self._get_int_var_type()] - - def _check_string_literal( - self, - literal: StringLiteral, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer]: - return type_stack + [self._get_str_var_type()] - - def _check_character_literal( - self, - literal: CharacterLiteral, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer]: - return type_stack + [self._get_char_var_type()] - - def _check_boolean_literal( - self, - literal: BooleanLiteral, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer]: - return type_stack + [self._get_bool_var_type()] - - def _check_get_function_pointer( - self, - get_func_ptr: GetFunctionPointer, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer]: - target = get_func_ptr.target - - argument_types: list[VariableType | FunctionPointer] | Never - return_types: list[VariableType | FunctionPointer] | Never - - if isinstance(target, Function): - argument_types = [arg.type for arg in target.arguments] - return_types = target.return_types - else: - assert isinstance(target, EnumConstructor) - argument_types = target.enum.variants[target.variant_name] - enum_var_type = VariableType(target.enum, [], False, target.position, False) - return_types = [enum_var_type] - - func_ptr = FunctionPointer(get_func_ptr.position, argument_types, return_types) - - return type_stack + [func_ptr] - - def _check_condition( - self, - function_body: FunctionBody, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - # Condition is a special type of function body: - # It should push exactly one boolean and not modify the type stack under it - condition_stack = self._check_function_body(function_body, copy(type_stack)) - - if isinstance(condition_stack, Never): - return Never() - - if condition_stack != type_stack + [self._get_bool_var_type()]: - raise ConditionTypeError( - function_body.position, type_stack, condition_stack - ) - - return condition_stack - - def _check_branch( - self, branch: Branch, type_stack: list[VariableType | FunctionPointer] - ) -> list[VariableType | FunctionPointer] | Never: - condition_stack = self._check_condition(branch.condition, copy(type_stack)) - - if isinstance(condition_stack, Never): - return Never() - - # The bool pushed by the condition is removed when evaluated, - # so we can use type_stack as the stack for both the if- and else- bodies. - if_stack = self._check_function_body(branch.if_body, copy(type_stack)) - - if branch.else_body: - else_stack = self._check_function_body(branch.else_body, copy(type_stack)) - else: - else_stack = copy(type_stack) - - # If a branch doesn't return, the return type will be whatever - # the other one returns. This works even if neither branch returns. - if isinstance(if_stack, Never): - return else_stack - - if isinstance(else_stack, Never): - return if_stack - - # Regardless whether the if- or else- branch is taken, - # afterwards the stack should be the same. - if if_stack != else_stack: - raise BranchTypeError(branch.position, type_stack, if_stack, else_stack) - - # we can return either one, since they are the same - return if_stack - - def _check_while_loop( - self, - while_loop: WhileLoop, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - condition_stack = self._check_condition(while_loop.condition, copy(type_stack)) - - if isinstance(condition_stack, Never): - return Never() - - # The bool pushed by the condition is removed when evaluated, - # so we can use type_stack as the stack for the loop body. - body_stack = self._check_function_body(while_loop.body, copy(type_stack)) - - if isinstance(body_stack, Never): - return Never() - - if body_stack != type_stack: - raise WhileLoopTypeError(while_loop.position, type_stack, body_stack) - - condition_items = while_loop.condition.items - - if ( - len(condition_items) == 1 - and isinstance(condition_items[0], BooleanLiteral) - and condition_items[0].value - ): - # We found a `while true` loop - return Never() - - return body_stack - - def _check_function_body( - self, - function_body: FunctionBody, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - checkers: dict[ - type[FunctionBodyItem], - Callable[ - [Any, list[VariableType | FunctionPointer]], - list[VariableType | FunctionPointer] | Never, - ], - ] = { - Assignment: self._check_assignment, - BooleanLiteral: self._check_boolean_literal, - Branch: self._check_branch, - CallFunction: self._check_call_function, - CallType: self._check_call_type, - CallVariable: self._check_call_variable, - CallEnumConstructor: self._check_call_enum_constructor, - CharacterLiteral: self._check_character_literal, - ForeachLoop: self._check_foreach_loop, - FunctionPointer: self._check_function_pointer, - IntegerLiteral: self._check_integer_literal, - Return: self._check_return, - StringLiteral: self._check_string_literal, - StructFieldQuery: self._check_struct_field_query, - StructFieldUpdate: self._check_struct_field_update, - UseBlock: self._check_use_block, - WhileLoop: self._check_while_loop, - MatchBlock: self._check_match_block, - CallFunctionByPointer: self._check_call_by_function_pointer, - GetFunctionPointer: self._check_get_function_pointer, - } - - assert set(checkers.keys()) == set(FunctionBodyItem.__args__) # type: ignore - - stack = copy(type_stack) - for item_offset, item in enumerate(function_body.items): - stack = copy(stack) - - self.position_stacks[item.position] = copy(stack) - - checker = checkers[type(item)] - checked = checker(item, stack) - - if isinstance(checked, Never): - # Items following an item that never returns are dead code - if item_offset != len(function_body.items) - 1: - next_item = function_body.items[item_offset + 1] - raise UnreachableCode(next_item.position) - - return Never() - - stack = checked - - return stack - - def _check_case_block( - self, - block: CaseBlock, - type_stack: list[VariableType | FunctionPointer], - enum_type: Enum, - enum_var_type: VariableType, - ) -> list[VariableType | FunctionPointer] | Never: - if block.enum_type != enum_type: - raise CaseEnumTypeError(block, enum_type, block.enum_type) - - variant_name = block.variant_name - placeholders = enum_type.param_dict(enum_var_type) - - # The variant name is checked in the cross referencer so it cannot fail here. - associated_data = [ - self._apply_placeholders_in_type(item, placeholders) - for item in enum_type.variants[variant_name] - ] - - if block.variables: - if len(block.variables) != len(associated_data): - raise CaseAsArgumentCountError(block, len(associated_data)) - - for var, type_stack_item in zip( - block.variables, associated_data, strict=True - ): - if var.name in self.vars: - raise CollidingVariable(var, self.vars[var.name][0]) - - colliding = self._find_var_name_collision(var) - - if colliding: - raise CollidingVariable(var, colliding) - - self.vars[var.name] = (var, type_stack_item) - - # NOTE: all pushed associated data is immediately used, - # so we don't modify block_type_stack - - else: - type_stack += associated_data - - return_type_stack = self._check_function_body(block.body, type_stack) - - for var in block.variables: - del self.vars[var.name] - - return return_type_stack - - def _get_block_type_stacks( - self, - match_block: MatchBlock, - type_stack: list[VariableType | FunctionPointer], - enum_type: Enum, - ) -> tuple[list[list[VariableType | FunctionPointer] | Never], DefaultBlock | None]: - block_type_stacks: list[list[VariableType | FunctionPointer] | Never] = [] - found_default_block: DefaultBlock | None = None - - enum_var_type = type_stack[-1] - assert isinstance(enum_var_type, VariableType) - - for block in match_block.blocks: - block_type_stack: list[VariableType | FunctionPointer] | Never = copy( - type_stack[:-1] - ) - assert not isinstance(block_type_stack, Never) - - if isinstance(block, CaseBlock): - block_type_stack = self._check_case_block( - block, block_type_stack, enum_type, enum_var_type - ) - - else: - if found_default_block: - raise DuplicateCase(found_default_block, block) - - found_default_block = block - block_type_stack = self._check_function_body( - block.body, block_type_stack - ) - - block_type_stacks.append(block_type_stack) - return block_type_stacks, found_default_block - - def _check_match_block( - self, - match_block: MatchBlock, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - try: - matched_var_type = type_stack[-1] - except IndexError as e: - raise MatchTypeError(match_block, type_stack) from e - - if not isinstance(matched_var_type, VariableType): - raise MatchTypeError(match_block, type_stack) - - if not isinstance(matched_var_type.type, Enum): - raise MatchTypeError(match_block, type_stack) - - enum_type = matched_var_type.type - - found_enum_variants: dict[str, CaseBlock] = {} - - for block in match_block.blocks: - if isinstance(block, CaseBlock): - if block.enum_type != enum_type: - raise CaseEnumTypeError(block, enum_type, block.enum_type) - - if block.variant_name in found_enum_variants: - colliding_case_block = found_enum_variants[block.variant_name] - raise DuplicateCase(colliding_case_block, block) - - found_enum_variants[block.variant_name] = block - - block_type_stacks, found_default_block = self._get_block_type_stacks( - match_block, type_stack, enum_type - ) - - missing_enum_variants = set(enum_type.variants.keys()) - set( - found_enum_variants.keys() - ) - - if missing_enum_variants and not found_default_block: - raise MissingEnumCases(match_block, enum_type, missing_enum_variants) - - if not missing_enum_variants and found_default_block: - raise UnreachableDefaultBlock(found_default_block) - - match_stack: Never | list[VariableType | FunctionPointer] = Never() - - for block_type_stack in block_type_stacks: - if isinstance(block_type_stack, Never): - continue - - if isinstance(match_stack, Never): - match_stack = block_type_stack - continue - - if match_stack != block_type_stack: - raise CaseStackTypeError(match_block.blocks, block_type_stacks) - - return match_stack - - def _check_return( - self, return_: Return, type_stack: list[VariableType | FunctionPointer] - ) -> Never: - if not self._confirm_return_types(type_stack): - raise ReturnTypesError(return_.position, type_stack, self.function) - - return Never() - - def _check_call_variable( - self, - call_var: CallVariable, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer]: - try: - var_or_arg, var_type = self.vars[call_var.name] - except KeyError as e: - raise UnknownVariableOrFunction(call_var.name, call_var.position) from e - - if call_var.has_type_params: - # Handles cases like: - # fn foo { 0 use c { c[b] } } - # fn foo args a as int { a[b] drop } - raise InvalidCallWithTypeParameters(call_var, var_or_arg) - - # Push variable on stack - return type_stack + [var_type] - - def __check_call_function( - self, - type_params: list[VariableType | FunctionPointer], - arguments: list[VariableType | FunctionPointer], - return_types: list[VariableType | FunctionPointer] | Never, - call: Function | EnumConstructor | CallFunctionByPointer, - position: Position, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - error_types_stack = self.position_stacks[position] - assert not isinstance(error_types_stack, Never) - - stack = copy(type_stack) - arg_count = len(arguments) - - if len(stack) < arg_count: - raise StackTypesError(position, error_types_stack, call) - - placeholder_types: dict[str, VariableType | FunctionPointer] = {} - - if isinstance(call, Function): - if len(type_params) == len(call.type_params): - for type_param_name, type_param in zip( - call.type_param_names, type_params, strict=True - ): - placeholder_types[type_param_name] = type_param - elif len(type_params) != 0: - # Unreachable, should be caught by CrossReferencer - raise NotImplementedError - - types = stack[len(stack) - arg_count :] - - for argument, type in zip(arguments, types, strict=True): - try: - placeholder_types = self._match_signature_items( - argument, copy(type), placeholder_types - ) - except SignatureItemMismatch as e: - expected_stack_top_override: list[ - VariableType | FunctionPointer - ] | None = None - - if isinstance(call, Function): - expected_stack_top_override = [ - self._apply_placeholders_in_type(item.type, placeholder_types) - for item in call.arguments - ] - - raise StackTypesError( - position, - error_types_stack, - call, - type_params=type_params, - expected_stack_top_override=expected_stack_top_override, - ) from e - - stack = stack[: len(stack) - arg_count] - - if isinstance(return_types, Never): - return Never() - - for return_type in return_types: - stack_item = self._apply_placeholders_in_type( - return_type, placeholder_types - ) - stack.append(stack_item) - - return stack - - def _get_equals_function( - self, - call_func: CallFunction, - type_stack: list[VariableType | FunctionPointer], - ) -> Function: - type = type_stack[-1] - - if isinstance(type, FunctionPointer): - return self.functions[(self.builtins_path, "fn_equals")] - - func_file = type.type.position.file - func_name = f"{type.name}:=" - - try: - return self.functions[(func_file, func_name)] - except KeyError as e: - raise UnsupportedOperator(type.name, "=", call_func.position) from e - - def _check_call_function( - self, - call_function: CallFunction, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - if call_function.function.name == "=": - call_function.function = self._get_equals_function( - call_function, type_stack - ) - - self.position_stacks[call_function.position] = copy(type_stack) - - arguments = [argument.type for argument in call_function.function.arguments] - - type_stack_afterwards = self.__check_call_function( - call_function.type_params, - arguments, - call_function.function.return_types, - call_function.function, - call_function.position, - type_stack, - ) - - called = call_function.function - - if called.position.file == self.builtins_path and called.name == "copy": - assert isinstance(type_stack_afterwards, list) - assert isinstance(type_stack_afterwards[-1], VariableType) - type_stack_afterwards[-1].is_const = False - - return type_stack_afterwards - - def _check_call_by_function_pointer( - self, - call_by_func_ptr: CallFunctionByPointer, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - self.position_stacks[call_by_func_ptr.position] = copy(type_stack) - - try: - func_ptr = type_stack.pop() - except IndexError as e: - raise StackTypesError( - call_by_func_ptr.position, copy(type_stack), call_by_func_ptr - ) from e - - if not isinstance(func_ptr, FunctionPointer): - raise StackTypesError( - call_by_func_ptr.position, - type_stack + [func_ptr], - call_by_func_ptr, - ) - - return self.__check_call_function( - [], - func_ptr.argument_types, - func_ptr.return_types, - call_by_func_ptr, - call_by_func_ptr.position, - type_stack, - ) - - def _check_call_enum_constructor( - self, - call_enum_ctor: CallEnumConstructor, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - self.position_stacks[call_enum_ctor.position] = copy(type_stack) - - enum = call_enum_ctor.enum_ctor.enum - variant_name = call_enum_ctor.enum_ctor.variant_name - - found_param_count = len(call_enum_ctor.enum_var_type.params) - expected_param_count = len(enum.get_resolved().type_params) - - if expected_param_count != found_param_count: - raise UnexpectedTypeParameterCount( - call_enum_ctor.position, expected_param_count, found_param_count - ) - - placeholder_types = enum.param_dict(call_enum_ctor.enum_var_type) - - variant_associated_data_after = [ - self._apply_placeholders_in_type(item, placeholder_types) - for item in enum.get_resolved().variants[variant_name] - ] - - return self.__check_call_function( - [], - variant_associated_data_after, - [call_enum_ctor.enum_var_type], - call_enum_ctor.enum_ctor, - call_enum_ctor.position, - type_stack, - ) - - def _check_call_type( - self, - call_type: CallType, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer]: - return type_stack + [call_type.var_type] - - def _check_test_function(self) -> None: - if isinstance(self.function.return_types, Never): - raise InvalidTestSignuture(self.function) - - if not all( - [ - len(self.function.arguments) == 0, - len(self.function.return_types) == 0, - len(self.function.type_params) == 0, - ] - ): - raise InvalidTestSignuture(self.function) - - def _check_member_function(self) -> None: - struct_type_key = ( - self.function.position.file, - self.function.struct_name, - ) - - try: - type = self.types[struct_type_key] - except KeyError as e: - raise MemberFunctionTypeNotFound(self.function) from e - - # Make sure first argument of member function is the associated type - if not ( - len(self.function.arguments) >= 1 - and isinstance(self.function.arguments[0].type, VariableType) - and self.function.arguments[0].type.type == type - ): - raise InvalidMemberFunctionSignature(self.function, type) - - if ( - self.function.func_name == "=" - and not self._has_valid_equals_member_function_signature() - ): - raise InvalidEqualsFunctionSignature(self.function) - - def _has_valid_equals_member_function_signature(self) -> bool: - # Equals member-function should: - # - take 2 const arguments of same type - # - return bool - - arguments = self.function.arguments - return_types = self.function.return_types - - return ( - len(arguments) == 2 - and isinstance(arguments[0].type, VariableType) - and isinstance(arguments[1].type, VariableType) - and arguments[0].type.is_const - and arguments[0].type == arguments[1].type - and isinstance(return_types, list) - and len(return_types) == 1 - and isinstance(return_types[0], VariableType) - and return_types[0].type == self._get_bool_var_type().type - ) - - def _get_struct_field_type( - self, - node: StructFieldQuery | StructFieldUpdate, - struct_var_type: VariableType | FunctionPointer, - ) -> VariableType | FunctionPointer: - if isinstance(struct_var_type, FunctionPointer): - raise UseFieldOfFunctionPointerException(node) - - struct = struct_var_type.type - - if isinstance(struct, Enum): - raise UseFieldOfEnumException(node) - - field_name = node.field_name.value - try: - field_type = struct.fields[field_name] - except KeyError as e: - raise UnknownField(node.position, struct, field_name) from e - - if isinstance(field_type, FunctionPointer): - return field_type - - if struct_var_type.is_const: - field_type = copy(field_type) - field_type.is_const = True - - struct_param_dict = struct.param_dict(struct_var_type) - - return self._apply_placeholders_in_type(field_type, struct_param_dict) - - def _check_struct_field_query( - self, - field_query: StructFieldQuery, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer]: - literal = StringLiteral(field_query.field_name) - self.position_stacks[literal.position] = copy(type_stack) - - self.position_stacks[field_query.operator_position] = copy(type_stack) - - if len(type_stack) < 1: - raise StackTypesError(field_query.position, type_stack, field_query) - - struct_var_type = type_stack[-1] - - field_type = self._get_struct_field_type(field_query, struct_var_type) - - # pop struct and field name, push field - return type_stack[:-1] + [field_type] - - def _check_struct_field_update( - self, - field_update: StructFieldUpdate, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - literal = StringLiteral(field_update.field_name) - self.position_stacks[literal.position] = copy(type_stack) - - type_stack_before = type_stack - type_stack_after = self._check_function_body( - field_update.new_value_expr, copy(type_stack_before) - ) - self.position_stacks[field_update.operator_position] = copy(type_stack_after) - - if isinstance(type_stack_after, Never): - return type_stack_after - - type_stack = type_stack_after - - if len(type_stack) < 2: - raise StackTypesError(field_update.position, type_stack, field_update) - - struct_var_type, update_expr_type = type_stack[-2:] - - if isinstance(struct_var_type, FunctionPointer): - raise UseFieldOfFunctionPointerException(field_update) - - struct_type = struct_var_type.type - - if isinstance(struct_type, Enum): - raise UseFieldOfEnumException(field_update) - - if not all( - [ - len(type_stack_before) == len(type_stack) - 1, - type_stack_before == type_stack[:-1], - ] - ): - raise StructUpdateStackError( - field_update.position, type_stack, type_stack_before - ) - - if struct_var_type.is_const: - raise UpdateConstStructError(field_update, struct_type.name) - - field_type = self._get_struct_field_type(field_update, struct_var_type) - - if field_type != update_expr_type: - raise StructUpdateTypeError( - position=field_update.new_value_expr.position, - type_stack=type_stack, - struct_type=struct_type, - field_name=field_update.field_name.value, - found_type=update_expr_type, - expected_type=field_type, - ) - - # pop struct, field name and new value - return type_stack[:-2] - - def _lookup_function( - self, var_type: VariableType, func_name: str - ) -> Function | None: - # NOTE It is required that member funcitions are - # defined in same file as the type they operate on. - file = var_type.type.position.file - name = f"{var_type.name}:{func_name}" - return self.functions.get((file, name)) - - def _check_foreach_loop( - self, - foreach_loop: ForeachLoop, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - type_stack_before_foreach = copy(type_stack) - - if not type_stack: - raise MissingIterable(foreach_loop.position) - - iterable_type = type_stack[-1] - - if not isinstance(iterable_type, VariableType): - raise InvalidIterable(foreach_loop.position, iterable_type) - - if iterable_type.is_const: - iter_func = self._lookup_function(iterable_type, "const_iter") - else: - iter_func = self._lookup_function(iterable_type, "iter") - - if not iter_func: - raise InvalidIterable(foreach_loop.position, iterable_type) - - if isinstance(iter_func.return_types, Never): - raise InvalidIterable(foreach_loop.position, iterable_type) - - if not all( - [ - len(iter_func.arguments) == 1, - len(iter_func.return_types) == 1, - ] - ): - raise InvalidIterable(foreach_loop.position, iterable_type) - - dummy_path = Position(Path("/dev/null"), -1, -1) - call_function = CallFunction(iter_func, [], dummy_path) - - type_stack_after_iter = self._check_call_function(call_function, type_stack) - - assert isinstance(type_stack_after_iter, list) # This was checked earlier - type_stack = type_stack_after_iter - - # duplicate iterator - iterator_var_type = type_stack[-1] - - type_stack.append(iterator_var_type) - - iterator_type = iter_func.return_types[0] - - if not isinstance(iterator_type, VariableType): - raise InvalidIterator(foreach_loop.position, iterable_type, iterator_type) - - next_func = self._lookup_function(iterator_type, "next") - - if not next_func: - raise InvalidIterator(foreach_loop.position, iterable_type, iterator_type) - - if isinstance(next_func.return_types, Never): - raise InvalidIterator(foreach_loop.position, iterable_type, iterator_type) - - if not all( - [ - len(next_func.arguments) == 1, - len(next_func.return_types) >= 2, - next_func.return_types[-1] == self._get_bool_var_type(), - ] - ): - raise InvalidIterator(foreach_loop.position, iterable_type, iterator_type) - - if iterable_type.is_const: - for return_type in next_func.return_types[:-1]: - if isinstance(return_type, VariableType) and not return_type.is_const: - raise InvalidIterator( - foreach_loop.position, iterable_type, iterator_type - ) - - dummy_path = Position(Path("/dev/null"), -1, -1) - call_function = CallFunction(next_func, [], dummy_path) - - type_stack_after_next = self._check_call_function(call_function, type_stack) - - assert isinstance(type_stack_after_next, list) # This was checked earlier - type_stack = type_stack_after_next - - # boolean return value from next gets consumed by foreach construct - type_stack.pop() - - type_stack_after = self._check_function_body(foreach_loop.body, type_stack) - - if isinstance(type_stack_after, Never): - return Never() - - # foreach consumes iterable - expected_type_stack_after_foreach = type_stack_before_foreach[:-1] + [ - iterator_var_type - ] - - if type_stack_after != expected_type_stack_after_foreach: - raise ForeachLoopTypeError( - foreach_loop.position, - expected_type_stack_after_foreach, - type_stack_after, - ) - - return type_stack_after[:-1] - - def _find_var_name_collision( - self, var: Variable - ) -> Struct | Enum | Function | None: - builtins_key = (self.builtins_path, var.name) - key = (self.function.position.file, var.name) - - if builtins_key in self.types: - return self.types[builtins_key] - if key in self.types: - return self.types[key] - - if builtins_key in self.functions: - return self.functions[builtins_key] - if key in self.functions: - return self.functions[key] - - return None - - def _check_use_block( - self, - use_block: UseBlock, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - use_var_count = len(use_block.variables) - - if len(type_stack) < use_var_count: - raise UseBlockStackUnderflow(len(type_stack), use_block) - - for var, type_stack_item in zip( - use_block.variables, type_stack[-use_var_count:], strict=True - ): - if var.name in self.vars: - raise CollidingVariable(var, self.vars[var.name][0]) - - colliding = self._find_var_name_collision(var) - - if colliding: - raise CollidingVariable(var, colliding) - - self.vars[var.name] = (var, type_stack_item) - - type_stack = type_stack[:-use_var_count] - returned_type_stack = self._check_function_body(use_block.body, type_stack) - - for var in use_block.variables: - del self.vars[var.name] - - return returned_type_stack - - def _check_assignment( - self, - assignment: Assignment, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer] | Never: - assign_stack = self._check_function_body(assignment.body, []) - - expected_var_types: list[VariableType | FunctionPointer] = [] - - for var in assignment.variables: - try: - _, type = self.vars[var.name] - except KeyError as e: - raise UnknownVariableOrFunction(var.name, var.position) from e - - if isinstance(type, VariableType): - if type.is_const: - raise AssignConstValueError(var, type) - else: - assert isinstance(type, FunctionPointer) - - expected_var_types.append(type) - - if isinstance(assign_stack, Never): - return Never() - - if len(assign_stack) != len(expected_var_types): - raise AssignmentTypeError(expected_var_types, assign_stack, assignment) - - for expected, found in zip(expected_var_types, assign_stack, strict=True): - if expected != found: - raise AssignmentTypeError(expected_var_types, assign_stack, assignment) - - return type_stack - - def _check_function_pointer( - self, - func_ptr: FunctionPointer, - type_stack: list[VariableType | FunctionPointer], - ) -> list[VariableType | FunctionPointer]: - return type_stack + [func_ptr] diff --git a/python/manage.py b/python/manage.py deleted file mode 100755 index efab6905..00000000 --- a/python/manage.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env -S python3 -u - -from typing import Any - -import click - -from aaa.runner.runner import Runner -from aaa.runner.test_runner import TestRunner - - -@click.group() -def cli() -> None: - ... - - -@cli.command() -@click.argument("file_or_code", type=str) -@click.argument("binary_path", type=str) -@click.option("-v", "--verbose", is_flag=True) -@click.option("-t", "--runtime-type-checks", is_flag=True) -def compile(**kwargs: Any) -> None: - exit(Runner.compile_command(**kwargs)) - - -@cli.command() -@click.argument("file_or_code", type=str) -@click.option("-t", "--runtime-type-checks", is_flag=True) -@click.option("-v", "--verbose", is_flag=True) -@click.argument("args", nargs=-1) -def run(**kwargs: Any) -> None: - exit(Runner.run_command(**kwargs)) - - -@cli.command() -@click.argument("path", type=click.Path(exists=True)) -@click.option("-o", "--binary") -@click.option("-t", "--runtime-type-checks", is_flag=True) -@click.option("-v", "--verbose", is_flag=True) -def test(**kwargs: Any) -> None: - exit(TestRunner.test_command(**kwargs)) - - -if __name__ == "__main__": - cli() diff --git a/python/pdm.lock b/python/pdm.lock deleted file mode 100644 index 0c85bcab..00000000 --- a/python/pdm.lock +++ /dev/null @@ -1,497 +0,0 @@ -# This file is @generated by PDM. -# It is not intended for manual editing. - -[metadata] -groups = ["default", "dev"] -strategy = ["cross_platform"] -lock_version = "4.4" -content_hash = "sha256:2e3fd3c8e520be693705a76901a1b0875ac56ab2208022fbbae1e69b117ca9c2" - -[[package]] -name = "attrs" -version = "23.1.0" -requires_python = ">=3.7" -summary = "Classes Without Boilerplate" -files = [ - {file = "attrs-23.1.0-py3-none-any.whl", hash = "sha256:1f28b4522cdc2fb4256ac1a020c78acf9cba2c6b461ccd2c126f3aa8e8335d04"}, - {file = "attrs-23.1.0.tar.gz", hash = "sha256:6279836d581513a26f1bf235f9acd333bc9115683f14f7e8fae46c98fc50e015"}, -] - -[[package]] -name = "basil-parser" -version = "0.4.3" -requires_python = ">=3.11" -summary = "Parser library using JSON file to describe language." -files = [ - {file = "basil_parser-0.4.3-py3-none-any.whl", hash = "sha256:26e64f01aeb2d7c8d26266e71b79ebecb6857579e4fdcbd479350470059c79a4"}, - {file = "basil_parser-0.4.3.tar.gz", hash = "sha256:2835fda231bc7a58eec60bbd607d004187836414456c24d63f756b3f085f3797"}, -] - -[[package]] -name = "certifi" -version = "2023.11.17" -requires_python = ">=3.6" -summary = "Python package for providing Mozilla's CA Bundle." -files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, -] - -[[package]] -name = "cfgv" -version = "3.4.0" -requires_python = ">=3.8" -summary = "Validate configuration and produce human readable error messages." -files = [ - {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, - {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, -] - -[[package]] -name = "charset-normalizer" -version = "3.3.2" -requires_python = ">=3.7.0" -summary = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, -] - -[[package]] -name = "click" -version = "8.1.7" -requires_python = ">=3.7" -summary = "Composable command line interface toolkit" -dependencies = [ - "colorama; platform_system == \"Windows\"", -] -files = [ - {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, - {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, -] - -[[package]] -name = "colorama" -version = "0.4.6" -requires_python = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -summary = "Cross-platform colored terminal text." -files = [ - {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, - {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, -] - -[[package]] -name = "coverage" -version = "7.3.3" -requires_python = ">=3.8" -summary = "Code coverage measurement for Python" -files = [ - {file = "coverage-7.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a2ac4245f18057dfec3b0074c4eb366953bca6787f1ec397c004c78176a23d56"}, - {file = "coverage-7.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9191be7af41f0b54324ded600e8ddbcabea23e1e8ba419d9a53b241dece821d"}, - {file = "coverage-7.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c0b1b8b5a4aebf8fcd227237fc4263aa7fa0ddcd4d288d42f50eff18b0bac4"}, - {file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee453085279df1bac0996bc97004771a4a052b1f1e23f6101213e3796ff3cb85"}, - {file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1191270b06ecd68b1d00897b2daddb98e1719f63750969614ceb3438228c088e"}, - {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:007a7e49831cfe387473e92e9ff07377f6121120669ddc39674e7244350a6a29"}, - {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:af75cf83c2d57717a8493ed2246d34b1f3398cb8a92b10fd7a1858cad8e78f59"}, - {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:811ca7373da32f1ccee2927dc27dc523462fd30674a80102f86c6753d6681bc6"}, - {file = "coverage-7.3.3-cp312-cp312-win32.whl", hash = "sha256:733537a182b5d62184f2a72796eb6901299898231a8e4f84c858c68684b25a70"}, - {file = "coverage-7.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:e995efb191f04b01ced307dbd7407ebf6e6dc209b528d75583277b10fd1800ee"}, - {file = "coverage-7.3.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:d299d379b676812e142fb57662a8d0d810b859421412b4d7af996154c00c31bb"}, - {file = "coverage-7.3.3.tar.gz", hash = "sha256:df04c64e58df96b4427db8d0559e95e2df3138c9916c96f9f6a4dd220db2fdb7"}, -] - -[[package]] -name = "coverage" -version = "7.3.3" -extras = ["toml"] -requires_python = ">=3.8" -summary = "Code coverage measurement for Python" -dependencies = [ - "coverage==7.3.3", -] -files = [ - {file = "coverage-7.3.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a2ac4245f18057dfec3b0074c4eb366953bca6787f1ec397c004c78176a23d56"}, - {file = "coverage-7.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f9191be7af41f0b54324ded600e8ddbcabea23e1e8ba419d9a53b241dece821d"}, - {file = "coverage-7.3.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31c0b1b8b5a4aebf8fcd227237fc4263aa7fa0ddcd4d288d42f50eff18b0bac4"}, - {file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee453085279df1bac0996bc97004771a4a052b1f1e23f6101213e3796ff3cb85"}, - {file = "coverage-7.3.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1191270b06ecd68b1d00897b2daddb98e1719f63750969614ceb3438228c088e"}, - {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:007a7e49831cfe387473e92e9ff07377f6121120669ddc39674e7244350a6a29"}, - {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:af75cf83c2d57717a8493ed2246d34b1f3398cb8a92b10fd7a1858cad8e78f59"}, - {file = "coverage-7.3.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:811ca7373da32f1ccee2927dc27dc523462fd30674a80102f86c6753d6681bc6"}, - {file = "coverage-7.3.3-cp312-cp312-win32.whl", hash = "sha256:733537a182b5d62184f2a72796eb6901299898231a8e4f84c858c68684b25a70"}, - {file = "coverage-7.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:e995efb191f04b01ced307dbd7407ebf6e6dc209b528d75583277b10fd1800ee"}, - {file = "coverage-7.3.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:d299d379b676812e142fb57662a8d0d810b859421412b4d7af996154c00c31bb"}, - {file = "coverage-7.3.3.tar.gz", hash = "sha256:df04c64e58df96b4427db8d0559e95e2df3138c9916c96f9f6a4dd220db2fdb7"}, -] - -[[package]] -name = "distlib" -version = "0.3.8" -summary = "Distribution utilities" -files = [ - {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, - {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, -] - -[[package]] -name = "fancycompleter" -version = "0.9.1" -summary = "colorful TAB completion for Python prompt" -dependencies = [ - "pyreadline; platform_system == \"Windows\"", - "pyrepl>=0.8.2", -] -files = [ - {file = "fancycompleter-0.9.1-py3-none-any.whl", hash = "sha256:dd076bca7d9d524cc7f25ec8f35ef95388ffef9ef46def4d3d25e9b044ad7080"}, - {file = "fancycompleter-0.9.1.tar.gz", hash = "sha256:09e0feb8ae242abdfd7ef2ba55069a46f011814a80fe5476be48f51b00247272"}, -] - -[[package]] -name = "filelock" -version = "3.13.1" -requires_python = ">=3.8" -summary = "A platform independent file lock." -files = [ - {file = "filelock-3.13.1-py3-none-any.whl", hash = "sha256:57dbda9b35157b05fb3e58ee91448612eb674172fab98ee235ccb0b5bee19a1c"}, - {file = "filelock-3.13.1.tar.gz", hash = "sha256:521f5f56c50f8426f5e03ad3b281b490a87ef15bc6c526f168290f0c7148d44e"}, -] - -[[package]] -name = "identify" -version = "2.5.33" -requires_python = ">=3.8" -summary = "File identification library for Python" -files = [ - {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, - {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, -] - -[[package]] -name = "idna" -version = "3.6" -requires_python = ">=3.5" -summary = "Internationalized Domain Names in Applications (IDNA)" -files = [ - {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, - {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -requires_python = ">=3.7" -summary = "brain-dead simple config-ini parsing" -files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, -] - -[[package]] -name = "mypy" -version = "1.7.1" -requires_python = ">=3.8" -summary = "Optional static typing for Python" -dependencies = [ - "mypy-extensions>=1.0.0", - "typing-extensions>=4.1.0", -] -files = [ - {file = "mypy-1.7.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6dbdec441c60699288adf051f51a5d512b0d818526d1dcfff5a41f8cd8b4aaf1"}, - {file = "mypy-1.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4fc3d14ee80cd22367caaaf6e014494415bf440980a3045bf5045b525680ac33"}, - {file = "mypy-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c6e4464ed5f01dc44dc9821caf67b60a4e5c3b04278286a85c067010653a0eb"}, - {file = "mypy-1.7.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:d9b338c19fa2412f76e17525c1b4f2c687a55b156320acb588df79f2e6fa9fea"}, - {file = "mypy-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:204e0d6de5fd2317394a4eff62065614c4892d5a4d1a7ee55b765d7a3d9e3f82"}, - {file = "mypy-1.7.1-py3-none-any.whl", hash = "sha256:f7c5d642db47376a0cc130f0de6d055056e010debdaf0707cd2b0fc7e7ef30ea"}, - {file = "mypy-1.7.1.tar.gz", hash = "sha256:fcb6d9afb1b6208b4c712af0dafdc650f518836065df0d4fb1d800f5d6773db2"}, -] - -[[package]] -name = "mypy-extensions" -version = "1.0.0" -requires_python = ">=3.5" -summary = "Type system extensions for programs checked with the mypy type checker." -files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, -] - -[[package]] -name = "nodeenv" -version = "1.8.0" -requires_python = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" -summary = "Node.js virtual environment builder" -dependencies = [ - "setuptools", -] -files = [ - {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, - {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, -] - -[[package]] -name = "packaging" -version = "23.2" -requires_python = ">=3.7" -summary = "Core utilities for Python packages" -files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, -] - -[[package]] -name = "pdbpp" -version = "0.10.3" -summary = "pdb++, a drop-in replacement for pdb" -dependencies = [ - "fancycompleter>=0.8", - "pygments", - "wmctrl", -] -files = [ - {file = "pdbpp-0.10.3-py2.py3-none-any.whl", hash = "sha256:79580568e33eb3d6f6b462b1187f53e10cd8e4538f7d31495c9181e2cf9665d1"}, - {file = "pdbpp-0.10.3.tar.gz", hash = "sha256:d9e43f4fda388eeb365f2887f4e7b66ac09dce9b6236b76f63616530e2f669f5"}, -] - -[[package]] -name = "platformdirs" -version = "4.1.0" -requires_python = ">=3.8" -summary = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, -] - -[[package]] -name = "pluggy" -version = "1.3.0" -requires_python = ">=3.8" -summary = "plugin and hook calling mechanisms for python" -files = [ - {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, - {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, -] - -[[package]] -name = "pre-commit" -version = "3.6.0" -requires_python = ">=3.9" -summary = "A framework for managing and maintaining multi-language pre-commit hooks." -dependencies = [ - "cfgv>=2.0.0", - "identify>=1.0.0", - "nodeenv>=0.11.1", - "pyyaml>=5.1", - "virtualenv>=20.10.0", -] -files = [ - {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, - {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, -] - -[[package]] -name = "pygments" -version = "2.17.2" -requires_python = ">=3.7" -summary = "Pygments is a syntax highlighting package written in Python." -files = [ - {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, - {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, -] - -[[package]] -name = "pyreadline" -version = "2.1" -summary = "A python implmementation of GNU readline." -files = [ - {file = "pyreadline-2.1.zip", hash = "sha256:4530592fc2e85b25b1a9f79664433da09237c1a270e4d78ea5aa3a2c7229e2d1"}, -] - -[[package]] -name = "pyrepl" -version = "0.9.0" -summary = "A library for building flexible command line interfaces" -files = [ - {file = "pyrepl-0.9.0.tar.gz", hash = "sha256:292570f34b5502e871bbb966d639474f2b57fbfcd3373c2d6a2f3d56e681a775"}, -] - -[[package]] -name = "pytest" -version = "7.4.3" -requires_python = ">=3.7" -summary = "pytest: simple powerful testing with Python" -dependencies = [ - "colorama; sys_platform == \"win32\"", - "iniconfig", - "packaging", - "pluggy<2.0,>=0.12", -] -files = [ - {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"}, - {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"}, -] - -[[package]] -name = "pytest-cov" -version = "4.1.0" -requires_python = ">=3.7" -summary = "Pytest plugin for measuring coverage." -dependencies = [ - "coverage[toml]>=5.2.1", - "pytest>=4.6", -] -files = [ - {file = "pytest-cov-4.1.0.tar.gz", hash = "sha256:3904b13dfbfec47f003b8e77fd5b589cd11904a21ddf1ab38a64f204d6a10ef6"}, - {file = "pytest_cov-4.1.0-py3-none-any.whl", hash = "sha256:6ba70b9e97e69fcc3fb45bfeab2d0a138fb65c4d0d6a41ef33983ad114be8c3a"}, -] - -[[package]] -name = "pyyaml" -version = "6.0.1" -requires_python = ">=3.6" -summary = "YAML parser and emitter for Python" -files = [ - {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, - {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, - {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, - {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, - {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, - {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, -] - -[[package]] -name = "requests" -version = "2.31.0" -requires_python = ">=3.7" -summary = "Python HTTP for Humans." -dependencies = [ - "certifi>=2017.4.17", - "charset-normalizer<4,>=2", - "idna<4,>=2.5", - "urllib3<3,>=1.21.1", -] -files = [ - {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, - {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, -] - -[[package]] -name = "ruff" -version = "0.1.8" -requires_python = ">=3.7" -summary = "An extremely fast Python linter and code formatter, written in Rust." -files = [ - {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7de792582f6e490ae6aef36a58d85df9f7a0cfd1b0d4fe6b4fb51803a3ac96fa"}, - {file = "ruff-0.1.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:c8e3255afd186c142eef4ec400d7826134f028a85da2146102a1172ecc7c3696"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff78a7583020da124dd0deb835ece1d87bb91762d40c514ee9b67a087940528b"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd8ee69b02e7bdefe1e5da2d5b6eaaddcf4f90859f00281b2333c0e3a0cc9cd6"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a05b0ddd7ea25495e4115a43125e8a7ebed0aa043c3d432de7e7d6e8e8cd6448"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:e6f08ca730f4dc1b76b473bdf30b1b37d42da379202a059eae54ec7fc1fbcfed"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f35960b02df6b827c1b903091bb14f4b003f6cf102705efc4ce78132a0aa5af3"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d076717c67b34c162da7c1a5bda16ffc205e0e0072c03745275e7eab888719f"}, - {file = "ruff-0.1.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a21ab023124eafb7cef6d038f835cb1155cd5ea798edd8d9eb2f8b84be07d9"}, - {file = "ruff-0.1.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ce697c463458555027dfb194cb96d26608abab920fa85213deb5edf26e026664"}, - {file = "ruff-0.1.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:db6cedd9ffed55548ab313ad718bc34582d394e27a7875b4b952c2d29c001b26"}, - {file = "ruff-0.1.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:05ffe9dbd278965271252704eddb97b4384bf58b971054d517decfbf8c523f05"}, - {file = "ruff-0.1.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:5daaeaf00ae3c1efec9742ff294b06c3a2a9db8d3db51ee4851c12ad385cda30"}, - {file = "ruff-0.1.8-py3-none-win32.whl", hash = "sha256:e49fbdfe257fa41e5c9e13c79b9e79a23a79bd0e40b9314bc53840f520c2c0b3"}, - {file = "ruff-0.1.8-py3-none-win_amd64.whl", hash = "sha256:f41f692f1691ad87f51708b823af4bb2c5c87c9248ddd3191c8f088e66ce590a"}, - {file = "ruff-0.1.8-py3-none-win_arm64.whl", hash = "sha256:aa8ee4f8440023b0a6c3707f76cadce8657553655dcbb5fc9b2f9bb9bee389f6"}, - {file = "ruff-0.1.8.tar.gz", hash = "sha256:f7ee467677467526cfe135eab86a40a0e8db43117936ac4f9b469ce9cdb3fb62"}, -] - -[[package]] -name = "setuptools" -version = "69.0.2" -requires_python = ">=3.8" -summary = "Easily download, build, install, upgrade, and uninstall Python packages" -files = [ - {file = "setuptools-69.0.2-py3-none-any.whl", hash = "sha256:1e8fdff6797d3865f37397be788a4e3cba233608e9b509382a2777d25ebde7f2"}, - {file = "setuptools-69.0.2.tar.gz", hash = "sha256:735896e78a4742605974de002ac60562d286fa8051a7e2299445e8e8fbb01aa6"}, -] - -[[package]] -name = "types-click" -version = "7.1.8" -summary = "Typing stubs for click" -files = [ - {file = "types-click-7.1.8.tar.gz", hash = "sha256:b6604968be6401dc516311ca50708a0a28baa7a0cb840efd7412f0dbbff4e092"}, - {file = "types_click-7.1.8-py3-none-any.whl", hash = "sha256:8cb030a669e2e927461be9827375f83c16b8178c365852c060a34e24871e7e81"}, -] - -[[package]] -name = "types-requests" -version = "2.31.0.10" -requires_python = ">=3.7" -summary = "Typing stubs for requests" -dependencies = [ - "urllib3>=2", -] -files = [ - {file = "types-requests-2.31.0.10.tar.gz", hash = "sha256:dc5852a76f1eaf60eafa81a2e50aefa3d1f015c34cf0cba130930866b1b22a92"}, - {file = "types_requests-2.31.0.10-py3-none-any.whl", hash = "sha256:b32b9a86beffa876c0c3ac99a4cd3b8b51e973fb8e3bd4e0a6bb32c7efad80fc"}, -] - -[[package]] -name = "typing-extensions" -version = "4.9.0" -requires_python = ">=3.8" -summary = "Backported and Experimental Type Hints for Python 3.8+" -files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, -] - -[[package]] -name = "urllib3" -version = "2.1.0" -requires_python = ">=3.8" -summary = "HTTP library with thread-safe connection pooling, file post, and more." -files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, -] - -[[package]] -name = "virtualenv" -version = "20.25.0" -requires_python = ">=3.7" -summary = "Virtual Python Environment builder" -dependencies = [ - "distlib<1,>=0.3.7", - "filelock<4,>=3.12.2", - "platformdirs<5,>=3.9.1", -] -files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, -] - -[[package]] -name = "wmctrl" -version = "0.5" -requires_python = ">=2.7" -summary = "A tool to programmatically control windows inside X" -dependencies = [ - "attrs", -] -files = [ - {file = "wmctrl-0.5-py2.py3-none-any.whl", hash = "sha256:ae695c1863a314c899e7cf113f07c0da02a394b968c4772e1936219d9234ddd7"}, - {file = "wmctrl-0.5.tar.gz", hash = "sha256:7839a36b6fe9e2d6fd22304e5dc372dbced2116ba41283ea938b2da57f53e962"}, -] diff --git a/python/pyproject.toml b/python/pyproject.toml deleted file mode 100644 index bc769fe6..00000000 --- a/python/pyproject.toml +++ /dev/null @@ -1,98 +0,0 @@ -[project] -authors = [ - {name = "Luuk Verweij", email = "luuk_verweij@msn.com"}, -] -requires-python = ">=3.12.1,<4.0" -dependencies = [ - "click>=8.1.3", - "basil-parser==0.4.3", -] -name = "Aaa" -version = "0.1.0" -description = "Stack-based languge" - -[build-system] -requires = ["pdm-pep517>=1.0"] -build-backend = "pdm.pep517.api" - -[tool.ruff] -# Exclude a variety of commonly ignored directories. -exclude = [ - ".bzr", - ".direnv", - ".eggs", - ".git", - ".git-rewrite", - ".hg", - ".mypy_cache", - ".nox", - ".pants.d", - ".pytype", - ".ruff_cache", - ".svn", - ".tox", - ".venv", - "__pypackages__", - "_build", - "buck-out", - "build", - "dist", - "node_modules", - "venv", -] - -line-length = 88 # Default width of Black -indent-width = 4 - -# Assume Python 3.12 -target-version = "py312" - -[tool.ruff.lint] -select = [ # Full list: https://docs.astral.sh/ruff/rules/ - "E", # pycodestyle - "F", # pyflakes - "UP", # pyupgrade - "B", # flake8-bugbear - "SIM", # flake8-simplify - "I", # isort -] -ignore = [ - "SIM108", # use ternary instead of if-else -] - -# Allow fix for all enabled rules (when `--fix`) is provided. -fixable = ["ALL"] -unfixable = [] - -# Allow unused variables when underscore-prefixed. -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" - -[tool.ruff.format] -# Like Black, use double quotes for strings. -quote-style = "double" - -# Like Black, indent with spaces, rather than tabs. -indent-style = "space" - -# Like Black, respect magic trailing commas. -skip-magic-trailing-comma = false - -# Like Black, automatically detect the appropriate line ending. -line-ending = "auto" - -[tool.pdm] -[tool.pdm.dev-dependencies] -dev = [ - "mypy>=1.7.1", - "pdbpp>=0.10.3", - "pre-commit>=3.0.1", - "pytest>=7.2.1", - "pytest-cov>=4.0.0", - "types-click>=7.1.8", - "requests>=2.31.0", - "types-requests>=2.31.0.1", - "ruff>=0.1.8", -] - -[tool.pdm.build] -includes = [] diff --git a/python/syntax.json b/python/syntax.json deleted file mode 100644 index e746e356..00000000 --- a/python/syntax.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "keyword_tokens": { - "args": "args(?![_a-zA-Z])", - "as": "as(?![_a-zA-Z])", - "builtin": "builtin(?![_a-zA-Z])", - "call": "call(?![_a-zA-Z])", - "case": "case(?![_a-zA-Z])", - "const": "const(?![_a-zA-Z])", - "default": "default(?![_a-zA-Z])", - "else": "else(?![_a-zA-Z])", - "enum": "enum(?![_a-zA-Z])", - "false": "false(?![_a-zA-Z])", - "foreach": "foreach(?![_a-zA-Z])", - "from": "from(?![_a-zA-Z])", - "fn": "fn(?![_a-zA-Z])", - "if": "if(?![_a-zA-Z])", - "import": "import(?![_a-zA-Z])", - "match": "match(?![_a-zA-Z])", - "never": "never(?![_a-zA-Z])", - "return": "return(?![_a-zA-Z])", - "struct": "struct(?![_a-zA-Z])", - "true": "true(?![_a-zA-Z])", - "use": "use(?![_a-zA-Z])", - "while": "while(?![_a-zA-Z])" - }, - "regular_tokens": { - "assign": "<-", - "end": "}", - "start": "{", - "char": "'(([^'\\t\\n\\r\\f\\v\\\\\"])|(\\\\[/0befnrt\\\\\"'])|(\\\\x[0-9a-fA-F]{2})|(\\\\u[0-9a-fA-F]{4})|(\\\\U((0[0-9])|10)[0-9a-fA-F]{4})|\")'", - "colon": ":", - "comma": ",", - "comment": "//[^\n]*", - "get_field": "\\?", - "identifier": "(-(?![0-9]))|(!=)|(\\.)|(\\*)|(/(?!/))|(%)|(\\+)|(<(?!=))|(<=)|(=)|(>(?!=))|(>=)|([a-zA-Z_]+)", - "integer": "(-)?[0-9]+", - "set_field": "!", - "sq_end": "]", - "sq_start": "\\[", - "string": "\"(([^'\\t\\n\\r\\f\\v\\\\\"])|(\\\\[/0befnrt\\\\\"'])|(\\\\x[0-9a-fA-F]{2})|(\\\\u[0-9a-fA-F]{4})|(\\\\U((0[0-9])|10)[0-9a-fA-F]{4})|')*\"", - "whitespace": "\\s+" - }, - "filtered_tokens": [ - "comment", - "whitespace" - ], - "nodes": { - "ARGUMENT": "identifier as TYPE_OR_FUNCTION_POINTER_LITERAL", - "ARGUMENTS": "args ARGUMENT (comma ARGUMENT)* comma?", - "ASSIGNMENT": "VARIABLES assign FUNCTION_BODY_BLOCK", - "BOOLEAN": "true | false", - "BRANCH": "if FUNCTION_BODY FUNCTION_BODY_BLOCK (else FUNCTION_BODY_BLOCK)?", - "CASE_BLOCK": "case CASE_LABEL FUNCTION_BODY_BLOCK", - "CASE_LABEL": "identifier colon identifier (as VARIABLES)?", - "COMMA_SEPARATED_TYPE_LIST": "TYPE_OR_FUNCTION_POINTER_LITERAL (comma TYPE_OR_FUNCTION_POINTER_LITERAL)* comma?", - "DEFAULT_BLOCK": "default FUNCTION_BODY_BLOCK", - "ENUM_DECLARATION": "enum FLAT_TYPE_LITERAL", - "ENUM_DEFINITION": "ENUM_DECLARATION start ENUM_VARIANTS end", - "ENUM_VARIANT_ASSOCIATED_DATA": "TYPE_OR_FUNCTION_POINTER_LITERAL | (start COMMA_SEPARATED_TYPE_LIST end)", - "ENUM_VARIANT": "identifier (as ENUM_VARIANT_ASSOCIATED_DATA)?", - "ENUM_VARIANTS": "ENUM_VARIANT (comma ENUM_VARIANT)* comma?", - "FLAT_TYPE_LITERAL": "identifier FLAT_TYPE_PARAMS?", - "FLAT_TYPE_PARAMS": "sq_start identifier (comma identifier)* comma? sq_end", - "FOREACH_LOOP": "foreach FUNCTION_BODY_BLOCK", - "FREE_FUNCTION_CALL": "identifier TYPE_PARAMS?", - "FREE_FUNCTION_NAME": "identifier FLAT_TYPE_PARAMS?", - "FUNCTION_BODY_BLOCK": "start FUNCTION_BODY end", - "FUNCTION_BODY_ITEM": "ASSIGNMENT | BOOLEAN | BRANCH | call | char | FOREACH_LOOP | FUNCTION_CALL | FUNCTION_POINTER_TYPE_LITERAL | GET_FUNCTION_POINTER | integer | MATCH_BLOCK | return | STRUCT_FIELD_QUERY | STRUCT_FIELD_UPDATE | USE_BLOCK | WHILE_LOOP | string", - "FUNCTION_BODY": "FUNCTION_BODY_ITEM+", - "FUNCTION_CALL": "MEMBER_FUNCTION_CALL | FREE_FUNCTION_CALL", - "FUNCTION_DECLARATION": "fn FUNCTION_NAME ARGUMENTS? RETURN_TYPES?", - "FUNCTION_DEFINITION": "(builtin FUNCTION_DECLARATION) | (FUNCTION_DECLARATION FUNCTION_BODY_BLOCK)", - "FUNCTION_NAME": "MEMBER_FUNCTION_NAME | FREE_FUNCTION_NAME", - "FUNCTION_POINTER_TYPE_LITERAL": "fn sq_start COMMA_SEPARATED_TYPE_LIST? sq_end sq_start ((never comma?) | COMMA_SEPARATED_TYPE_LIST)? sq_end", - "GET_FUNCTION_POINTER": "string fn", - "IMPORT_ITEM": "identifier (as identifier)?", - "IMPORT_ITEMS": "IMPORT_ITEM (comma IMPORT_ITEM)* comma?", - "IMPORT": "from string import IMPORT_ITEMS", - "MATCH_BLOCK": "match start (CASE_BLOCK | DEFAULT_BLOCK)* end", - "MEMBER_FUNCTION_CALL": "identifier (colon identifier TYPE_PARAMS?) | (TYPE_PARAMS colon identifier)", - "MEMBER_FUNCTION_NAME": "identifier FLAT_TYPE_PARAMS? colon identifier", - "RETURN_TYPES": "return never | (TYPE_OR_FUNCTION_POINTER_LITERAL (comma TYPE_OR_FUNCTION_POINTER_LITERAL)* comma?)", - "SOURCE_FILE": "(ENUM_DEFINITION | FUNCTION_DEFINITION | IMPORT | STRUCT_DEFINITION)*", - "STRUCT_DECLARATION": "struct FLAT_TYPE_LITERAL", - "STRUCT_DEFINITION": "(builtin STRUCT_DECLARATION) | (STRUCT_DECLARATION start STRUCT_FIELDS? end)", - "STRUCT_FIELD_QUERY": "string get_field", - "STRUCT_FIELD_UPDATE": "string FUNCTION_BODY_BLOCK set_field", - "STRUCT_FIELD": "identifier as TYPE_OR_FUNCTION_POINTER_LITERAL", - "STRUCT_FIELDS": "STRUCT_FIELD (comma STRUCT_FIELD)* comma?", - "TYPE_LITERAL": "const? identifier TYPE_PARAMS?", - "TYPE_OR_FUNCTION_POINTER_LITERAL": "FUNCTION_POINTER_TYPE_LITERAL | TYPE_LITERAL", - "TYPE_PARAMS": "sq_start TYPE_OR_FUNCTION_POINTER_LITERAL (comma TYPE_OR_FUNCTION_POINTER_LITERAL)* comma? sq_end", - "USE_BLOCK": "use VARIABLES FUNCTION_BODY_BLOCK", - "VARIABLES": "identifier (comma identifier)* comma?", - "WHILE_LOOP": "while FUNCTION_BODY FUNCTION_BODY_BLOCK" - }, - "root_node": "SOURCE_FILE" -} diff --git a/python/tests/__init__.py b/python/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/tests/aaa/__init__.py b/python/tests/aaa/__init__.py deleted file mode 100644 index 371e15a9..00000000 --- a/python/tests/aaa/__init__.py +++ /dev/null @@ -1,90 +0,0 @@ -from collections.abc import Sequence -from pathlib import Path -from typing import Any - -from aaa import AaaException -from aaa.runner.exceptions import AaaTranslationException -from aaa.runner.runner import Runner - - -def check_aaa_main( - code: str, - expected_output: str, - expected_exception_types: list[type[Exception]], -) -> Sequence[AaaException]: - code = "fn main {\n" + code + "\n}" - return check_aaa_full_source(code, expected_output, expected_exception_types) - - -def check_aaa_full_source( - code: str, - expected_output: str, - expected_exception_types: list[type[Exception]], - **run_kwargs: Any, -) -> Sequence[AaaException]: - files = {Path("main.aaa"): code} - return check_aaa_full_source_multi_file( - files, expected_output, expected_exception_types, **run_kwargs - ) - - -def check_aaa_full_source_multi_file( - file_dict: dict[Path, str], - expected_output: str, - expected_exception_types: list[type[Exception]], - **run_kwargs: Any, -) -> Sequence[AaaException]: - entrypoint = Path("main.aaa") - runner = Runner(entrypoint, file_dict) - - exception_types: list[type[AaaException]] = [] - - # Make type-checker happy - stdout = "" - stderr = "" - - try: - completed_process = runner._run_process( - compile=True, - binary_path=None, - run=True, - args=[], - capture_output=True, - runtime_type_checks=True, - **run_kwargs, - ) - except AaaTranslationException as e: - exception_types = [type(exc) for exc in e.exceptions] - else: - stderr = completed_process.stderr.decode("utf-8") - stdout = completed_process.stdout.decode("utf-8") - - exception_types = [type(exc) for exc in runner.exceptions] - - if exception_types != expected_exception_types: - # If we reach this code, some test for Aaa code is broken. - # We print some info useful for debugging. - - error = ( - "\n" - + "Expected exception types: " - + " ".join(exc_type.__name__ for exc_type in expected_exception_types) - + "\n" - + " Got exception types: " - + " ".join(exc_type.__name__ for exc_type in exception_types) - + "\n" - ) - - if runner.exceptions: - error += "\nException(s):\n" - - for exception in runner.exceptions: - error += f"{exception}\n" - - raise AssertionError(error) - - if not expected_exception_types: - assert expected_output == stdout - assert stderr == "" - - return runner.exceptions diff --git a/python/tests/aaa/docs/__init__.py b/python/tests/aaa/docs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/tests/aaa/docs/test_examples.py b/python/tests/aaa/docs/test_examples.py deleted file mode 100644 index 2804879e..00000000 --- a/python/tests/aaa/docs/test_examples.py +++ /dev/null @@ -1,136 +0,0 @@ -import subprocess -from ipaddress import IPv4Address -from pathlib import Path - -import pytest -import requests -from pytest import CaptureFixture - -from aaa.runner.runner import Runner - - -def expected_fizzbuzz_output() -> str: - fizzbuzz_table = { - 0: "fizzbuzz", - 3: "fizz", - 5: "buzz", - 6: "fizz", - 9: "fizz", - 10: "buzz", - 12: "fizz", - } - - output: list[str] = [] - - for i in range(1, 101): - output.append(fizzbuzz_table.get(i % 15, str(i))) - - return "\n".join(output) + "\n" - - -EXPECTED_EXAMPLE_OUTPUT = { - "../examples/one_to_ten.aaa": "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n", - "../examples/print_number.aaa": "42\n", - "../examples/fizzbuzz.aaa": expected_fizzbuzz_output(), - "../examples/print_twice.aaa": "hello!\nhello!\n", - "../examples/function_demo.aaa": "a=1\nb=2\nc=3\n", - "../examples/typing_playground.aaa": "five = 5\n3 5 max = 5\n" - + "4 factorial = 24\n7 dup_twice = 777\n", - "../examples/renamed_import/main.aaa": "5", -} - - -@pytest.mark.parametrize( - ["entrypoint", "expected_output"], - [ - pytest.param( - "../examples/one_to_ten.aaa", - "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n", - id="one_to_ten.aaa", - ), - pytest.param( - "../examples/fizzbuzz.aaa", - expected_fizzbuzz_output(), - id="fizzbuzz.aaa", - ), - pytest.param( - "../examples/renamed_import/main.aaa", - "5\n", - id="renamed_import/main.aaa", - ), - pytest.param( - "../examples/import/main.aaa", "5\n", id="renamed_import/main.aaa" - ), - ], -) -def test_examples( - entrypoint: Path, expected_output: str, capfd: CaptureFixture[str] -) -> None: - runner = Runner(entrypoint) - runner.run( - compile=True, - binary_path=None, - run=True, - args=[], - runtime_type_checks=True, - ) - stdout, stderr = capfd.readouterr() - assert str(stderr) == "" - assert str(stdout) == expected_output - - -def test_http_server() -> None: - entrypoint = Path("../examples/http_server.aaa") - runner = Runner(entrypoint) - - binary = "/tmp/aaa/test_http_server" - - exit_code = runner.run( - compile=True, - binary_path=Path(binary), - run=False, - args=[], - runtime_type_checks=True, - ) - assert exit_code == 0 - - subproc = subprocess.Popen(binary) - - try: - r = requests.get("http://localhost:8080") - assert r.status_code == 200 - assert r.json() == {"message": "Hello world!"} - assert r.headers["Content-Type"] == "application/json" - finally: - subproc.terminate() - subproc.wait() - - -@pytest.mark.skip() # TODO #131 Extend http examples and redo Aaa's `connect()` -def test_http_client(capfd: CaptureFixture[str]) -> None: - entrypoint = Path("../examples/http_client.aaa") - runner = Runner(entrypoint) - runner.run( - compile=True, - binary_path=None, - run=True, - args=[], - runtime_type_checks=True, - ) - - stdout, stderr = capfd.readouterr() - stdout_lines = stdout.split("\r\n") - - assert stderr == "" - assert stdout_lines[0] == "HTTP/1.1 200 OK" - IPv4Address(stdout_lines[-1].strip()) # should not fail - - -@pytest.mark.skip() -def test_shell() -> None: - raise NotImplementedError # TODO #71 Implement test for examples/shell.aaa - - -@pytest.mark.skip() -def test_sudoku_solver() -> None: - raise NotImplementedError # TODO #72 Implement test for examples/sudoku_solver.aaa diff --git a/python/tests/aaa/exceptions/__init__.py b/python/tests/aaa/exceptions/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/tests/aaa/exceptions/test_exceptions.py b/python/tests/aaa/exceptions/test_exceptions.py deleted file mode 100644 index 020114aa..00000000 --- a/python/tests/aaa/exceptions/test_exceptions.py +++ /dev/null @@ -1,1447 +0,0 @@ -from pathlib import Path - -import pytest - -from aaa.cross_referencer.exceptions import ( - CircularDependencyError, - CollidingEnumVariant, - CollidingIdentifier, - FunctionPointerTargetNotFound, - ImportedItemNotFound, - IndirectImportException, - InvalidArgument, - InvalidEnumType, - InvalidEnumVariant, - InvalidFunctionPointerTarget, - InvalidReturnType, - InvalidType, - UnexpectedBuiltin, - UnexpectedTypeParameterCount, - UnknownIdentifier, -) -from aaa.parser.exceptions import AaaParserBaseException, FileReadError -from aaa.runner.runner import RUNNER_FILE_DICT_ROOT_PATH -from aaa.type_checker.exceptions import ( - AssignConstValueError, - AssignmentTypeError, - BranchTypeError, - CaseAsArgumentCountError, - CaseEnumTypeError, - CaseStackTypeError, - CollidingVariable, - ConditionTypeError, - DuplicateCase, - ForeachLoopTypeError, - FunctionTypeError, - InvalidCallWithTypeParameters, - InvalidEqualsFunctionSignature, - InvalidIterable, - InvalidIterator, - InvalidMainSignuture, - InvalidMemberFunctionSignature, - InvalidTestSignuture, - MainFunctionNotFound, - MatchTypeError, - MemberFunctionTypeNotFound, - MissingEnumCases, - MissingIterable, - ReturnTypesError, - StackTypesError, - StructUpdateStackError, - StructUpdateTypeError, - UnknownField, - UnknownVariableOrFunction, - UnreachableCode, - UnreachableDefaultBlock, - UnsupportedOperator, - UpdateConstStructError, - UseBlockStackUnderflow, - UseFieldOfEnumException, - WhileLoopTypeError, -) -from tests.aaa import check_aaa_full_source, check_aaa_full_source_multi_file - -# Ignore line length linter errors for this file -# ruff: noqa: E501 - - -@pytest.mark.parametrize( - ["code", "expected_exception_type", "expected_exception_message"], - [ - pytest.param( - 'fn main { if true { 3 } else { "" } }', - BranchTypeError, - "/foo/main.aaa:1:11: Inconsistent stacks for branches\n" - + " before: \n" - + " after if-branch: int\n" - + "after else-branch: str", - id="branch-type", - ), - pytest.param( - "fn main { if 3 true { nop } }", - ConditionTypeError, - "/foo/main.aaa:1:14: Condition type error\n" - + "stack before: \n" - + " stack after: int bool", - id="condition-type-branch", - ), - pytest.param( - "fn main { while 3 true { nop } }", - ConditionTypeError, - "/foo/main.aaa:1:17: Condition type error\n" - + "stack before: \n" - + " stack after: int bool", - id="condition-type-while-loop", - ), - pytest.param( - """ - fn foo { nop } - fn foo { nop } - fn main { nop } - """, - CollidingIdentifier, - "Found name collision:\n" - + "/foo/main.aaa:2:13: function foo\n" - + "/foo/main.aaa:3:13: function foo", - id="funcname-funcname-collision", - ), - pytest.param( - """ - fn bar { 5 } - fn main { nop } - """, - FunctionTypeError, - "/foo/main.aaa:2:13: Function bar returns wrong type(s)\n" - + "expected return types: \n" - + " found return types: int", - id="function-type", - ), - pytest.param( - "fn main { while true { 0 } }", - WhileLoopTypeError, - "/foo/main.aaa:1:11: Invalid stack modification inside while loop body\n" - + "before while loop: \n" - + " after while loop: int", - id="while-loop-type", - ), - pytest.param( - 'fn main { 3 " " + . }', - StackTypesError, - "/foo/main.aaa:1:17: Invalid stack types when calling +\n" - + "Expected stack top: (const int) (const int)\n" - + " Found stack: int str", - id="stack-types", - ), - pytest.param( - "fn main { drop }", - StackTypesError, - "/foo/main.aaa:1:11: Invalid stack types when calling drop\n" - + "Expected stack top: A\n" - + " Found stack: ", - id="stack-types-underflow", - ), - pytest.param( - """ - fn main { bar } - """, - UnknownVariableOrFunction, - "/foo/main.aaa:2:23: Usage of unknown variable or function bar", - id="unknown-function", - ), - pytest.param( - """ - fn foo return A { nop } - fn main { nop } - """, - UnknownIdentifier, - "/foo/main.aaa:2:27: Usage of unknown identifier A", - id="unknown-placeholder-type", - ), - pytest.param( - "fn main args a as int { nop }", - InvalidMainSignuture, - "/foo/main.aaa:1:1: Main function has wrong signature, it should have:\n" - + "- no type parameters\n" - + "- either no arguments or one vec[str] argument\n" - + "- return either nothing or an int", - id="invalid-main-signature-argument", - ), - pytest.param( - "fn main { 5 }", - FunctionTypeError, - "/foo/main.aaa:1:1: Function main returns wrong type(s)\n" - + "expected return types: \n" - + " found return types: int", - id="invalid-main-signature-return-type", - ), - pytest.param( - "fn main[A] { nop }", - InvalidMainSignuture, - "/foo/main.aaa:1:1: Main function has wrong signature, it should have:\n" - + "- no type parameters\n" - + "- either no arguments or one vec[str] argument\n" - + "- return either nothing or an int", - id="invalid-main-type-params", - ), - pytest.param( - """ - struct bar { x as int } - fn foo { bar "y" ? . drop } - fn main { nop } - """, - UnknownField, - "/foo/main.aaa:3:26: Usage of unknown field y of type bar", - id="unknown-struct-field-get", - ), - pytest.param( - """ - struct bar { x as int } - fn foo { bar "y" { 3 } ! drop } - fn main { nop } - """, - UnknownField, - "/foo/main.aaa:3:26: Usage of unknown field y of type bar", - id="unknown-struct-field-set", - ), - pytest.param( - """ - fn foo { 5 "x" { 3 } ! drop } - fn main { nop } - """, - UnknownField, - "/foo/main.aaa:2:24: Usage of unknown field x of type int", - id="set-field-of-non-struct", - ), - pytest.param( - """ - struct bar { x as int } - fn foo { bar "x" { 34 35 } ! "x" ? . "\\n" . drop } - fn main { nop } - """, - StructUpdateStackError, - "/foo/main.aaa:3:26: Incorrect stack modification when updating struct field\n" - + " Expected: bar \n" - + " Found: bar int int", - id="struct-update-stack-error", - ), - pytest.param( - """ - struct bar { x as int } - fn foo { bar "x" { false } ! drop } - fn main { nop } - """, - StructUpdateTypeError, - "/foo/main.aaa:3:32: Attempt to set field x of bar to wrong type\n" - + "Expected type: int" - + "\n Found type: bool\n" - + "\n" - + "Type stack: bar bool", - id="struct-update-type-error", - ), - pytest.param( - """ - struct bar { x as int } - fn bar:foo { nop } - fn main { nop } - """, - InvalidMemberFunctionSignature, - "/foo/main.aaa:3:13: Function bar:foo has invalid member-function signature\n" - + "\n" - + "Expected arg types: bar ...\n" - + " Found arg types: ", - id="member-func-without-arg-or-return-type", - ), - pytest.param( - """ - struct bar { x as int } - fn bar:foo return bar { nop } - fn main { nop } - """, - InvalidMemberFunctionSignature, - "/foo/main.aaa:3:13: Function bar:foo has invalid member-function signature\n" - + "\n" - + "Expected arg types: bar ...\n" - + " Found arg types: ", - id="member-func-without-arg-type", - ), - pytest.param( - """ - fn main { nop } - fn bar { nop } - struct bar { x as int } - """, - CollidingIdentifier, - "Found name collision:\n" - + "/foo/main.aaa:3:13: function bar\n" - + "/foo/main.aaa:4:13: struct bar", - id="struct-name-collision", - ), - pytest.param( - """ - struct main { x as int } - """, - MainFunctionNotFound, - "/foo/main.aaa: No main function found", - id="struct-named-main", - ), - pytest.param( - """ - fn foo { nop } - """, - MainFunctionNotFound, - "/foo/main.aaa: No main function found", - id="main-not-found", - ), - pytest.param( - """ - fn main { nop } - fn foo args b as bar { nop } - """, - UnknownIdentifier, - "/foo/main.aaa:3:30: Usage of unknown identifier bar", - id="unknown-argument-type", - ), - pytest.param( - """ - fn main { nop } - fn foo args m as main { nop } - """, - InvalidArgument, - "/foo/main.aaa:3:30: Cannot use main as argument\n" - + "/foo/main.aaa:2:13: function main collides", - id="invalid-type-parameter", - ), - pytest.param( - """ - fn main { nop } - fn bar[bar] { nop } - """, - CollidingIdentifier, - "Found name collision:\n" - + "/foo/main.aaa:3:13: function bar\n" - + "/foo/main.aaa:3:20: struct bar", - id="funcname-param-collision", - ), - pytest.param( - """ - fn main { nop } - fn foo[bar] args bar as int { nop } - """, - CollidingIdentifier, - "Found name collision:\n" - + "/foo/main.aaa:3:20: struct bar\n" - + "/foo/main.aaa:3:30: function argument bar", - id="argument-param-collision", - ), - pytest.param( - "fn main { vec[int,int] drop }", - UnexpectedTypeParameterCount, - "/foo/main.aaa:1:11: Unexpected number of type parameters\n" - + "Expected parameter count: 1\n" - + " Found parameter count: 2", - id="call-with-too-many-type-params", - ), - pytest.param( - "fn main { vec drop }", - UnexpectedTypeParameterCount, - "/foo/main.aaa:1:11: Unexpected number of type parameters\n" - + "Expected parameter count: 1\n" - + " Found parameter count: 0", - id="call-with-too-few-type-params", - ), - pytest.param( - """ - fn main { nop } - fn foo args a as vec[int,int] { nop } - """, - UnexpectedTypeParameterCount, - "/foo/main.aaa:3:25: Unexpected number of type parameters\n" - + "Expected parameter count: 1\n" - + " Found parameter count: 2", - id="argument-with-too-many-type-params", - ), - pytest.param( - """ - fn main { nop } - fn foo args a as vec { nop } - """, - UnexpectedTypeParameterCount, - "/foo/main.aaa:3:25: Unexpected number of type parameters\n" - + "Expected parameter count: 1\n" - + " Found parameter count: 0", - id="argument-with-too-few-type-params", - ), - pytest.param( - """ - fn main { nop } - fn foo return main { nop } - """, - InvalidReturnType, - "/foo/main.aaa:2:13: Cannot use function main as return type", - id="return-func-name", - ), - pytest.param( - """ - fn main { nop } - fn bar return vec[main] { nop } - """, - InvalidType, - "/foo/main.aaa:2:13: Cannot use function main as type", - id="function-as-return-type-param", - ), - pytest.param( - """ - fn main { nop } - fn bar return vec { nop } - """, - UnexpectedTypeParameterCount, - "/foo/main.aaa:3:27: Unexpected number of type parameters\n" - + "Expected parameter count: 1\n" - + " Found parameter count: 0", - id="returntype-with-too-few-type-params", - ), - pytest.param( - """ - fn main { nop } - fn bar return vec[int,int] { nop } - """, - UnexpectedTypeParameterCount, - "/foo/main.aaa:3:27: Unexpected number of type parameters\n" - + "Expected parameter count: 1\n" - + " Found parameter count: 2", - id="returntype-with-too-many-type-params", - ), - pytest.param( - """ - fn main { foreach { nop } } - """, - MissingIterable, - "/foo/main.aaa:2:23: Cannot use foreach, function stack is empty.", - id="missing-iterable", - ), - pytest.param( - """ - fn main { 0 foreach { nop } } - """, - InvalidIterable, - "/foo/main.aaa:2:25: Invalid iterable type int.\n" - + "Iterable types need to have a function named iter which:\n" - + "- takes one argument (the iterable)\n" - + "- returns one value (an iterator)", - id="invalid-iterable-no-next-func", - ), - pytest.param( - """ - struct foo { x as int } - fn foo:iter args f as foo, y as int return int { 0 } - fn main { foo foreach { nop } } - """, - InvalidIterable, - "/foo/main.aaa:4:27: Invalid iterable type foo.\n" - + "Iterable types need to have a function named iter which:\n" - + "- takes one argument (the iterable)\n" - + "- returns one value (an iterator)", - id="invalid-iterable-iter-two-arguments", - ), - pytest.param( - """ - struct foo { x as int } - fn foo:iter args f as foo return int, int { 0 0 } - fn main { foo foreach { nop } } - """, - InvalidIterable, - "/foo/main.aaa:4:27: Invalid iterable type foo.\n" - + "Iterable types need to have a function named iter which:\n" - + "- takes one argument (the iterable)\n" - + "- returns one value (an iterator)", - id="invalid-iterable-iter-two-return-values", - ), - pytest.param( - """ - struct foo { x as int } - fn foo:iter args f as foo { nop } - fn main { foo foreach { nop } } - """, - InvalidIterable, - "/foo/main.aaa:4:27: Invalid iterable type foo.\n" - + "Iterable types need to have a function named iter which:\n" - + "- takes one argument (the iterable)\n" - + "- returns one value (an iterator)", - id="invalid-iterable-iter-no-return-values", - ), - pytest.param( - """ - struct foo { x as int } - fn foo:iter args f as foo return never { 0 exit } - fn main { foo foreach { nop } } - """, - InvalidIterable, - "/foo/main.aaa:4:27: Invalid iterable type foo.\n" - + "Iterable types need to have a function named iter which:\n" - + "- takes one argument (the iterable)\n" - + "- returns one value (an iterator)", - id="invalid-iterable-iter-no-return-values", - ), - pytest.param( - """ - struct bar { x as int } - struct foo { x as int } - fn foo:iter args f as foo return bar { bar } - fn main { foo foreach { nop } } - """, - InvalidIterator, - "/foo/main.aaa:5:27: Invalid iterator type bar to iterate over foo.\n" - + "Iterator types need to have a function named next which:\n" - + "- takes one argument (the iterator)\n" - + "- returns at least 2 values, the last being a boolean\n" - + "- indicates if more data is present in the iterable with this last return value\n" - + "- for const iterators all return values of `next` except the last one must be const", - id="invalid-iterator-no-iter-func", - ), - pytest.param( - """ - struct bar { x as int } - fn bar:next args b as bar, y as int return int, bool { 0 false } - struct foo { x as int } - fn foo:iter args f as foo return bar { bar } - fn main { foo foreach { nop } } - """, - InvalidIterator, - "/foo/main.aaa:6:27: Invalid iterator type bar to iterate over foo.\n" - + "Iterator types need to have a function named next which:\n" - + "- takes one argument (the iterator)\n" - + "- returns at least 2 values, the last being a boolean\n" - + "- indicates if more data is present in the iterable with this last return value\n" - + "- for const iterators all return values of `next` except the last one must be const", - id="invalid-iterator-two-arguments", - ), - pytest.param( - """ - struct bar { x as int } - fn bar:next args b as bar, y as int return bool { false } - struct foo { x as int } - fn foo:iter args f as foo return bar { bar } - fn main { foo foreach { nop } } - """, - InvalidIterator, - "/foo/main.aaa:6:27: Invalid iterator type bar to iterate over foo.\n" - + "Iterator types need to have a function named next which:\n" - + "- takes one argument (the iterator)\n" - + "- returns at least 2 values, the last being a boolean\n" - + "- indicates if more data is present in the iterable with this last return value\n" - + "- for const iterators all return values of `next` except the last one must be const", - id="invalid-iterator-one-return-value", - ), - pytest.param( - """ - struct bar { x as int } - fn bar:next args b as bar, y as int return int, int { 0 0 } - struct foo { x as int } - fn foo:iter args f as foo return bar { bar } - fn main { foo foreach { nop } } - """, - InvalidIterator, - "/foo/main.aaa:6:27: Invalid iterator type bar to iterate over foo.\n" - + "Iterator types need to have a function named next which:\n" - + "- takes one argument (the iterator)\n" - + "- returns at least 2 values, the last being a boolean\n" - + "- indicates if more data is present in the iterable with this last return value\n" - + "- for const iterators all return values of `next` except the last one must be const", - id="invalid-iterator-no-last-bool-return-value", - ), - pytest.param( - """ - struct bar { x as int } - fn bar:next args b as bar, y as int return never { 0 exit } - struct foo { x as int } - fn foo:iter args f as foo return bar { bar } - fn main { foo foreach { nop } } - """, - InvalidIterator, - "/foo/main.aaa:6:27: Invalid iterator type bar to iterate over foo.\n" - + "Iterator types need to have a function named next which:\n" - + "- takes one argument (the iterator)\n" - + "- returns at least 2 values, the last being a boolean\n" - + "- indicates if more data is present in the iterable with this last return value\n" - + "- for const iterators all return values of `next` except the last one must be const", - id="invalid-iterator-never-return", - marks=pytest.mark.skip, - ), - pytest.param( - """ - fn main { vec[int] foreach { nop } } - """, - ForeachLoopTypeError, - "/foo/main.aaa:2:32: Invalid stack modification inside foreach loop body\n" - + "stack at end of foreach loop: vec_iter[int] int\n" - + " expected stack: vec_iter[int]", - id="foreach-loop-type-error", - ), - pytest.param( - """ - fn main { use a { nop } } - """, - UseBlockStackUnderflow, - "/foo/main.aaa:2:23: Use block consumes more values than can be found on the stack\n" - + " stack size: 0\n" - + "used variables: 1", - id="use-block-stack-underflow", - ), - pytest.param( - """ - fn main { 0 use a { a <- { false } } } - """, - AssignmentTypeError, - "/foo/main.aaa:2:33: Assignment with wrong number and/or type of values\n" - + "expected types: int\n" - + " found types: bool", - id="assignment-type-error-wrong-type", - ), - pytest.param( - """ - fn main { 0 use a { a <- { nop } } } - """, - AssignmentTypeError, - "/foo/main.aaa:2:33: Assignment with wrong number and/or type of values\n" - + "expected types: int\n" - + " found types: ", - id="assignment-type-error-empty-stack", - ), - pytest.param( - """ - fn main { 0 use a { a <- { "" false } } } - """, - AssignmentTypeError, - "/foo/main.aaa:2:33: Assignment with wrong number and/or type of values\n" - + "expected types: int\n" - + " found types: str bool", - id="assignment-type-error-too-many-types", - ), - pytest.param( - """ - fn main { 0 use a { a <- { 0 0 } } } - """, - AssignmentTypeError, - "/foo/main.aaa:2:33: Assignment with wrong number and/or type of values\n" - + "expected types: int\n" - + " found types: int int", - id="assignment-type-error-one-too-much", - ), - pytest.param( - """ - fn main { a <- { nop } } - """, - UnknownVariableOrFunction, - "/foo/main.aaa:2:23: Usage of unknown variable or function a", - id="unknown-var", - ), - pytest.param( - """ - fn main { 0 0 use a { use a { nop } } } - """, - CollidingVariable, - "Found name collision:\n" - + "/foo/main.aaa:2:31: local variable a\n" - + "/foo/main.aaa:2:39: local variable a", - id="colliding-identifier-var-var", - ), - pytest.param( - """ - fn main { nop } - fn foo args a as int { 0 use a { nop } } - """, - CollidingVariable, - "Found name collision:\n" - + "/foo/main.aaa:3:25: function argument a\n" - + "/foo/main.aaa:3:42: local variable a", - id="colliding-identifier-var-arg", - ), - pytest.param( - """ - struct bar { x as int } - fn main { 0 use bar { nop } } - """, - CollidingVariable, - "Found name collision:\n" - + "/foo/main.aaa:2:13: struct bar\n" - + "/foo/main.aaa:3:29: local variable bar", - id="colliding-identifier-var-identifier", - ), - pytest.param( - """ - fn main { 3 foo } - fn foo args x as const int { x bar } - fn bar args x as int { nop } - """, - StackTypesError, - "/foo/main.aaa:3:44: Invalid stack types when calling bar\n" - + "Expected stack top: int\n" - + " Found stack: (const int)", - id="stack-types-error-const", - ), - pytest.param( - """ - struct foo { x as int } - fn foo:bar args f as const foo { f "x" { 3 } ! } - fn main { nop } - """, - UpdateConstStructError, - "/foo/main.aaa:3:48: Cannot update field x on const struct foo", - id="update-const-struct", - ), - pytest.param( - """ - fn main { nop } - fn foo args x as const int { x <- { 3 } } - """, - AssignConstValueError, - "/foo/main.aaa:3:42: Cannot assign to (const int) x", - id="assign-to-const-value", - ), - pytest.param( - """ - struct foo { x as int } - fn foo:bar args f as const foo { f "x" ? use x { x <- { 3 } } } - fn main { nop } - """, - AssignConstValueError, - "/foo/main.aaa:3:62: Cannot assign to (const int) x", - id="assign-to-const-value", - ), - pytest.param( - """ - fn main { 3 make_const use x { x <- { 5 } } } - """, - AssignConstValueError, - "/foo/main.aaa:2:44: Cannot assign to (const int) x", - id="assign-to-const-value-makeconst", - ), - pytest.param( - "fn", - AaaParserBaseException, - "/foo/main.aaa: Unexpected end of file\n" + "Expected one of: identifier", - id="end-of-file-exception", - ), - pytest.param( - "fn main { 3 use c { c[int] } }", - InvalidCallWithTypeParameters, - "/foo/main.aaa:1:21: Cannot use variable c with type parameters", - id="invalid-call-with-type-params-argument", - ), - pytest.param( - """ - fn main { nop } - fn foo args a as int { a[b] drop } - """, - InvalidCallWithTypeParameters, - "/foo/main.aaa:3:36: Cannot use argument a with type parameters", - id="invalid-call-with-type-params-argument", - ), - pytest.param( - 'fn main return str { "" }', - InvalidMainSignuture, - "/foo/main.aaa:1:1: Main function has wrong signature, it should have:\n" - + "- no type parameters\n" - + "- either no arguments or one vec[str] argument\n" - + "- return either nothing or an int", - id="main-returning-wrong-type", - ), - pytest.param( - """ - fn main { nop } - fn foo return never { 0 } - """, - FunctionTypeError, - "/foo/main.aaa:3:13: Function foo returns wrong type(s)\n" - + "expected return types: never\n" - + " found return types: int", - id="return-with-return-type-never", - ), - pytest.param( - """ - fn main { nop } - fn foo return int { false } - """, - FunctionTypeError, - "/foo/main.aaa:3:13: Function foo returns wrong type(s)\n" - + "expected return types: int\n" - + " found return types: bool", - id="return-wrong-type", - ), - pytest.param( - """ - fn main { nop } - fn foo return int { false return } - """, - ReturnTypesError, - "/foo/main.aaa:3:39: Invalid stack types when returning.\n" - + "function returns: int\n" - + " found stack: bool", - id="return-wrong-type-explicit", - ), - pytest.param( - """ - fn main { nop } - fn foo return vec[int] { vec[bool] } - """, - FunctionTypeError, - "/foo/main.aaa:3:13: Function foo returns wrong type(s)\n" - + "expected return types: vec[int]\n" - + " found return types: vec[bool]", - id="return-wrong-type-param", - ), - pytest.param( - """ - fn main { nop } - fn foo return const int { 5 } - """, - FunctionTypeError, - "/foo/main.aaa:3:13: Function foo returns wrong type(s)\n" - + "expected return types: (const int)\n" - + " found return types: int", - id="return-not-const", - ), - pytest.param( - """ - fn main { nop } - fn foo return int { 5 return 3 } - """, - UnreachableCode, - "/foo/main.aaa:3:42: Found unreachable code.", - id="unreachable-code", - ), - pytest.param( - """ - enum foo { a as int } - fn main { match { case main:bar { nop } } } - """, - InvalidEnumType, - "/foo/main.aaa:3:31: Cannot use function main as enum type", - id="invalid-enum-type-non-enum", - ), - pytest.param( - """ - fn main { match { case int:bar { nop } } } - """, - InvalidEnumType, - "/foo/main.aaa:2:31: Cannot use struct int as enum type", - id="use-non-type-as-enum-type", - ), - pytest.param( - """ - enum foo { a as int } - fn main { match { case foo:b { nop } } } - """, - InvalidEnumVariant, - "/foo/main.aaa:3:31: Variant b of enum foo does not exist", - id="non-existent-enum-variant", - ), - pytest.param( - """ - enum foo { bar as int } - fn main { match { case foo:bar { nop } } } - """, - MatchTypeError, - "/foo/main.aaa:3:23: Cannot match on this stack:\n" - + "expected stack types: \n" - + " found stack types: ", - id="match-empty-stack", - ), - pytest.param( - """ - enum foo { bar as int } - fn main { 3 match { case foo:bar { nop } } } - """, - MatchTypeError, - "/foo/main.aaa:3:25: Cannot match on this stack:\n" - + "expected stack types: \n" - + " found stack types: int", - id="match-non-emum", - ), - pytest.param( - """ - enum foo { bar as int } - enum baz { quux as int } - fn main { 3 foo:bar match { case baz:quux { nop } } } - """, - CaseEnumTypeError, - "/foo/main.aaa:4:41: Cannot use case for enum baz when matching on enum foo", - id="case-from-different-enum", - ), - pytest.param( - """ - enum foo { a as int, b as int } - fn main { - 3 foo:a - match { - case foo:a { nop } - case foo:b { 5 } - } - } - """, - CaseStackTypeError, - "Inconsistent stack types for match cases:\n" - + "/foo/main.aaa:6:21: (case foo:a) int\n" - + "/foo/main.aaa:7:21: (case foo:b) int int", - id="case-stack-type-error", - ), - pytest.param( - """ - enum foo { a as int, b as int } - fn main { 3 foo:a match { case foo:a { nop } } } - """, - MissingEnumCases, - "/foo/main.aaa:3:31: Missing cases for enum foo.\n- foo:b", - id="missing-enum-case", - ), - pytest.param( - """ - enum foo { a as int, b as int } - fn main { 3 foo:a match { case foo:a { nop } case foo:a { nop } } } - """, - DuplicateCase, - "Duplicate case found in match block:\n" - + "/foo/main.aaa:3:39: case foo:a\n" - + "/foo/main.aaa:3:58: case foo:a", - id="duplicate-case", - ), - pytest.param( - """ - enum foo { a as int, b as int } - fn main { 3 foo:a match { default { nop } default { nop } } } - """, - DuplicateCase, - "Duplicate case found in match block:\n" - + "/foo/main.aaa:3:39: default\n" - + "/foo/main.aaa:3:55: default", - id="duplicate-default", - ), - pytest.param( - """ - enum foo { a as int, b as int } - fn main { 3 foo:a match { case foo:a { drop } case foo:b { drop } default { nop } } } - """, - UnreachableDefaultBlock, - "/foo/main.aaa:3:79: Unreachable default block.", - id="unreachable-default-block", - ), - pytest.param( - """ - enum foo { x as int, x as int } - fn main { nop } - """, - CollidingEnumVariant, - "Duplicate enum variant name collision:\n" - + "/foo/main.aaa:2:24: enum variant foo:x\n" - + "/foo/main.aaa:2:34: enum variant foo:x", - id="colliding-enum-variant", - ), - pytest.param( - """ - enum foo { bar } - fn main { foo match { case foo:bar as a, b, c { nop } } } - """, - CaseAsArgumentCountError, - "/foo/main.aaa:3:35: Unexpected number of case-arguments.\n" - + "Expected arguments: 3\n" - + " Found arguments: 0", - id="case-as-argument-count-error", - ), - pytest.param( - """ - enum Foo { bar as int } - fn main { - Foo - match { - case Foo:bar as value { - 3 - use value { - nop - } - } - } - } - """, - CollidingVariable, - "Found name collision:\n" - + "/foo/main.aaa:6:37: local variable value\n" - + "/foo/main.aaa:8:29: local variable value", - id="case-as-use-collision", - ), - pytest.param( - """ - enum Foo { bar as int } - fn main { - 3 - use value { - Foo - match { - case Foo:bar as value { - nop - } - } - } - } - """, - CollidingVariable, - "Found name collision:\n" - + "/foo/main.aaa:5:21: local variable value\n" - + "/foo/main.aaa:8:41: local variable value", - id="use-case-as-collision", - ), - pytest.param( - """ - enum Foo { bar as int } - fn main { Foo "field" { 3 } ! } - """, - UseFieldOfEnumException, - "/foo/main.aaa:3:27: Cannot set field on Enum", - id="set-field-on-enum", - ), - pytest.param( - """ - enum Foo { bar as int } - fn main { Foo "field" ? drop } - """, - UseFieldOfEnumException, - "/foo/main.aaa:3:27: Cannot get field on Enum", - id="set-field-on-enum", - ), - pytest.param( - """ - struct Foo {} - fn main { "Foo" fn drop } - """, - InvalidFunctionPointerTarget, - "/foo/main.aaa:3:23: Cannot create function pointer to struct Foo", - id="invalid-function-pointer-target", - ), - pytest.param( - """ - fn main { "foo" fn drop } - """, - FunctionPointerTargetNotFound, - "/foo/main.aaa:2:23: Cannot create pointer to function foo which was not found", - id="function-pointer-target-not-found", - ), - pytest.param( - """ - fn main { 5 drop[str] } - """, - StackTypesError, - "/foo/main.aaa:2:25: Invalid stack types when calling drop[str]\n" - "Expected stack top: str\n" - " Found stack: int", - id="function-call-with-wrong-type-parameter", - ), - pytest.param( - """ - fn main { nop } - builtin fn foo - """, - UnexpectedBuiltin, - "/foo/main.aaa:3:21: Builtins are not allowed outside the builtins file.", - id="unexpected-builtin-function", - ), - pytest.param( - """ - fn main { nop } - builtin struct foo - """, - UnexpectedBuiltin, - "/foo/main.aaa:3:21: Builtins are not allowed outside the builtins file.", - id="unexpected-builtin-struct", - ), - pytest.param( - """ - struct Foo {} - fn main { Foo Foo = } - """, - UnsupportedOperator, - "/foo/main.aaa:3:31: Type Foo does not support operator =", - id="unsupported-operator", - ), - ], -) -def test_one_error( - code: str, expected_exception_type: type[Exception], expected_exception_message: str -) -> None: - exceptions = check_aaa_full_source(code, "", [expected_exception_type]) - - assert len(exceptions) == 1 - exception_message = str(exceptions[0]) - - exception_message = exception_message.replace( - str(RUNNER_FILE_DICT_ROOT_PATH), "/foo" - ) - assert exception_message == expected_exception_message - - -@pytest.mark.parametrize( - ["files", "expected_exception_type", "expected_exception_message"], - [ - pytest.param( - { - "main.aaa": """ - from "five" import six as foo - fn main { nop } - """, - "five.aaa": """ - fn five return int { 5 } - """, - }, - ImportedItemNotFound, - "/foo/main.aaa:2:36: Could not import six from /foo/five.aaa", - id="imported-item-not-found", - ), - pytest.param( - {}, - FileReadError, - f"{Path('main.aaa').resolve()}: Could not read file. It may not exist.", - id="file-not-found", - ), - pytest.param( - { - "main.aaa": """ - from "five" import five - fn main { nop } - """, - "five.aaa": """ - from "six" import six - fn five return int { six 1 - } - """, - "six.aaa": """ - from "five" import five - fn six return int { five 1 + } - """, - }, - CircularDependencyError, - "Circular dependency detected:\n" - + "- /foo/main.aaa\n" - + "- /foo/five.aaa\n" - + "- /foo/six.aaa\n" - + "- /foo/five.aaa", - id="circular-dependency", - ), - pytest.param( - { - "main.aaa": """ - from \"foo\" import bar - fn main { nop } - """, - "foo.aaa": 'from "bar" import bar', - "bar.aaa": "fn bar { nop }", - }, - IndirectImportException, - "/foo/main.aaa:2:31: Indirect imports are forbidden.", - id="indirect-import", - ), - pytest.param( - { - "main.aaa": """ - from "type" import foo - fn foo:bar args f as foo { nop } - fn main { foo foo:bar } - """, - "type.aaa": "struct foo { x as int }", - }, - MemberFunctionTypeNotFound, - "/foo/main.aaa:3:17: Cannot find type foo in same file as member function definition.", - id="member-function-in-different-file", - ), - pytest.param( - { - "main.aaa": """ - from "test_foo" import test_bar - fn main { nop } - """, - "test_foo.aaa": "fn test_bar args a as int { nop }", - }, - InvalidTestSignuture, - "/foo/test_foo.aaa:1:1: Test function test_bar should have no arguments and no return types", - id="invalid-test-signature-args", - ), - pytest.param( - { - "main.aaa": """ - from "test_foo" import test_bar - fn main { nop } - """, - "test_foo.aaa": "fn test_bar return int { 4 }", - }, - InvalidTestSignuture, - "/foo/test_foo.aaa:1:1: Test function test_bar should have no arguments and no return types", - id="invalid-test-signature-return-types", - ), - pytest.param( - { - "main.aaa": """ - from "test_foo" import test_bar - fn main { nop } - """, - "test_foo.aaa": "fn test_bar return never { 0 exit }", - }, - InvalidTestSignuture, - "/foo/test_foo.aaa:1:1: Test function test_bar should have no arguments and no return types", - id="invalid-test-signature-return-never", - marks=pytest.mark.skip, - ), - ], -) -def test_multi_file_errors( - files: dict[str, str], - expected_exception_type: type[Exception], - expected_exception_message: str, -) -> None: - file_dict = {Path(file): code for file, code in files.items()} - - exceptions = check_aaa_full_source_multi_file( - file_dict, "", [expected_exception_type] - ) - - assert len(exceptions) == 1 - exception_message = str(exceptions[0]) - - exception_message = exception_message.replace( - str(RUNNER_FILE_DICT_ROOT_PATH), "/foo" - ) - assert exception_message == expected_exception_message - - -@pytest.mark.parametrize( - ["files", "expected_exception_message"], - [ - pytest.param( - { - "main.aaa": """ - fn bar { nop } - fn foo args bar as int { nop } - fn main { nop } - """ - }, - "Found name collision:\n" - + "/foo/main.aaa:2:17: function bar\n" - + "/foo/main.aaa:3:29: function argument bar", - id="argname-other-func-name", - ), - pytest.param( - { - "main.aaa": """ - fn foo args foo as int { nop } - fn main { nop } - """ - }, - "Found name collision:\n" - + "/foo/main.aaa:2:17: function foo\n" - + "/foo/main.aaa:2:29: function argument foo", - id="argname-same-funcname", - ), - pytest.param( - { - "main.aaa": """ - fn foo args bar as int, bar as int { nop } - fn main { nop } - """ - }, - "Found name collision:\n" - + "/foo/main.aaa:2:29: function argument bar\n" - + "/foo/main.aaa:2:41: function argument bar", - id="argname-argname", - ), - pytest.param( - { - "main.aaa": """ - struct bar { x as int } - fn foo args bar as int { nop } - fn main { nop } - """ - }, - "Found name collision:\n" - + "/foo/main.aaa:2:17: struct bar\n" - + "/foo/main.aaa:3:29: function argument bar", - id="argname-struct", - ), - pytest.param( - { - "main.aaa": """ - from "five" import five - fn foo args five as int { nop } - fn main { nop } - """, - "five.aaa": """ - fn five return int { 5 } - """, - }, - "Found name collision:\n" - + "/foo/main.aaa:2:36: imported identifier five\n" - + "/foo/main.aaa:3:29: function argument five", - id="argname-import", - ), - pytest.param( - { - "main.aaa": """ - fn foo { nop } - fn foo { nop } - fn main { nop } - """ - }, - "Found name collision:\n" - + "/foo/main.aaa:2:17: function foo\n" - + "/foo/main.aaa:3:17: function foo", - id="funcname-funcname", - ), - pytest.param( - { - "main.aaa": """ - struct foo { x as int } - fn foo { nop } - fn main { nop } - """ - }, - "Found name collision:\n" - + "/foo/main.aaa:2:17: struct foo\n" - + "/foo/main.aaa:3:17: function foo", - id="funcname-struct", - ), - pytest.param( - { - "main.aaa": """ - from "five" import five - fn five { nop } - fn main { nop } - """, - "five.aaa": """ - fn five return int { 5 } - """, - }, - "Found name collision:\n" - + "/foo/main.aaa:2:36: imported identifier five\n" - + "/foo/main.aaa:3:17: function five", - id="funcname-import", - ), - pytest.param( - { - "main.aaa": """ - fn foo { nop } - struct foo { x as int } - fn main { nop } - """, - }, - "Found name collision:\n" - + "/foo/main.aaa:2:17: function foo\n" - + "/foo/main.aaa:3:17: struct foo", - id="struct-funcname", - ), - pytest.param( - { - "main.aaa": """ - struct foo { x as int } - struct foo { x as int } - fn main { nop } - """, - }, - "Found name collision:\n" - + "/foo/main.aaa:2:17: struct foo\n" - + "/foo/main.aaa:3:17: struct foo", - id="struct-struct", - ), - pytest.param( - { - "main.aaa": """ - from "five" import five - struct five { x as int } - fn main { nop } - """, - "five.aaa": """ - fn five return int { 5 } - """, - }, - "Found name collision:\n" - + "/foo/main.aaa:2:36: imported identifier five\n" - + "/foo/main.aaa:3:17: struct five", - id="struct-import", - ), - pytest.param( - { - "main.aaa": """ - from "five" import five as bar - fn foo args bar as int { nop } - fn main { nop } - """, - "five.aaa": """ - fn five return int { 5 } - """, - }, - "Found name collision:\n" - + "/foo/main.aaa:2:36: imported identifier bar\n" - + "/foo/main.aaa:3:29: function argument bar", - id="argname-import-renamed", - ), - pytest.param( - { - "main.aaa": """ - from "five" import five as foo - from "five" import five as foo - fn main { nop } - """, - "five.aaa": """ - fn five return int { 5 } - """, - }, - "Found name collision:\n" - + "/foo/main.aaa:2:36: imported identifier foo\n" - + "/foo/main.aaa:3:36: imported identifier foo", - id="import-import", - ), - ], -) -def test_colliding_identifier( - files: dict[str, str], expected_exception_message: str -) -> None: - file_dict = {Path(file): code for file, code in files.items()} - - exceptions = check_aaa_full_source_multi_file(file_dict, "", [CollidingIdentifier]) - - assert len(exceptions) == 1 - exception_message = str(exceptions[0]) - exception_message = exception_message.replace( - str(RUNNER_FILE_DICT_ROOT_PATH), "/foo" - ) - - print(repr(exception_message)) - print() - assert exception_message == expected_exception_message - - -@pytest.mark.parametrize( - ["func_def_code", "expect_ok"], - [ - ("fn Foo:= args lhs as const Foo, rhs as const Foo return bool { todo }", True), - ("fn Foo:= args lhs as const Foo, rhs as Foo return bool { todo }", False), - ("fn Foo:= args lhs as Foo, rhs as Foo return bool { todo }", False), - ("fn Foo:= args lhs as Foo, rhs as const Foo return bool { todo }", False), - ("fn Foo:= args lhs as const Foo, rhs as const Foo return int { todo }", False), - ( - "fn Foo:= args lhs as const Foo, rhs as const Foo return never { todo }", - False, - ), - ("fn Foo:= args lhs as const Foo, rhs as fn[][] return bool { todo }", False), - ( - "fn Foo:= args lhs as const Foo, rhs as const Foo return bool, bool { todo }", - False, - ), - ("fn Foo:= args lhs as const Foo return bool { todo }", False), - ("fn Foo:= args lhs as Foo { nop }", False), - ], -) -def test_equals_function_signature(func_def_code: str, expect_ok: bool) -> None: - code = "fn main { nop } struct Foo {} " + func_def_code - - expected_exception_types: list[type[Exception]] = [] - - if not expect_ok: - expected_exception_types = [InvalidEqualsFunctionSignature] - - exceptions = check_aaa_full_source(code, "", expected_exception_types) - - assert expect_ok == (not exceptions) diff --git a/python/tests/aaa/lang_constructs/__init__.py b/python/tests/aaa/lang_constructs/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/assignment.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/assignment.aaa deleted file mode 100644 index 1e8feb53..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/assignment.aaa +++ /dev/null @@ -1,14 +0,0 @@ - - -fn add args a as int, b as int, return int { - a b + -} - -fn main { - fn[int, int][int] - use func { - func <- { "add" fn } - 34 35 func call . - } - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/basic.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/basic.aaa deleted file mode 100644 index 2925cb1c..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/basic.aaa +++ /dev/null @@ -1,37 +0,0 @@ - -fn add args a as int, b as int, return int { - a b + -} - -fn calculate args a as int, b as int, callable as fn[int, int][int] return int { - a b callable call -} - -fn calculate_nested args a as int, b as int, callable as fn[int, int, fn[int, int][int]][int] return int { - a b "add" fn callable call -} - -fn main { - // call by function pointer - 34 35 "add" fn calculate . - "\n" . - - // call by function pointer variable - "add" fn - use add_ { - 34 35 add_ call . - } - "\n" . - - // call by nested function pointer - "add" fn - "calculate" fn - use add_, calculate_ { - 34 35 add_ calculate_ call . - } - "\n" . - - // call function with nested function pointer - 34 35 "calculate" fn calculate_nested . - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/imported/importee.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/imported/importee.aaa deleted file mode 100644 index 378d12df..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/imported/importee.aaa +++ /dev/null @@ -1,26 +0,0 @@ - -fn bar return int { - 69 -} - -struct Foo { - value as int, -} - -fn Foo:print_value args foo as Foo { - foo "value" ? . -} - -// We don't use Option from stdlib, so we can add member function. -enum OptionalInt { - none, - some as int, -} - -fn OptionalInt:print_value args optional_int as OptionalInt { - optional_int - match { - case OptionalInt:some { . } - default { "none" . } - } -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/imported/main.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/imported/main.aaa deleted file mode 100644 index 592e17f7..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/imported/main.aaa +++ /dev/null @@ -1,18 +0,0 @@ -from "importee" import bar, Foo, OptionalInt - -fn main { - bar . - "\n" . - - Foo - dup "value" { 69 } ! - "Foo:print_value" fn call - "\n" . - - "OptionalInt:some" fn - use optional_int_some { - 69 optional_int_some call - OptionalInt:print_value - } - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/in_struct.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/in_struct.aaa deleted file mode 100644 index bc1fe785..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/in_struct.aaa +++ /dev/null @@ -1,17 +0,0 @@ - -fn add args a as int, b as int, return int { - a b + -} - -struct Foo { - func as fn[int, int][int], -} - -fn main { - Foo - use foo { - foo "func" { "add" fn } ! - 34 35 foo "func" ? call . - } - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/in_vector.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/in_vector.aaa deleted file mode 100644 index 93a8a223..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/in_vector.aaa +++ /dev/null @@ -1,24 +0,0 @@ - - -fn add args a as int, b as int, return int { - a b + -} - -struct Foo { - func as fn[int, int][int], -} - -fn main { - vec[fn[int, int][int]] - use v { - v "add" fn vec:push - - v - foreach { - use func { - 34 35 func call . - } - } - } - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/never_returns.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/never_returns.aaa deleted file mode 100644 index 485ed0e7..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/never_returns.aaa +++ /dev/null @@ -1,16 +0,0 @@ -fn does_not_return return never { - "does_not_return was called!\n" . - 0 exit -} - -fn call_does_not_return args func as fn[][never] return never { - func call -} - -fn main { - fn[][never] - use func { - func <- { "does_not_return" fn } - func call - } -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/returned.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/returned.aaa deleted file mode 100644 index 90834bdc..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/returned.aaa +++ /dev/null @@ -1,13 +0,0 @@ - -fn add args a as int, b as int, return int { - a b + -} - -fn get_add return fn[int, int][int] { - "add" fn -} - -fn main { - 34 35 get_add call . - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/to_assert.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/to_assert.aaa deleted file mode 100644 index 5ffcfa5a..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/to_assert.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main { - false "assert" fn call -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/to_builtins.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/to_builtins.aaa deleted file mode 100644 index 2ec29bc9..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/to_builtins.aaa +++ /dev/null @@ -1,13 +0,0 @@ -fn main { - // get pointer to builtin function - 69 "dup" fn call . drop - "\n" . - - // get pointer to builtin member-function - "6" "9" "str:append" fn call . - "\n" . - - // get pointer to builtin function with unusual chars - 34 35 "+" fn call . - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/to_enum_associated.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/to_enum_associated.aaa deleted file mode 100644 index d067dc47..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/to_enum_associated.aaa +++ /dev/null @@ -1,25 +0,0 @@ -// We're not using Option from stdlib here, so we can add a method in this file. -enum OptionalInt { - none, - some as int, -} - -fn OptionalInt:print_value args optional_int as OptionalInt { - optional_int - match { - case OptionalInt:some { . } - default { "none" . } - } -} - - -fn main { - "OptionalInt:print_value" fn - use print_value { - 69 OptionalInt:some - use optional_int { - optional_int print_value call - } - } - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/to_enum_constructor.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/to_enum_constructor.aaa deleted file mode 100644 index c618eac0..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/to_enum_constructor.aaa +++ /dev/null @@ -1,27 +0,0 @@ -// We can't use the standard library's Option type because it's generic, and -// we don't support function variables with type parameters. So we'll just use our own. -enum OptionalInt { - none, - some as { int }, -} - -fn main { - "OptionalInt:none" fn - use optional_int_none { - optional_int_none call - match { - case OptionalInt:none { nop } - default { unreachable } - } - } - - "OptionalInt:some" fn - use optional_int_some { - 69 optional_int_some call - match { - case OptionalInt:some { . } - default { unreachable } - } - } - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/to_struct_associated.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/to_struct_associated.aaa deleted file mode 100644 index 84e6f97d..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/to_struct_associated.aaa +++ /dev/null @@ -1,20 +0,0 @@ -struct Bar { - value as int, -} - -fn Bar:increment args bar as Bar { - bar "value" { bar "value" ? 1 + } ! -} - -fn main { - Bar - use bar { - bar "value" { 68 } ! - "Bar:increment" fn - use increment { - bar increment call - } - bar "value" ? . - } - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/to_todo.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/to_todo.aaa deleted file mode 100644 index 483dbb15..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/to_todo.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main { - "todo" fn call -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/to_unreachable.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/to_unreachable.aaa deleted file mode 100644 index 1dd9204a..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/to_unreachable.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main { - "unreachable" fn call -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/zero_value_in_enum.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/zero_value_in_enum.aaa deleted file mode 100644 index 2ac1ec86..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/zero_value_in_enum.aaa +++ /dev/null @@ -1,10 +0,0 @@ -enum Calculation { - some as fn[int, int][int], -} - -fn main { - Calculation - match { - case Calculation:some as func { 34 35 func call drop } - } -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/zero_value_in_struct.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/zero_value_in_struct.aaa deleted file mode 100644 index 5e8a26b9..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/zero_value_in_struct.aaa +++ /dev/null @@ -1,8 +0,0 @@ -struct Foo { - func as fn[int, int][int], -} - - -fn main { - 34 35 Foo "func" ? call drop -} diff --git a/python/tests/aaa/lang_constructs/src/function_pointers/zero_value_on_stack.aaa b/python/tests/aaa/lang_constructs/src/function_pointers/zero_value_on_stack.aaa deleted file mode 100644 index d3e679c9..00000000 --- a/python/tests/aaa/lang_constructs/src/function_pointers/zero_value_on_stack.aaa +++ /dev/null @@ -1,4 +0,0 @@ - -fn main { - 34 35 fn[int, int][int] call drop -} diff --git a/python/tests/aaa/lang_constructs/src/struct_type_parameters/make_tree_with_type_param.aaa b/python/tests/aaa/lang_constructs/src/struct_type_parameters/make_tree_with_type_param.aaa deleted file mode 100644 index 06cc8cb0..00000000 --- a/python/tests/aaa/lang_constructs/src/struct_type_parameters/make_tree_with_type_param.aaa +++ /dev/null @@ -1,13 +0,0 @@ -struct Tree[T] { - children as vec[Tree[T]], - data as T, -} - -fn make_tree[T] args data as T return Tree[T] { - Tree[T] - dup "data" { data } ! -} - -fn main { - 5 make_tree[int] drop -} diff --git a/python/tests/aaa/lang_constructs/src/struct_type_parameters/pair.aaa b/python/tests/aaa/lang_constructs/src/struct_type_parameters/pair.aaa deleted file mode 100644 index 9cfcceab..00000000 --- a/python/tests/aaa/lang_constructs/src/struct_type_parameters/pair.aaa +++ /dev/null @@ -1,42 +0,0 @@ -struct Pair[A, B] { - a as A, - b as B, -} - -fn make_pair[A, B] args a as A, b as B return Pair[A, B] { - Pair[A, B] - dup "a" { a } ! - dup "b" { b } ! -} - -fn Pair[A, B]:print args pair as Pair[A, B] { - pair . - "\n" . - - "a = " . - pair "a" ? . - "\n" . - - "b = " . - pair "b" ? . - "\n" . -} - -fn main { - 69 "hello" make_pair - - use pair { - pair Pair:print - pair "a" ? 69 = assert - pair "b" ? "hello" = assert - - - pair "a" { 420 } ! - pair "b" { "world" } ! - - pair "a" ? 420 = assert - pair "b" ? "world" = assert - - pair Pair:print - } -} diff --git a/python/tests/aaa/lang_constructs/src/struct_type_parameters/pair_partial.aaa b/python/tests/aaa/lang_constructs/src/struct_type_parameters/pair_partial.aaa deleted file mode 100644 index c8047f70..00000000 --- a/python/tests/aaa/lang_constructs/src/struct_type_parameters/pair_partial.aaa +++ /dev/null @@ -1,29 +0,0 @@ -struct Pair[A, B] { - a as A, - b as B, -} - -fn make_pair_partial[A] args a as A return Pair[A, str] { - Pair[A, str] - dup "a" { a } ! - dup "b" { "hello" } ! -} - -fn main { - 69 make_pair_partial - - use pair { - pair "a" ? 69 = assert - pair "b" ? "hello" = assert - } - - "foo" make_pair_partial - - use pair { - pair "a" ? "foo" = assert - pair "b" ? "hello" = assert - - pair . - "\n" . - } -} diff --git a/python/tests/aaa/lang_constructs/src/struct_type_parameters/tree.aaa b/python/tests/aaa/lang_constructs/src/struct_type_parameters/tree.aaa deleted file mode 100644 index 0e65e0f5..00000000 --- a/python/tests/aaa/lang_constructs/src/struct_type_parameters/tree.aaa +++ /dev/null @@ -1,22 +0,0 @@ -struct Tree[T] { - children as vec[Tree[T]], - data as T, -} - -fn make_tree[T] args data as T return Tree[T] { - Tree[T] - dup "data" { data } ! -} - -fn Tree[T]:push args tree as Tree[T], child as Tree[T] { - tree "children" ? child vec:push -} - -fn main { - 5 make_tree - use t { - t 4 make_tree Tree:push - t . - } - "\n" . -} diff --git a/python/tests/aaa/lang_constructs/test_function_pointers.py b/python/tests/aaa/lang_constructs/test_function_pointers.py deleted file mode 100644 index 319f372f..00000000 --- a/python/tests/aaa/lang_constructs/test_function_pointers.py +++ /dev/null @@ -1,63 +0,0 @@ -from pathlib import Path - -import pytest - -from aaa.runner.runner import compile_run - -SOURCE_PREFIX = Path(__file__).parent / "src/function_pointers" - - -@pytest.mark.parametrize( - ["source_path", "expected_stdout"], - [ - ("basic.aaa", "69\n69\n69\n69\n"), - ("in_struct.aaa", "69\n"), - ("in_vector.aaa", "69\n"), - ("assignment.aaa", "69\n"), - ("returned.aaa", "69\n"), - ("to_builtins.aaa", "69\n69\n69\n"), - ("never_returns.aaa", "does_not_return was called!\n"), - ("to_enum_constructor.aaa", "69\n"), - ("to_enum_associated.aaa", "69\n"), - ("to_struct_associated.aaa", "69\n"), - ("imported/main.aaa", "69\n69\n69\n"), - ], -) -def test_function_pointer(source_path: Path, expected_stdout: str) -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / source_path) - - assert expected_stdout == stdout - assert stderr == "" - assert exit_code == 0 - - -@pytest.mark.parametrize( - ["source_path"], - [ - ("zero_value_in_enum.aaa",), - ("zero_value_in_struct.aaa",), - ("zero_value_on_stack.aaa",), - ], -) -def test_zero_value(source_path: Path) -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / source_path) - - assert stdout == "" - assert stderr == "Function pointer with zero-value was called.\n" - assert exit_code == 1 - - -@pytest.mark.parametrize( - ["source_path", "expected_stderr"], - [ - ("to_todo.aaa", "Code at ??:??:?? is not implemented\n"), - ("to_unreachable.aaa", "Code at ??:??:?? should be unreachable\n"), - ("to_assert.aaa", "Assertion failure at ??:??:??\n"), - ], -) -def test_print_calling_location(source_path: Path, expected_stderr: str) -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / source_path) - - assert stdout == "" - assert expected_stderr == stderr - assert exit_code == 1 diff --git a/python/tests/aaa/lang_constructs/test_imports.py b/python/tests/aaa/lang_constructs/test_imports.py deleted file mode 100644 index c14d0666..00000000 --- a/python/tests/aaa/lang_constructs/test_imports.py +++ /dev/null @@ -1,68 +0,0 @@ -from pathlib import Path - -import pytest - -from aaa.cross_referencer.exceptions import ImportedItemNotFound -from aaa.parser.exceptions import FileReadError -from tests.aaa import check_aaa_full_source_multi_file - - -@pytest.mark.parametrize( - ["files", "expected_output", "expected_exception_types"], - [ - pytest.param( - { - "add.aaa": "fn add args a as int, b as int, return int, { a b + }", - "main.aaa": 'from "add" import add,\n fn main { 3 2 add . }', - }, - "5", - [], - id="two-files", - ), - pytest.param( - { - "add.aaa": "fn add args a as int, b as int, return int, { a b + }", - "main.aaa": 'from "add" import add as foo,\n fn main { 3 2 foo . }', - }, - "5", - [], - id="two-files-alias", - ), - pytest.param( - { - "add.aaa": "fn add args a as int, b as int, return int, { a b + }", - "main.aaa": 'from "add" import foo,\n fn main { 3 2 foo . }', - }, - "", - [ImportedItemNotFound], - id="two-files-nonexistent-function", - ), - pytest.param( - { - "main.aaa": 'from "add" import add,\n fn main { 3 2 add . }', - }, - "", - [FileReadError], - id="two-files-nonexistent-file", - ), - pytest.param( - { - "five.aaa": "fn five return int { 5 }", - "six.aaa": 'from "five" import five\n fn six return int { five 1 + }', - "main.aaa": 'from "six" import six\n fn main { six . }', - }, - "6", - [], - id="three-files", - ), - ], -) -def test_imports( - files: dict[str, str], - expected_output: str, - expected_exception_types: list[type[Exception]], -) -> None: - file_dict = {Path(file): code for file, code in files.items()} - check_aaa_full_source_multi_file( - file_dict, expected_output, expected_exception_types - ) diff --git a/python/tests/aaa/lang_constructs/test_return.py b/python/tests/aaa/lang_constructs/test_return.py deleted file mode 100644 index c29fc6af..00000000 --- a/python/tests/aaa/lang_constructs/test_return.py +++ /dev/null @@ -1,33 +0,0 @@ -import pytest - -from tests.aaa import check_aaa_full_source - - -@pytest.mark.parametrize( - ["code"], - [ - pytest.param( - """ - fn main { nop } - fn foo return int { - if true { - 5 return - } - 7 - } - """, - id="if-else", - ), - pytest.param( - """ - fn main { nop } - fn foo return int { 3 return } - """, - id="never-coerce", - ), - ], -) -def test_return( - code: str, -) -> None: - check_aaa_full_source(code, "", []) diff --git a/python/tests/aaa/lang_constructs/test_struct_type_parameters.py b/python/tests/aaa/lang_constructs/test_struct_type_parameters.py deleted file mode 100644 index 78682a7f..00000000 --- a/python/tests/aaa/lang_constructs/test_struct_type_parameters.py +++ /dev/null @@ -1,35 +0,0 @@ -from pathlib import Path - -import pytest - -from aaa.runner.runner import compile_run - -SOURCE_PREFIX = Path(__file__).parent / "src/struct_type_parameters" - - -@pytest.mark.parametrize( - ["source_path", "expected_stdout"], - [ - ( - "pair.aaa", - 'Pair{a: 69, b: "hello"}\n' - "a = 69\n" - "b = hello\n" - 'Pair{a: 420, b: "world"}\n' - "a = 420\n" - "b = world\n", - ), - ("pair_partial.aaa", 'Pair{a: "foo", b: "hello"}\n'), - ( - "tree.aaa", - "Tree{children: [Tree{children: [], data: 4}], data: 5}\n", - ), - ("make_tree_with_type_param.aaa", ""), - ], -) -def test_struct_type_parameters(source_path: Path, expected_stdout: str) -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / source_path) - - assert expected_stdout == stdout - assert stderr == "" - assert exit_code == 0 diff --git a/python/tests/aaa/misc/__init__.py b/python/tests/aaa/misc/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/tests/aaa/misc/src/assert_false.aaa b/python/tests/aaa/misc/src/assert_false.aaa deleted file mode 100644 index 033cd256..00000000 --- a/python/tests/aaa/misc/src/assert_false.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main { - false assert -} diff --git a/python/tests/aaa/misc/src/assert_true.aaa b/python/tests/aaa/misc/src/assert_true.aaa deleted file mode 100644 index 5c042845..00000000 --- a/python/tests/aaa/misc/src/assert_true.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main { - true assert -} diff --git a/python/tests/aaa/misc/src/assignment.aaa b/python/tests/aaa/misc/src/assignment.aaa deleted file mode 100644 index 6f90d57a..00000000 --- a/python/tests/aaa/misc/src/assignment.aaa +++ /dev/null @@ -1,69 +0,0 @@ - -fn main { - - 0 - use a { - a <- { 1 } - a . - "\n" . - } - - false - use b { - b <- { true } - b . - "\n" . - } - - "foo" - use c { - c <- { "bar" } - c . - "\n" . - } - - - vec[int] - dup 1 vec:push - - use d { - d <- { - vec[int] - dup 2 vec:push - } - d . - "\n" . - } - - map[str, int] - dup "one" 1 map:set - - use e { - e <- { - map[str, int] - dup "two" 2 map:set - } - e . - "\n" . - } - - set[int] - dup 1 set:add - - use f { - f <- { - set[int] - dup 2 set:add - } - f . - "\n" . - } - - 5 some_function -} - -fn some_function args x as int { - x <- { 6 } - x . - "\n" . -} diff --git a/python/tests/aaa/misc/src/chdir_fail.aaa b/python/tests/aaa/misc/src/chdir_fail.aaa deleted file mode 100644 index 1c1c838b..00000000 --- a/python/tests/aaa/misc/src/chdir_fail.aaa +++ /dev/null @@ -1,7 +0,0 @@ -fn main { - "/non-existent" chdir - not assert - - getcwd . - "\n" . -} diff --git a/python/tests/aaa/misc/src/chdir_ok.aaa b/python/tests/aaa/misc/src/chdir_ok.aaa deleted file mode 100644 index f7b036ec..00000000 --- a/python/tests/aaa/misc/src/chdir_ok.aaa +++ /dev/null @@ -1,7 +0,0 @@ -fn main { - "/tmp" chdir - assert - - getcwd . - "\n" . -} diff --git a/python/tests/aaa/misc/src/const.aaa b/python/tests/aaa/misc/src/const.aaa deleted file mode 100644 index a4c02821..00000000 --- a/python/tests/aaa/misc/src/const.aaa +++ /dev/null @@ -1,34 +0,0 @@ - -// const function arg example - -fn add args x as const int, y as const int return int { - x y + -} - -fn main { - 34 35 add . - "\n" . - - vec[int] - use x { - x - dup - dup 5 vec:push - . - "\n" . - . - "\n" . - } - - vec[int] - use x { - x - copy - dup 5 vec:push - . - "\n" . - . - "\n" . - } - -} diff --git a/python/tests/aaa/misc/src/divide_by_zero.aaa b/python/tests/aaa/misc/src/divide_by_zero.aaa deleted file mode 100644 index bc513e39..00000000 --- a/python/tests/aaa/misc/src/divide_by_zero.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main { - 3 0 / drop -} diff --git a/python/tests/aaa/misc/src/enum.aaa b/python/tests/aaa/misc/src/enum.aaa deleted file mode 100644 index 4369ee83..00000000 --- a/python/tests/aaa/misc/src/enum.aaa +++ /dev/null @@ -1,130 +0,0 @@ -struct Bar { - value as int, -} - -enum Foo { - bool_ as bool, - int_ as int, - str_ as str, - vec_ as vec[str], - map_ as map[str, str], - set_ as set[str], - regex_ as regex, - enum_ as Foo, - struct_ as Bar, - no_data, - multiple_data as { int, vec[str] }, -} - -fn Foo:do_things args foo as Foo { - foo . - "\n" . - - foo - match { - case Foo:bool_ { "bool_ " . . "\n" . } - case Foo:int_ { "int_ " . . "\n" . } - case Foo:str_ { "str_ " . . "\n" . } - case Foo:vec_ { "vec_ " . . "\n" . } - case Foo:map_ { "map_ " . . "\n" . } - case Foo:set_ { "set_ " . . "\n" . } - case Foo:regex_ { "regex_ " . . "\n" . } - case Foo:enum_ { "enum_ " . . "\n" . } - case Foo:struct_ { "struct_ " . . "\n" . } - case Foo:no_data { "no_data " . "\n" . } - case Foo:multiple_data { "multiple_data " . . " " . . "\n" . } - } - - foo - match { - case Foo:bool_ as a { nop } - case Foo:int_ as a { nop } - case Foo:str_ as a { nop } - case Foo:vec_ as a { nop } - case Foo:map_ as a { nop } - case Foo:set_ as a { nop } - case Foo:regex_ as a { nop } - case Foo:enum_ as a { nop } - case Foo:struct_ as a { nop } - case Foo:no_data { nop } - case Foo:multiple_data as a, b { nop } - } - - - - foo copy drop drop -} - -enum OneVariant { - value as int, -} - -fn main { - Foo - Foo:do_things - "\n" . - - true Foo:bool_ - Foo:do_things - "\n" . - - 69 Foo:int_ - Foo:do_things - "\n" . - - "hello" Foo:str_ - Foo:do_things - "\n" . - - vec[str] Foo:vec_ - Foo:do_things - "\n" . - - map[str, str] Foo:map_ - Foo:do_things - "\n" . - - set[str] Foo:set_ - Foo:do_things - "\n" . - - regex Foo:regex_ - Foo:do_things - "\n" . - - Foo Foo:enum_ - Foo:do_things - "\n" . - - Bar Foo:struct_ - Foo:do_things - "\n" . - - Foo:no_data - Foo:do_things - "\n" . - - 3 vec[str] Foo:multiple_data - Foo:do_things - "\n" . - - Foo copy - use foo, foo_copy { - foo <- { true Foo:bool_ } - foo . "\n" . - foo_copy . "\n" . - } - - OneVariant - use one_variant { - one_variant - match { - case OneVariant:value { drop } - } - - one_variant - match { - default { nop } - } - } -} diff --git a/python/tests/aaa/misc/src/enum_case_as.aaa b/python/tests/aaa/misc/src/enum_case_as.aaa deleted file mode 100644 index 852929b2..00000000 --- a/python/tests/aaa/misc/src/enum_case_as.aaa +++ /dev/null @@ -1,42 +0,0 @@ -enum Event { - quit, - message as str, - click as { int, int }, -} - -fn Event:print args event as Event { - event - match { - // no associated data, so `as` syntax cannot be used - case Event:quit { - "quit\n" . - } - - case Event:message as text { - // stack is empty now, text can be used as variable - "text = " . - text . - "\n" . - } - - case Event:click as x, y { - // stack is empty, x and y can be used as variables - "x = " . - x . - " y = " . - y . - "\n" . - } - } -} - -fn main { - Event:quit - Event:print - - "hello" Event:message - Event:print - - 6 9 Event:click - Event:print -} diff --git a/python/tests/aaa/misc/src/enum_with_multiple_associated_fields.aaa b/python/tests/aaa/misc/src/enum_with_multiple_associated_fields.aaa deleted file mode 100644 index 44dbfce7..00000000 --- a/python/tests/aaa/misc/src/enum_with_multiple_associated_fields.aaa +++ /dev/null @@ -1,55 +0,0 @@ -enum Event { - click as { int, int }, - message as str, - message_with_brackets as { str }, - quit, -} - -fn Event:print args event as Event { - event - match { - case Event:quit { - "quit\n" . - } - case Event:message { - "message " . - use text { - "text=" . - text . - "\n" . - } - } - case Event:click { - "click " . - use x, y { - "x=" . - x . - " y=" . - y . - "\n" . - } - } - case Event:message_with_brackets { - "message_with_brackets " . - use text { - "text=" . - text . - "\n" . - } - } - } -} - -fn main { - Event:quit - Event:print - - "hello" Event:message - Event:print - - "world" Event:message_with_brackets - Event:print - - 6 9 Event:click - Event:print -} diff --git a/python/tests/aaa/misc/src/environ.aaa b/python/tests/aaa/misc/src/environ.aaa deleted file mode 100644 index 79f00298..00000000 --- a/python/tests/aaa/misc/src/environ.aaa +++ /dev/null @@ -1,4 +0,0 @@ -fn main { - environ . - "\n" . -} diff --git a/python/tests/aaa/misc/src/execve_fail.aaa b/python/tests/aaa/misc/src/execve_fail.aaa deleted file mode 100644 index f504a57d..00000000 --- a/python/tests/aaa/misc/src/execve_fail.aaa +++ /dev/null @@ -1,4 +0,0 @@ -fn main { - "/non-existent" vec[str] map[str, str] execve - not assert -} diff --git a/python/tests/aaa/misc/src/execve_ok.aaa b/python/tests/aaa/misc/src/execve_ok.aaa deleted file mode 100644 index 135a39e7..00000000 --- a/python/tests/aaa/misc/src/execve_ok.aaa +++ /dev/null @@ -1,13 +0,0 @@ -fn main { - "/bin/true" - - vec[str] - dup "/bin/true" vec:push - - environ - - execve - - // if all goes well, this is unreachable - assert -} diff --git a/python/tests/aaa/misc/src/exit_0.aaa b/python/tests/aaa/misc/src/exit_0.aaa deleted file mode 100644 index 47e572aa..00000000 --- a/python/tests/aaa/misc/src/exit_0.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main return never { - 0 exit -} diff --git a/python/tests/aaa/misc/src/exit_1.aaa b/python/tests/aaa/misc/src/exit_1.aaa deleted file mode 100644 index f00c2806..00000000 --- a/python/tests/aaa/misc/src/exit_1.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main return never { - 1 exit -} diff --git a/python/tests/aaa/misc/src/foreach_custom.aaa b/python/tests/aaa/misc/src/foreach_custom.aaa deleted file mode 100644 index 8a7dc482..00000000 --- a/python/tests/aaa/misc/src/foreach_custom.aaa +++ /dev/null @@ -1,35 +0,0 @@ -fn range args start as int, end as int return RangeIter { - RangeIter - dup "end" { end } ! - dup "next" { start } ! -} - -struct RangeIter { - end as int, - next as int, -} - -fn RangeIter:iter args r as RangeIter return RangeIter { - r -} - -fn RangeIter:next args r as RangeIter return int, bool { - if - r "next" ? - r "end" ? - >= - { - 0 false - } else { - r "next" ? true - r "next" { r "next" ? 1 + } ! - } -} - -fn main { - 1 5 range - foreach { - . - "\n" . - } -} diff --git a/python/tests/aaa/misc/src/foreach_map.aaa b/python/tests/aaa/misc/src/foreach_map.aaa deleted file mode 100644 index f8a11a83..00000000 --- a/python/tests/aaa/misc/src/foreach_map.aaa +++ /dev/null @@ -1,15 +0,0 @@ -fn main { - map[int, str] - dup 2 "two" map:set - dup 4 "four" map:set - dup 6 "six" map:set - dup 8 "eight" map:set - - foreach { - swap - . - " -> " . - . - "\n" . - } -} diff --git a/python/tests/aaa/misc/src/foreach_map_empty.aaa b/python/tests/aaa/misc/src/foreach_map_empty.aaa deleted file mode 100644 index 0fc27cb4..00000000 --- a/python/tests/aaa/misc/src/foreach_map_empty.aaa +++ /dev/null @@ -1,11 +0,0 @@ -fn main { - map[int, str] - - foreach { - swap - . - " -> " . - . - "\n" . - } -} diff --git a/python/tests/aaa/misc/src/foreach_set.aaa b/python/tests/aaa/misc/src/foreach_set.aaa deleted file mode 100644 index 78df1aec..00000000 --- a/python/tests/aaa/misc/src/foreach_set.aaa +++ /dev/null @@ -1,12 +0,0 @@ -fn main { - set[int] - dup 2 set:add - dup 4 set:add - dup 6 set:add - dup 8 set:add - - foreach { - . - "\n" . - } -} diff --git a/python/tests/aaa/misc/src/foreach_vector.aaa b/python/tests/aaa/misc/src/foreach_vector.aaa deleted file mode 100644 index f4383e4f..00000000 --- a/python/tests/aaa/misc/src/foreach_vector.aaa +++ /dev/null @@ -1,12 +0,0 @@ -fn main { - vec[int] - dup 2 vec:push - dup 4 vec:push - dup 6 vec:push - dup 8 vec:push - - foreach { - . - "\n" . - } -} diff --git a/python/tests/aaa/misc/src/foreach_vector_empty.aaa b/python/tests/aaa/misc/src/foreach_vector_empty.aaa deleted file mode 100644 index aac240f8..00000000 --- a/python/tests/aaa/misc/src/foreach_vector_empty.aaa +++ /dev/null @@ -1,8 +0,0 @@ -fn main { - vec[int] - - foreach { - . - "\n" . - } -} diff --git a/python/tests/aaa/misc/src/fork.aaa b/python/tests/aaa/misc/src/fork.aaa deleted file mode 100644 index b5d64af1..00000000 --- a/python/tests/aaa/misc/src/fork.aaa +++ /dev/null @@ -1,9 +0,0 @@ -fn main { - fork - if dup 0 = { - "child\n" . - } else { - "parent\n" . - } - drop -} diff --git a/python/tests/aaa/misc/src/getcwd.aaa b/python/tests/aaa/misc/src/getcwd.aaa deleted file mode 100644 index 1fad50ba..00000000 --- a/python/tests/aaa/misc/src/getcwd.aaa +++ /dev/null @@ -1,4 +0,0 @@ -fn main { - getcwd . - "\n" . -} diff --git a/python/tests/aaa/misc/src/getenv_fail.aaa b/python/tests/aaa/misc/src/getenv_fail.aaa deleted file mode 100644 index 2370c6da..00000000 --- a/python/tests/aaa/misc/src/getenv_fail.aaa +++ /dev/null @@ -1,5 +0,0 @@ -fn main { - "NON_EXISTENT" getenv - not assert - "" = assert -} diff --git a/python/tests/aaa/misc/src/getenv_ok.aaa b/python/tests/aaa/misc/src/getenv_ok.aaa deleted file mode 100644 index b606689c..00000000 --- a/python/tests/aaa/misc/src/getenv_ok.aaa +++ /dev/null @@ -1,5 +0,0 @@ -fn main { - "KEY" getenv - assert - "VALUE" = assert -} diff --git a/python/tests/aaa/misc/src/getpid.aaa b/python/tests/aaa/misc/src/getpid.aaa deleted file mode 100644 index 8e60bf66..00000000 --- a/python/tests/aaa/misc/src/getpid.aaa +++ /dev/null @@ -1,4 +0,0 @@ -fn main { - getpid . - "\n" . -} diff --git a/python/tests/aaa/misc/src/getppid.aaa b/python/tests/aaa/misc/src/getppid.aaa deleted file mode 100644 index cc0d95f0..00000000 --- a/python/tests/aaa/misc/src/getppid.aaa +++ /dev/null @@ -1,4 +0,0 @@ -fn main { - getppid . - "\n" . -} diff --git a/python/tests/aaa/misc/src/main_with_argv.aaa b/python/tests/aaa/misc/src/main_with_argv.aaa deleted file mode 100644 index 91c7b871..00000000 --- a/python/tests/aaa/misc/src/main_with_argv.aaa +++ /dev/null @@ -1,4 +0,0 @@ -fn main args argv as vec[str] { - argv . - "\n" . -} diff --git a/python/tests/aaa/misc/src/main_with_exitcode.aaa b/python/tests/aaa/misc/src/main_with_exitcode.aaa deleted file mode 100644 index 0ef0c80a..00000000 --- a/python/tests/aaa/misc/src/main_with_exitcode.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main return int { - 1 -} diff --git a/python/tests/aaa/misc/src/modulo_zero.aaa b/python/tests/aaa/misc/src/modulo_zero.aaa deleted file mode 100644 index 6dbda95f..00000000 --- a/python/tests/aaa/misc/src/modulo_zero.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main { - 3 0 % drop -} diff --git a/python/tests/aaa/misc/src/recursion.aaa b/python/tests/aaa/misc/src/recursion.aaa deleted file mode 100644 index 4e7f3316..00000000 --- a/python/tests/aaa/misc/src/recursion.aaa +++ /dev/null @@ -1,40 +0,0 @@ - -struct S { - a as vec[S] -} - -enum Json { - null, - boolean as bool, - integer as int, - string as str, - array as vec[Json], - object as map[str, Json] -} - - -fn main { - S - dup "a" { - vec[S] - dup S vec:push - } ! - . - "\n" . - - Json:null - . - "\n" . - - vec[Json] - dup 5 Json:integer vec:push - Json:array - . - "\n" . - - map[str, Json] - dup "key" false Json:boolean map:set - Json:object - . - "\n" . -} diff --git a/python/tests/aaa/misc/src/return.aaa b/python/tests/aaa/misc/src/return.aaa deleted file mode 100644 index 2ca005ec..00000000 --- a/python/tests/aaa/misc/src/return.aaa +++ /dev/null @@ -1,97 +0,0 @@ - -struct Foo { - bar as int -} - -fn not_returning return never { - 0 exit -} - -fn a return never { - not_returning -} - -fn b return never { - 3 . - not_returning -} - -fn c return never { - if true { - not_returning - } else { - not_returning - } -} - -fn d return never { - while not_returning { - nop - } -} - -fn e return int { - while true { - not_returning - } -} - -fn f return int { - if true { - not_returning - } else { - 5 - } -} - -fn g return int { - if true { - 5 - } else { - not_returning - } -} - -fn h return int { - if true { - 5 return - } - 7 -} - -fn i return never { - if not_returning { - nop - } -} - -fn j return never { - 3 - use x { - x <- { not_returning } - } -} - -fn k return never { - Foo - "bar" { not_returning } ! -} - -fn l return never { - vec[int] - foreach { - drop - not_returning - } -} - -fn m return int { - 3 use x { - 5 return - } -} - - -fn main { - nop -} diff --git a/python/tests/aaa/misc/src/setenv.aaa b/python/tests/aaa/misc/src/setenv.aaa deleted file mode 100644 index 2cc0b738..00000000 --- a/python/tests/aaa/misc/src/setenv.aaa +++ /dev/null @@ -1,12 +0,0 @@ -fn main { - "KEY" "VALUE" setenv - "WITH_EQUALS_CHAR" "FOO=BAR" setenv - - "KEY" getenv - assert - "VALUE" = assert - - "WITH_EQUALS_CHAR" getenv - assert - "FOO=BAR" = assert -} diff --git a/python/tests/aaa/misc/src/struct.aaa b/python/tests/aaa/misc/src/struct.aaa deleted file mode 100644 index 46b48a69..00000000 --- a/python/tests/aaa/misc/src/struct.aaa +++ /dev/null @@ -1,48 +0,0 @@ -from "../../../../../stdlib/enums.aaa" import Option - -struct Bar { - value as int, -} - -struct Foo { - bool_ as bool, - int_ as int, - str_ as str, - vec_ as vec[str], - map_ as map[str,str], - set_ as set[str], - regex_ as regex, - enum_ as Option[int], - struct_ as Bar, -} - -fn main { - Foo copy - use foo, foo_copy { - foo "bool_" { true } ! - foo "int_" { 69 } ! - foo "str_" { "hello world" } ! - foo "vec_" { vec[str] dup "foo" vec:push } ! - foo "map_" { map[str, str] dup "3" "three" map:set } ! - foo "set_" { set[str] dup "hello" set:add } ! - foo "regex_" { ".*" make_regex assert } ! - foo "enum_" { 3 Option[int]:some } ! - foo "struct_" { Bar dup "value" { 123 } ! } ! - - " bool_ = " . foo "bool_" ? . "\n" . - " int_ = " . foo "int_" ? . "\n" . - " str_ = " . foo "str_" ? . "\n" . - " vec_ = " . foo "vec_" ? . "\n" . - " map_ = " . foo "map_" ? . "\n" . - " set_ = " . foo "set_" ? . "\n" . - " regex_ = " . foo "regex_" ? . "\n" . - " enum_ = " . foo "enum_" ? . "\n" . - "struct_ = " . foo "struct_" ? . "\n" . - - foo . - "\n" . - - foo_copy . - "\n" . - } -} diff --git a/python/tests/aaa/misc/src/time.aaa b/python/tests/aaa/misc/src/time.aaa deleted file mode 100644 index 19877f7b..00000000 --- a/python/tests/aaa/misc/src/time.aaa +++ /dev/null @@ -1,9 +0,0 @@ -fn main { - time - use unix_ts, usec { - unix_ts . - " " . - usec . - } - "\n" . -} diff --git a/python/tests/aaa/misc/src/todo.aaa b/python/tests/aaa/misc/src/todo.aaa deleted file mode 100644 index 15e60455..00000000 --- a/python/tests/aaa/misc/src/todo.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main { - todo -} diff --git a/python/tests/aaa/misc/src/unlink_fail.aaa b/python/tests/aaa/misc/src/unlink_fail.aaa deleted file mode 100644 index 5dc24543..00000000 --- a/python/tests/aaa/misc/src/unlink_fail.aaa +++ /dev/null @@ -1,4 +0,0 @@ -fn main { - "/tmp/unlink_dummy" unlink - not assert -} diff --git a/python/tests/aaa/misc/src/unlink_ok.aaa b/python/tests/aaa/misc/src/unlink_ok.aaa deleted file mode 100644 index 4d4bf986..00000000 --- a/python/tests/aaa/misc/src/unlink_ok.aaa +++ /dev/null @@ -1,4 +0,0 @@ -fn main { - "/tmp/unlink_dummy" unlink - assert -} diff --git a/python/tests/aaa/misc/src/unreachable.aaa b/python/tests/aaa/misc/src/unreachable.aaa deleted file mode 100644 index 174ed515..00000000 --- a/python/tests/aaa/misc/src/unreachable.aaa +++ /dev/null @@ -1,3 +0,0 @@ -fn main { - unreachable -} diff --git a/python/tests/aaa/misc/src/unsetenv.aaa b/python/tests/aaa/misc/src/unsetenv.aaa deleted file mode 100644 index 6e64d41c..00000000 --- a/python/tests/aaa/misc/src/unsetenv.aaa +++ /dev/null @@ -1,6 +0,0 @@ -fn main { - "KEY" unsetenv - "KEY" getenv - not assert - drop -} diff --git a/python/tests/aaa/misc/src/vector_sum.aaa b/python/tests/aaa/misc/src/vector_sum.aaa deleted file mode 100644 index bc146de0..00000000 --- a/python/tests/aaa/misc/src/vector_sum.aaa +++ /dev/null @@ -1,23 +0,0 @@ -fn main { - vec[int] - 0 - - use my_vec, total { - my_vec 1 vec:push - my_vec 2 vec:push - my_vec 3 vec:push - my_vec 4 vec:push - - my_vec - - foreach { - use item { - total <- { total item + } - } - } - - total . - "\n" . - - } -} diff --git a/python/tests/aaa/misc/src/waitpid_fail.aaa b/python/tests/aaa/misc/src/waitpid_fail.aaa deleted file mode 100644 index 1ec0331d..00000000 --- a/python/tests/aaa/misc/src/waitpid_fail.aaa +++ /dev/null @@ -1,25 +0,0 @@ -fn main { - fork - if dup 0 = { - drop - - "/bin/false" - - vec[str] - dup "/bin/false" vec:push - - environ - - execve - - // unreachable if execve succeeds - not assert - } else { - 0 waitpid - - drop - assert - 1 = assert - drop - } -} diff --git a/python/tests/aaa/misc/src/waitpid_ok.aaa b/python/tests/aaa/misc/src/waitpid_ok.aaa deleted file mode 100644 index 2275d4cf..00000000 --- a/python/tests/aaa/misc/src/waitpid_ok.aaa +++ /dev/null @@ -1,25 +0,0 @@ -fn main { - fork - if dup 0 = { - drop - - "/bin/true" - - vec[str] - dup "/bin/true" vec:push - - environ - - execve - - // unreachable if execve succeeds - not assert - } else { - 0 waitpid - - drop - assert - 0 = assert - drop - } -} diff --git a/python/tests/aaa/misc/test_misc.py b/python/tests/aaa/misc/test_misc.py deleted file mode 100644 index 77def9bd..00000000 --- a/python/tests/aaa/misc/test_misc.py +++ /dev/null @@ -1,441 +0,0 @@ -import json -import os -import re -import tempfile -from pathlib import Path -from time import time - -import pytest - -from aaa.runner.runner import compile_run - -SOURCE_PREFIX = Path(__file__).parent / "src" - -# Ignore line length linter errors for this file -# ruff: noqa: E501 - - -def test_assert_true() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "assert_true.aaa") - assert stdout == "" - assert stderr == "" - assert exit_code == 0 - - -def test_assert_false() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "assert_false.aaa") - assert stdout == "" - - project_root = str(Path(__file__).parents[3]) - assert ( - f"Assertion failure at {project_root}/tests/aaa/misc/src/assert_false.aaa:2:11\n" - == stderr - ) - assert exit_code == 1 - - -@pytest.mark.parametrize( - ["source", "expected_exitcode"], - [ - pytest.param("exit_0.aaa", 0, id="0"), - pytest.param("exit_1.aaa", 1, id="1"), - ], -) -def test_exit(source: str, expected_exitcode: int) -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / source) - assert stdout == "" - assert stderr == "" - assert expected_exitcode == exit_code - - -def test_time() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "time.aaa") - - printed = stdout.strip().split() - printed_usec = int(printed[1]) - printed_sec = int(printed[0]) - - current_time = int(time()) - assert current_time - printed_sec in [0, 1] - assert printed_usec in range(1_000_000) - - assert stderr == "" - assert exit_code == 0 - - -def test_getcwd() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "getcwd.aaa") - assert f"{os.getcwd()}\n" == stdout - assert stderr == "" - assert exit_code == 0 - - -@pytest.mark.parametrize( - ["source", "expected_stdout"], - [ - pytest.param("chdir_ok.aaa", "/tmp\n", id="ok"), - pytest.param("chdir_fail.aaa", f"{os.getcwd()}\n", id="fail"), - ], -) -def test_chdir(source: str, expected_stdout: str) -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / source) - assert expected_stdout == stdout - assert stderr == "" - assert exit_code == 0 - - -@pytest.mark.parametrize( - ["source", "expected_stdout"], - [ - pytest.param("execve_ok.aaa", "", id="ok"), - pytest.param("execve_fail.aaa", "", id="fail"), - ], -) -def test_execve(source: str, expected_stdout: str) -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / source) - assert expected_stdout == stdout - assert stderr == "" - assert exit_code == 0 - - -def test_fork() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "fork.aaa") - assert stdout in ["parent\nchild\n", "child\nparent\n"] - assert stderr == "" - assert exit_code == 0 - - -@pytest.mark.parametrize( - ["source"], - [ - pytest.param("waitpid_ok.aaa", id="ok"), - pytest.param("waitpid_fail.aaa", id="fail"), - ], -) -def test_waitpid(source: str) -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / source) - assert stdout == "" - assert stderr == "" - assert exit_code == 0 - - -def test_getpid() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "getpid.aaa") - int(stdout.strip()) - assert stderr == "" - assert exit_code == 0 - - -def test_getppid() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "getppid.aaa") - - assert f"{os.getpid()}\n" == stdout - assert stderr == "" - assert exit_code == 0 - - -DUMMY_ENV_VARS = { - "KEY": "VALUE", - "WITH_EQUALS_CHAR": "FOO=BAR", -} - - -def test_environ() -> None: - stdout, stderr, exit_code = compile_run( - SOURCE_PREFIX / "environ.aaa", env=DUMMY_ENV_VARS - ) - - # NOTE: loading this output as json is a hack and may break in the future - assert json.loads(stdout) == DUMMY_ENV_VARS - assert stderr == "" - assert exit_code == 0 - - -@pytest.mark.parametrize( - ["source"], - [ - pytest.param("getenv_ok.aaa", id="ok"), - pytest.param("getenv_fail.aaa", id="fail"), - ], -) -def test_getenv(source: str) -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / source, env=DUMMY_ENV_VARS) - assert stdout == "" - assert stderr == "" - assert exit_code == 0 - - -def test_setenv() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "setenv.aaa", env={}) - assert stdout == "" - assert stderr == "" - assert exit_code == 0 - - -def test_unsetenv() -> None: - stdout, stderr, exit_code = compile_run( - SOURCE_PREFIX / "unsetenv.aaa", env=DUMMY_ENV_VARS - ) - assert stdout == "" - assert stderr == "" - assert exit_code == 0 - - -@pytest.mark.parametrize( - ["source", "success"], - [ - pytest.param("unlink_ok.aaa", True, id="ok"), - pytest.param("unlink_fail.aaa", False, id="fail"), - ], -) -def test_unlink(source: str, success: bool) -> None: - dummy_file = Path("/tmp/unlink_dummy") - - if success: - dummy_file.touch() - - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / source) - - if success: - assert not dummy_file.exists() - - assert stdout == "" - assert stderr == "" - assert exit_code == 0 - - -def test_foreach_vector() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "foreach_vector.aaa") - assert stdout == "2\n4\n6\n8\n" - assert stderr == "" - assert exit_code == 0 - - -def test_foreach_vector_empty() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "foreach_vector_empty.aaa") - assert stdout == "" - assert stderr == "" - assert exit_code == 0 - - -def test_foreach_map() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "foreach_map.aaa") - - lines = set(stdout.strip().split("\n")) - expected_lines = {"2 -> two", "4 -> four", "6 -> six", "8 -> eight"} - - assert expected_lines == lines - assert stderr == "" - assert exit_code == 0 - - -def test_foreach_map_empty() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "foreach_map_empty.aaa") - assert stdout == "" - assert stderr == "" - assert exit_code == 0 - - -def test_foreach_custom() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "foreach_custom.aaa") - assert stdout == "1\n2\n3\n4\n" - assert stderr == "" - assert exit_code == 0 - - -def test_foreach_set() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "foreach_set.aaa") - - lines = set(stdout.strip().split("\n")) - expected_lines = {"2", "4", "6", "8"} - - assert expected_lines == lines - assert stderr == "" - assert exit_code == 0 - - -def test_assignment() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "assignment.aaa") - assert stdout == '1\ntrue\nbar\n[2]\n{"two": 2}\n{2}\n6\n' - assert stderr == "" - assert exit_code == 0 - - -def test_const() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "const.aaa") - assert stdout == "69\n[5]\n[5]\n[5]\n[]\n" - assert stderr == "" - assert exit_code == 0 - - -def test_main_with_exit_code() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "main_with_exitcode.aaa") - assert stdout == "" - assert stderr == "" - assert exit_code == 1 - - -def test_main_with_argv() -> None: - temp_files_root = Path(tempfile.gettempdir()) - - binary_path = temp_files_root / "test_main_with_argv" - try: - stdout, stderr, exit_code = compile_run( - SOURCE_PREFIX / "main_with_argv.aaa", binary_path=binary_path - ) - assert f'["{binary_path}"]\n' == stdout - assert stderr == "" - assert exit_code == 0 - finally: - binary_path.unlink(missing_ok=True) - - -def test_return() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "return.aaa") - assert stdout == "" - assert stderr == "" - assert exit_code == 0 - - -def test_enum() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "enum.aaa") - - expected_stdout = ( - "Foo:bool_{false}\n" - + "bool_ false\n" - + "\n" - + "Foo:bool_{true}\n" - + "bool_ true\n" - + "\n" - + "Foo:int_{69}\n" - + "int_ 69\n" - + "\n" - + 'Foo:str_{"hello"}\n' - + "str_ hello\n" - + "\n" - + "Foo:vec_{[]}\n" - + "vec_ []\n" - + "\n" - + "Foo:map_{{}}\n" - + "map_ {}\n" - + "\n" - + "Foo:set_{{}}\n" - + "set_ {}\n" - + "\n" - + "Foo:regex_{regex}\n" - + "regex_ regex\n" - + "\n" - + "Foo:enum_{Foo:bool_{false}}\n" - + "enum_ Foo:bool_{false}\n" - + "\n" - + "Foo:struct_{Bar{value: 0}}\n" - + "struct_ Bar{value: 0}\n" - + "\n" - + "Foo:no_data{}\n" - + "no_data \n" - + "\n" - + "Foo:multiple_data{3, []}\n" - + "multiple_data [] 3\n" - + "\n" - + "Foo:bool_{true}\n" - + "Foo:bool_{false}\n" - ) - - assert expected_stdout == stdout - assert stderr == "" - assert exit_code == 0 - - -def test_recursion() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "recursion.aaa") - - expected_output = ( - "S{a: [S{a: []}]}\n" - + "Json:null{}\n" - + "Json:array{[Json:integer{5}]}\n" - + 'Json:object{{"key": Json:boolean{false}}}\n' - ) - - assert expected_output == stdout - assert stderr == "" - assert exit_code == 0 - - -def test_todo() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "todo.aaa") - - assert stdout == "" - assert stderr.startswith("Code at ") - assert stderr.endswith(" is not implemented\n") - assert exit_code == 1 - - -def test_unreachable() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "unreachable.aaa") - - assert stdout == "" - assert stderr.startswith("Code at ") - assert stderr.endswith(" should be unreachable\n") - assert exit_code == 1 - - -def test_enum_with_multiple_associated_fields() -> None: - stdout, stderr, exit_code = compile_run( - SOURCE_PREFIX / "enum_with_multiple_associated_fields.aaa" - ) - - assert ( - stdout - == "quit\nmessage text=hello\nmessage_with_brackets text=world\nclick x=6 y=9\n" - ) - assert stderr == "" - assert exit_code == 0 - - -def test_enum_case_as() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "enum_case_as.aaa") - - assert stdout == "quit\ntext = hello\nx = 6 y = 9\n" - assert stderr == "" - assert exit_code == 0 - - -def test_struct() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "struct.aaa") - - expected_output = ( - " bool_ = true\n" - + " int_ = 69\n" - + " str_ = hello world\n" - + ' vec_ = ["foo"]\n' - + ' map_ = {"3": "three"}\n' - + ' set_ = {"hello"}\n' - + " regex_ = regex\n" - + " enum_ = Option:some{3}\n" - + "struct_ = Bar{value: 123}\n" - + 'Foo{bool_: true, int_: 69, str_: "hello world", vec_: ["foo"], map_: {"3": "three"}, set_: {"hello"}, regex_: regex, enum_: Option:some{3}, struct_: Bar{value: 123}}\n' - + 'Foo{bool_: false, int_: 0, str_: "", vec_: [], map_: {}, set_: {}, regex_: regex, enum_: Option:none{}, struct_: Bar{value: 0}}\n' - ) - - expected_output = re.sub("UserStruct[0-9a-f]+", "UserStructXYZ", expected_output) - stdout = re.sub("UserStruct[0-9a-f]+", "UserStructXYZ", stdout) - - assert expected_output == stdout - assert stderr == "" - assert exit_code == 0 - - -def test_divide_by_zero() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "divide_by_zero.aaa") - - assert stdout == "" - assert "Cannot divide by zero!" in stderr - assert exit_code == 101 - - -def test_modulo_zero() -> None: - stdout, stderr, exit_code = compile_run(SOURCE_PREFIX / "modulo_zero.aaa") - - assert stdout == "" - assert "Cannot use modulo zero!" in stderr - assert exit_code == 101 diff --git a/python/tests/aaa/test_aaa_unit_tests.py b/python/tests/aaa/test_aaa_unit_tests.py deleted file mode 100644 index a6a6ea76..00000000 --- a/python/tests/aaa/test_aaa_unit_tests.py +++ /dev/null @@ -1,8 +0,0 @@ -from aaa.runner.test_runner import TestRunner - - -def test_tokenizer_unittests() -> None: - exit_code = TestRunner.test_command( - ".", verbose=False, binary=None, runtime_type_checks=True - ) - assert exit_code == 0 diff --git a/python/tests/conftest.py b/python/tests/conftest.py deleted file mode 100644 index 5676d960..00000000 --- a/python/tests/conftest.py +++ /dev/null @@ -1,3 +0,0 @@ -pytest_plugins = [ - "tests.fixtures.stdlib_path", -] diff --git a/python/tests/fixtures/__init__.py b/python/tests/fixtures/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/tests/fixtures/stdlib_path.py b/python/tests/fixtures/stdlib_path.py deleted file mode 100644 index 2a1da917..00000000 --- a/python/tests/fixtures/stdlib_path.py +++ /dev/null @@ -1,13 +0,0 @@ -import os -from collections.abc import Generator -from pathlib import Path -from unittest.mock import patch - -import pytest - - -@pytest.fixture(autouse=True, scope="session") -def setup_test_environment() -> Generator[None, None, None]: - stdlib_path = Path(os.environ["AAA_STDLIB_PATH"]) / "builtins.aaa" - with patch("aaa.get_stdlib_path", return_value=stdlib_path): - yield diff --git a/python/tests/selfhosting/__init__.py b/python/tests/selfhosting/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/python/tests/selfhosting/test_tokenizer.py b/python/tests/selfhosting/test_tokenizer.py deleted file mode 100644 index af9ccb0d..00000000 --- a/python/tests/selfhosting/test_tokenizer.py +++ /dev/null @@ -1,65 +0,0 @@ -import secrets -import subprocess -from glob import glob -from pathlib import Path -from tempfile import gettempdir - -import pytest -from _pytest.mark.structures import ParameterSet -from pytest import CaptureFixture - -from aaa import aaa_project_root -from aaa.parser.parser import aaa_file_parser -from aaa.runner.runner import Runner - - -def tokenize_with_python(aaa_file: Path) -> str: - tokens = aaa_file_parser.tokenize_file(aaa_file, filter_token_types=True) - - output = "" - for token in tokens: - position = token.position - output += f"{position.file}:{position.line}:{position.column}" - output += f" {token.type} {token.value}\n" - - return output - - -@pytest.fixture(scope="session") -def tokenizer_excecutable() -> str: - binary_folder = Path(gettempdir()) / "aaa/transpiled/tests" - binary_path = binary_folder / "".join( - secrets.choice("0123456789abcdef") for _ in range(16) - ) - - exit_code = Runner.compile_command( - file_or_code="../examples/selfhosting/tokenizer.aaa", - verbose=False, - binary_path=str(binary_path), - runtime_type_checks=True, - ) - - assert exit_code == 0 - return str(binary_path) - - -def aaa_source_files() -> tuple[list[str], list[ParameterSet]]: - return ( - ["source_file"], - [ - pytest.param(Path(file).resolve(), id=str(file)) - for file in glob("**/*.aaa", root_dir=aaa_project_root(), recursive=True) - ], - ) - - -@pytest.mark.parametrize(*aaa_source_files()) -def test_tokenizer_output( - tokenizer_excecutable: str, capfd: CaptureFixture[str], source_file: Path -) -> None: - completed_process = subprocess.run([tokenizer_excecutable, str(source_file)]) - assert completed_process.returncode == 0 - - captured = capfd.readouterr() - assert captured.out == tokenize_with_python(source_file) - assert captured.err == "" diff --git a/python/tests/test_parser.py b/python/tests/test_parser.py deleted file mode 100644 index 62f8a1c2..00000000 --- a/python/tests/test_parser.py +++ /dev/null @@ -1,680 +0,0 @@ -from glob import glob -from pathlib import Path - -import pytest -from basil.exceptions import ParseError - -from aaa import aaa_project_root -from aaa.parser.models import ( - AaaParseModel, - Argument, - Arguments, - Assignment, - Boolean, - Branch, - CaseBlock, - CaseLabel, - CommaSeparatedTypeList, - DefaultBlock, - Enum, - EnumDeclaration, - EnumVariant, - EnumVariantAssociatedData, - EnumVariants, - FlatTypeLiteral, - FlatTypeParams, - ForeachLoop, - FreeFunctionCall, - FreeFunctionName, - Function, - FunctionBody, - FunctionBodyBlock, - FunctionBodyItem, - FunctionCall, - FunctionDeclaration, - FunctionName, - FunctionPointerTypeLiteral, - GetFunctionPointer, - Import, - ImportItem, - ImportItems, - MatchBlock, - MemberFunctionCall, - MemberFunctionName, - ReturnTypes, - SourceFile, - Struct, - StructDeclaration, - StructField, - StructFieldQuery, - StructFields, - StructFieldUpdate, - TypeLiteral, - TypeOrFunctionPointerLiteral, - TypeParams, - UseBlock, - Variables, - WhileLoop, -) -from aaa.parser.parser import NODE_TYPE_TO_MODEL, AaaParser, unescape_string - -MODEL_TO_NODE_TYPE = { - model_type: node_type for (node_type, model_type) in NODE_TYPE_TO_MODEL.items() -} - - -PARSE_MODEL_TEST_VALUES: list[tuple[type[AaaParseModel], str, bool]] = [ - (Argument, "", False), - (Argument, "3", False), - (Argument, "foo as bar", True), - (Argument, "foo as bar[", False), - (Argument, "foo as bar[]", False), - (Argument, "foo as bar[A,]", True), - (Argument, "foo as bar[A,B,]", True), - (Argument, "foo as bar[A,B]", True), - (Argument, "foo as bar[A", False), - (Argument, "foo as bar[A]", True), - (Argument, "foo as", False), - (Argument, "foo", False), - (Arguments, "", False), - (Arguments, "3", False), - (Arguments, "args foo as bar", True), - (Arguments, "args foo as bar[A,B],", True), - (Arguments, "args foo as bar[A,B],foo as bar[A,B],", True), - (Arguments, "args foo as bar[A,B],foo as bar[A,B]", True), - (Arguments, "args foo as bar[A,B]", True), - (Arguments, "args foo as bar[A]", True), - (Arguments, "args foo as", False), - (Arguments, "args foo", False), - (Arguments, "args", False), - (Assignment, "<- { nop } ", False), - (Assignment, "3", False), - (Assignment, "a <- { nop } ", True), - (Assignment, "a, <- { nop } ", True), - (Assignment, "a,b <- { nop } ", True), - (Assignment, "a,b, <- { nop } ", True), - (Assignment, "a,b,c <- { nop } ", True), - (Assignment, "a,b,c, <- { nop } ", True), - (Boolean, "", False), - (Boolean, "3", False), - (Boolean, "false", True), - (Boolean, "true", True), - (Branch, 'if true { "x" ? }', True), - (Branch, 'if true { "x" { nop } ! }', True), - (Branch, 'if true { "x" }', True), - (Branch, 'if true { "x" 3 }', True), - (Branch, "", False), - (Branch, "3", False), - (Branch, "if true { if true { nop } else { nop } }", True), - (Branch, "if true { nop } else ", False), - (Branch, "if true { nop } else { nop }", True), - (Branch, "if true { nop } else { nop", False), - (Branch, "if true { nop } else {", False), - (Branch, "if true { nop }", True), - (Branch, "if true { nop", False), - (Branch, "if true { while true { nop } }", True), - (Branch, "if true {", False), - (Branch, "if true", False), - (Branch, "if", False), - (CaseBlock, "", False), - (CaseBlock, "3", False), - (CaseBlock, "case a:b { nop }", True), - (CaseBlock, "case a:b as c { nop }", True), - (CaseBlock, "case a:b as c,d { nop }", True), - (CaseLabel, "", False), - (CaseLabel, "3", False), - (CaseLabel, "a:", False), - (CaseLabel, "a:b as c,", True), - (CaseLabel, "a:b as c,d,", True), - (CaseLabel, "a:b as c,d", True), - (CaseLabel, "a:b as c", True), - (CaseLabel, "a:b as", False), - (CaseLabel, "a:b", True), - (CaseLabel, "a", False), - (CommaSeparatedTypeList, "", False), - (CommaSeparatedTypeList, "3", False), - (CommaSeparatedTypeList, "fn[][],", True), - (CommaSeparatedTypeList, "fn[][]", True), - (CommaSeparatedTypeList, "fn[int][vec[str]], fn[int][vec[str]]", True), - (CommaSeparatedTypeList, "int, bool", True), - (CommaSeparatedTypeList, "int,", True), - (CommaSeparatedTypeList, "int", True), - (CommaSeparatedTypeList, "vec[str], vec[str]", True), - (CommaSeparatedTypeList, "vec[str]", True), - (DefaultBlock, "", False), - (DefaultBlock, "3", False), - (DefaultBlock, "default { nop ", False), - (DefaultBlock, "default { nop }", True), - (DefaultBlock, "default {", False), - (DefaultBlock, "default", False), - (Enum, "", False), - (Enum, "3", False), - (Enum, "enum foo { a }", True), - (Enum, "enum foo { a as { int } }", True), - (Enum, "enum foo { a as { int, } }", True), - (Enum, "enum foo { a as { int, str } }", True), - (Enum, "enum foo { a as { vec[str], map[int, bool] } }", True), - (Enum, "enum foo { a as int }", True), - (Enum, "enum foo { a as int, }", True), - (Enum, "enum foo {", False), - (Enum, "enum foo {}", False), - (EnumDeclaration, "", False), - (EnumDeclaration, "3", False), - (EnumDeclaration, "enum foo", True), - (EnumDeclaration, "enum", False), - (EnumVariant, "", False), - (EnumVariant, "3", False), - (EnumVariant, "a as { int }", True), - (EnumVariant, "a as { int, }", True), - (EnumVariant, "a as { int, str }", True), - (EnumVariant, "a as { int, str, }", True), - (EnumVariant, "a as { vec[str], map[int, set[str]] }", True), - (EnumVariant, "a as {", False), - (EnumVariant, "a as {}", False), - (EnumVariant, "a as int", True), - (EnumVariant, "a as map[str, set[int]]", True), - (EnumVariant, "a as", False), - (EnumVariant, "a", True), - (EnumVariantAssociatedData, "{ int }", True), - (EnumVariantAssociatedData, "{ int, }", True), - (EnumVariantAssociatedData, "{ int, str }", True), - (EnumVariantAssociatedData, "{ int, str, }", True), - (EnumVariantAssociatedData, "{ int", False), - (EnumVariantAssociatedData, "{ vec[str], map[int, set[str]] }", True), - (EnumVariantAssociatedData, "{", False), - (EnumVariantAssociatedData, "{}", False), - (EnumVariantAssociatedData, "3", False), - (EnumVariantAssociatedData, "int", True), - (EnumVariantAssociatedData, "map[str, set[int]]", True), - (EnumVariants, "", False), - (EnumVariants, "3", False), - (EnumVariants, "a as int,", True), - (EnumVariants, "a as int,b as str,", True), - (EnumVariants, "a as int", True), - (EnumVariants, "a,", True), - (EnumVariants, "a,b", True), - (EnumVariants, "a", True), - (FlatTypeLiteral, "", False), - (FlatTypeLiteral, "3", False), - (FlatTypeLiteral, "foo", True), - (FlatTypeLiteral, "foo[", False), - (FlatTypeLiteral, "foo[]", False), - (FlatTypeLiteral, "foo[A,", False), - (FlatTypeLiteral, "foo[A,]", True), - (FlatTypeLiteral, "foo[A,B,]", True), - (FlatTypeLiteral, "foo[A,B,C,]", True), - (FlatTypeLiteral, "foo[A,B,C", False), - (FlatTypeLiteral, "foo[A,B,C]", True), - (FlatTypeLiteral, "foo[A,B", False), - (FlatTypeLiteral, "foo[A,B]", True), - (FlatTypeLiteral, "foo[A", False), - (FlatTypeLiteral, "foo[A[B]]", False), - (FlatTypeLiteral, "foo[A]", True), - (FlatTypeParams, "", False), - (FlatTypeParams, "[,A]", False), - (FlatTypeParams, "[", False), - (FlatTypeParams, "[]", False), - (FlatTypeParams, "[A,", False), - (FlatTypeParams, "[A,]", True), - (FlatTypeParams, "[A,B,]", True), - (FlatTypeParams, "[A,B,C,]", True), - (FlatTypeParams, "[A,B,C]", True), - (FlatTypeParams, "[A,B", False), - (FlatTypeParams, "[A,B]", True), - (FlatTypeParams, "[A", False), - (FlatTypeParams, "[A]", True), - (FlatTypeParams, "3", False), - (ForeachLoop, "", False), - (ForeachLoop, "3", False), - (ForeachLoop, "foreach { nop ", False), - (ForeachLoop, "foreach { nop }", True), - (ForeachLoop, "foreach {", False), - (ForeachLoop, "foreach", False), - (FreeFunctionCall, "", False), - (FreeFunctionCall, "3", False), - (FreeFunctionCall, "bar", True), - (FreeFunctionCall, "bar[int,]", True), - (FreeFunctionCall, "bar[int]", True), - (FreeFunctionCall, "bar[vec[int],]", True), - (FreeFunctionCall, "bar[vec[int]]", True), - (FreeFunctionCall, "foo:bar", False), - (FreeFunctionName, "", False), - (FreeFunctionName, "3", False), - (FreeFunctionName, "bar", True), - (FreeFunctionName, "bar[A,]", True), - (FreeFunctionName, "bar[A]", True), - (FreeFunctionName, "bar[vec[int]]", False), - (FreeFunctionName, "foo:bar", False), - (Function, "3", False), - (Function, "fn foo { nop }", True), - (Function, "fn foo { nop", False), - (Function, "fn foo {", False), - (Function, "fn foo args a as b { while true { nop } }", True), - (Function, "fn foo args a as int { nop }", True), - (Function, "fn foo args a as vec[map[int,str]] { nop }", True), - (Function, "fn foo", False), - (Function, "fn", False), - (FunctionBody, '"foo" ?', True), - (FunctionBody, '"foo" { nop } !', True), - (FunctionBody, '"foo"', True), - (FunctionBody, 'if true { nop } "foo" ?', True), - (FunctionBody, 'if true { nop } "foo" { nop } !', True), - (FunctionBody, 'if true { nop } "foo"', True), - (FunctionBody, "", False), - (FunctionBody, "3", True), - (FunctionBody, "case", False), - (FunctionBody, "false", True), - (FunctionBody, "foo:bar", True), - (FunctionBody, "foo", True), - (FunctionBody, "foo[A,]", True), - (FunctionBody, "foo[A,B,]", True), - (FunctionBody, "foo[A,B]", True), - (FunctionBody, "foo[A]", True), - (FunctionBody, "if true { nop } 3", True), - (FunctionBody, "if true { nop } else { nop }", True), - (FunctionBody, "if true { nop } false", True), - (FunctionBody, "if true { nop } foo:bar", True), - (FunctionBody, "if true { nop } foo", True), - (FunctionBody, "if true { nop } foo[A,]", True), - (FunctionBody, "if true { nop } foo[A,B,]", True), - (FunctionBody, "if true { nop } foo[A,B]", True), - (FunctionBody, "if true { nop } foo[A]", True), - (FunctionBody, "if true { nop } if true { nop } else { nop }", True), - (FunctionBody, "if true { nop } if true { nop }", True), - (FunctionBody, "if true { nop } while true { nop }", True), - (FunctionBody, "if true { nop }", True), - (FunctionBody, "while true { nop }", True), - (FunctionBodyBlock, "", False), - (FunctionBodyBlock, "{ nop }", True), - (FunctionBodyBlock, "{ nop", False), - (FunctionBodyBlock, "{", False), - (FunctionBodyBlock, "3", False), - (FunctionBodyItem, '"foo" ?', True), - (FunctionBodyItem, '"foo" { nop } !', True), - (FunctionBodyItem, '"foo"', True), - (FunctionBodyItem, "", False), - (FunctionBodyItem, "3", True), - (FunctionBodyItem, "case", False), - (FunctionBodyItem, "false", True), - (FunctionBodyItem, "foo:bar", True), - (FunctionBodyItem, "foo", True), - (FunctionBodyItem, "foo[A,]", True), - (FunctionBodyItem, "foo[A,B,]", True), - (FunctionBodyItem, "foo[A,B]", True), - (FunctionBodyItem, "foo[A]", True), - (FunctionBodyItem, "if true { nop } else { nop }", True), - (FunctionBodyItem, "if true { nop }", True), - (FunctionBodyItem, "while true { nop }", True), - (FunctionCall, "", False), - (FunctionCall, "fn", False), - (FunctionCall, "foo:bar", True), - (FunctionCall, "foo", True), - (FunctionCall, "foo[A,]", True), - (FunctionCall, "foo[A,B,]", True), - (FunctionCall, "foo[A,B]", True), - (FunctionCall, "foo[A]", True), - (FunctionDeclaration, "", False), - (FunctionDeclaration, "3", False), - (FunctionDeclaration, "fn a args b as int, c as int,", True), - (FunctionDeclaration, "fn a args b as int, c as int", True), - (FunctionDeclaration, "fn a args b as int,", True), - (FunctionDeclaration, "fn a args b as int", True), - (FunctionDeclaration, "fn a args b as vec[int,], c as vec[int,],", True), - (FunctionDeclaration, "fn a args b as vec[int], c as vec[int],", True), - (FunctionDeclaration, "fn a args", False), - (FunctionDeclaration, "fn a return int,", True), - (FunctionDeclaration, "fn a return int", True), - (FunctionDeclaration, "fn a return vec[", False), - (FunctionDeclaration, "fn a return vec[int],map[int,int],", True), - (FunctionDeclaration, "fn a return vec[int],map[int,int]", True), - (FunctionDeclaration, "fn a return vec[int],map[int,vec[int]],", True), - (FunctionDeclaration, "fn a return vec[int]", True), - (FunctionDeclaration, "fn a return", False), - (FunctionDeclaration, "fn a return", False), - (FunctionDeclaration, "fn a,", False), - (FunctionDeclaration, "fn a", True), - (FunctionName, "", False), - (FunctionName, "3", False), - (FunctionName, "foo:", False), - (FunctionName, "foo:bar", True), - (FunctionName, "foo", True), - (FunctionName, "foo[", False), - (FunctionName, "foo[]:", False), - (FunctionName, "foo[]", False), - (FunctionName, "foo[A,]:bar", True), - (FunctionName, "foo[A,]", True), - (FunctionName, "foo[A,B,]:bar", True), - (FunctionName, "foo[A,B,]", True), - (FunctionName, "foo[A,B,C,]:bar", True), - (FunctionName, "foo[A,B,C,]", True), - (FunctionName, "foo[A,B,C]:bar", True), - (FunctionName, "foo[A,B,C]", True), - (FunctionName, "foo[A,B]:bar", True), - (FunctionName, "foo[A,B]", True), - (FunctionName, "foo[A]:bar", True), - (FunctionName, "foo[A]", True), - (FunctionPointerTypeLiteral, "fn [][]", True), - (FunctionPointerTypeLiteral, "fn[ ][]", True), - (FunctionPointerTypeLiteral, "fn[,][]", False), - (FunctionPointerTypeLiteral, "fn[] []", True), - (FunctionPointerTypeLiteral, "fn[][ ]", True), - (FunctionPointerTypeLiteral, "fn[][,]", False), - (FunctionPointerTypeLiteral, "fn[][]", True), - (FunctionPointerTypeLiteral, "fn[][int,int,]", True), - (FunctionPointerTypeLiteral, "fn[][int,int]", True), - (FunctionPointerTypeLiteral, "fn[][int]", True), - (FunctionPointerTypeLiteral, "fn[fn[][]][]", True), - (FunctionPointerTypeLiteral, "fn[int,][]", True), - (FunctionPointerTypeLiteral, "fn[int,int,][]", True), - (FunctionPointerTypeLiteral, "fn[int,int][]", True), - (FunctionPointerTypeLiteral, "fn[int][]", True), - (FunctionPointerTypeLiteral, "fn[vec[int]][]", True), - (GetFunctionPointer, '"foo" fn', True), - (GetFunctionPointer, '"foo"', False), - (GetFunctionPointer, "", False), - (GetFunctionPointer, "3", False), - (Import, 'from "a" import b as c,', True), - (Import, 'from "a" import b as c,d as e,', True), - (Import, 'from "a" import b as c,d as e', True), - (Import, 'from "a" import b as c,d,', True), - (Import, 'from "a" import b as c,d,', True), - (Import, 'from "a" import b as c,d', True), - (Import, 'from "a" import b as c', True), - (Import, 'from "a" import b,', True), - (Import, 'from "a" import b,d as e,', True), - (Import, 'from "a" import b,d,', True), - (Import, 'from "a" import b', True), - (Import, 'from "a" import', False), - (Import, 'from "a"', False), - (Import, "", False), - (Import, "3", False), - (Import, "from a import b", False), - (Import, "from", False), - (ImportItem, "", False), - (ImportItem, "3", False), - (ImportItem, "foo as bar", True), - (ImportItem, "foo as", False), - (ImportItem, "foo", True), - (ImportItems, "", False), - (ImportItems, "3", False), - (ImportItems, "foo as bar,", True), - (ImportItems, "foo as bar,foo as bar,", True), - (ImportItems, "foo as bar,foo as bar", True), - (ImportItems, "foo as bar,foo as", False), - (ImportItems, "foo as bar,foo,", True), - (ImportItems, "foo as bar,foo", True), - (ImportItems, "foo as bar,foo", True), - (ImportItems, "foo as bar", True), - (ImportItems, "foo as", False), - (ImportItems, "foo,", True), - (ImportItems, "foo,bar,", True), - (ImportItems, "foo,bar", True), - (ImportItems, "foo,foo as bar,", True), - (ImportItems, "foo,foo as bar", True), - (ImportItems, "foo", True), - (MatchBlock, "", False), - (MatchBlock, "3", False), - (MatchBlock, "match { }", True), - (MatchBlock, "match { case a:b { nop } }", True), - (MatchBlock, "match { case a:b { nop } default { nop } }", True), - (MatchBlock, "match { default { nop } }", True), - (MatchBlock, "match {", False), - (MatchBlock, "match", False), - (MemberFunctionCall, "", False), - (MemberFunctionCall, "3", False), - (MemberFunctionCall, "bar", False), - (MemberFunctionCall, "foo:bar", True), - (MemberFunctionCall, "foo:bar[int]", True), - (MemberFunctionCall, "foo[int]:", False), - (MemberFunctionCall, "foo[int]:bar", True), - (MemberFunctionName, "", False), - (MemberFunctionName, "3", False), - (MemberFunctionName, "bar", False), - (MemberFunctionName, "foo:bar", True), - (MemberFunctionName, "foo[A,]:bar", True), - (MemberFunctionName, "foo[A]:bar", True), - (MemberFunctionName, "foo[A]:bar[B]", False), - (MemberFunctionName, "foo[vec[int]]:bar", False), - (ReturnTypes, "", False), - (ReturnTypes, "3", False), - (ReturnTypes, "return foo,", True), - (ReturnTypes, "return foo", True), - (ReturnTypes, "return foo[A,B],", True), - (ReturnTypes, "return foo[A,B],foo[A,B],", True), - (ReturnTypes, "return foo[A,B],foo[A,B]", True), - (ReturnTypes, "return foo[A,B]", True), - (ReturnTypes, "return foo[A],", True), - (ReturnTypes, "return foo[A]", True), - (ReturnTypes, "return", False), - (SourceFile, 'from "foo" import bar', True), - (SourceFile, "", True), - (SourceFile, "3", False), - (SourceFile, "builtin fn a builtin fn b", True), - (SourceFile, "builtin fn a", True), - (SourceFile, "builtin struct a builtin struct b", True), - (SourceFile, "builtin struct a", True), - (SourceFile, "builtin struct a", True), - ( - SourceFile, - "builtin struct a[A,B] builtin fn a args b as map[int,int]", - True, - ), - (SourceFile, "enum foo { x as int }", True), - (SourceFile, "fn a { nop }", True), - (SourceFile, "fn a", False), - (SourceFile, "struct a", False), - (SourceFile, "struct foo { x as int }", True), - (Struct, "", False), - (Struct, "3", False), - (Struct, "struct a { b as int }", True), - (Struct, "struct a { b as int, }", True), - (Struct, "struct a { b as int, }", True), - (Struct, "struct a { b as int, c as int }", True), - (Struct, "struct a { b as int, c as int, }", True), - (Struct, "struct a { b as map[int,vec[int]] }", True), - (Struct, "struct a { b as map[int,vec[int]], }", True), - (Struct, "struct a {", False), - (Struct, "struct a {}", True), - (Struct, "struct a", False), - (StructDeclaration, "", False), - (StructDeclaration, "3", False), - (StructDeclaration, "struct foo", True), - (StructDeclaration, "struct foo[", False), - (StructDeclaration, "struct foo[]", False), - (StructDeclaration, "struct foo[A,", False), - (StructDeclaration, "struct foo[A,]", True), - (StructDeclaration, "struct foo[A,B,]", True), - (StructDeclaration, "struct foo[A,B,C,]", True), - (StructDeclaration, "struct foo[A,B,C", False), - (StructDeclaration, "struct foo[A,B,C]", True), - (StructDeclaration, "struct foo[A,B", False), - (StructDeclaration, "struct foo[A,B]", True), - (StructDeclaration, "struct foo[A", False), - (StructDeclaration, "struct foo[A]", True), - (StructField, "", False), - (StructField, "3", False), - (StructField, "x as fn[vec[int]][set[str]]", True), - (StructField, "x as int", True), - (StructField, "x as", False), - (StructField, "x", False), - (StructFieldQuery, '"foo" ?', True), - (StructFieldQuery, '"foo"', False), - (StructFieldQuery, "", False), - (StructFieldQuery, "3", False), - (StructFields, "", False), - (StructFields, "3", False), - (StructFields, "x as int, y as str,", True), - (StructFields, "x as int, y as str", True), - (StructFields, "x as int,", True), - (StructFields, "x as int", True), - (StructFields, "x as vec[int], y as map[str, set[int]]", True), - (StructFieldUpdate, '"foo" { "x" ? } !', True), - (StructFieldUpdate, '"foo" { "x" { nop } !', False), - (StructFieldUpdate, '"foo" { "x" } !', True), - (StructFieldUpdate, '"foo" { "x" 3 } !', True), - (StructFieldUpdate, '"foo" { nop } !', True), - (StructFieldUpdate, '"foo" { nop }', False), - (StructFieldUpdate, '"foo" { nop', False), - (StructFieldUpdate, '"foo" { while true { nop } } !', True), - (StructFieldUpdate, '"foo" { while True { nop } } !', True), - (StructFieldUpdate, '"foo" {', False), - (StructFieldUpdate, '"foo"', False), - (StructFieldUpdate, "", False), - (StructFieldUpdate, "3", False), - (TypeLiteral, "", False), - (TypeLiteral, "[A]", False), - (TypeLiteral, "3", False), - (TypeLiteral, "const foo", True), - (TypeLiteral, "const foo[A]", True), - (TypeLiteral, "foo", True), - (TypeLiteral, "foo[,", False), - (TypeLiteral, "foo[[A]B]", False), - (TypeLiteral, "foo[]", False), - (TypeLiteral, "foo[A,", False), - (TypeLiteral, "foo[A,]", True), - (TypeLiteral, "foo[A,B,]", True), - (TypeLiteral, "foo[A,B]", True), - (TypeLiteral, "foo[A", False), - (TypeLiteral, "foo[A[B,],]", True), - (TypeLiteral, "foo[A[B,]]", True), - (TypeLiteral, "foo[A[B[C]],D]", True), - (TypeLiteral, "foo[A[B],C]", True), - (TypeLiteral, "foo[A[B]]", True), - (TypeLiteral, "foo[A]", True), - (TypeLiteral, "foo[const A,B]", True), - (TypeLiteral, "foo[const A]", True), - (TypeOrFunctionPointerLiteral, "", False), - (TypeOrFunctionPointerLiteral, "3", False), - (TypeOrFunctionPointerLiteral, "fn[str][vec[int]]", True), - (TypeOrFunctionPointerLiteral, "int", True), - (TypeOrFunctionPointerLiteral, "vec[str]", True), - (TypeParams, "", False), - (TypeParams, "[,", False), - (TypeParams, "[[A]B]", False), - (TypeParams, "[]", False), - (TypeParams, "[A,", False), - (TypeParams, "[A,]", True), - (TypeParams, "[A,B,]", True), - (TypeParams, "[A,B]", True), - (TypeParams, "[A", False), - (TypeParams, "[A[B,]]", True), - (TypeParams, "[A[B[C]],D]", True), - (TypeParams, "[A[B],C]", True), - (TypeParams, "[A[B]]", True), - (TypeParams, "[A]", True), - (TypeParams, "3", False), - (UseBlock, "3", False), - (UseBlock, "use { nop } ", False), - (UseBlock, "use a { nop } ", True), - (UseBlock, "use a, { nop } ", True), - (UseBlock, "use a,b { nop } ", True), - (UseBlock, "use a,b, { nop } ", True), - (UseBlock, "use a,b,c { nop } ", True), - (UseBlock, "use a,b,c, { nop } ", True), - (Variables, "3", False), - (Variables, "a,", True), - (Variables, "a,b,", True), - (Variables, "a,b,c,", True), - (Variables, "a,b,c", True), - (Variables, "a,b", True), - (Variables, "a", True), - (WhileLoop, 'while true { "x" ? }', True), - (WhileLoop, 'while true { "x" { nop } ! }', True), - (WhileLoop, 'while true { "x" }', True), - (WhileLoop, 'while true { "x" 3 }', True), - (WhileLoop, "", False), - (WhileLoop, "3", False), - (WhileLoop, "while true { if true { nop } else { nop } }", True), - (WhileLoop, "while true { nop }", True), - (WhileLoop, "while true { nop", False), - (WhileLoop, "while true { while true { nop } }", True), - (WhileLoop, "while true {", False), - (WhileLoop, "while true", False), - (WhileLoop, "while", False), -] - - -@pytest.mark.parametrize( - ["model_type", "text", "should_parse"], - [ - pytest.param( - model_type, - text, - should_parse, - id=f"{model_type.__name__}-{repr(text)}", - ) - for model_type, text, should_parse in PARSE_MODEL_TEST_VALUES - ], -) -def test_parser(model_type: type[AaaParseModel], text: str, should_parse: bool) -> None: - node_type = MODEL_TO_NODE_TYPE[model_type] - - try: - model = AaaParser(False).parse_text(text, node_type) - except ParseError: - assert not should_parse - else: - assert should_parse - assert isinstance(model, model_type) - - -def test_all_models_are_tested() -> None: - all_models = set(NODE_TYPE_TO_MODEL.values()) - tested_models = {item[0] for item in PARSE_MODEL_TEST_VALUES} - - untested_models = all_models - tested_models - - if untested_models: - raise Exception( - "Found untested models: " - + ", ".join(sorted(model.__name__ for model in untested_models)), - ) - - -def get_source_files() -> list[Path]: - aaa_files: set[Path] = { - Path(file).resolve() - for file in glob("**/*.aaa", root_dir=aaa_project_root(), recursive=True) - } - return sorted(aaa_files) - - -@pytest.mark.parametrize( - ["file"], - [pytest.param(file, id=str(file)) for file in get_source_files()], -) -def test_parse__all_source_files(file: Path) -> None: - AaaParser(False).parse_file(file) - - -@pytest.mark.parametrize( - ["escaped", "expected_unescaped"], - [ - ("", ""), - ("abc", "abc"), - ("\\\\", "\\"), - ("a\\\\b", "a\\b"), - ("\\\\b", "\\b"), - ("a\\\\", "a\\"), - ('a\\"b', 'a"b'), - ("a\\'b", "a'b"), - ("a\\/b", "a/b"), - ("a\\0b", "a\0b"), - ("a\\bb", "a\bb"), - ("a\\eb", "a\x1bb"), - ("a\\fb", "a\fb"), - ("a\\nb", "a\nb"), - ("a\\rb", "a\rb"), - ("a\\tb", "a\tb"), - ("a\\u0000b", "a\u0000b"), - ("a\\u9999b", "a\u9999b"), - ("a\\uaaaab", "a\uaaaab"), - ("a\\uffffb", "a\uffffb"), - ("a\\uAAAAb", "a\uaaaab"), - ("a\\uFFFFb", "a\uffffb"), - ("a\\U00000000b", "a\U00000000b"), - ("a\\U0001F600b", "a\U0001F600b"), - ], -) -def test_unescape_string(escaped: str, expected_unescaped: str) -> None: - assert expected_unescaped == unescape_string(escaped) diff --git a/python/tests/test_tokenizer.py b/python/tests/test_tokenizer.py deleted file mode 100644 index 0d51c5a8..00000000 --- a/python/tests/test_tokenizer.py +++ /dev/null @@ -1,304 +0,0 @@ -import pytest -from basil.exceptions import TokenizerException - -from aaa.parser.parser import AaaParser - -TOKENIZER_TEST_VALUES: list[tuple[str, str, bool]] = [ - ("args", "", False), - ("args", "args", True), - ("args", "argsa", False), - ("as", "", False), - ("as", "as", True), - ("as", "asa", False), - ("assign", "", False), - ("assign", "<-", True), - ("builtin", "", False), - ("builtin", "builtin", True), - ("builtin", "builtina", False), - ("call", "", False), - ("call", "call", True), - ("call", "calla", False), - ("case", "", False), - ("case", "case", True), - ("case", "casea", False), - ("char", "'''", False), - ("char", "'", False), - ("char", "'/'", True), - ("char", "'\"'", True), - ("char", "'\\''", True), - ("char", "'\\'", False), - ("char", "'\\/'", True), - ("char", "'\\\"'", True), - ("char", "'\\\\'", True), - ("char", "'\\0'", True), - ("char", "'\\b'", True), - ("char", "'\\e'", True), - ("char", "'\\f'", True), - ("char", "'\\n'", True), - ("char", "'\\r'", True), - ("char", "'\\t'", True), - ("char", "'\\u0000'", True), - ("char", "'\\U000000'", True), - ("char", "'\\U009999'", True), - ("char", "'\\U00aaaa'", True), - ("char", "'\\U00AAAA'", True), - ("char", "'\\U00ffff'", True), - ("char", "'\\U00FFFF'", True), - ("char", "'\\U090000'", True), - ("char", "'\\U099999'", True), - ("char", "'\\U09aaaa'", True), - ("char", "'\\U09AAAA'", True), - ("char", "'\\U09ffff'", True), - ("char", "'\\U09FFFF'", True), - ("char", "'\\U100000'", True), - ("char", "'\\U109999'", True), - ("char", "'\\U10aaaa'", True), - ("char", "'\\U10AAAA'", True), - ("char", "'\\U10ffff'", True), - ("char", "'\\U10FFFF'", True), - ("char", "'\\U110000'", False), - ("char", "'\\U119999'", False), - ("char", "'\\U11aaaa'", False), - ("char", "'\\U11AAAA'", False), - ("char", "'\\U11ffff'", False), - ("char", "'\\U11FFFF'", False), - ("char", "'\\u9999'", True), - ("char", "'\\uaaaa'", True), - ("char", "'\\uAAAA'", True), - ("char", "'\\uffff'", True), - ("char", "'\\uFFFF'", True), - ("char", "'\\x00'", True), - ("char", "'\\x99'", True), - ("char", "'\\xaa'", True), - ("char", "'\\xAA'", True), - ("char", "'\\xff'", True), - ("char", "'\\xFF'", True), - ("char", "'\\xgg'", False), - ("char", "'\\xGG'", False), - ("char", "'\f'", False), - ("char", "'\n'", False), - ("char", "'\r'", False), - ("char", "'\t'", False), - ("char", "'\v'", False), - ("char", "'a'", True), - ("char", "'A'", True), - ("char", "'a", False), - ("char", "'z'", True), - ("char", "'Z'", True), - ("char", "", False), - ("colon", ":", True), - ("colon", "", False), - ("comma", ",", True), - ("comma", "", False), - ("comment", "", False), - ("comment", "// This is a comment.", True), - ("comment", "//", True), - ("comment", "3", False), - ("const", "", False), - ("const", "const", True), - ("const", "consta", False), - ("default", "", False), - ("default", "default", True), - ("default", "defaulta", False), - ("else", "", False), - ("else", "else", True), - ("else", "elsea", False), - ("end", "", False), - ("end", "}", True), - ("enum", "", False), - ("enum", "enum", True), - ("enum", "enuma", False), - ("false", "", False), - ("false", "false", True), - ("false", "falsea", False), - ("fn", "", False), - ("fn", "fn", True), - ("fn", "fna", False), - ("foreach", "", False), - ("foreach", "foreach", True), - ("foreach", "foreacha", False), - ("from", "", False), - ("from", "from", True), - ("from", "froma", False), - ("get_field", "?", True), - ("get_field", "", False), - ("identifier", "____", True), - ("identifier", "_", True), - ("identifier", "-", True), - ("identifier", "-0", False), - ("identifier", "-9", False), - ("identifier", "!=", True), - ("identifier", ".", True), - ("identifier", "", False), - ("identifier", "*", True), - ("identifier", "/", True), - ("identifier", "//", False), - ("identifier", "%", True), - ("identifier", "+", True), - ("identifier", "<", True), - ("identifier", "<=", True), - ("identifier", "=", True), - ("identifier", ">", True), - ("identifier", ">=", True), - ("identifier", "3", False), - ("identifier", "a", True), - ("identifier", "A", True), - ("identifier", "aaaa", True), - ("identifier", "AAAA", True), - ("identifier", "Some_Thing", True), - ("identifier", "z", True), - ("identifier", "Z", True), - ("identifier", "zzzz", True), - ("identifier", "ZZZZ", True), - ("if", "", False), - ("if", "if", True), - ("if", "ifa", False), - ("import", "", False), - ("import", "import", True), - ("import", "importa", False), - ("integer", "-", False), - ("integer", "-0", True), - ("integer", "-1234567890", True), - ("integer", "-3", True), - ("integer", "", False), - ("integer", "1234567890", True), - ("integer", "3", True), - ("integer", "foo", False), - ("match", "", False), - ("match", "match", True), - ("match", "matcha", False), - ("never", "", False), - ("never", "never", True), - ("never", "nevera", False), - ("return", "", False), - ("return", "return", True), - ("return", "returna", False), - ("set_field", "!", True), - ("set_field", "", False), - ("sq_end", "", False), - ("sq_end", "]", True), - ("sq_start", "", False), - ("sq_start", "[", True), - ("start", "", False), - ("start", "{", True), - ("string", '"', False), - ("string", '""', True), - ("string", '"""', False), - ("string", '"\'"', True), - ("string", '"\\"', False), - ("string", '"\\""', True), - ("string", '"\\/"', True), - ("string", '"\\\'"', True), - ("string", '"\\\\"', True), - ("string", '"\\0"', True), - ("string", '"\\b"', True), - ("string", '"\\e"', True), - ("string", '"\\f"', True), - ("string", '"\\n"', True), - ("string", '"\\r"', True), - ("string", '"\\u0000"', True), - ("string", '"\\U000000"', True), - ("string", '"\\U009999"', True), - ("string", '"\\U00aaaa"', True), - ("string", '"\\U00AAAA"', True), - ("string", '"\\U00ffff"', True), - ("string", '"\\U00FFFF"', True), - ("string", '"\\U090000"', True), - ("string", '"\\U099999"', True), - ("string", '"\\U09aaaa"', True), - ("string", '"\\U09AAAA"', True), - ("string", '"\\U09ffff"', True), - ("string", '"\\U09FFFF"', True), - ("string", '"\\U100000"', True), - ("string", '"\\U109999"', True), - ("string", '"\\U10aaaa"', True), - ("string", '"\\U10AAAA"', True), - ("string", '"\\U10ffff"', True), - ("string", '"\\U10FFFF"', True), - ("string", '"\\U110000"', False), - ("string", '"\\U119999"', False), - ("string", '"\\U11aaaa"', False), - ("string", '"\\U11AAAA"', False), - ("string", '"\\U11ffff"', False), - ("string", '"\\U11FFFF"', False), - ("string", '"\\u9999"', True), - ("string", '"\\uaaaa"', True), - ("string", '"\\uAAAA"', True), - ("string", '"\\uffff"', True), - ("string", '"\\uFFFF"', True), - ("string", '"\\x00"', True), - ("string", '"\\x99"', True), - ("string", '"\\xaa"', True), - ("string", '"\\xAA"', True), - ("string", '"\\xff"', True), - ("string", '"\\xFF"', True), - ("string", '"\\xgg"', False), - ("string", '"\\xGG"', False), - ("string", '"\f"', False), - ("string", '"\n"', False), - ("string", '"\r"', False), - ("string", '"\t"', False), - ("string", '"\v"', False), - ("string", '"a', False), - ("string", '"a"', True), - ("string", '"abc"', True), - ("string", "", False), - ("string", "3", False), - ("struct", "", False), - ("struct", "struct", True), - ("struct", "structa", False), - ("true", "", False), - ("true", "true", True), - ("true", "truea", False), - ("use", "", False), - ("use", "use", True), - ("use", "usea", False), - ("while", "", False), - ("while", "while", True), - ("while", "whilea", False), - ("whitespace", " ", True), - ("whitespace", "", False), - ("whitespace", "\n", True), - ("whitespace", "\n\n", True), - ("whitespace", "\r", True), - ("whitespace", "\r\n", True), - ("whitespace", "\r\n\r\n", True), - ("whitespace", "\r\r", True), - ("whitespace", "\t", True), - ("whitespace", "3", False), -] - - -@pytest.mark.parametrize( - ["token_type", "text", "should_tokenize"], - [ - pytest.param(token_type, text, should_tokenize, id=f"{token_type}-{repr(text)}") - for token_type, text, should_tokenize in TOKENIZER_TEST_VALUES - ], -) -def test_tokenizer(token_type: str, text: str, should_tokenize: bool) -> None: - try: - tokens = AaaParser(False).tokenize_text(text) - except TokenizerException: - assert not should_tokenize - return - - if should_tokenize: - assert len(tokens) == 1 - assert tokens[0].type == token_type - else: - if not tokens: - return - assert tokens[0].type != token_type - - -def test_all_token_types_are_tested() -> None: - all_token_types = AaaParser(False).file_parser.token_types - untested_token_types = {item[0] for item in TOKENIZER_TEST_VALUES} - - untested_token_types = all_token_types - untested_token_types - - if untested_token_types: - raise Exception( - "Found untested token types: " + ", ".join(sorted(untested_token_types)), - ) diff --git a/stdlib/builtins.aaa b/stdlib/builtins.aaa index 609e219b..0787941f 100644 --- a/stdlib/builtins.aaa +++ b/stdlib/builtins.aaa @@ -1,26 +1,69 @@ // elementary types -builtin struct bool -builtin struct int -builtin struct str -builtin struct char +builtin struct bool {} +builtin struct int {} +builtin struct str {} +builtin struct char {} + +builtin fn bool:show args self as bool return str +builtin fn bool:debug args self as bool return str + +builtin fn int:show args self as int return str +builtin fn int:debug args self as int return str + +builtin fn str:show args self as str return str +builtin fn str:debug args self as str return str + +builtin fn char:show args self as char return str +builtin fn char:debug args self as char return str + // container types -builtin struct map[K, V] -builtin struct set[T] -builtin struct vec[T] +builtin struct map[K, V] {} +builtin struct set[T] {} +builtin struct vec[T] {} -// container iterator types -builtin struct map_const_iter[K, V] -builtin struct map_iter[K, V] -builtin struct set_iter[T] -builtin struct vec_const_iter[T] -builtin struct vec_iter[T] +builtin fn map[K, V]:show args self as map[K, V] return str +builtin fn map[K, V]:debug args self as map[K, V] return str + +builtin fn set[T]:show args self as set[T] return str +builtin fn set[T]:debug args self as set[T] return str + +builtin fn vec[T]:show args self as vec[T] return str +builtin fn vec[T]:debug args self as vec[T] return str -builtin fn make_const[A] args a as A return const A + +// container iterator types +builtin struct map_const_iter[K, V] {} +builtin struct map_iter[K, V] {} +builtin struct set_iter[T] {} +builtin struct vec_const_iter[T] {} +builtin struct vec_iter[T] {} // compiled regular expression -builtin struct regex +builtin struct regex {} + +builtin enum Option[T] { + none, + some as T, +} + +builtin fn Option[T]:unwrap args option as Option[T] return T +builtin fn Option[T]:unwrap_or args option as Option[T], default_ as T return T +builtin fn Option[T]:is_some args option as Option[T] return bool +builtin fn Option[T]:is_none args option as Option[T] return bool + +builtin enum Result[T, E] { + ok as T, + error as E, +} + +builtin fn Result[T, E]:unwrap args result as Result[T, E] return T +builtin fn Result[T, E]:unwrap_error args result as Result[T, E] return E +builtin fn Result[T, E]:is_ok args result as Result[T, E] return bool +builtin fn Result[T, E]:is_error args result as Result[T, E] return bool + +builtin fn make_const[A] args a as A return const A // stack operations builtin fn copy[A] args a as A return A, A // The top object will be non-const @@ -31,8 +74,8 @@ builtin fn rot[A, B, C] args a as A, b as B, c as C return B, C, A builtin fn swap[A, B] args a as A, b as B return B, A // special operators -builtin fn .[A] args a as const A -builtin fn repr[A] args a as const A return str +builtin fn . args a as const Show +builtin fn repr args a as const Debug return str builtin fn assert args a as const bool builtin fn nop builtin fn =[A] args lhs as const A, rhs as const A return bool @@ -147,10 +190,7 @@ builtin fn regex:= args lhs as const regex, rhs as const regex return bool builtin fn todo return never builtin fn unreachable return never - - // Syscall list: http://faculty.nps.edu/cseagle/assembly/sys_call.html - builtin fn accept args fd as const int return str, int, int, bool builtin fn bind args fd as const int, ip_addr as str, port as int return bool builtin fn chdir args dir as const str return bool @@ -176,3 +216,15 @@ builtin fn unsetenv args name as const str builtin fn usleep args microseconds as int builtin fn waitpid args pid as const int, options as const int return int, int, bool, bool builtin fn write args fd as const int, data as const str return int, bool + +builtin interface Show { + fn Self:show args self as const Self return str +} + +builtin interface Debug { + fn Self:debug args self as const Self return str +} + +// TODO detect name collisions of interface +// TODO detect name collisions inside interface +// TODO support checking if a type implements a certain interface diff --git a/stdlib/enums.aaa b/stdlib/enums.aaa deleted file mode 100644 index a4abb352..00000000 --- a/stdlib/enums.aaa +++ /dev/null @@ -1,78 +0,0 @@ -// TODO #208 Make `Option` and `Result` usuable without import statements. - -enum Option[T] { - none, - some as T, -} - -fn Option[T]:unwrap args option as Option[T] return T { - option - match { - case Option:some as value { value } - case Option:none { - "unwrap called on Option:none.\n" . - 1 exit - } - } -} - -fn Option[T]:unwrap_or args option as Option[T], default_ as T return T { - option - match { - case Option:some as value { value } - case Option:none { default_ } - } -} - -fn Option[T]:is_some args option as Option[T] return bool { - option - match { - case Option:some { drop true } - case Option:none { false } - } -} - -fn Option[T]:is_none args option as Option[T] return bool { - option Option:is_some not -} - -enum Result[T, E] { - ok as T, - error as E, -} - -fn Result[T, E]:unwrap args result as Result[T, E] return T { - result - match { - case Result:ok as value { value } - case Result:error { - drop - "unwrap was called on Result:error.\n" . - 1 exit - } - } -} - -fn Result[T, E]:unwrap_error args result as Result[T, E] return E { - result - match { - case Result:ok { - drop - "unwrap_error called on Result:ok.\n" . - 1 exit - } - case Result:error as error { error } - } -} - -fn Result[T, E]:is_ok args result as Result[T, E] return bool { - result - match { - case Result:ok { drop true } - case Result:error { drop false } - } -} - -fn Result[T, E]:is_error args result as Result[T, E] return bool { - result Result:is_ok not -} diff --git a/stdlib/test_builtins.aaa b/stdlib/test_builtins.aaa index a962a715..c95f7791 100644 --- a/stdlib/test_builtins.aaa +++ b/stdlib/test_builtins.aaa @@ -1067,3 +1067,81 @@ fn test_fn_equals_false { "test_fn_equals_true" fn = not assert } + +fn test_option_unwrap_some { + 3 Option[int]:some + Option:unwrap + 3 = assert +} + +fn test_option_unwrap_or_some { + 3 Option[int]:some 4 + Option:unwrap_or + 3 = assert +} + +fn test_option_unwrap_or_none { + Option[int]:none 4 + Option:unwrap_or + 4 = assert +} + +fn test_option_is_some_true { + 3 Option[int]:some + Option:is_some + assert +} + +fn test_option_is_some_false { + Option[int]:none + Option:is_some + not assert +} + +fn test_option_is_none_true { + 3 Option[int]:some + Option:is_none + not assert +} + +fn test_option_is_none_false { + Option[int]:none + Option:is_none + assert +} + +fn test_result_unwrap { + 3 Result[int, str]:ok + Result:unwrap + 3 = assert +} + +fn test_result_unwrap_error { + "oh no!" Result[int, str]:error + Result:unwrap_error + "oh no!" = assert +} + +fn test_result_is_ok_true { + 3 Result[int, str]:ok + Result:is_ok + assert +} + +fn test_result_is_ok_false { + "oh no!" Result[int, str]:error + Result:is_ok + not assert +} + +fn test_result_is_error_true { + "oh no!" Result[int, str]:error + Result:is_error + assert +} + +fn test_result_is_error_false { + 3 Result[int, str]:ok + Result:is_error + not assert +} diff --git a/stdlib/test_enums.aaa b/stdlib/test_enums.aaa deleted file mode 100644 index 916fcce3..00000000 --- a/stdlib/test_enums.aaa +++ /dev/null @@ -1,79 +0,0 @@ -from "enums" import Option, Result - -fn test_option_unwrap_some { - 3 Option[int]:some - Option:unwrap - 3 = assert -} - -fn test_option_unwrap_or_some { - 3 Option[int]:some 4 - Option:unwrap_or - 3 = assert -} - -fn test_option_unwrap_or_none { - Option[int]:none 4 - Option:unwrap_or - 4 = assert -} - -fn test_option_is_some_true { - 3 Option[int]:some - Option:is_some - assert -} - -fn test_option_is_some_false { - Option[int]:none - Option:is_some - not assert -} - -fn test_option_is_none_true { - 3 Option[int]:some - Option:is_none - not assert -} - -fn test_option_is_none_false { - Option[int]:none - Option:is_none - assert -} - -fn test_result_unwrap { - 3 Result[int, str]:ok - Result:unwrap - 3 = assert -} - -fn test_result_unwrap_error { - "oh no!" Result[int, str]:error - Result:unwrap_error - "oh no!" = assert -} - -fn test_result_is_ok_true { - 3 Result[int, str]:ok - Result:is_ok - assert -} - -fn test_result_is_ok_false { - "oh no!" Result[int, str]:error - Result:is_ok - not assert -} - -fn test_result_is_error_true { - "oh no!" Result[int, str]:error - Result:is_error - assert -} - -fn test_result_is_error_false { - 3 Result[int, str]:ok - Result:is_error - not assert -}