aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--Cargo.lock144
-rw-r--r--Cargo.toml3
-rw-r--r--build.rs3
-rw-r--r--progs/bench/fib.sy6
-rw-r--r--progs/bench/fib_iter.sy22
-rw-r--r--progs/bench/sum.sy14
-rw-r--r--progs/tests/_simple.sy23
-rw-r--r--progs/tests/auto/add.sy3
-rw-r--r--progs/tests/auto/advanced_break.sy22
-rw-r--r--progs/tests/auto/advanced_continue.sy22
-rw-r--r--progs/tests/auto/assignment.sy7
-rw-r--r--progs/tests/auto/blob_complex.sy19
-rw-r--r--progs/tests/auto/blob_infer.sy8
-rw-r--r--progs/tests/auto/blob_simple.sy10
-rw-r--r--progs/tests/auto/calls_inside_calls.sy11
-rw-r--r--progs/tests/auto/cluster.sy14
-rw-r--r--progs/tests/auto/compare_constants_equality.sy5
-rw-r--r--progs/tests/auto/compare_constants_unequality.sy5
-rw-r--r--progs/tests/auto/compare_variable.sy9
-rw-r--r--progs/tests/auto/conflict_markers.sy11
-rw-r--r--progs/tests/auto/constant_function.sy4
-rw-r--r--progs/tests/auto/constant_function_closure.sy13
-rw-r--r--progs/tests/auto/constant_function_complex.sy19
-rw-r--r--progs/tests/auto/constants_in_inner_functions.sy20
-rw-r--r--progs/tests/auto/div.sy5
-rw-r--r--progs/tests/auto/else_.sy10
-rw-r--r--progs/tests/auto/else_if.sy12
-rw-r--r--progs/tests/auto/expressions.sy6
-rw-r--r--progs/tests/auto/factorial.sy12
-rw-r--r--progs/tests/auto/field.sy3
-rw-r--r--progs/tests/auto/field_assign.sy5
-rw-r--r--progs/tests/auto/field_get.sy7
-rw-r--r--progs/tests/auto/in_rhs.sy3
-rw-r--r--progs/tests/auto/instantiate.sy5
-rw-r--r--progs/tests/auto/invalid_assign.sy7
-rw-r--r--progs/tests/auto/more_types.sy4
-rw-r--r--progs/tests/auto/mul.sy3
-rw-r--r--progs/tests/auto/multiple_fields.sy11
-rw-r--r--progs/tests/auto/multiple_returns.sy12
-rw-r--r--progs/tests/auto/negation.sy7
-rw-r--r--progs/tests/auto/not.sy5
-rw-r--r--progs/tests/auto/one_arg.sy5
-rw-r--r--progs/tests/auto/param_1.sy4
-rw-r--r--progs/tests/auto/param_2.sy7
-rw-r--r--progs/tests/auto/param_and_return.sy7
-rw-r--r--progs/tests/auto/parenthesis.sy3
-rw-r--r--progs/tests/auto/passing_functions.sy9
-rw-r--r--progs/tests/auto/passing_functions_mixed.sy9
-rw-r--r--progs/tests/auto/precedence.sy8
-rw-r--r--progs/tests/auto/return_1.sy6
-rw-r--r--progs/tests/auto/returning_closures.sy24
-rw-r--r--progs/tests/auto/simple.sy10
-rw-r--r--progs/tests/auto/simple_add.sy9
-rw-r--r--progs/tests/auto/simple_break.sy12
-rw-r--r--progs/tests/auto/simple_continue.sy12
-rw-r--r--progs/tests/auto/simple_sub.sy9
-rw-r--r--progs/tests/auto/simplest.sy4
-rw-r--r--progs/tests/auto/single_variable.sy4
-rw-r--r--progs/tests/auto/stack_ordering.sy6
-rw-r--r--progs/tests/auto/strange.sy15
-rw-r--r--progs/tests/auto/sub.sy3
-rw-r--r--progs/tests/auto/terms_and_factors.sy4
-rw-r--r--progs/tests/auto/three_arg.sy5
-rw-r--r--progs/tests/auto/two_arg.sy5
-rw-r--r--progs/tests/auto/two_variables.sy6
-rw-r--r--progs/tests/auto/types.sy4
-rw-r--r--progs/tests/auto/uncallable_type.sy9
-rw-r--r--progs/tests/auto/wrong_params.sy7
-rw-r--r--progs/tests/auto/wrong_ret.sy7
-rw-r--r--progs/tests/constants_declaration_order.sy6
-rw-r--r--progs/tests/constants_declaration_order_wrong.sy8
-rw-r--r--progs/tests/faulty.sy2
-rw-r--r--progs/tests/fib.sy16
-rw-r--r--progs/tests/for.sy32
-rw-r--r--progs/tests/global_collision.sy10
-rw-r--r--progs/tests/global_constants.sy17
-rw-r--r--progs/tests/scoping.sy23
-rw-r--r--progs/tests/simple.sy2
-rw-r--r--progs/tests/unreachable.sy6
-rw-r--r--src/compiler.rs788
-rw-r--r--src/lib.rs618
-rw-r--r--src/sectionizer.rs105
-rw-r--r--src/tokenizer.rs3
-rw-r--r--src/vm.rs135
-rw-r--r--sylt_macro/Cargo.lock45
-rw-r--r--sylt_macro/Cargo.toml5
-rw-r--r--sylt_macro/src/lib.rs104
88 files changed, 1728 insertions, 946 deletions
diff --git a/.gitignore b/.gitignore
index c2a9b6f..0399f3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,2 @@
-/target
+target
tags
diff --git a/Cargo.lock b/Cargo.lock
index f7d2ec1..31ff301 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -31,9 +31,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bstr"
-version = "0.2.14"
+version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf"
+checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d"
dependencies = [
"lazy_static",
"memchr",
@@ -43,9 +43,9 @@ dependencies = [
[[package]]
name = "bumpalo"
-version = "3.6.0"
+version = "3.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
+checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]]
name = "byteorder"
@@ -80,12 +80,6 @@ dependencies = [
]
[[package]]
-name = "const_fn"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
-
-[[package]]
name = "criterion"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -144,12 +138,11 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
-version = "0.9.1"
+version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d"
+checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
dependencies = [
"cfg-if",
- "const_fn",
"crossbeam-utils",
"lazy_static",
"memoffset",
@@ -158,9 +151,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
+checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
dependencies = [
"autocfg",
"cfg-if",
@@ -202,6 +195,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
+name = "getrandom"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
name = "half"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -242,9 +246,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "js-sys"
-version = "0.3.47"
+version = "0.3.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cfb73131c35423a367daf8cbd24100af0d077668c8c2943f0e7dd775fef0f65"
+checksum = "dc9f84f9b115ce7843d60706df1422a916680bfdfcbdb0447c5614ff9d7e4d78"
dependencies = [
"wasm-bindgen",
]
@@ -257,9 +261,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.85"
+version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
+checksum = "03b07a082330a35e43f63177cc01689da34fbffa0105e1246cf0311472cac73a"
[[package]]
name = "log"
@@ -368,6 +372,12 @@ dependencies = [
]
[[package]]
+name = "ppv-lite86"
+version = "0.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
+
+[[package]]
name = "proc-macro2"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -378,14 +388,54 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.8"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
+name = "rand"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
name = "rayon"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -481,9 +531,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
-version = "1.0.123"
+version = "1.0.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
+checksum = "bd761ff957cb2a45fbb9ab3da6512de9de55872866160b23c25f1a841e99d29f"
[[package]]
name = "serde_cbor"
@@ -497,9 +547,9 @@ dependencies = [
[[package]]
name = "serde_derive"
-version = "1.0.123"
+version = "1.0.124"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31"
+checksum = "1800f7693e94e186f5e25a28291ae1570da908aff7d97a095dec1e56ff99069b"
dependencies = [
"proc-macro2",
"quote",
@@ -508,9 +558,9 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.61"
+version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
+checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
@@ -524,6 +574,7 @@ dependencies = [
"criterion",
"logos",
"owo-colors",
+ "rand",
"sylt_macro",
]
@@ -531,15 +582,16 @@ dependencies = [
name = "sylt_macro"
version = "0.1.0"
dependencies = [
+ "proc-macro2",
"quote",
"syn",
]
[[package]]
name = "syn"
-version = "1.0.60"
+version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081"
+checksum = "123a78a3596b24fee53a6464ce52d8ecbf62241e6294c7e7fe12086cd161f512"
dependencies = [
"proc-macro2",
"quote",
@@ -557,9 +609,9 @@ dependencies = [
[[package]]
name = "tinytemplate"
-version = "1.2.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2ada8616fad06a2d0c455adc530de4ef57605a8120cc65da9653e0e9623ca74"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
dependencies = [
"serde",
"serde_json",
@@ -595,10 +647,16 @@ dependencies = [
]
[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
+[[package]]
name = "wasm-bindgen"
-version = "0.2.70"
+version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be"
+checksum = "7ee1280240b7c461d6a0071313e08f34a60b0365f14260362e5a2b17d1d31aa7"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@@ -606,9 +664,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.70"
+version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7bc45447f0d4573f3d65720f636bbcc3dd6ce920ed704670118650bcd47764c7"
+checksum = "5b7d8b6942b8bb3a9b0e73fc79b98095a27de6fa247615e59d096754a3bc2aa8"
dependencies = [
"bumpalo",
"lazy_static",
@@ -621,9 +679,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.70"
+version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b8853882eef39593ad4174dd26fc9865a64e84026d223f63bb2c42affcbba2c"
+checksum = "e5ac38da8ef716661f0f36c0d8320b89028efe10c7c0afde65baffb496ce0d3b"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -631,9 +689,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.70"
+version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4133b5e7f2a531fa413b3a1695e925038a05a71cf67e87dafa295cb645a01385"
+checksum = "cc053ec74d454df287b9374ee8abb36ffd5acb95ba87da3ba5b7d3fe20eb401e"
dependencies = [
"proc-macro2",
"quote",
@@ -644,15 +702,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.70"
+version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd4945e4943ae02d15c13962b38a5b1e81eadd4b71214eee75af64a4d6a4fd64"
+checksum = "7d6f8ec44822dd71f5f221a5847fb34acd9060535c1211b70a05844c0f6383b1"
[[package]]
name = "web-sys"
-version = "0.3.47"
+version = "0.3.48"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c40dc691fc48003eba817c38da7113c15698142da971298003cac3ef175680b3"
+checksum = "ec600b26223b2948cedfde2a0aa6756dcf1fef616f43d7b3097aaf53a6c4d92b"
dependencies = [
"js-sys",
"wasm-bindgen",
diff --git a/Cargo.toml b/Cargo.toml
index f6b763f..44b1f66 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -10,8 +10,9 @@ name = "sylt"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-logos = "0.11.4"
+logos = "~0.11.4"
owo-colors = { git="https://github.com/FredTheDino/owo-colors.git" }
+rand = "0.8"
sylt_macro = { path = "sylt_macro" }
criterion = { version = "0.3", optional = true }
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..bcfc238
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,3 @@
+fn main() {
+ println!("cargo:rerun-if-changed=progs/")
+}
diff --git a/progs/bench/fib.sy b/progs/bench/fib.sy
index de68f5c..ad10e20 100644
--- a/progs/bench/fib.sy
+++ b/progs/bench/fib.sy
@@ -7,4 +7,8 @@ fib :: fn a:int -> int {
ret fib(a - 1) + fib(a - 2)
}
// 23 is around where things start getting slow.
-fib(23) <=> 28657
+start :: fn {
+ fib(23) <=> 28657
+}
+
+// flags: no_print
diff --git a/progs/bench/fib_iter.sy b/progs/bench/fib_iter.sy
index c51469a..7ed1021 100644
--- a/progs/bench/fib_iter.sy
+++ b/progs/bench/fib_iter.sy
@@ -1,14 +1,18 @@
// A Fibonacci implementation that is a little
// less awful. But we run it 1000 times instead.
-j := 0
-for , j < 1000, j = j + 1 {
- a := 0
- b := 1
+start :: fn {
+ j := 0
+ for , j < 1000, j = j + 1 {
+ a := 0
+ b := 1
- for i := 0, i < 50, i = i + 1 {
- c := a
- a = b
- b = c + b
+ for i := 0, i < 50, i = i + 1 {
+ c := a
+ a = b
+ b = c + b
+ }
+ a <=> 12586269025
}
- a <=> 12586269025
}
+
+// flags: no_print
diff --git a/progs/bench/sum.sy b/progs/bench/sum.sy
index bb6870f..76fe392 100644
--- a/progs/bench/sum.sy
+++ b/progs/bench/sum.sy
@@ -1,6 +1,10 @@
-// Adds the numbers 0 to 10000
-sum := 0
-for i := 0, i <= 100000, i += 1 {
- sum += i
+// Adds the numbers 0 to 100000
+start :: fn {
+ sum := 0
+ for i := 0, i <= 100000, i += 1 {
+ sum += i
+ }
+ sum <=> 5000050000
}
-sum <=> 5000050000
+
+// flags: no_print
diff --git a/progs/tests/_simple.sy b/progs/tests/_simple.sy
new file mode 100644
index 0000000..84bc86d
--- /dev/null
+++ b/progs/tests/_simple.sy
@@ -0,0 +1,23 @@
+//
+// import A
+
+//
+f :: fn {
+ g!
+ print q
+}
+
+//
+q :: 1
+
+//
+a := 1
+
+qq :: fn {
+ g!
+ print q
+}
+
+
+// Steg 1: Hitta sektioner
+// Dela sektioner, compilera felera sektioner efter varandra
diff --git a/progs/tests/auto/add.sy b/progs/tests/auto/add.sy
new file mode 100644
index 0000000..4e8e643
--- /dev/null
+++ b/progs/tests/auto/add.sy
@@ -0,0 +1,3 @@
+start :: fn {
+(1, 2, 3, 4) + (4, 3, 2, 1) <=> (5, 5, 5, 5)
+}
diff --git a/progs/tests/auto/advanced_break.sy b/progs/tests/auto/advanced_break.sy
new file mode 100644
index 0000000..1cfcece
--- /dev/null
+++ b/progs/tests/auto/advanced_break.sy
@@ -0,0 +1,22 @@
+start :: fn {
+
+a := 0
+for i := 0, i < 10, i += 1 {
+ q := 0
+ qq := 0
+ qqq := 0
+ qqqq := 0
+
+ a = a + 1
+ if i == 2 {
+ break
+ }
+
+ q
+ qq
+ qqq
+ qqqq
+}
+a <=> 3
+
+}
diff --git a/progs/tests/auto/advanced_continue.sy b/progs/tests/auto/advanced_continue.sy
new file mode 100644
index 0000000..70a8671
--- /dev/null
+++ b/progs/tests/auto/advanced_continue.sy
@@ -0,0 +1,22 @@
+start :: fn {
+
+a := 0
+for i := 0, i < 4, i += 1 {
+ q := 0
+ qq := 0
+ qqq := 0
+ qqqq := 0
+
+ if i == 2 {
+ continue
+ }
+ a = a + 1
+
+ q
+ qq
+ qqq
+ qqqq
+}
+a <=> 3
+
+}
diff --git a/progs/tests/auto/assignment.sy b/progs/tests/auto/assignment.sy
new file mode 100644
index 0000000..51cbecc
--- /dev/null
+++ b/progs/tests/auto/assignment.sy
@@ -0,0 +1,7 @@
+start :: fn {
+a := 1
+ b := 2
+ a = b
+ a <=> 2
+ b <=> 2
+}
diff --git a/progs/tests/auto/blob_complex.sy b/progs/tests/auto/blob_complex.sy
new file mode 100644
index 0000000..d40082f
--- /dev/null
+++ b/progs/tests/auto/blob_complex.sy
@@ -0,0 +1,19 @@
+start :: fn {
+
+a := A()
+b := B()
+c := C()
+b2 := B()
+
+a
+b
+c
+b2
+
+blob A {
+ c: C
+}
+blob C { }
+blob B { }
+
+}
diff --git a/progs/tests/auto/blob_infer.sy b/progs/tests/auto/blob_infer.sy
new file mode 100644
index 0000000..cee18bf
--- /dev/null
+++ b/progs/tests/auto/blob_infer.sy
@@ -0,0 +1,8 @@
+start :: fn {
+
+blob A { }
+
+a : A = A()
+a
+
+}
diff --git a/progs/tests/auto/blob_simple.sy b/progs/tests/auto/blob_simple.sy
new file mode 100644
index 0000000..f05ec07
--- /dev/null
+++ b/progs/tests/auto/blob_simple.sy
@@ -0,0 +1,10 @@
+start :: fn {
+
+a := A()
+a
+
+blob A {
+ a: int
+}
+
+}
diff --git a/progs/tests/auto/calls_inside_calls.sy b/progs/tests/auto/calls_inside_calls.sy
new file mode 100644
index 0000000..578f418
--- /dev/null
+++ b/progs/tests/auto/calls_inside_calls.sy
@@ -0,0 +1,11 @@
+start :: fn {
+one := fn -> int {
+ ret 1
+ }
+ add := fn a: int, b: int -> int {
+ ret a + b
+ }
+ add(one(), one()) <=> 2
+ add(add(one(), one()), one()) <=> 3
+ add(one(), add(one(), one())) <=> 3
+}
diff --git a/progs/tests/auto/cluster.sy b/progs/tests/auto/cluster.sy
new file mode 100644
index 0000000..8f279e7
--- /dev/null
+++ b/progs/tests/auto/cluster.sy
@@ -0,0 +1,14 @@
+start :: fn {
+
+blob A { a: int }
+a := A()
+a.a = 0
+a.a += 1
+a.a <=> 1
+a.a *= 2
+a.a <=> 2
+a.a /= 2
+a.a <=> 1
+a.a -= 1
+a.a <=> 0
+}
diff --git a/progs/tests/auto/compare_constants_equality.sy b/progs/tests/auto/compare_constants_equality.sy
new file mode 100644
index 0000000..c8c399b
--- /dev/null
+++ b/progs/tests/auto/compare_constants_equality.sy
@@ -0,0 +1,5 @@
+start :: fn {
+if 1 == 2 {
+ <!>
+ }
+}
diff --git a/progs/tests/auto/compare_constants_unequality.sy b/progs/tests/auto/compare_constants_unequality.sy
new file mode 100644
index 0000000..8c115b4
--- /dev/null
+++ b/progs/tests/auto/compare_constants_unequality.sy
@@ -0,0 +1,5 @@
+start :: fn {
+if 1 != 1 {
+ <!>
+ }
+}
diff --git a/progs/tests/auto/compare_variable.sy b/progs/tests/auto/compare_variable.sy
new file mode 100644
index 0000000..25d21f4
--- /dev/null
+++ b/progs/tests/auto/compare_variable.sy
@@ -0,0 +1,9 @@
+start :: fn {
+a := 1
+ if a == 0 {
+ <!>
+ }
+ if a != 1 {
+ <!>
+ }
+}
diff --git a/progs/tests/auto/conflict_markers.sy b/progs/tests/auto/conflict_markers.sy
new file mode 100644
index 0000000..167a751
--- /dev/null
+++ b/progs/tests/auto/conflict_markers.sy
@@ -0,0 +1,11 @@
+start :: fn {
+
+<<<<<<< HEAD
+print extern_test(4.0)
+=======
+print extern_test(5.0)
+>>>>>>> 2
+
+}
+
+// errors: [ErrorKind::SyntaxError(_, _), ErrorKind::GitConflictError(3, 7)]
diff --git a/progs/tests/auto/constant_function.sy b/progs/tests/auto/constant_function.sy
new file mode 100644
index 0000000..2ec4019
--- /dev/null
+++ b/progs/tests/auto/constant_function.sy
@@ -0,0 +1,4 @@
+a :: fn {}
+start :: fn {
+ a()
+}
diff --git a/progs/tests/auto/constant_function_closure.sy b/progs/tests/auto/constant_function_closure.sy
new file mode 100644
index 0000000..6c7f0d7
--- /dev/null
+++ b/progs/tests/auto/constant_function_closure.sy
@@ -0,0 +1,13 @@
+q : int = 1
+
+f :: fn -> int {
+ q += 1
+ ret q
+}
+
+start :: fn {
+ f() <=> 2
+ f() <=> 3
+ f() <=> 4
+ f() <=> 5
+}
diff --git a/progs/tests/auto/constant_function_complex.sy b/progs/tests/auto/constant_function_complex.sy
new file mode 100644
index 0000000..6a60ebe
--- /dev/null
+++ b/progs/tests/auto/constant_function_complex.sy
@@ -0,0 +1,19 @@
+h :: fn -> int {
+ ret 3
+}
+
+k :: fn -> int {
+ ret h()
+}
+
+a :: fn -> int {
+ ret q()
+}
+
+q :: fn -> int {
+ ret k()
+}
+
+start :: fn {
+ a() <=> 3
+}
diff --git a/progs/tests/auto/constants_in_inner_functions.sy b/progs/tests/auto/constants_in_inner_functions.sy
new file mode 100644
index 0000000..3371393
--- /dev/null
+++ b/progs/tests/auto/constants_in_inner_functions.sy
@@ -0,0 +1,20 @@
+start :: fn {
+
+q : int = 0
+
+f :: fn -> fn -> {
+ g :: fn {
+ q += 1
+ }
+ ret g
+}
+
+g := f()
+g()
+q <=> 1
+g()
+q <=> 2
+g()
+q <=> 3
+
+}
diff --git a/progs/tests/auto/div.sy b/progs/tests/auto/div.sy
new file mode 100644
index 0000000..3073270
--- /dev/null
+++ b/progs/tests/auto/div.sy
@@ -0,0 +1,5 @@
+start :: fn {
+a := 2
+a /= 2
+a <=> 1
+}
diff --git a/progs/tests/auto/else_.sy b/progs/tests/auto/else_.sy
new file mode 100644
index 0000000..896aeb4
--- /dev/null
+++ b/progs/tests/auto/else_.sy
@@ -0,0 +1,10 @@
+start :: fn {
+a := 1
+ res := 0
+ if a == 0 {
+ <!>
+ } else {
+ res = 1
+ }
+ res <=> 1
+}
diff --git a/progs/tests/auto/else_if.sy b/progs/tests/auto/else_if.sy
new file mode 100644
index 0000000..18b64f6
--- /dev/null
+++ b/progs/tests/auto/else_if.sy
@@ -0,0 +1,12 @@
+start :: fn {
+a := 1
+ res := 0
+ if a == 0 {
+ <!>
+ } else if a == 1 {
+ res = 1
+ } else {
+ <!>
+ }
+ res <=> 1
+}
diff --git a/progs/tests/auto/expressions.sy b/progs/tests/auto/expressions.sy
new file mode 100644
index 0000000..0f6d6fc
--- /dev/null
+++ b/progs/tests/auto/expressions.sy
@@ -0,0 +1,6 @@
+start :: fn {
+1 + 1 // blargh
+ 2 // blargh
+ // HARGH
+
+}
diff --git a/progs/tests/auto/factorial.sy b/progs/tests/auto/factorial.sy
new file mode 100644
index 0000000..770bc33
--- /dev/null
+++ b/progs/tests/auto/factorial.sy
@@ -0,0 +1,12 @@
+factorial :: fn n: int -> int {
+ if n <= 1 {
+ ret 1
+ }
+ ret n * factorial(n - 1)
+}
+
+start :: fn {
+ factorial(5) <=> 120
+ factorial(6) <=> 720
+ factorial(12) <=> 479001600
+}
diff --git a/progs/tests/auto/field.sy b/progs/tests/auto/field.sy
new file mode 100644
index 0000000..c985297
--- /dev/null
+++ b/progs/tests/auto/field.sy
@@ -0,0 +1,3 @@
+start :: fn {
+blob A { a: int }
+}
diff --git a/progs/tests/auto/field_assign.sy b/progs/tests/auto/field_assign.sy
new file mode 100644
index 0000000..93837ea
--- /dev/null
+++ b/progs/tests/auto/field_assign.sy
@@ -0,0 +1,5 @@
+start :: fn {
+blob A { a: int }
+ a := A()
+ a.a = 2
+}
diff --git a/progs/tests/auto/field_get.sy b/progs/tests/auto/field_get.sy
new file mode 100644
index 0000000..0905f6f
--- /dev/null
+++ b/progs/tests/auto/field_get.sy
@@ -0,0 +1,7 @@
+start :: fn {
+blob A { a: int }
+ a := A()
+ a.a = 2
+ a.a <=> 2
+ 2 <=> a.a
+}
diff --git a/progs/tests/auto/in_rhs.sy b/progs/tests/auto/in_rhs.sy
new file mode 100644
index 0000000..60e4873
--- /dev/null
+++ b/progs/tests/auto/in_rhs.sy
@@ -0,0 +1,3 @@
+start :: fn {
+5 <=> 1 * 2 + 3
+}
diff --git a/progs/tests/auto/instantiate.sy b/progs/tests/auto/instantiate.sy
new file mode 100644
index 0000000..7d32bf9
--- /dev/null
+++ b/progs/tests/auto/instantiate.sy
@@ -0,0 +1,5 @@
+start :: fn {
+blob A {}
+ a := A()
+ a
+}
diff --git a/progs/tests/auto/invalid_assign.sy b/progs/tests/auto/invalid_assign.sy
new file mode 100644
index 0000000..a8b18f0
--- /dev/null
+++ b/progs/tests/auto/invalid_assign.sy
@@ -0,0 +1,7 @@
+start :: fn {
+a := 1
+a = 0.1
+a
+}
+
+// errors: [ErrorKind::TypeMismatch(Type::Int, Type::Float)]
diff --git a/progs/tests/auto/more_types.sy b/progs/tests/auto/more_types.sy
new file mode 100644
index 0000000..18d825e
--- /dev/null
+++ b/progs/tests/auto/more_types.sy
@@ -0,0 +1,4 @@
+start :: fn {
+a: (str, bool, int) = ("abc", true, 1)
+a
+}
diff --git a/progs/tests/auto/mul.sy b/progs/tests/auto/mul.sy
new file mode 100644
index 0000000..78cc14e
--- /dev/null
+++ b/progs/tests/auto/mul.sy
@@ -0,0 +1,3 @@
+start :: fn {
+(0, 1, 2) * (2, 3, 4) <=> (0, 3, 8)
+}
diff --git a/progs/tests/auto/multiple_fields.sy b/progs/tests/auto/multiple_fields.sy
new file mode 100644
index 0000000..2a4b3c0
--- /dev/null
+++ b/progs/tests/auto/multiple_fields.sy
@@ -0,0 +1,11 @@
+start :: fn {
+blob A {
+ a: int
+ b: int
+ }
+ a := A()
+ a.a = 2
+ a.b = 3
+ a.a + a.b <=> 5
+ 5 <=> a.a + a.b
+}
diff --git a/progs/tests/auto/multiple_returns.sy b/progs/tests/auto/multiple_returns.sy
new file mode 100644
index 0000000..8abc000
--- /dev/null
+++ b/progs/tests/auto/multiple_returns.sy
@@ -0,0 +1,12 @@
+start :: fn {
+f := fn a: int -> int {
+ if a == 1 {
+ ret 2
+ } else {
+ ret 3
+ }
+ }
+ f(0) <=> 3
+ f(1) <=> 2
+ f(2) <=> 3
+}
diff --git a/progs/tests/auto/negation.sy b/progs/tests/auto/negation.sy
new file mode 100644
index 0000000..41388d8
--- /dev/null
+++ b/progs/tests/auto/negation.sy
@@ -0,0 +1,7 @@
+start :: fn {
+-1 <=> 0 - 1
+ -1 + 2 <=> 1
+ -(1 + 2) <=> -3
+ 1 + -1 <=> 0
+ 2 * -1 <=> -2
+}
diff --git a/progs/tests/auto/not.sy b/progs/tests/auto/not.sy
new file mode 100644
index 0000000..712690a
--- /dev/null
+++ b/progs/tests/auto/not.sy
@@ -0,0 +1,5 @@
+start :: fn {
+f := fn {}
+ f!
+
+}
diff --git a/progs/tests/auto/one_arg.sy b/progs/tests/auto/one_arg.sy
new file mode 100644
index 0000000..9523a67
--- /dev/null
+++ b/progs/tests/auto/one_arg.sy
@@ -0,0 +1,5 @@
+start :: fn {
+f := fn a:int { a <=> 1 }
+ f! 1
+
+}
diff --git a/progs/tests/auto/param_1.sy b/progs/tests/auto/param_1.sy
new file mode 100644
index 0000000..1781eaf
--- /dev/null
+++ b/progs/tests/auto/param_1.sy
@@ -0,0 +1,4 @@
+start :: fn {
+f := fn a: int {}
+ f(1)
+}
diff --git a/progs/tests/auto/param_2.sy b/progs/tests/auto/param_2.sy
new file mode 100644
index 0000000..ac5fdfd
--- /dev/null
+++ b/progs/tests/auto/param_2.sy
@@ -0,0 +1,7 @@
+start :: fn {
+add := fn a: int, b: int -> int {
+ ret a + b
+ }
+ add(1, 1) <=> 2
+ add(10, 20) <=> 30
+}
diff --git a/progs/tests/auto/param_and_return.sy b/progs/tests/auto/param_and_return.sy
new file mode 100644
index 0000000..7e39775
--- /dev/null
+++ b/progs/tests/auto/param_and_return.sy
@@ -0,0 +1,7 @@
+start :: fn {
+f := fn a: int -> int {
+ ret a * 2
+ }
+ f(1) <=> 2
+ f(5) <=> 10
+}
diff --git a/progs/tests/auto/parenthesis.sy b/progs/tests/auto/parenthesis.sy
new file mode 100644
index 0000000..b456769
--- /dev/null
+++ b/progs/tests/auto/parenthesis.sy
@@ -0,0 +1,3 @@
+start :: fn {
+(1 + 2) * 3 <=> 9
+}
diff --git a/progs/tests/auto/passing_functions.sy b/progs/tests/auto/passing_functions.sy
new file mode 100644
index 0000000..d1ff70e
--- /dev/null
+++ b/progs/tests/auto/passing_functions.sy
@@ -0,0 +1,9 @@
+start :: fn {
+g := fn -> int {
+ ret 1
+ }
+ f := fn inner: fn -> int -> int {
+ ret inner()
+ }
+ f(g) <=> 1
+}
diff --git a/progs/tests/auto/passing_functions_mixed.sy b/progs/tests/auto/passing_functions_mixed.sy
new file mode 100644
index 0000000..292e7ce
--- /dev/null
+++ b/progs/tests/auto/passing_functions_mixed.sy
@@ -0,0 +1,9 @@
+start :: fn {
+g := fn a: int -> int {
+ ret a * 2
+ }
+ f := fn inner: fn int -> int, a: int -> int {
+ ret inner(a)
+ }
+ f(g, 2) <=> 4
+}
diff --git a/progs/tests/auto/precedence.sy b/progs/tests/auto/precedence.sy
new file mode 100644
index 0000000..0aee658
--- /dev/null
+++ b/progs/tests/auto/precedence.sy
@@ -0,0 +1,8 @@
+start :: fn {
+f := fn a: int, b: int -> int {
+ ret a + b
+ }
+ 1 + f(2, 3) <=> 6
+ 2 * f(2, 3) <=> 10
+ f(2, 3) - (2 + 3) <=> 0
+}
diff --git a/progs/tests/auto/return_1.sy b/progs/tests/auto/return_1.sy
new file mode 100644
index 0000000..993f9fd
--- /dev/null
+++ b/progs/tests/auto/return_1.sy
@@ -0,0 +1,6 @@
+start :: fn {
+f := fn -> int {
+ ret 1
+ }
+ f() <=> 1
+}
diff --git a/progs/tests/auto/returning_closures.sy b/progs/tests/auto/returning_closures.sy
new file mode 100644
index 0000000..1c4c091
--- /dev/null
+++ b/progs/tests/auto/returning_closures.sy
@@ -0,0 +1,24 @@
+start :: fn {
+
+f : fn -> fn -> int = fn -> fn -> int {
+ x : int = 0
+ f := fn -> int {
+ x = x + 1
+ ret x
+ }
+ f() <=> 1
+ ret f
+}
+
+a := f()
+b := f()
+
+a() <=> 2
+a() <=> 3
+
+b() <=> 2
+b() <=> 3
+
+a() <=> 4
+
+}
diff --git a/progs/tests/auto/simple.sy b/progs/tests/auto/simple.sy
new file mode 100644
index 0000000..436dcae
--- /dev/null
+++ b/progs/tests/auto/simple.sy
@@ -0,0 +1,10 @@
+start :: fn {
+
+a :: 1
+a <=> 1
+b := 2
+{
+ a <=> 1
+ b <=> 2
+}
+}
diff --git a/progs/tests/auto/simple_add.sy b/progs/tests/auto/simple_add.sy
new file mode 100644
index 0000000..f1952d3
--- /dev/null
+++ b/progs/tests/auto/simple_add.sy
@@ -0,0 +1,9 @@
+start :: fn {
+
+a := 0
+b := 99999
+a += 1
+a <=> 1
+b <=> 99999
+
+}
diff --git a/progs/tests/auto/simple_break.sy b/progs/tests/auto/simple_break.sy
new file mode 100644
index 0000000..11c06ad
--- /dev/null
+++ b/progs/tests/auto/simple_break.sy
@@ -0,0 +1,12 @@
+start :: fn {
+
+a := 0
+for i := 0, i < 10, i += 1 {
+ a = a + 1
+ if i == 2 {
+ break
+ }
+}
+a <=> 3
+
+}
diff --git a/progs/tests/auto/simple_continue.sy b/progs/tests/auto/simple_continue.sy
new file mode 100644
index 0000000..ac2bf81
--- /dev/null
+++ b/progs/tests/auto/simple_continue.sy
@@ -0,0 +1,12 @@
+start :: fn {
+
+a := 0
+for i := 0, i < 4, i += 1 {
+ if i == 2 {
+ continue
+ }
+ a = a + 1
+}
+a <=> 3
+
+}
diff --git a/progs/tests/auto/simple_sub.sy b/progs/tests/auto/simple_sub.sy
new file mode 100644
index 0000000..5dad340
--- /dev/null
+++ b/progs/tests/auto/simple_sub.sy
@@ -0,0 +1,9 @@
+start :: fn {
+
+a := 0
+b := 99999
+a -= 1
+a <=> -1
+b <=> 99999
+
+}
diff --git a/progs/tests/auto/simplest.sy b/progs/tests/auto/simplest.sy
new file mode 100644
index 0000000..5669dc0
--- /dev/null
+++ b/progs/tests/auto/simplest.sy
@@ -0,0 +1,4 @@
+start :: fn {
+f := fn {}
+ f()
+}
diff --git a/progs/tests/auto/single_variable.sy b/progs/tests/auto/single_variable.sy
new file mode 100644
index 0000000..00217d2
--- /dev/null
+++ b/progs/tests/auto/single_variable.sy
@@ -0,0 +1,4 @@
+start :: fn {
+a := 1
+ a <=> 1
+}
diff --git a/progs/tests/auto/stack_ordering.sy b/progs/tests/auto/stack_ordering.sy
new file mode 100644
index 0000000..85a36bd
--- /dev/null
+++ b/progs/tests/auto/stack_ordering.sy
@@ -0,0 +1,6 @@
+start :: fn {
+a := 1
+ b := 2
+ b <=> 2
+ a <=> 1
+}
diff --git a/progs/tests/auto/strange.sy b/progs/tests/auto/strange.sy
new file mode 100644
index 0000000..c495041
--- /dev/null
+++ b/progs/tests/auto/strange.sy
@@ -0,0 +1,15 @@
+start :: fn {
+
+a := 0
+{
+ b := 99999
+ {
+ a := 99999
+ a
+ }
+ b
+ a -= 1
+}
+a <=> -1
+
+}
diff --git a/progs/tests/auto/sub.sy b/progs/tests/auto/sub.sy
new file mode 100644
index 0000000..755ae67
--- /dev/null
+++ b/progs/tests/auto/sub.sy
@@ -0,0 +1,3 @@
+start :: fn {
+(1, -2, 3, -4) - (4, 3, -2, -1) <=> (-3, 1, 1, -5)
+}
diff --git a/progs/tests/auto/terms_and_factors.sy b/progs/tests/auto/terms_and_factors.sy
new file mode 100644
index 0000000..1fef724
--- /dev/null
+++ b/progs/tests/auto/terms_and_factors.sy
@@ -0,0 +1,4 @@
+start :: fn {
+1 + 1 * 2 <=> 3
+ 1 * 2 + 3 <=> 5
+}
diff --git a/progs/tests/auto/three_arg.sy b/progs/tests/auto/three_arg.sy
new file mode 100644
index 0000000..d948fa0
--- /dev/null
+++ b/progs/tests/auto/three_arg.sy
@@ -0,0 +1,5 @@
+start :: fn {
+f := fn a:int, b:int, c:int { c <=> 13 }
+ f! 1, 1 + 2, 1 + 4 * 3
+
+}
diff --git a/progs/tests/auto/two_arg.sy b/progs/tests/auto/two_arg.sy
new file mode 100644
index 0000000..60645b5
--- /dev/null
+++ b/progs/tests/auto/two_arg.sy
@@ -0,0 +1,5 @@
+start :: fn {
+f := fn a:int, b:int { b <=> 3 }
+ f! 1, 1 + 2
+
+}
diff --git a/progs/tests/auto/two_variables.sy b/progs/tests/auto/two_variables.sy
new file mode 100644
index 0000000..9ae183f
--- /dev/null
+++ b/progs/tests/auto/two_variables.sy
@@ -0,0 +1,6 @@
+start :: fn {
+a := 1
+ b := 2
+ a <=> 1
+ b <=> 2
+}
diff --git a/progs/tests/auto/types.sy b/progs/tests/auto/types.sy
new file mode 100644
index 0000000..c045b34
--- /dev/null
+++ b/progs/tests/auto/types.sy
@@ -0,0 +1,4 @@
+start :: fn {
+a: (int, float, int) = (1, 1., 1)
+a
+}
diff --git a/progs/tests/auto/uncallable_type.sy b/progs/tests/auto/uncallable_type.sy
new file mode 100644
index 0000000..06ff71a
--- /dev/null
+++ b/progs/tests/auto/uncallable_type.sy
@@ -0,0 +1,9 @@
+start :: fn {
+
+ f := fn i: int {
+ i()
+ }
+ f
+}
+
+// errors: [ErrorKind::InvalidProgram]
diff --git a/progs/tests/auto/wrong_params.sy b/progs/tests/auto/wrong_params.sy
new file mode 100644
index 0000000..6b56e90
--- /dev/null
+++ b/progs/tests/auto/wrong_params.sy
@@ -0,0 +1,7 @@
+start :: fn {
+
+ f : fn -> int = fn a: int -> int {}
+f
+}
+
+// errors: [ErrorKind::TypeMismatch(_, _), ErrorKind::TypeMismatch(Type::Void, Type::Int)]
diff --git a/progs/tests/auto/wrong_ret.sy b/progs/tests/auto/wrong_ret.sy
new file mode 100644
index 0000000..81f2517
--- /dev/null
+++ b/progs/tests/auto/wrong_ret.sy
@@ -0,0 +1,7 @@
+start :: fn {
+
+ f : fn -> int = fn {}
+f
+}
+
+// errors: [ErrorKind::TypeMismatch(_, _)]
diff --git a/progs/tests/constants_declaration_order.sy b/progs/tests/constants_declaration_order.sy
new file mode 100644
index 0000000..93e6117
--- /dev/null
+++ b/progs/tests/constants_declaration_order.sy
@@ -0,0 +1,6 @@
+b :: 1
+a :: b + 1
+
+start :: fn {
+ a <=> b + 1
+}
diff --git a/progs/tests/constants_declaration_order_wrong.sy b/progs/tests/constants_declaration_order_wrong.sy
new file mode 100644
index 0000000..37f5050
--- /dev/null
+++ b/progs/tests/constants_declaration_order_wrong.sy
@@ -0,0 +1,8 @@
+a :: b + 1
+b :: 1
+
+start :: fn {
+ a <=> b + 1
+}
+
+// errors: [ErrorKind::SyntaxError(1, _)]
diff --git a/progs/tests/faulty.sy b/progs/tests/faulty.sy
index 369b8ff..ea1b40d 100644
--- a/progs/tests/faulty.sy
+++ b/progs/tests/faulty.sy
@@ -1,3 +1,5 @@
asdflökja;;;;
123
asd
+
+// errors: [ErrorKind::SyntaxError(_, _)]
diff --git a/progs/tests/fib.sy b/progs/tests/fib.sy
index 1dde6a8..62f38e7 100644
--- a/progs/tests/fib.sy
+++ b/progs/tests/fib.sy
@@ -1,9 +1,11 @@
-a := 0
-b := 1
+start :: fn {
+ a := 0
+ b := 1
-for i := 0, i < 90, i = i + 1 {
- c := a
- a = b
- b = c + b
+ for i := 0, i < 90, i = i + 1 {
+ c := a
+ a = b
+ b = c + b
+ }
+ a <=> 2880067194370816120
}
-a <=> 2880067194370816120
diff --git a/progs/tests/for.sy b/progs/tests/for.sy
index a9f8cd2..285cbc3 100644
--- a/progs/tests/for.sy
+++ b/progs/tests/for.sy
@@ -1,19 +1,21 @@
-a := 0
-for i := 0, i < 3, i = i + 1 {
- a = a + i
-}
-a <=> 3
+start :: fn {
+ a := 0
+ for i := 0, i < 3, i = i + 1 {
+ a = a + i
+ }
+ a <=> 3
-a = 0
-for i := 0, i <= 3, i = i + 1 {
- a = a + i
-}
-a <=> 6
+ a = 0
+ for i := 0, i <= 3, i = i + 1 {
+ a = a + i
+ }
+ a <=> 6
-a = 0
-for i := 0, i < 3, i = i + 1 {
- for j := 0, j < 3, j = j + 1 {
- a = a + i * j
+ a = 0
+ for i := 0, i < 3, i = i + 1 {
+ for j := 0, j < 3, j = j + 1 {
+ a = a + i * j
+ }
}
+ a <=> 9
}
-a <=> 9
diff --git a/progs/tests/global_collision.sy b/progs/tests/global_collision.sy
new file mode 100644
index 0000000..6bce509
--- /dev/null
+++ b/progs/tests/global_collision.sy
@@ -0,0 +1,10 @@
+fac :: fn a: int -> int {
+ if a < 1 { ret 1 }
+ ret a * fac! a - 1
+}
+
+a :: fac! 4
+
+start :: fn {
+ a <=> 24
+}
diff --git a/progs/tests/global_constants.sy b/progs/tests/global_constants.sy
new file mode 100644
index 0000000..7ee6ca5
--- /dev/null
+++ b/progs/tests/global_constants.sy
@@ -0,0 +1,17 @@
+// TODO(ed): Pure functions
+fac :: fn n: int -> int {
+ if n < 1 { ret 1 }
+ ret n * fac! n - 1
+}
+
+a :: fac! 4
+b :: a + fac! 2
+c := b + 1
+
+start :: fn {
+ a <=> 24
+ b <=> 24 + 2
+ c <=> 24 + 2 + 1
+ c += 1
+ c <=> 24 + 2 + 2
+}
diff --git a/progs/tests/scoping.sy b/progs/tests/scoping.sy
index 7679948..dfcf92c 100644
--- a/progs/tests/scoping.sy
+++ b/progs/tests/scoping.sy
@@ -1,13 +1,16 @@
-a : int = 1
-{
+start :: fn {
+ a : int = 1
+ {
+ a <=> 1
+ a : int = a + a
+ a <=> 2
+ }
a <=> 1
- a : int = a + a
- a <=> 2
-}
-a <=> 1
-{
- a = 2
- a : int = 1
+ {
+ a = 2
+ a : int = 1
+ a
+ }
+ a <=> 2
}
-a <=> 2
diff --git a/progs/tests/simple.sy b/progs/tests/simple.sy
deleted file mode 100644
index 48394d7..0000000
--- a/progs/tests/simple.sy
+++ /dev/null
@@ -1,2 +0,0 @@
-print test(3.0)
-print test(3.0, 4.0)
diff --git a/progs/tests/unreachable.sy b/progs/tests/unreachable.sy
index f016a14..5a59ae1 100644
--- a/progs/tests/unreachable.sy
+++ b/progs/tests/unreachable.sy
@@ -1 +1,5 @@
-<!>
+start :: fn {
+ <!>
+}
+
+// errors: [ErrorKind::Unreachable]
diff --git a/src/compiler.rs b/src/compiler.rs
index 124606e..dc3bd96 100644
--- a/src/compiler.rs
+++ b/src/compiler.rs
@@ -1,11 +1,12 @@
-use std::{borrow::Cow, path::{Path, PathBuf}};
+use std::path::{Path, PathBuf};
use std::cell::RefCell;
use std::collections::{HashMap, hash_map::Entry};
use std::rc::Rc;
use crate::{Blob, Block, Op, Prog, RustFunction, Type, Value};
use crate::error::{Error, ErrorKind};
-use crate::tokenizer::{Token, TokenStream};
+use crate::sectionizer::Section;
+use crate::tokenizer::Token;
macro_rules! nextable_enum {
( $name:ident { $( $thing:ident ),* $( , )? } ) => {
@@ -39,34 +40,37 @@ macro_rules! expect {
};
}
+// NOTE(ed): This can cause some strange bugs. It would be nice if we could
+// remove this function, but to do that we need to rewrite some code. Like
+// tuples and fields. Might be worth it tough.
macro_rules! parse_branch {
($compiler:expr, $block:expr, [ $( $call:expr ),* ]) => {
{
let block_length = $block.ops.len();
- let token_length = $compiler.curr;
+ let token_length = $compiler.current_token;
let num_errors = $compiler.errors.len();
let mut stored_errors = Vec::new();
// Closures for early return on success.
- let success = (|| {
+ let success = loop {
// We risk getting a lot of errors if we are in an invalid state
// when we start the parse.
if $compiler.panic {
- return false;
+ break false;
}
$(
$call;
if !$compiler.panic {
- return true;
+ break true;
}
$compiler.panic = false;
- $compiler.curr = token_length;
+ $compiler.current_token = token_length;
let thrown_errors = $compiler.errors.len() - num_errors - 1;
stored_errors.extend($compiler.errors.split_off(thrown_errors));
$block.ops.truncate(block_length);
)*
- false
- })();
+ break false;
+ };
if !success {
$compiler.errors.extend(stored_errors);
@@ -79,31 +83,102 @@ macro_rules! parse_branch {
($compiler:expr, $block:expr, $call:expr) => {
{
let block_length = $block.ops.len();
- let token_length = $compiler.curr;
+ let token_length = $compiler.current_token;
let num_errors = $compiler.errors.len();
let mut stored_errors = Vec::new();
// Closures for early return on success.
- (|| {
+ loop {
// We risk getting a lot of errors if we are in an invalid state
// when we start the parse.
if $compiler.panic {
- return false;
+ break false;
}
$call;
if !$compiler.panic {
- return true;
+ break true;
}
$compiler.panic = false;
- $compiler.curr = token_length;
+ $compiler.current_token = token_length;
let thrown_errors = $compiler.errors.len() - num_errors - 1;
stored_errors.extend($compiler.errors.split_off(thrown_errors));
$block.ops.truncate(block_length);
- false
- })()
+ break false;
+ }
+ }
+ };
+}
+
+macro_rules! push_frame {
+ ($compiler:expr, $block:expr, $code:tt) => {
+ {
+ $compiler.frames_mut().push(Frame::new());
+
+ // Return value stored as a variable
+ let var = Variable::new("", true, Type::Unknown);
+ $compiler.define(var).unwrap();
+
+ $code
+
+ let frame = $compiler.frames_mut().pop().unwrap();
+ // 0-th slot is the function itself.
+ for var in frame.stack.iter().skip(1) {
+ if !(var.read || var.upvalue) {
+ let e = ErrorKind::SyntaxError(
+ var.line,
+ Token::Identifier(var.name.clone()
+ ));
+ $compiler.error_on_line(
+ e,
+ var.line,
+ Some(format!("Unused value '{}'.", var.name))
+ );
+ }
+ $compiler.panic = false;
+ }
+ // The 0th slot is the return value, which is passed out
+ // from functions, and should not be popped.
+ 0
}
};
}
+macro_rules! push_scope {
+ ($compiler:expr, $block:expr, $code:tt) => {
+ let ss = $compiler.stack().len();
+ $compiler.frame_mut().scope += 1;
+
+ $code;
+
+ $compiler.frame_mut().scope -= 1;
+
+ let mut errors = Vec::new();
+ for var in $compiler.frame().stack.iter().skip(ss).rev() {
+ if !(var.read || var.upvalue) {
+ let e = ErrorKind::SyntaxError(
+ var.line,
+ Token::Identifier(var.name.clone()
+ ));
+ errors.push((
+ e,
+ var.line,
+ format!("Usage of undefined value: '{}'.", var.name),)
+ );
+ }
+ if var.captured {
+ add_op($compiler, $block, Op::PopUpvalue);
+ } else {
+ add_op($compiler, $block, Op::Pop);
+ }
+ }
+
+ for (e, l, m) in errors.iter() {
+ $compiler.error_on_line(e.clone(), *l, Some(m.clone()));
+ $compiler.panic = false;
+ }
+ $compiler.stack_mut().truncate(ss);
+ };
+}
+
nextable_enum!(Prec {
No,
Assert,
@@ -152,11 +227,13 @@ impl Variable {
}
}
+#[derive(Debug)]
enum LoopOp {
Continue,
Break,
}
+#[derive(Debug)]
struct Frame {
loops: Vec<Vec<(usize, usize, LoopOp)>>,
stack: Vec<Variable>,
@@ -208,6 +285,17 @@ impl Frame {
}
}
+ fn find_outer(&self, name: &str) -> Option<Variable> {
+ // Only really makes sense in the outermost frame
+ // where declaration order doesn't matter
+ for var in self.stack.iter().rev() {
+ if var.name == name {
+ return Some(var.clone());
+ }
+ }
+ None
+ }
+
fn find_local(&self, name: &str) -> Option<Variable> {
for var in self.stack.iter().rev() {
if var.name == name && var.active {
@@ -240,94 +328,48 @@ impl Frame {
}
}
-pub(crate) struct Compiler {
- curr: usize,
- tokens: TokenStream,
- current_file: PathBuf,
+type Namespace = HashMap<String, Name>;
+struct CompilerContext {
frames: Vec<Frame>,
-
- panic: bool,
- errors: Vec<Error>,
-
- blocks: Vec<Rc<RefCell<Block>>>,
- blob_id: usize,
- unknown: HashMap<String, (usize, usize)>,
-
- functions: HashMap<String, (usize, RustFunction)>,
- constants: Vec<Value>,
- strings: Vec<String>,
+ namespace: Namespace,
}
-macro_rules! push_frame {
- ($compiler:expr, $block:expr, $code:tt) => {
- {
- $compiler.frames.push(Frame::new());
+impl CompilerContext {
+ fn new() -> Self {
+ Self {
+ frames: vec![Frame::new()],
+ namespace: Namespace::new(),
+ }
+ }
+}
- // Return value stored as a variable
- let var = Variable::new("", true, Type::Unknown);
- $compiler.define(var).unwrap();
+#[derive(Debug, Clone)]
+enum Name {
+ Slot(usize, usize),
+ Unknown(usize, usize),
+ Namespace(PathBuf),
+}
- $code
+pub(crate) struct Compiler {
+ current_token: usize,
+ current_section: usize,
+ sections: Vec<Section>,
- let frame = $compiler.frames.pop().unwrap();
- // 0-th slot is the function itself.
- for var in frame.stack.iter().skip(1) {
- if !(var.read || var.upvalue) {
- let e = ErrorKind::SyntaxError(
- var.line,
- Token::Identifier(var.name.clone()
- ));
- $compiler.error_on_line(
- e,
- var.line,
- Some(format!("Unused value '{}'.", var.name))
- );
- }
- $compiler.panic = false;
- }
- // The 0th slot is the return value, which is passed out
- // from functions, and should not be popped.
- 0
- }
- };
-}
+ contextes: HashMap<PathBuf, CompilerContext>,
-macro_rules! push_scope {
- ($compiler:expr, $block:expr, $code:tt) => {
- let ss = $compiler.stack().len();
- $compiler.frame_mut().scope += 1;
+ panic: bool,
+ errors: Vec<Error>,
- $code;
+ blocks: Vec<Rc<RefCell<Block>>>,
+ blob_id: usize,
- $compiler.frame_mut().scope -= 1;
+ functions: HashMap<String, (usize, RustFunction)>,
- let mut errors = Vec::new();
- for var in $compiler.frame().stack.iter().skip(ss).rev() {
- if !(var.read || var.upvalue) {
- let e = ErrorKind::SyntaxError(
- var.line,
- Token::Identifier(var.name.clone()
- ));
- errors.push((
- e,
- var.line,
- format!("Usage of undefined value: '{}'.", var.name),)
- );
- }
- if var.captured {
- add_op($compiler, $block, Op::PopUpvalue);
- } else {
- add_op($compiler, $block, Op::Pop);
- }
- }
+ strings: Vec<String>,
- for (e, l, m) in errors.iter() {
- $compiler.error_on_line(e.clone(), *l, Some(m.clone()));
- $compiler.panic = false;
- }
- $compiler.stack_mut().truncate(ss);
- };
+ constants: Vec<Value>,
+ values: HashMap<Value, usize>,
}
/// Helper function for adding operations to the given block.
@@ -336,36 +378,32 @@ fn add_op(compiler: &Compiler, block: &mut Block, op: Op) -> usize {
}
impl Compiler {
- pub(crate) fn new(current_file: &Path, tokens: TokenStream) -> Self {
+ pub(crate) fn new(sections: Vec<Section>) -> Self {
+ let contextes = sections
+ .iter()
+ .map(|section| (section.path.to_path_buf(), CompilerContext::new()))
+ .collect();
+
Self {
- curr: 0,
- tokens,
- current_file: PathBuf::from(current_file),
+ current_token: 0,
+ current_section: 0,
+ sections,
- frames: vec![Frame::new()],
+ contextes,
panic: false,
errors: vec![],
blocks: Vec::new(),
blob_id: 0,
- unknown: HashMap::new(),
functions: HashMap::new(),
- constants: vec![Value::Nil],
strings: Vec::new(),
- }
- }
- fn nil_value(&self) -> usize {
- self.constants.iter()
- .enumerate()
- .find_map(|(i, x)|
- match x {
- Value::Nil => Some(i),
- _ => None,
- }).unwrap()
+ constants: vec![],
+ values: HashMap::new(),
+ }
}
fn new_blob_id(&mut self) -> usize {
@@ -374,9 +412,39 @@ impl Compiler {
id
}
+ fn add_namespace(&mut self, name: String) {
+ let path = Path::new(&format!("{}.sy", name)).to_path_buf();
+ match self.names_mut().entry(name.clone()) {
+ Entry::Vacant(v) => {
+ v.insert(Name::Namespace(path));
+ }
+ Entry::Occupied(_) => {
+ error!(self, format!("Namespace {} already present.", name));
+ }
+ }
+ }
+
fn add_constant(&mut self, value: Value) -> usize {
- self.constants.push(value);
- self.constants.len() - 1
+ if matches!(value, Value::Float(_)
+ | Value::Int(_)
+ | Value::Bool(_)
+ | Value::String(_)
+ | Value::Tuple(_)
+ | Value::Nil)
+ {
+ let entry = self.values.entry(value.clone());
+ if let Entry::Occupied(entry) = entry {
+ *entry.get()
+ } else {
+ let slot = self.constants.len();
+ self.constants.push(value);
+ entry.or_insert(slot);
+ slot
+ }
+ } else {
+ self.constants.push(value);
+ self.constants.len() - 1
+ }
}
fn intern_string(&mut self, string: String) -> usize {
@@ -384,14 +452,45 @@ impl Compiler {
self.strings.len() - 1
}
+ fn section(&self) -> &Section {
+ &self.sections[self.current_section]
+ }
+
+ fn current_file(&self) -> &Path {
+ &self.section().path
+ }
+
+ fn current_context(&self) -> &CompilerContext {
+ self.contextes.get(self.current_file()).unwrap()
+ }
+
+ fn current_context_mut(&mut self) -> &mut CompilerContext {
+ let file = self.current_file().to_path_buf();
+ self.contextes.get_mut(&file).unwrap()
+ }
+
fn frame(&self) -> &Frame {
- let last = self.frames.len() - 1;
- &self.frames[last]
+ self.current_context().frames.last().unwrap()
}
fn frame_mut(&mut self) -> &mut Frame {
- let last = self.frames.len() - 1;
- &mut self.frames[last]
+ self.current_context_mut().frames.last_mut().unwrap()
+ }
+
+ fn frames(&self) -> &[Frame] {
+ &self.current_context().frames
+ }
+
+ fn frames_mut(&mut self) -> &mut Vec<Frame> {
+ &mut self.current_context_mut().frames
+ }
+
+ fn names(&self) -> &Namespace {
+ &self.current_context().namespace
+ }
+
+ fn names_mut(&mut self) -> &mut Namespace {
+ &mut self.current_context_mut().namespace
}
/// Marks a variable as read. Also marks upvalues.
@@ -401,16 +500,16 @@ impl Compiler {
return;
}
-
- if if let Some(up) = self.frames[frame_id].upvalues.get(var.slot) {
- up.name == var.name
- } else { false } {
- let mut inner_var = self.frames[frame_id].upvalues[var.slot].clone();
- inner_var.slot = inner_var.outer_slot;
- self.mark_read(frame_id - 1, &inner_var);
- self.frames[frame_id].upvalues[var.slot].read = true;
- } else {
- self.frames[frame_id].stack[var.slot].read = true;
+ match self.frames()[frame_id].upvalues.get(var.slot) {
+ Some(up) if up.name == var.name => {
+ let mut inner_var = self.frames()[frame_id].upvalues[var.slot].clone();
+ inner_var.slot = inner_var.outer_slot;
+ self.mark_read(frame_id - 1, &inner_var);
+ self.frames_mut()[frame_id].upvalues[var.slot].read = true;
+ }
+ _ => {
+ self.frames_mut()[frame_id].stack[var.slot].read = true;
+ }
}
}
@@ -446,22 +545,26 @@ impl Compiler {
self.panic = true;
self.errors.push(Error {
kind,
- file: self.current_file.clone(),
+ file: self.current_file().to_path_buf(),
line,
message,
});
}
+ fn init_section(&mut self, section: usize) {
+ self.current_token = 0;
+ self.current_section = section;
+ }
fn peek(&self) -> Token {
self.peek_at(0)
}
fn peek_at(&self, at: usize) -> Token {
- if self.tokens.len() <= self.curr + at {
+ if self.section().tokens.len() <= self.current_token + at {
crate::tokenizer::Token::EOF
} else {
- self.tokens[self.curr + at].0.clone()
+ self.section().tokens[self.current_token + at].0.clone()
}
}
@@ -472,12 +575,12 @@ impl Compiler {
fn eat(&mut self) -> Token {
let t = self.peek();
- self.curr += 1;
+ self.current_token += 1;
match t {
Token::GitConflictBegin => {
- self.curr -= 1;
+ self.current_token -= 1;
let start = self.line();
- self.curr += 1;
+ self.current_token += 1;
while !matches!(self.eat(), Token::GitConflictEnd) {}
self.panic = false;
self.error_on_line(ErrorKind::GitConflictError(start, self.line()), start, None);
@@ -490,10 +593,11 @@ impl Compiler {
/// The line of the current token.
fn line(&self) -> usize {
- if self.curr < self.tokens.len() {
- self.tokens[self.curr].1
+ if self.section().tokens.len() == 0 {
+ // unreachable!("An error occured without a section.");
+ 666666
} else {
- self.tokens[self.tokens.len() - 1].1
+ self.section().tokens[std::cmp::min(self.current_token, self.section().tokens.len() - 1)].1
}
}
@@ -679,8 +783,15 @@ impl Compiler {
}
}
- fn find_and_capture_variable<'a, I>(name: &str, mut iterator: I) -> Option<Variable>
- where I: Iterator<Item = &'a mut Frame> {
+ fn find_namespace(&self, name: &str) -> Option<&Namespace> {
+ match self.names().get(name) {
+ Some(Name::Namespace(path)) => Some(&self.contextes.get(path).unwrap().namespace),
+ _ => None,
+ }
+ }
+
+ fn find_and_capture_variable<'i, I>(name: &str, mut iterator: I) -> Option<Variable>
+ where I: Iterator<Item = &'i mut Frame> {
if let Some(frame) = iterator.next() {
if let Some(res) = frame.find_local(name) {
frame.stack[res.slot].captured = true;
@@ -710,22 +821,73 @@ impl Compiler {
return Some(res);
}
- Self::find_and_capture_variable(name, self.frames.iter_mut().rev())
+ Self::find_and_capture_variable(name, self.frames_mut().iter_mut().rev())
}
fn find_constant(&mut self, name: &str) -> usize {
- let res = self.constants.iter().enumerate().find_map(|(i, x)| match x {
- Value::Blob(b) if b.name == name => Some(i),
- Value::Function(_, b) if b.borrow().name == name => Some(i),
- _ => None,
- });
- if let Some(res) = res {
- return res;
+ match self.names_mut().entry(name.to_string()) {
+ Entry::Occupied(entry) => {
+ match entry.get() {
+ Name::Slot(i, _) => { return *i; },
+ Name::Unknown(i, _) => { return *i; },
+ _ => {
+ error!(self, format!("Tried to find constant '{}' but it was a namespace.", name));
+ return 0;
+ }
+ }
+ },
+ Entry::Vacant(_) => {},
+ };
+
+ let slot = self.add_constant(Value::Unknown);
+ let line = self.line();
+ self.names_mut().insert(name.to_string(), Name::Unknown(slot, line));
+ slot
+ }
+
+ fn named_constant(&mut self, name: String, value: Value) -> usize {
+ let line = self.line();
+ match self.names_mut().entry(name.clone()) {
+ Entry::Occupied(mut entry) => {
+ let slot = if let Name::Unknown(slot, _) = entry.get() {
+ *slot
+ } else {
+ error!(self, format!("Constant named \"{}\" already has a value.", name));
+ return 0;
+ };
+ entry.insert(Name::Slot(slot, line));
+ self.constants[slot] = value;
+ return slot;
+ },
+ Entry::Vacant(_) => {},
}
- let constant = self.add_constant(Value::Nil);
+ let slot = self.add_constant(value);
+ self.names_mut().insert(name, Name::Slot(slot, line));
+ slot
+ }
+
+ fn forward_constant(&mut self, name: String) -> usize {
let line = self.line();
- let entry = self.unknown.entry(name.to_string());
- entry.or_insert((constant, line)).0
+ let slot = self.add_constant(Value::Unknown);
+ match self.names_mut().entry(name.clone()) {
+ Entry::Occupied(_) => {
+ error!(self, format!("Constant named \"{}\" already has a value.", name));
+ 0
+ },
+ Entry::Vacant(entry) => {
+ entry.insert(Name::Unknown(slot, line));
+ slot
+ },
+ }
+ }
+
+ fn call_maybe(&mut self, block: &mut Block) -> bool {
+ if matches!(self.peek(), Token::Bang | Token::LeftParen) {
+ self.call(block);
+ true
+ } else {
+ false
+ }
}
fn call(&mut self, block: &mut Block) {
@@ -793,25 +955,21 @@ impl Compiler {
}
// TODO(ed): de-complexify
- fn function(&mut self, block: &mut Block, name: Option<&str>) {
+ fn function(&mut self, block: &mut Block, in_name: Option<&str>) {
expect!(self, Token::Fn, "Expected 'fn' at start of function.");
- let top = self.stack().len() - 1;
- let name = if let Some(name) = name {
- Cow::Owned(String::from(name))
- } else if !self.stack()[top].active {
- self.stack_mut()[top].active = true;
- Cow::Borrowed(&self.stack()[top].name)
+ let name = if let Some(name) = in_name {
+ String::from(name)
} else {
- Cow::Owned(format!("λ {}@{:03}", self.current_file.display(), self.line()))
+ format!("λ {}@{:03}", self.current_file().display(), self.line())
};
let mut args = Vec::new();
let mut return_type = Type::Void;
- let mut function_block = Block::new(&name, &self.current_file);
+ let mut function_block = Block::new(&name, self.current_file());
let block_id = self.blocks.len();
- let temp_block = Block::new(&name, &self.current_file);
+ let temp_block = Block::new(&name, self.current_file());
self.blocks.push(Rc::new(RefCell::new(temp_block)));
let _ret = push_frame!(self, function_block, {
@@ -822,7 +980,8 @@ impl Compiler {
expect!(self, Token::Colon, "Expected ':' after parameter name.");
if let Ok(typ) = self.parse_type() {
args.push(typ.clone());
- let var = Variable::new(&name, true, typ);
+ let mut var = Variable::new(&name, true, typ);
+ var.read = true;
if let Ok(slot) = self.define(var) {
self.stack_mut()[slot].active = true;
}
@@ -859,12 +1018,13 @@ impl Compiler {
}
});
+ let nil = self.add_constant(Value::Nil);
for op in function_block.ops.iter().rev() {
match op {
Op::Pop | Op::PopUpvalue => {}
Op::Return => { break; } ,
_ => {
- add_op(self, &mut function_block, Op::Constant(self.nil_value()));
+ add_op(self, &mut function_block, Op::Constant(nil));
add_op(self, &mut function_block, Op::Return);
break;
}
@@ -872,28 +1032,65 @@ impl Compiler {
}
if function_block.ops.is_empty() {
- add_op(self, &mut function_block, Op::Constant(self.nil_value()));
+ add_op(self, &mut function_block, Op::Constant(nil));
add_op(self, &mut function_block, Op::Return);
}
function_block.ty = Type::Function(args, Box::new(return_type));
let function_block = Rc::new(RefCell::new(function_block));
-
// Note(ed): We deliberately add the constant as late as possible.
// This behaviour is used in `constant_statement`.
let function = Value::Function(Vec::new(), Rc::clone(&function_block));
self.blocks[block_id] = function_block;
- let constant = self.add_constant(function);
+ let constant = if in_name.is_some() {
+ self.named_constant(name, function)
+ } else {
+ self.add_constant(function)
+ };
add_op(self, block, Op::Constant(constant));
}
fn variable_expression(&mut self, block: &mut Block) {
- let name = match self.eat() {
+ let name = match self.peek() {
Token::Identifier(name) => name,
_ => unreachable!(),
};
+ if let Some(_) = self.find_namespace(&name) {
+ self.eat();
+ // TODO(ed): This is a clone I would love to get rid of...
+ let mut namespace = self.find_namespace(&name).unwrap().clone();
+ loop {
+ if self.eat() != Token::Dot {
+ error!(self, "Expect '.' after namespace.");
+ return;
+ }
+ if let Token::Identifier(field) = self.eat() {
+ match namespace.get(&field) {
+ Some(Name::Slot(slot, _)) | Some(Name::Unknown(slot, _)) => {
+ add_op(self, block, Op::Constant(*slot));
+ self.call_maybe(block);
+ return;
+ }
+ Some(Name::Namespace(inner_name)) => {
+ namespace = self.contextes
+ .get(inner_name)
+ .unwrap().namespace
+ .clone();
+ }
+ _ => {
+ error!(self, "Invalid namespace field.");
+ }
+ }
+ } else {
+ error!(self, "Expected fieldname after '.'.");
+ }
+ }
+ }
+
+ self.eat();
+
// Global functions take precedence
if let Some(slot) = self.find_extern_function(&name) {
let string = self.add_constant(Value::ExternFunction(slot));
@@ -904,7 +1101,7 @@ impl Compiler {
// Variables
if let Some(var) = self.find_variable(&name) {
- self.mark_read(self.frames.len() - 1, &var);
+ self.mark_read(self.frames().len() - 1, &var);
if var.upvalue {
add_op(self, block, Op::ReadUpvalue(var.slot));
} else {
@@ -923,7 +1120,7 @@ impl Compiler {
}
}
_ => {
- if !parse_branch!(self, block, self.call(block)) {
+ if !self.call_maybe(block) {
return;
}
}
@@ -934,68 +1131,85 @@ impl Compiler {
// Blobs - Always returns a blob since it's filled in if it isn't used.
let con = self.find_constant(&name);
add_op(self, block, Op::Constant(con));
- parse_branch!(self, block, self.call(block));
+ self.call_maybe(block);
}
fn define(&mut self, mut var: Variable) -> Result<usize, ()> {
- if let Some(var) = self.find_variable(&var.name) {
- if var.scope == self.frame().scope {
+ let frame = self.frame();
+
+ if let Some(res) = frame.find_local(&var.name).or(frame.find_upvalue(&var.name)) {
+ if res.scope == frame.scope {
error!(self, format!("Multiple definitions of '{}' in this block.",
- var.name));
+ res.name));
return Err(());
}
}
let slot = self.stack().len();
var.slot = slot;
- var.scope = self.frame().scope;
+ var.scope = frame.scope;
var.line = self.line();
self.stack_mut().push(var);
Ok(slot)
}
fn definition_statement(&mut self, name: &str, typ: Type, block: &mut Block) {
- let var = Variable::new(name, true, typ.clone());
- let slot = self.define(var);
- self.expression(block);
- let constant = self.add_constant(Value::Ty(typ));
- add_op(self, block, Op::Define(constant));
+ if self.frames().len() <= 1 {
+ // Global
+ let var = self.frame().find_outer(name)
+ .expect(&format!("Couldn't find variable '{}' during prepass.", name));
+ assert!(var.mutable);
+
+ self.expression(block);
+ self.stack_mut()[var.slot].active = true;
+ } else {
+ // Local
+ let var = Variable::new(name, true, typ.clone());
+ let slot = self.define(var);
+ self.expression(block);
+ let constant = self.add_constant(Value::Ty(typ));
+ add_op(self, block, Op::Define(constant));
- if let Ok(slot) = slot {
- self.stack_mut()[slot].active = true;
+ if let Ok(slot) = slot {
+ self.stack_mut()[slot].active = true;
+ }
}
}
fn constant_statement(&mut self, name: &str, typ: Type, block: &mut Block) {
// Magical global constants
- if self.frames.len() <= 1 {
- if parse_branch!(self, block, self.function(block, Some(name))) {
- // Remove the function, since it's a constant and we already
- // added it.
- block.ops.pop().unwrap();
- let slot = if let Entry::Occupied(entry) = self.unknown.entry(String::from(name)) {
- let (_, (slot, _)) = entry.remove_entry();
- self.constants[slot] = self.constants.pop().unwrap();
- slot
- } else {
- self.constants.len() - 1
- };
- add_op(self, block, Op::Link(slot));
- if let Value::Function(_, block) = &self.constants[slot] {
- block.borrow_mut().mark_constant();
- } else {
- unreachable!();
- }
- return;
+ if self.frames().len() <= 1 && self.peek() == Token::Fn {
+ self.function(block, Some(name));
+ // Remove the function, since it's a constant and we already
+ // added it.
+ block.ops.pop().unwrap();
+ let slot = self.find_constant(name);
+ add_op(self, block, Op::Link(slot));
+ if let Value::Function(_, block) = &self.constants[slot] {
+ block.borrow_mut().mark_constant();
+ } else {
+ unreachable!();
}
+ return;
}
- let var = Variable::new(name, false, typ);
- let slot = self.define(var);
- self.expression(block);
+ if self.frames().len() <= 1 {
+ // Global
+ let var = self.frame().find_outer(name)
+ .expect(&format!("Couldn't find constant '{}' during prepass.", name));
+ assert!(!var.mutable);
- if let Ok(slot) = slot {
- self.stack_mut()[slot].active = true;
+ self.expression(block);
+ self.stack_mut()[var.slot].active = true;
+ } else {
+ // Local
+ let var = Variable::new(name, false, typ);
+ let slot = self.define(var);
+ self.expression(block);
+
+ if let Ok(slot) = slot {
+ self.stack_mut()[slot].active = true;
+ }
}
}
@@ -1254,21 +1468,30 @@ impl Compiler {
expect!(self, Token::RightBrace, "Expected '}' after 'blob' body. AKA '}'.");
let blob = Value::Blob(Rc::new(blob));
- if let Entry::Occupied(entry) = self.unknown.entry(name) {
- let (_, (slot, _)) = entry.remove_entry();
- self.constants[slot] = blob;
+ self.named_constant(name, blob);
+ }
+
+ fn access_dotted(&mut self, block: &mut Block) {
+ let name = match self.peek() {
+ Token::Identifier(name) => name,
+ _ => unreachable!(),
+ };
+ if let Some(_) = self.find_namespace(&name) {
+ self.expression(block);
} else {
- self.constants.push(blob);
+ parse_branch!(self, block, [self.blob_field(block), self.expression(block)]);
}
}
+ //TODO rename
fn blob_field(&mut self, block: &mut Block) {
let name = match self.eat() {
Token::Identifier(name) => name,
_ => unreachable!(),
};
+
if let Some(var) = self.find_variable(&name) {
- self.mark_read(self.frames.len() - 1, &var);
+ self.mark_read(self.frames().len() - 1, &var);
if var.upvalue {
add_op(self, block, Op::ReadUpvalue(var.slot));
} else {
@@ -1316,7 +1539,7 @@ impl Compiler {
return;
}
_ => {
- if !parse_branch!(self, block, self.call(block)) {
+ if !self.call_maybe(block) {
error!(self, "Unexpected token when parsing blob-field.");
return;
}
@@ -1329,6 +1552,44 @@ impl Compiler {
}
}
+ fn outer_statement(&mut self, block: &mut Block) {
+ self.clear_panic();
+ match self.peek_four() {
+ (Token::Identifier(name), Token::ColonEqual, ..) => {
+ self.eat();
+ self.eat();
+ self.definition_statement(&name, Type::Unknown, block);
+ },
+
+ (Token::Identifier(name), Token::ColonColon, ..) => {
+ self.eat();
+ self.eat();
+ self.constant_statement(&name, Type::Unknown, block);
+ },
+
+ (Token::Blob, Token::Identifier(_), ..) => {
+ self.blob_statement(block);
+ },
+
+ (Token::Identifier(name), Token::Colon, ..) => {
+ self.eat();
+ self.eat();
+ if let Ok(typ) = self.parse_type() {
+ expect!(self, Token::Equal, "Expected assignment.");
+ self.definition_statement(&name, typ, block);
+ } else {
+ error!(self, format!("Expected type found '{:?}'.", self.peek()));
+ }
+ }
+
+ (Token::Newline, ..) => {}
+
+ (a, b, c, d) => {
+ error!(self, format!("Unknown outer token sequence: {:?} {:?} {:?} {:?}.", a, b, c, d))
+ }
+ }
+ }
+
fn statement(&mut self, block: &mut Block) {
self.clear_panic();
@@ -1350,7 +1611,7 @@ impl Compiler {
}
(Token::Identifier(_), Token::Dot, ..) => {
- parse_branch!(self, block, [self.blob_field(block), self.expression(block)]);
+ self.access_dotted(block);
}
(Token::Identifier(name), Token::Colon, ..) => {
@@ -1433,42 +1694,117 @@ impl Compiler {
add_op(self, block, Op::Pop);
}
}
-
}
pub(crate) fn compile(&mut self, name: &str, file: &Path, functions: &[(String, RustFunction)]) -> Result<Prog, Vec<Error>> {
+ let main = Variable::new("/main/", false, Type::Void);
+ let slot = self.define(main).unwrap();
+ self.frame_mut().stack[slot].read = true;
+
+ for section in 0..self.sections.len() {
+ self.init_section(section);
+ let section = &mut self.sections[section];
+ match (section.tokens.get(0), section.tokens.get(1), section.tokens.get(2)) {
+ (Some((Token::Use, _)),
+ Some((Token::Identifier(name), _)), ..) => {
+ let name = name.to_string();
+ self.add_namespace(name);
+ }
+
+ (Some((Token::Identifier(name), _)),
+ Some((Token::ColonColon, _)),
+ Some((Token::Fn, _))) => {
+ let name = name.to_string();
+ self.forward_constant(name);
+ }
+
+ (Some((Token::Blob, _)),
+ Some((Token::Identifier(name), _)), ..) => {
+ let name = name.to_string();
+ self.forward_constant(name);
+ }
+
+ (Some((Token::Identifier(name), _)),
+ Some((Token::Colon, _)), ..) => {
+ let name = name.to_string();
+ self.eat();
+ self.eat();
+ if let Ok(ty) = self.parse_type() {
+ let is_mut = self.peek() == Token::Equal;
+ let var = Variable::new(&name, is_mut, ty);
+ let _ = self.define(var).unwrap();
+ } else {
+ error!(self, format!("Failed to parse type global '{}'.", name));
+ }
+ }
+
+ (Some((Token::Identifier(name), _)),
+ Some((Token::ColonColon, _)), ..) => {
+ let var = Variable::new(name, false, Type::Unknown);
+ let _ = self.define(var).unwrap();
+ }
+
+ (Some((Token::Identifier(name), _)),
+ Some((Token::ColonEqual, _)), ..) => {
+ let var = Variable::new(name, true, Type::Unknown);
+ let _ = self.define(var).unwrap();
+ }
+
+
+ (None, ..) => {}
+
+ (a, b, c) => {
+ section.faulty = true;
+ let msg = format!("Unknown outer token sequence: {:?} {:?} {:?}. Expected 'use', function, blob or variable.", a, b, c);
+ error!(self, msg);
+ }
+ }
+ }
+
self.functions = functions
.to_vec()
.into_iter()
.enumerate()
.map(|(i, (s, f))| (s, (i, f)))
.collect();
- let main = Variable::new("/main/", false, Type::Void);
- let _ = self.define(main);
-
let mut block = Block::new(name, file);
- while self.peek() != Token::EOF {
- self.statement(&mut block);
- expect!(self, Token::Newline | Token::EOF,
- "Expect newline or EOF after expression.");
+ for section in 0..self.sections.len() {
+ self.init_section(section);
+ if self.sections[section].faulty {
+ continue;
+ }
+ while !matches!(self.peek(), Token::EOF | Token::Use) {
+ self.outer_statement(&mut block);
+ expect!(self, Token::Newline | Token::EOF,
+ "Expect newline or EOF after expression.");
+ }
}
- add_op(self, &mut block, Op::Constant(self.nil_value()));
- add_op(self, &mut block, Op::Return);
block.ty = Type::Function(Vec::new(), Box::new(Type::Void));
- if self.unknown.len() != 0 {
- let errors: Vec<_> = self.unknown.iter().map(|(name, (_, line))|
- (ErrorKind::SyntaxError(*line, Token::Identifier(name.clone())),
- *line,
- format!("Usage of undefined value: '{}'.", name,)
- ))
- .collect();
+ if self.names().len() != 0 {
+ let errors: Vec<_> = self.names().iter().filter_map(|(name, kind)|
+ if let Name::Unknown(_, line) = kind {
+ Some((ErrorKind::SyntaxError(*line, Token::Identifier(name.clone())),
+ *line,
+ format!("Usage of undefined value: '{}'.", name,)))
+ } else {
+ None
+ }) .collect();
for (e, l, m) in errors.iter() {
self.error_on_line(e.clone(), *l, Some(m.clone()));
}
}
- for var in self.frames.pop().unwrap().stack.iter().skip(1) {
+ self.init_section(0);
+ let constant = self.find_constant("start");
+ add_op(self, &mut block, Op::Constant(constant));
+ add_op(self, &mut block, Op::Call(0));
+
+ let tmp = self.add_constant(Value::Unknown);
+ add_op(self, &mut block, Op::Constant(tmp));
+ add_op(self, &mut block, Op::Return);
+
+ for var in self.frames_mut().pop().unwrap().stack.iter().skip(1) {
if !(var.read || var.upvalue) {
let e = ErrorKind::SyntaxError(var.line, Token::Identifier(var.name.clone()));
let m = format!("Unused value '{}'.", var.name);
diff --git a/src/lib.rs b/src/lib.rs
index 858c88f..e94eaad 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,11 +4,11 @@ use std::collections::hash_map::Entry;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
use std::rc::Rc;
+use std::hash::{Hash, Hasher};
use owo_colors::OwoColorize;
use error::Error;
-use tokenizer::TokenStream;
use crate::error::ErrorKind;
@@ -16,16 +16,18 @@ pub mod error;
pub mod vm;
mod compiler;
+mod sectionizer;
mod tokenizer;
/// Compiles a file and links the supplied functions as callable external
/// functions. Use this if you want your programs to be able to yield.
-pub fn compile_file(path: &Path,
- print: bool,
- functions: Vec<(String, RustFunction)>
- ) -> Result<vm::VM, Vec<Error>> {
- let tokens = tokenizer::file_to_tokens(path);
- match compiler::Compiler::new(path, tokens).compile("main", path, &functions) {
+pub fn compile_file(
+ path: &Path,
+ print: bool,
+ functions: Vec<(String, RustFunction)>
+) -> Result<vm::VM, Vec<Error>> {
+ let sections = sectionizer::sectionize(path);
+ match compiler::Compiler::new(sections).compile("main", path, &functions) {
Ok(prog) => {
let mut vm = vm::VM::new();
vm.print_blocks = print;
@@ -42,18 +44,19 @@ pub fn compile_file(path: &Path,
/// external functions. If you want your program to be able to yield, use
/// [compile_file].
pub fn run_file(path: &Path, print: bool, functions: Vec<(String, RustFunction)>) -> Result<(), Vec<Error>> {
- run(tokenizer::file_to_tokens(path), path, print, functions)
+ run(path, print, functions)
}
-/// Compile and run a string containing source code. The supplied functions are
-/// linked as callable external functions. This is useful for short test
-/// programs.
-pub fn run_string(s: &str, print: bool, functions: Vec<(String, RustFunction)>) -> Result<(), Vec<Error>> {
- run(tokenizer::string_to_tokens(s), Path::new("builtin"), print, functions)
+pub fn run_string(source: &str, print: bool, functions: Vec<(String, RustFunction)>) -> Result<(), Vec<Error>> {
+ let mut path = std::env::temp_dir();
+ path.push(format!("test_{}.sy", rand::random::<u32>()));
+ std::fs::write(path.clone(), source).expect("Failed to write source to temporary file");
+ run(&path, print, functions)
}
-fn run(tokens: TokenStream, path: &Path, print: bool, functions: Vec<(String, RustFunction)>) -> Result<(), Vec<Error>> {
- match compiler::Compiler::new(path, tokens).compile("main", path, &functions) {
+fn run(path: &Path, print: bool, functions: Vec<(String, RustFunction)>) -> Result<(), Vec<Error>> {
+ let sections = sectionizer::sectionize(path);
+ match compiler::Compiler::new(sections).compile("main", path, &functions) {
Ok(prog) => {
let mut vm = vm::VM::new();
vm.print_blocks = print;
@@ -198,6 +201,43 @@ impl Debug for Value {
}
}
+impl PartialEq<Value> for Value {
+ fn eq(&self, other: &Value) -> bool {
+ match (self, other) {
+ (Value::Float(a), Value::Float(b)) => a == b,
+ (Value::Int(a), Value::Int(b)) => a == b,
+ (Value::Bool(a), Value::Bool(b)) => a == b,
+ (Value::String(a), Value::String(b)) => a == b,
+ (Value::Tuple(a), Value::Tuple(b)) => {
+ a.len() == b.len() && a.iter().zip(b.iter()).all(|(a, b)| a == b)
+ }
+ (Value::Nil, Value::Nil) => true,
+ _ => false,
+ }
+ }
+}
+
+impl Eq for Value {}
+
+impl Hash for Value {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ match self {
+ Value::Float(a) => {
+ // We have to limit the values, because
+ // floats are wierd.
+ assert!(a.is_finite());
+ a.to_bits().hash(state);
+ },
+ Value::Int(a) => a.hash(state),
+ Value::Bool(a) => a.hash(state),
+ Value::String(a) => a.hash(state),
+ Value::Tuple(a) => a.hash(state),
+ Value::Nil => state.write_i8(0),
+ _ => {},
+ };
+ }
+}
+
impl Value {
fn identity(self) -> Self {
match self {
@@ -762,12 +802,6 @@ pub struct Prog {
#[cfg(test)]
mod tests {
- use std::path::Path;
-
- use crate::error::ErrorKind;
-
- use super::{run_file, run_string};
-
#[macro_export]
macro_rules! assert_errs {
($result:expr, [ $( $kind:pat ),* ]) => {
@@ -808,6 +842,7 @@ mod tests {
use owo_colors::OwoColorize;
// Shamelessly stolen from https://github.com/rust-lang/rfcs/issues/2798
+ #[allow(dead_code)]
pub fn panic_after<T, F>(d: Duration, f: F) -> T
where
T: Send + 'static,
@@ -837,544 +872,27 @@ mod tests {
}
#[macro_export]
- macro_rules! test_string {
- ($fn:ident, $prog:literal) => {
+ macro_rules! test_file {
+ ($fn:ident, $path:literal, $print:expr) => {
#[test]
fn $fn() {
- crate::tests::panic_after(std::time::Duration::from_millis(500), || {
- match $crate::run_string($prog, true, Vec::new()) {
- Ok(()) => {},
- Err(errs) => {
- for e in errs.iter() {
- eprintln!("{}", e);
- }
- eprintln!(" {} - failed\n", stringify!($fn));
- unreachable!();
- }
- }
- });
+ let file = std::path::Path::new($path);
+ crate::run_file(&file, $print, Vec::new()).unwrap();
}
};
- ($fn:ident, $prog:literal, $errs:tt) => {
+ ($fn:ident, $path:literal, $print:expr, $errs:tt) => {
#[test]
fn $fn() {
- crate::tests::panic_after(std::time::Duration::from_millis(500), || {
- $crate::assert_errs!($crate::run_string($prog, true, Vec::new()), $errs);
- })
- }
- }
- }
+ use crate::error::ErrorKind;
+ #[allow(unused_imports)]
+ use crate::Type;
- #[macro_export]
- macro_rules! test_file {
- ($fn:ident, $path:literal) => {
- #[test]
- fn $fn() {
- let file = Path::new($path);
- run_file(&file, true, Vec::new()).unwrap();
+ let file = std::path::Path::new($path);
+ let res = crate::run_file(&file, $print, Vec::new());
+ $crate::assert_errs!(res, $errs);
}
};
}
- #[test]
- fn unreachable_token() {
- assert_errs!(run_string("<!>\n", true, Vec::new()), [ErrorKind::Unreachable]);
- }
-
- #[test]
- fn assign_to_constant() {
- assert_errs!(run_string("a :: 2\na = 2", true, Vec::new()), [ErrorKind::SyntaxError(_, _)]);
- }
-
- #[test]
- fn assign_to_constant_upvalue() {
- assert_errs!(run_string("a :: 2\nq :: fn { a = 2 }\nq()\na", true, Vec::new()), [ErrorKind::SyntaxError(_, _)]);
- }
-
- #[test]
- fn undefined_blob() {
- assert_errs!(run_string("a :: B()\n", true, Vec::new()), [ErrorKind::SyntaxError(_, _)]);
- }
-
- #[test]
- fn call_before_link() {
- let prog = "
-a := 1
-f()
-c := 5
-
-f :: fn {
- c <=> 5
-}
-a
- ";
- assert_errs!(run_string(prog, true, Vec::new()), [ErrorKind::InvalidProgram, ErrorKind::TypeError(_, _)]);
- }
-
- #[test]
- fn unused_variable() {
- assert_errs!(run_string("a := 1", true, Vec::new()), [ErrorKind::SyntaxError(1, _)]);
- }
-
- #[test]
- fn unused_upvalue() {
- assert_errs!(run_string("a := 1\nf :: fn { a = 2 }\nf()", true, Vec::new()), [ErrorKind::SyntaxError(1, _)]);
- }
-
- #[test]
- fn unused_function() {
- assert_errs!(run_string("a := 1\nf := fn { a }\n", true, Vec::new()), [ErrorKind::SyntaxError(2, _)]);
- }
-
- macro_rules! test_multiple {
- ($mod:ident, $( $fn:ident : $prog:literal ),+ $( , )? ) => {
- mod $mod {
- $( test_string!($fn, $prog); )+
- }
- }
- }
-
- test_multiple!(
- order_of_operations,
- terms_and_factors: "1 + 1 * 2 <=> 3
- 1 * 2 + 3 <=> 5",
- in_rhs: "5 <=> 1 * 2 + 3",
- parenthesis: "(1 + 2) * 3 <=> 9",
- negation: "-1 <=> 0 - 1
- -1 + 2 <=> 1
- -(1 + 2) <=> -3
- 1 + -1 <=> 0
- 2 * -1 <=> -2",
- );
-
- test_multiple!(
- variables,
- single_variable: "a := 1
- a <=> 1",
- two_variables: "a := 1
- b := 2
- a <=> 1
- b <=> 2",
- stack_ordering: "a := 1
- b := 2
- b <=> 2
- a <=> 1",
- assignment: "a := 1
- b := 2
- a = b
- a <=> 2
- b <=> 2",
- );
-
- test_multiple!(
- if_,
- compare_constants_equality: "if 1 == 2 {
- <!>
- }",
- compare_constants_unequality: "if 1 != 1 {
- <!>
- }",
- compare_variable: "a := 1
- if a == 0 {
- <!>
- }
- if a != 1 {
- <!>
- }",
- else_: "a := 1
- res := 0
- if a == 0 {
- <!>
- } else {
- res = 1
- }
- res <=> 1",
- else_if: "a := 1
- res := 0
- if a == 0 {
- <!>
- } else if a == 1 {
- res = 1
- } else {
- <!>
- }
- res <=> 1",
- );
-
- test_multiple!(
- fun,
- simplest: "f := fn {}
- f()",
- param_1: "f := fn a: int {}
- f(1)",
- return_1: "f := fn -> int {
- ret 1
- }
- f() <=> 1",
- param_and_return: "f := fn a: int -> int {
- ret a * 2
- }
- f(1) <=> 2
- f(5) <=> 10",
- param_2: "add := fn a: int, b: int -> int {
- ret a + b
- }
- add(1, 1) <=> 2
- add(10, 20) <=> 30",
- calls_inside_calls: "one := fn -> int {
- ret 1
- }
- add := fn a: int, b: int -> int {
- ret a + b
- }
- add(one(), one()) <=> 2
- add(add(one(), one()), one()) <=> 3
- add(one(), add(one(), one())) <=> 3",
- passing_functions: "g := fn -> int {
- ret 1
- }
- f := fn inner: fn -> int -> int {
- ret inner()
- }
- f(g) <=> 1",
- passing_functions_mixed: "g := fn a: int -> int {
- ret a * 2
- }
- f := fn inner: fn int -> int, a: int -> int {
- ret inner(a)
- }
- f(g, 2) <=> 4",
- multiple_returns: "f := fn a: int -> int {
- if a == 1 {
- ret 2
- } else {
- ret 3
- }
- }
- f(0) <=> 3
- f(1) <=> 2
- f(2) <=> 3",
- precedence: "f := fn a: int, b: int -> int {
- ret a + b
- }
- 1 + f(2, 3) <=> 6
- 2 * f(2, 3) <=> 10
- f(2, 3) - (2 + 3) <=> 0",
- factorial: "factorial : fn int -> int = fn n: int -> int {
- if n <= 1 {
- ret 1
- }
- ret n * factorial(n - 1)
- }
- factorial(5) <=> 120
- factorial(6) <=> 720
- factorial(12) <=> 479001600",
-
- returning_closures: "
-f : fn -> fn -> int = fn -> fn -> int {
- x : int = 0
- f := fn -> int {
- x = x + 1
- ret x
- }
- f() <=> 1
- ret f
-}
-
-a := f()
-b := f()
-
-a() <=> 2
-a() <=> 3
-
-b() <=> 2
-b() <=> 3
-
-a() <=> 4
-",
- );
-
- test_multiple!(
- blob,
- simple: "blob A {}",
- instantiate: "blob A {}
- a := A()
- a",
- field: "blob A { a: int }",
- field_assign: "blob A { a: int }
- a := A()
- a.a = 2",
- field_get: "blob A { a: int }
- a := A()
- a.a = 2
- a.a <=> 2
- 2 <=> a.a",
- multiple_fields: "blob A {
- a: int
- b: int
- }
- a := A()
- a.a = 2
- a.b = 3
- a.a + a.b <=> 5
- 5 <=> a.a + a.b",
- blob_infer: "
-blob A { }
-a : A = A()
-a
-",
- );
-
- test_multiple!(tuples,
- add: "(1, 2, 3, 4) + (4, 3, 2, 1) <=> (5, 5, 5, 5)",
- sub: "(1, -2, 3, -4) - (4, 3, -2, -1) <=> (-3, 1, 1, -5)",
- mul: "(0, 1, 2) * (2, 3, 4) <=> (0, 3, 8)",
- types: "a: (int, float, int) = (1, 1., 1)\na",
- more_types: "a: (str, bool, int) = (\"abc\", true, 1)\na",
- );
-
- test_file!(scoping, "progs/tests/scoping.sy");
- test_file!(for_, "progs/tests/for.sy");
-
- test_multiple!(
- op_assign,
- add: "a := 1\na += 1\na <=> 2",
- sub: "a := 2\na -= 1\na <=> 1",
- mul: "a := 2\na *= 2\na <=> 4",
- div: "a := 2\na /= 2\na <=> 1",
- cluster: "
-blob A { a: int }
-a := A()
-a.a = 0
-a.a += 1
-a.a <=> 1
-a.a *= 2
-a.a <=> 2
-a.a /= 2
-a.a <=> 1
-a.a -= 1
-a.a <=> 0"
- );
-
- test_multiple!(
- fancy_call,
- not: "f := fn {}\n f!\n",
- one_arg: "f := fn a:int { a <=> 1 }\n f! 1\n",
- two_arg: "f := fn a:int, b:int { b <=> 3 }\n f! 1, 1 + 2\n",
- three_arg: "f := fn a:int, b:int, c:int { c <=> 13 }\n f! 1, 1 + 2, 1 + 4 * 3\n",
- );
-
- test_multiple!(
- newline_regression,
- simple: "a := 1 // blargh \na += 1 // blargh \n a <=> 2 // HARGH",
- expressions: "1 + 1 // blargh \n 2 // blargh \n // HARGH \n",
- );
-
- test_multiple!(
- break_and_continue,
- simple_break: "
-a := 0
-for i := 0, i < 10, i += 1 {
- a = a + 1
- if i == 2 {
- break
- }
-}
-a <=> 3
-",
-
- simple_continue: "
-a := 0
-for i := 0, i < 4, i += 1 {
- if i == 2 {
- continue
- }
- a = a + 1
-}
-a <=> 3
-",
-
- advanced_break: "
-a := 0
-for i := 0, i < 10, i += 1 {
- q := 0
- qq := 0
- qqq := 0
- qqqq := 0
-
- a = a + 1
- if i == 2 {
- break
- }
-}
-a <=> 3
-",
-
- advanced_continue: "
-a := 0
-for i := 0, i < 4, i += 1 {
- q := 0
- qq := 0
- qqq := 0
- qqqq := 0
-
- if i == 2 {
- continue
- }
- a = a + 1
-}
-a <=> 3
-",
- );
-
- test_multiple!(
- read_constants,
- simple: "
-a :: 1
-a <=> 1
-b := 2
-{
- a <=> 1
- b <=> 2
-}",
- );
-
- test_multiple!(
- assignment_op_regression,
- simple_add: "
-a := 0
-b := 99999
-a += 1
-a <=> 1
-b <=> 99999
-",
-
- simple_sub: "
-a := 0
-b := 99999
-a -= 1
-a <=> -1
-b <=> 99999
-",
-
- strange: "
-a := 0
-{
- b := 99999
- {
- a := 99999
- }
- a -= 1
-}
-a <=> -1
-",
- );
-
- test_multiple!(
- declaration_order,
- blob_simple: "
-a := A()
-a
-
-blob A {
- a: int
-}
-",
-
- blob_complex: "
-a := A()
-b := B()
-c := C()
-b2 := B()
-
-a
-b
-c
-b2
-
-blob A {
- c: C
-}
-blob C { }
-blob B { }
-",
-
- blob_infer: "
-blob A { }
-
-a : A = A()
-a
-",
-
-
- constant_function: "
-a()
-a :: fn {}
-",
-
- constant_function_complex: "
-h :: fn -> int {
- ret 3
-}
-
-a() <=> 3
-
-k :: fn -> int {
- ret h()
-}
-
-a :: fn -> int {
- ret q()
-}
-
-q :: fn -> int {
- ret k()
-}
-",
-
- constant_function_closure: "
-q := 1
-
-f :: fn -> int {
- q += 1
- ret q
-}
-
-f() <=> 2
-f() <=> 3
-f() <=> 4
-f() <=> 5
-",
-
- constants_in_inner_functions: "
-q : int = 0
-
-f :: fn -> fn -> {
- g :: fn {
- q += 1
- }
- ret g
-}
-
-g := f()
-g()
-q <=> 1
-g()
-q <=> 2
-g()
-q <=> 3
-",
-
- );
-
- test_string!(conflict_markers, "
-<<<<<<< HEAD
-print extern_test(4.0)
-=======
-print extern_test(5.0)
->>>>>>> 2
-",
- [ErrorKind::SyntaxError(_, _), ErrorKind::GitConflictError(2, 6)]
- );
-
+ sylt_macro::find_tests!();
}
diff --git a/src/sectionizer.rs b/src/sectionizer.rs
new file mode 100644
index 0000000..8c5e238
--- /dev/null
+++ b/src/sectionizer.rs
@@ -0,0 +1,105 @@
+use crate::tokenizer::{PlacedToken, Token, file_to_tokens};
+
+use std::collections::HashSet;
+use std::path::{Path, PathBuf};
+
+pub struct Section {
+ pub tokens: Vec<PlacedToken>,
+ pub path: PathBuf,
+ pub faulty: bool,
+}
+
+impl Section {
+ fn new(path: PathBuf, tokens: &[PlacedToken]) -> Self {
+ Self {
+ tokens: Vec::from(tokens),
+ path,
+ faulty: false,
+ }
+ }
+}
+
+pub fn sectionize(path: &Path) -> Vec<Section> {
+ let mut read_files = HashSet::new();
+ read_files.insert(path.to_path_buf());
+ let tokens = file_to_tokens(path);
+ let mut all_tokens = vec![(path.to_path_buf(), tokens)];
+ let mut sections = Vec::new();
+
+ let mut i = 0;
+ while i < all_tokens.len() {
+ let (path, tokens) = all_tokens[i].clone();
+ i += 1;
+ let mut last = 0;
+ let mut curr = 0;
+ while curr < tokens.len() {
+ if match (tokens.get(curr + 0), tokens.get(curr + 1), tokens.get(curr + 2)) {
+ (Some((Token::Newline, _)), ..)
+ => {
+ if curr == last {
+ last += 1;
+ }
+ false
+ },
+
+ (Some((Token::Use, _)),
+ Some((Token::Identifier(use_file), _)),
+ Some((Token::Newline, _))) => {
+ let use_file: PathBuf = format!("{}.sy", use_file).into();
+ if !read_files.contains(&use_file) {
+ let use_file_tokens = file_to_tokens(&use_file);
+ read_files.insert(use_file.clone());
+ all_tokens.push((use_file, use_file_tokens))
+ }
+ true
+ },
+
+ (Some((Token::LeftBrace, _)), ..)
+ => {
+ let mut blocks = 1;
+ loop {
+ curr += 1;
+ match tokens.get(curr) {
+ Some((Token::LeftBrace, _)) => {
+ blocks += 1;
+ }
+
+ Some((Token::RightBrace, _)) => {
+ curr += 1;
+ blocks -= 1;
+ if blocks <= 0 {
+ break;
+ }
+ }
+
+ None => {
+ break;
+ }
+
+ _ => {}
+ }
+ }
+ false
+ },
+
+ (Some((Token::Identifier(_), _)),
+ Some((Token::ColonColon, _)),
+ Some(_))
+ => true,
+
+ (Some((Token::Identifier(_), _)),
+ Some((Token::ColonEqual, _)),
+ Some(_))
+ => true,
+
+ _ => false,
+ } {
+ sections.push(Section::new(path.clone(), &tokens[last..curr]));
+ last = curr;
+ }
+ curr += 1;
+ }
+ sections.push(Section::new(path.clone(), &tokens[last..curr]));
+ }
+ sections
+}
diff --git a/src/tokenizer.rs b/src/tokenizer.rs
index 2c8e5e8..3b61e5f 100644
--- a/src/tokenizer.rs
+++ b/src/tokenizer.rs
@@ -127,6 +127,9 @@ pub enum Token {
#[token("\n")]
Newline,
+ #[token("use")]
+ Use,
+
#[token("<<<<<<<")]
GitConflictBegin,
#[token(">>>>>>>")]
diff --git a/src/vm.rs b/src/vm.rs
index f5e5c58..7cfc8d3 100644
--- a/src/vm.rs
+++ b/src/vm.rs
@@ -6,7 +6,7 @@ use std::rc::Rc;
use owo_colors::OwoColorize;
-use crate::{Block, Op, Prog, UpValue, Value, op};
+use crate::{Block, BlockLinkState, Op, Prog, UpValue, Value, op};
use crate::error::{Error, ErrorKind};
use crate::RustFunction;
use crate::Type;
@@ -236,22 +236,26 @@ impl VM {
let offset = self.frame().stack_offset;
let constant = self.constant(value).clone();
let value = match constant {
- Value::Function(_, block) => {
- let mut ups = Vec::new();
- for (slot, is_up, _) in block.borrow().upvalues.iter() {
- let up = if *is_up {
- if let Value::Function(local_ups, _) = &self.stack[offset] {
- Rc::clone(&local_ups[*slot])
+ Value::Function(ups, block) => {
+ if matches!(block.borrow().linking, BlockLinkState::Linked) {
+ Value::Function(ups.clone(), block)
+ } else {
+ let mut ups = Vec::new();
+ for (slot, is_up, _) in block.borrow().upvalues.iter() {
+ let up = if *is_up {
+ if let Value::Function(local_ups, _) = &self.stack[offset] {
+ Rc::clone(&local_ups[*slot])
+ } else {
+ unreachable!()
+ }
} else {
- unreachable!()
- }
- } else {
- let slot = self.frame().stack_offset + slot;
- Rc::clone(self.find_upvalue(slot))
- };
- ups.push(up);
+ let slot = self.frame().stack_offset + slot;
+ Rc::clone(self.find_upvalue(slot))
+ };
+ ups.push(up);
+ }
+ Value::Function(ups, block)
}
- Value::Function(ups, block)
},
value => value,
};
@@ -553,36 +557,37 @@ impl VM {
match self.constant(value).clone() {
Value::Function(_, block) => {
self.push(Value::Function(Vec::new(), block.clone()));
+ if !matches!(block.borrow().linking, BlockLinkState::Linked) {
+ if block.borrow().needs_linking() {
+ error!(self,
+ ErrorKind::InvalidProgram,
+ format!("Calling function '{}' before all captured variables are declared.",
+ block.borrow().name));
+ }
- if block.borrow().needs_linking() {
- error!(self,
- ErrorKind::InvalidProgram,
- format!("Calling function '{}' before all captured variables are declared.",
- block.borrow().name));
- }
-
- let mut types = Vec::new();
- for (slot, is_up, ty) in block.borrow().upvalues.iter() {
- if *is_up {
- types.push(ty.clone());
- } else {
- types.push(Type::from(&self.stack[*slot]));
+ let mut types = Vec::new();
+ for (slot, is_up, ty) in block.borrow().upvalues.iter() {
+ if *is_up {
+ types.push(ty.clone());
+ } else {
+ types.push(Type::from(&self.stack[*slot]));
+ }
}
- }
- let mut block_mut = block.borrow_mut();
- for (i, (_, is_up, ty)) in block_mut.upvalues.iter_mut().enumerate() {
- if *is_up { continue; }
+ let mut block_mut = block.borrow_mut();
+ for (i, (_, is_up, ty)) in block_mut.upvalues.iter_mut().enumerate() {
+ if *is_up { continue; }
- let suggestion = &types[i];
- if matches!(ty, Type::Unknown) {
- *ty = suggestion.clone();
- } else {
- if ty != suggestion {
- error!(self, ErrorKind::CannotInfer(ty.clone(), suggestion.clone()));
+ let suggestion = &types[i];
+ if matches!(ty, Type::Unknown) {
+ *ty = suggestion.clone();
+ } else {
+ if ty != suggestion {
+ error!(self, ErrorKind::CannotInfer(ty.clone(), suggestion.clone()));
+ }
}
- }
- };
+ };
+ }
},
value => {
self.push(value.clone());
@@ -652,6 +657,7 @@ impl VM {
let inner = self.frame().block.borrow();
let ret = inner.ret();
if Type::from(&a) != *ret {
+
error!(self, ErrorKind::TypeMismatch(a.into(), ret.clone()),
"Value does not match return type.");
}
@@ -679,6 +685,29 @@ impl VM {
match self.constant(slot).clone() {
Value::Function(_, block) => {
block.borrow_mut().link();
+
+ let mut types = Vec::new();
+ for (slot, is_up, ty) in block.borrow().upvalues.iter() {
+ if *is_up {
+ types.push(ty.clone());
+ } else {
+ types.push(Type::from(&self.stack[*slot]));
+ }
+ }
+
+ let mut block_mut = block.borrow_mut();
+ for (i, (_, is_up, ty)) in block_mut.upvalues.iter_mut().enumerate() {
+ if *is_up { continue; }
+
+ let suggestion = &types[i];
+ if matches!(ty, Type::Unknown) {
+ *ty = suggestion.clone();
+ } else {
+ if ty != suggestion {
+ error!(self, ErrorKind::CannotInfer(ty.clone(), suggestion.clone()));
+ }
+ }
+ }
}
value => {
error!(self,
@@ -824,29 +853,3 @@ impl VM {
}
}
}
-
-#[cfg(test)]
-mod tests {
- mod typing {
- use crate::error::ErrorKind;
- use crate::{test_string, Type};
-
- test_string!(uncallable_type, "
- f := fn i: int {
- i()
- }
- f",
- [ErrorKind::InvalidProgram]);
-
- test_string!(invalid_assign, "a := 1\na = 0.1\na",
- [ErrorKind::TypeMismatch(Type::Int, Type::Float)]);
-
- test_string!(wrong_params, "
- f : fn -> int = fn a: int -> int {}\nf",
- [ErrorKind::TypeMismatch(_, _), ErrorKind::TypeMismatch(Type::Void, Type::Int)]);
-
- test_string!(wrong_ret, "
- f : fn -> int = fn {}\nf",
- [ErrorKind::TypeMismatch(_, _)]);
- }
-}
diff --git a/sylt_macro/Cargo.lock b/sylt_macro/Cargo.lock
new file mode 100644
index 0000000..137e02a
--- /dev/null
+++ b/sylt_macro/Cargo.lock
@@ -0,0 +1,45 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "proc-macro2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "sylt_macro"
+version = "0.1.0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.62"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "123a78a3596b24fee53a6464ce52d8ecbf62241e6294c7e7fe12086cd161f512"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
diff --git a/sylt_macro/Cargo.toml b/sylt_macro/Cargo.toml
index 9ac045e..6d3c75f 100644
--- a/sylt_macro/Cargo.toml
+++ b/sylt_macro/Cargo.toml
@@ -10,5 +10,6 @@ proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-syn = { version = "1.0", features=["full"] }
-quote = "1.0"
+syn = { version = "1", features=["full"] }
+proc-macro2 = "1"
+quote = "1"
diff --git a/sylt_macro/src/lib.rs b/sylt_macro/src/lib.rs
index 3b8b37c..95b79c6 100644
--- a/sylt_macro/src/lib.rs
+++ b/sylt_macro/src/lib.rs
@@ -1,5 +1,6 @@
-use proc_macro::TokenStream;
-use quote::quote;
+use std::path::Path;
+
+use quote::{format_ident, quote};
use syn::{Expr, Pat, Token, parse::{Parse, ParseStream, Result}, parse_macro_input};
struct ExternBlock {
@@ -43,7 +44,7 @@ impl Parse for ExternFunction {
}
#[proc_macro]
-pub fn extern_function(tokens: TokenStream) -> TokenStream {
+pub fn extern_function(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let parsed: ExternFunction = parse_macro_input!(tokens);
let function = parsed.function;
@@ -89,7 +90,7 @@ pub fn extern_function(tokens: TokenStream) -> TokenStream {
}
}
};
- TokenStream::from(tokens)
+ proc_macro::TokenStream::from(tokens)
}
struct LinkRename {
@@ -137,9 +138,8 @@ impl Parse for Links {
}
}
-
#[proc_macro]
-pub fn link(tokens: TokenStream) -> TokenStream {
+pub fn link(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
let links: Links = parse_macro_input!(tokens);
let links: Vec<_> = links.links.iter().map(|link| {
@@ -157,5 +157,95 @@ pub fn link(tokens: TokenStream) -> TokenStream {
let tokens = quote! {
vec![ #(#links),* ]
};
- TokenStream::from(tokens)
+ proc_macro::TokenStream::from(tokens)
+}
+
+struct TestSettings {
+ errors: Option<String>,
+ print: bool,
+}
+
+impl Default for TestSettings {
+ fn default() -> Self {
+ Self {
+ errors: None,
+ print: true,
+ }
+ }
+}
+
+fn parse_test_settings(contents: String) -> TestSettings {
+ let mut settings = TestSettings::default();
+
+ for line in contents.split("\n") {
+ if line.starts_with("// errors: ") {
+ settings.errors = Some(line.strip_prefix("// errors: ").unwrap().to_string());
+ } else if line.starts_with("// flags: ") {
+ for flag in line.split(" ").skip(2) {
+ match flag {
+ "no_print" => {
+ settings.print = false;
+ }
+ _ => {
+ panic!("Unknown test flag '{}'", flag);
+ }
+ }
+ }
+ }
+ }
+
+ settings
+}
+
+fn find_test_paths(directory: &Path) -> proc_macro2::TokenStream {
+ let mut tests = quote! {};
+
+ for entry in std::fs::read_dir(directory).unwrap() {
+ let path = entry.unwrap().path();
+ let file_name = path.file_name().unwrap().to_str().unwrap();
+
+ if file_name.starts_with("_") {
+ continue;
+ }
+
+ if path.is_dir() {
+ tests.extend(find_test_paths(&path));
+ } else {
+ assert!(!path.to_str().unwrap().contains(","), "You should be ashamed.");
+
+ let path_string = path.to_str().unwrap();
+ let test_name = format_ident!("file_{}", file_name.replace(".sy", ""));
+
+ let settings = parse_test_settings(std::fs::read_to_string(path.clone()).unwrap());
+ let print = settings.print;
+ let tokens = if let Some(wanted_errs) = settings.errors {
+ let wanted_errs: proc_macro2::TokenStream = wanted_errs.parse().unwrap();
+ quote! {
+ test_file!(#test_name, #path_string, #print, #wanted_errs);
+ }
+ } else {
+ quote! {
+ test_file!(#test_name, #path_string, #print);
+ }
+ };
+
+ tests.extend(tokens);
+ }
+ }
+
+ let directory = directory.file_name().unwrap().to_str().unwrap().replace("/", "");
+ let directory = format_ident!("{}", directory);
+ quote! {
+ mod #directory {
+ #tests
+ }
+ }
+}
+
+#[proc_macro]
+pub fn find_tests(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream {
+ assert!(tokens.is_empty());
+
+ let tokens = find_test_paths(Path::new("progs/"));
+ proc_macro::TokenStream::from(tokens)
}