83 Commits

Author SHA1 Message Date
greg
7282e9f05e Fix blocks 2020-03-24 03:00:10 -07:00
greg
91e7dcb6d0 Lambdas 2020-03-15 22:10:38 -07:00
greg
08ce718fb6 While expression 2020-03-15 16:03:22 -07:00
greg
5da61658ec Whitespace fixes 2020-03-15 03:45:52 -07:00
greg
3ce73a4b55 Fix for expression 2020-03-15 02:00:39 -07:00
greg
cde52efcf2 Fix??? block 2020-03-15 01:43:58 -07:00
greg
e0a4ea1675 Use tag in block 2020-03-15 00:57:22 -07:00
greg
ed68b57736 Hack new-style parsing into tests
Messy - clean this up later!
2020-03-14 02:38:20 -07:00
greg
df99ff1e06 Specialize to strings 2020-03-14 01:54:35 -07:00
greg
7ffd6b2bd7 Record pattern 2020-03-13 23:04:16 -07:00
greg
67b98c47fc tuple struct 2020-03-13 22:52:20 -07:00
greg
ad0434007b More type name stuff - compiling! 2020-03-12 04:02:26 -07:00
greg
78b86b3531 WIP stuff about type names 2020-03-11 04:19:24 -07:00
greg
efda75860c For expr + whitespace 2020-03-10 01:45:15 -07:00
greg
b6a60a05ba Starting for expr 2020-03-09 04:13:45 -07:00
greg
744ba2fc74 if expression 2020-03-09 03:45:03 -07:00
greg
dd9a1b8a2e Whole lot of added code around if exprs 2020-03-08 05:11:00 -07:00
greg
33d4b28786 More work 2020-03-07 04:54:38 -08:00
greg
9e898d86b3 Start to parse type annos 2020-03-07 04:31:55 -08:00
greg
d034a99ac1 Some wording changes; added ws combinator 2020-03-07 04:24:31 -08:00
greg
31da29c324 Fix 0x, 0b literals 2020-03-07 03:54:49 -08:00
greg
ce848906c9 More work 2020-03-07 03:26:32 -08:00
greg
8f0dc6cab8 Clean up features 2020-03-07 03:13:51 -08:00
greg
34fd29ebca Try using error formatting 2020-03-07 03:12:24 -08:00
greg
b1ffcd709b Use VerboseError 2020-02-14 16:43:28 -08:00
greg
84455d11d5 Add TODO note 2020-02-14 16:43:21 -08:00
greg
16559d2e55 Type alias 2020-02-14 10:20:27 -08:00
greg
43cad55735 Expand parser more 2020-02-14 02:55:45 -08:00
greg
a6d065864c A lot of stuff around identifiers 2020-02-14 01:50:24 -08:00
greg
e2fc454c82 Make a bunch of things more concise 2020-02-13 03:11:46 -08:00
greg
54649246b0 Bugfix 2020-02-13 03:01:48 -08:00
greg
6759640389 Fix whitespace 2020-02-13 02:57:30 -08:00
greg
c6b0f7d7d1 Pratt parsing 2020-02-13 02:48:38 -08:00
greg
b7f7ba57d7 Added compiling nom-based parser beginnings 2020-02-13 00:38:10 -08:00
greg
a0955e07dc Fix attribute 2020-02-12 22:14:21 -08:00
greg
afcb10bb72 Add random idea 2019-11-18 03:11:00 -08:00
greg
8de625e540 Got rid of symbol table from eval 2019-11-10 03:28:31 -08:00
greg
a2bd9a3985 Remove symbol table from evaluator 2019-11-09 19:52:05 -08:00
greg
e4a1a23f4d Moved sym lookup logic from eval to ast reducer 2019-11-09 19:49:02 -08:00
greg
2cd325ba12 Add plan of attack notes 2019-11-08 18:56:15 -08:00
greg
8218007f1c Commit this temporary fix 2019-11-08 18:53:38 -08:00
greg
040ab11873 Move reduction of values into separate method 2019-11-07 03:28:18 -08:00
greg
b967fa1911 to_repl() doesn't need symbol table handle 2019-11-07 02:42:17 -08:00
greg
4c718ed977 Add TODO for symbol resolver 2019-11-06 18:41:37 -08:00
greg
d20acf7166 Add tokenization for string literal prefixes 2019-11-05 02:22:11 -08:00
greg
efc8497235 Rearchitect parser
To ensure that the prelude gets parsed with the same ItemId context as
normal REPL input
2019-10-25 01:49:15 -07:00
greg
d824b8d6ef Idea for pattern matching 2019-10-24 03:09:17 -07:00
greg
4a1987b5a2 Test for modules in symbol table 2019-10-24 03:02:52 -07:00
greg
c96644ddce Modules in symbol table 2019-10-24 02:13:07 -07:00
greg
cc0ac83709 Refactor a lot of symbol table in prep for modules 2019-10-24 01:34:13 -07:00
greg
d6019e6f9a Improve REPL help message
Show help strings for children of a directive
2019-10-23 21:41:25 -07:00
greg
3344f6827d Clear out some compiler warnings 2019-10-23 16:07:10 -07:00
greg
b38c4b3298 SymbolTable passing, fix test for duplicate line 2019-10-23 14:47:18 -07:00
greg
a2f30b6136 Refactored symbol_table test 2019-10-23 14:47:18 -07:00
greg
11a9a60a34 Rejiggering some things with the SourceMap pointer in Parser 2019-10-23 14:47:18 -07:00
greg
5bb1a245c4 Have Parser accept SourceMap reference 2019-10-23 14:47:18 -07:00
greg
1ffe61cf5f Partway there in terms of implementing source map lookup 2019-10-23 14:47:18 -07:00
greg
7495f30e16 Pass SourceMapHandle to SymbolTable 2019-10-23 14:47:18 -07:00
greg
82520aa28d Start to add source map insertions 2019-10-23 14:47:18 -07:00
greg
129d9ec673 A bunch of infrastructure for keeping track of AST node locations
Plus a failing test to illustrate the reason we care
2019-10-23 14:47:18 -07:00
greg
7825ef1eb9 Partial module work 2019-10-23 14:47:18 -07:00
greg
f3ecdc61cb Remove old TODO 2019-10-23 02:22:10 -07:00
greg
bf59e6cc63 Just import all of AST in parse tests 2019-10-22 03:15:41 -07:00
greg
c560c29b2d Start to add module syntax 2019-10-22 03:15:14 -07:00
greg
4dcd9d0198 Some more parse trace improvements 2019-10-22 02:11:49 -07:00
greg
7ac63160c5 Remove extraneous debug print 2019-10-21 19:19:48 -07:00
greg
8656992945 Made parse trace output a bit nicer
Used ... instead of whitespace, removed extraneous "Production"
2019-10-21 19:18:47 -07:00
greg
bb87a87848 Remove this TODO; default args are parsed 2019-10-21 10:53:17 -07:00
greg
2f467702e3 Use common scope resolver
So that if you import something at the repl, it stays imported
2019-10-21 04:19:26 -07:00
greg
5ac5425fac Use symbol table handle in resolver 2019-10-21 04:17:30 -07:00
greg
944916d6af Alias for symbol table handle type 2019-10-21 04:09:43 -07:00
greg
3906210db8 Fix prelude 2019-10-21 03:26:38 -07:00
greg
f7357d4498 Add explicit panic for prelude errors
Maybe I want to handle this better in the future, but for now just panic
if the prelude is bad for some reason.
2019-10-21 03:25:45 -07:00
greg
1493d12a22 Reduce unused imports 2019-10-21 03:02:11 -07:00
greg
016d8fc900 Fixed tests
but I think importing is still not working properly
2019-10-21 02:56:21 -07:00
greg
86dc5eca02 Get rid of symbol segment kind
I don't think I need this after all
2019-10-18 18:24:57 -07:00
greg
e75958c2a2 Currently broken import all commit 2019-10-18 09:55:26 -07:00
greg
7a56b6dfc0 Add some more methods around this 2019-10-18 09:54:56 -07:00
greg
f9633ebe55 Add (broken) import all test 2019-10-18 09:53:44 -07:00
greg
854740a63f SymbolTrie 2019-10-17 03:15:39 -07:00
greg
ca10481d7c Symbol table test - multiple values 2019-10-16 22:46:58 -07:00
greg
26fa4a29ec Put type names into symbol table 2019-10-16 20:22:40 -07:00
greg
97b59d7e70 Symbol table tests to separate file 2019-10-16 19:51:43 -07:00
29 changed files with 1419 additions and 442 deletions

63
Cargo.lock generated
View File

@@ -171,6 +171,11 @@ dependencies = [
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "failure"
version = "0.1.5"
@@ -277,6 +282,18 @@ name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "lexical-core"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
"rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
"static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "libc"
version = "0.2.62"
@@ -346,6 +363,11 @@ dependencies = [
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nibble_vec"
version = "0.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "nix"
version = "0.14.1"
@@ -372,6 +394,16 @@ dependencies = [
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "nom"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "num"
version = "0.1.42"
@@ -517,6 +549,15 @@ dependencies = [
"proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "radix_trie"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"endian-type 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
"nibble_vec 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "rand"
version = "0.4.6"
@@ -710,6 +751,14 @@ name = "rustc-serialize"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ryu"
version = "1.0.0"
@@ -735,6 +784,8 @@ dependencies = [
"itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"radix_trie 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
"schala-lang-codegen 0.1.0",
"schala-repl 0.1.0",
"stopwatch 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -826,6 +877,11 @@ name = "smallvec"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "static_assertions"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "stopwatch"
version = "0.0.7"
@@ -1005,6 +1061,7 @@ dependencies = [
"checksum dirs 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901"
"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
"checksum ena 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f56c93cc076508c549d9bb747f79aa9b4eb098be7b8cad8830c3137ef52d1e00"
"checksum endian-type 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
"checksum flate2 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "e6234dd4468ae5d1e2dbb06fe2b058696fdc50a339c68a393aefbf00bc81e423"
@@ -1019,6 +1076,7 @@ dependencies = [
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum lexical-core 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2304bccb228c4b020f3a4835d247df0a02a7c4686098d4167762cfbbe4c5cb14"
"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba"
"checksum linefeed 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "28715d08e35c6c074f9ae6b2e6a2420bac75d050c66ecd669d7d5b98e2caa036"
"checksum llvm-sys 70.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e8e9fc46a848cf4170694144102ec07f6eada790d8b3d7e92ffa9cc7416fc869"
@@ -1027,9 +1085,11 @@ dependencies = [
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum miniz-sys 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9e3ae51cea1576ceba0dde3d484d30e6e5b86dee0b2d412fe3a16a15c98202"
"checksum mortal 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d96ca97d37cb87ea3d658e71c8c2e2f2d420eca5deec047c5a283aa810a900ff"
"checksum nibble_vec 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c8d77f3db4bce033f4d04db08079b2ef1c3d02b44e86f25d08886fafa7756ffa"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
"checksum nom 5.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c433f4d505fe6ce7ff78523d2fa13a0b9f2690e181fc26168bcbe5ccc5d14e07"
"checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e"
"checksum num-bigint 0.1.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"
"checksum num-complex 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "b288631d7878aaf59442cffd36910ea604ecd7745c36054328595114001c9656"
@@ -1046,6 +1106,7 @@ dependencies = [
"checksum proc-macro2 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "afdc77cc74ec70ed262262942ebb7dac3d479e9e5cfa2da1841c0806f6cdabcc"
"checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum radix_trie 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "deafbff7bda0a58975ce92723aa18be10eae2e9271f3c3f48de52d131d9f248c"
"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
@@ -1068,6 +1129,7 @@ dependencies = [
"checksum rust-argon2 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4ca4eaef519b494d1f2848fc602d18816fed808a981aedf4f1f00ceb7c9d32cf"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
@@ -1077,6 +1139,7 @@ dependencies = [
"checksum siphasher 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
"checksum smallstr 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aa65bb4d5b2bbc90d36af64e29802f788aa614783fa1d0df011800ddcec6e8e"
"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
"checksum stopwatch 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3d04b5ebc78da44d3a456319d8bc2783e7d8cc7ccbb5cb4dc3f54afbd93bf728"
"checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5"
"checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf"

25
TODO.md
View File

@@ -1,5 +1,27 @@
# Plan of attack
-ONLY two types of statement, Expressoin and Declaration
-modules and imports are just types of Declarables
1. modify visitor so it can handle scopes
-this is needed both to handle import scope correctly
-and also to support making FQSNs aware of function parameters
2. Once FQSNs are aware of function parameters, most of the Rc<String> things in eval.rs can go away
# TODO items
-use 'let' sigil in patterns for variables :
```
q is MyStruct(let a, Chrono::Trigga) then {
}
```
-idea: what if there was something like React jsx syntas built in? i.e. a way to automatically transform some kind of markup
into a function call, cf. `<h1 prop="arg">` -> h1(prop=arg)
## General code cleanup
- I think I can restructure the parser to get rid of most instances of expect!, at least at the beginning of a rule
DONE -experiment with storing metadata via ItemIds on AST nodes (cf. https://rust-lang.github.io/rustc-guide/hir.html, https://github.com/rust-lang/rust/blob/master/src/librustc/hir/mod.rs )
@@ -11,6 +33,9 @@ DONE -experiment with storing metadata via ItemIds on AST nodes (cf. https://rus
-look at https://gitlab.haskell.org/ghc/ghc/wikis/pattern-synonyms
2) the non-value-returning, default one like in rustc (cf. https://github.com/rust-unofficial/patterns/blob/master/patterns/visitor.md)
-parser error - should report subset of AST parsed *so far*
- what if you used python 'def' syntax to define a function? what error message makes sense here?
## Reduction
- make a good type for actual language builtins to avoid string comparisons

View File

@@ -32,6 +32,7 @@ impl Fold for RecursiveDescentFn {
if self.parse_level != 0 {
self.parse_level -= 1;
}
result.map_err(|mut parse_error: ParseError| {
parse_error.production_name = Some(stringify!(#ident).to_string());
parse_error

View File

@@ -14,6 +14,8 @@ ena = "0.11.0"
stopwatch = "0.0.7"
derivative = "1.0.3"
colored = "1.8"
radix_trie = "0.1.5"
nom = "5.1.0"
schala-lang-codegen = { path = "../codegen" }
schala-repl = { path = "../../schala-repl" }

View File

@@ -17,7 +17,7 @@ pub struct ItemId {
}
impl ItemId {
fn new(n: u32) -> ItemId {
pub fn new(n: u32) -> ItemId {
ItemId { idx: n }
}
}
@@ -65,6 +65,7 @@ pub enum StatementKind {
Expression(Expression),
Declaration(Declaration),
Import(ImportSpecifier),
Module(ModuleSpecifier),
}
pub type Block = Vec<Statement>;
@@ -297,3 +298,10 @@ pub enum ImportedNames {
List(Vec<Rc<String>>)
}
#[derive(Debug, PartialEq, Clone)]
pub struct ModuleSpecifier {
pub name: Rc<String>,
pub contents: Vec<Statement>,
}

View File

@@ -69,8 +69,8 @@ impl BinOp {
Some(binop_precedences(s))
}
pub fn get_precedence(&self) -> i32 {
binop_precedences(&self.sigil)
pub fn precedence(&self) -> i32 {
binop_precedences(self.sigil.as_str())
}
}

View File

@@ -29,6 +29,7 @@ pub trait ASTVisitor: Sized {
fn invocation_argument(&mut self, _arg: &InvocationArgument) {}
fn formal_param(&mut self, _param: &FormalParam) {}
fn import(&mut self, _import: &ImportSpecifier) {}
fn module(&mut self, _module: &ModuleSpecifier) {}
fn qualified_name(&mut self, _name: &QualifiedName) {}
fn nat_literal(&mut self, _n: u64) {}
fn float_literal(&mut self, _f: f64) {}

View File

@@ -22,7 +22,7 @@ impl ASTVisitor for Tester {
#[test]
fn foo() {
let mut tester = Tester { count: 0, float_count: 0 };
let ast = quick_ast(r#"
let (ast, _) = quick_ast(r#"
import gragh
let a = 20 + 84

View File

@@ -28,6 +28,10 @@ fn statement<V: ASTVisitor>(v: &mut V, statement: &Statement) {
declaration(v, decl);
},
Import(ref import_spec) => v.import(import_spec),
Module(ref module_spec) => {
v.module(module_spec);
walk_block(v, &module_spec.contents);
}
}
}

View File

@@ -1,27 +1,25 @@
use std::cell::RefCell;
use std::rc::Rc;
use std::fmt::Write;
use std::io;
use itertools::Itertools;
use crate::schala::SymbolTableHandle;
use crate::util::ScopeStack;
use crate::reduced_ast::{BoundVars, ReducedAST, Stmt, Expr, Lit, Func, Alternative, Subpattern};
use crate::symbol_table::{SymbolSpec, Symbol, SymbolTable, ScopeSegment, ScopeSegmentKind, FullyQualifiedSymbolName};
use crate::symbol_table::{SymbolSpec, Symbol, SymbolTable, FullyQualifiedSymbolName};
use crate::builtin::Builtin;
mod test;
pub struct State<'a> {
values: ScopeStack<'a, Rc<String>, ValueEntry>,
symbol_table_handle: Rc<RefCell<SymbolTable>>,
}
impl<'a> State<'a> {
pub fn new(symbol_table_handle: Rc<RefCell<SymbolTable>>) -> State<'a> {
pub fn new() -> State<'a> {
let values = ScopeStack::new(Some(format!("global")));
State { values, symbol_table_handle }
State { values }
}
pub fn debug_print(&self) -> String {
@@ -31,7 +29,6 @@ impl<'a> State<'a> {
fn new_frame(&'a self, items: &'a Vec<Node>, bound_vars: &BoundVars) -> State<'a> {
let mut inner_state = State {
values: self.values.new_scope(None),
symbol_table_handle: self.symbol_table_handle.clone(),
};
for (bound_var, val) in bound_vars.iter().zip(items.iter()) {
if let Some(bv) = bound_var.as_ref() {
@@ -70,12 +67,12 @@ fn paren_wrapped_vec(terms: impl Iterator<Item=String>) -> String {
impl Node {
fn to_repl(&self, symbol_table: &SymbolTable) -> String {
fn to_repl(&self) -> String {
match self {
Node::Expr(e) => e.to_repl(symbol_table),
Node::Expr(e) => e.to_repl(),
Node::PrimObject { name, items, .. } if items.len() == 0 => format!("{}", name),
Node::PrimObject { name, items, .. } => format!("{}{}", name, paren_wrapped_vec(items.iter().map(|x| x.to_repl(symbol_table)))),
Node::PrimTuple { items } => format!("{}", paren_wrapped_vec(items.iter().map(|x| x.to_repl(symbol_table)))),
Node::PrimObject { name, items, .. } => format!("{}{}", name, paren_wrapped_vec(items.iter().map(|x| x.to_repl()))),
Node::PrimTuple { items } => format!("{}", paren_wrapped_vec(items.iter().map(|x| x.to_repl()))),
}
}
fn is_true(&self) -> bool {
@@ -100,12 +97,10 @@ impl Expr {
fn to_node(self) -> Node {
Node::Expr(self)
}
fn to_repl(&self, symbol_table: &SymbolTable) -> String {
fn to_repl(&self) -> String {
use self::Lit::*;
use self::Func::*;
let _ = symbol_table;
match self {
Expr::Lit(ref l) => match l {
Nat(n) => format!("{}", n),
@@ -122,7 +117,7 @@ impl Expr {
Expr::Constructor { type_name, arity, .. } => {
format!("<constructor for `{}` arity {}>", type_name, arity)
},
Expr::Tuple(exprs) => paren_wrapped_vec(exprs.iter().map(|x| x.to_repl(symbol_table))),
Expr::Tuple(exprs) => paren_wrapped_vec(exprs.iter().map(|x| x.to_repl())),
_ => format!("{:?}", self),
}
}
@@ -157,8 +152,7 @@ impl<'a> State<'a> {
for statement in ast.0 {
match self.statement(statement) {
Ok(Some(ref output)) if repl => {
let ref symbol_table = self.symbol_table_handle.borrow();
acc.push(Ok(output.to_repl(symbol_table)))
acc.push(Ok(output.to_repl()))
},
Ok(_) => (),
Err(error) => {
@@ -212,7 +206,10 @@ impl<'a> State<'a> {
Node::Expr(expr) => match expr {
literal @ Lit(_) => Ok(Node::Expr(literal)),
Call { box f, args } => self.call_expression(f, args),
Sym(v) => self.handle_sym(v),
Sym(name) => Ok(match self.values.lookup(&name) {
Some(ValueEntry::Binding { val, .. }) => val.clone(),
None => return Err(format!("Could not look up symbol {}", name))
}),
Constructor { arity, ref name, tag, .. } if arity == 0 => Ok(Node::PrimObject { name: name.clone(), tag, items: vec![] }),
constructor @ Constructor { .. } => Ok(Node::Expr(constructor)),
func @ Func(_) => Ok(Node::Expr(func)),
@@ -264,7 +261,6 @@ impl<'a> State<'a> {
}
let mut func_state = State {
values: self.values.new_scope(name.map(|n| format!("{}", n))),
symbol_table_handle: self.symbol_table_handle.clone(),
};
for (param, val) in params.into_iter().zip(args.into_iter()) {
let val = func_state.expression(Node::Expr(val))?;
@@ -343,13 +339,11 @@ impl<'a> State<'a> {
/* builtin functions */
(IOPrint, &[ref anything]) => {
let ref symbol_table = self.symbol_table_handle.borrow();
print!("{}", anything.to_repl(symbol_table));
print!("{}", anything.to_repl());
Expr::Unit.to_node()
},
(IOPrintLn, &[ref anything]) => {
let ref symbol_table = self.symbol_table_handle.borrow();
println!("{}", anything.to_repl(symbol_table));
println!("{}", anything.to_repl());
Expr::Unit.to_node()
},
(IOGetLine, &[]) => {
@@ -458,45 +452,4 @@ impl<'a> State<'a> {
}
Err(format!("{:?} failed pattern match", cond))
}
//TODO if I don't need to lookup by name here...
fn handle_sym(&mut self, name: Rc<String>) -> EvalResult<Node> {
use self::ValueEntry::*;
use self::Func::*;
//TODO add a layer of indirection here to talk to the symbol table first, and only then look up
//in the values table
let symbol_table = self.symbol_table_handle.borrow();
let value = symbol_table.lookup_by_fqsn(&fqsn!(name ; tr));
Ok(match value {
Some(Symbol { local_name, spec, .. }) => match spec {
//TODO I'll need this type_name later to do a table lookup
SymbolSpec::DataConstructor { type_name: _type_name, type_args, .. } => {
if type_args.len() == 0 {
Node::PrimObject { name: local_name.clone(), tag: 0, items: vec![] }
} else {
return Err(format!("This data constructor thing not done"))
}
},
SymbolSpec::Func(_) => match self.values.lookup(&name) {
Some(Binding { val: Node::Expr(Expr::Func(UserDefined { name, params, body })), .. }) => {
Node::Expr(Expr::Func(UserDefined { name: name.clone(), params: params.clone(), body: body.clone() }))
},
_ => unreachable!(),
},
SymbolSpec::RecordConstructor { .. } => return Err(format!("This shouldn't be a record!")),
SymbolSpec::Binding => match self.values.lookup(&name) {
Some(Binding { val, .. }) => val.clone(),
None => return Err(format!("Symbol {} exists in symbol table but not in evaluator table", name))
}
},
//TODO ideally this should be returning a runtime error if this is ever None, but it's not
//handling all bindings correctly yet
//None => return Err(format!("Couldn't find value {}", name)),
None => match self.values.lookup(&name) {
Some(Binding { val, .. }) => val.clone(),
None => return Err(format!("Couldn't find value {}", name)),
}
})
}
}

View File

@@ -9,17 +9,17 @@ use crate::reduced_ast::reduce;
use crate::eval::State;
fn evaluate_all_outputs(input: &str) -> Vec<Result<String, String>> {
let symbol_table = Rc::new(RefCell::new(SymbolTable::new()));
let mut state = State::new(symbol_table);
let mut ast = crate::util::quick_ast(input);
state.symbol_table_handle.borrow_mut().add_top_level_symbols(&ast).unwrap();
let (mut ast, source_map) = crate::util::quick_ast(input);
let source_map = Rc::new(RefCell::new(source_map));
let symbol_table = Rc::new(RefCell::new(SymbolTable::new(source_map)));
symbol_table.borrow_mut().add_top_level_symbols(&ast).unwrap();
{
let mut t = &mut state.symbol_table_handle.borrow_mut();
let mut scope_resolver = ScopeResolver::new(&mut t);
let mut scope_resolver = ScopeResolver::new(symbol_table.clone());
let _ = scope_resolver.resolve(&mut ast);
}
let reduced = reduce(&ast, &state.symbol_table_handle.borrow());
let reduced = reduce(&ast, &symbol_table.borrow());
let mut state = State::new();
let all_output = state.evaluate(reduced, true);
all_output
}
@@ -258,3 +258,12 @@ milta()(10)
"#;
test_in_fresh_env!(source, "43");
}
#[test]
fn import_all() {
let source = r#"
type Option<T> = Some(T) | None
import Option::*
let x = Some(9); if x is Some(q) then { q } else { 0 }"#;
test_in_fresh_env!(source, "9");
}

View File

@@ -1,7 +1,4 @@
#![feature(trace_macros)]
#![feature(custom_attribute)]
//#![feature(unrestricted_attribute_tokens)]
#![feature(slice_patterns, box_patterns, box_syntax)]
#![feature(box_patterns, box_syntax, trace_macros, or_patterns)]
//! `schala-lang` is where the Schala programming language is actually implemented.
//! It defines the `Schala` type, which contains the state for a Schala REPL, and implements
@@ -18,6 +15,7 @@ extern crate schala_lang_codegen;
extern crate ena;
extern crate derivative;
extern crate colored;
extern crate radix_trie;
macro_rules! bx {
@@ -32,6 +30,7 @@ mod debugging;
mod tokenizing;
mod ast;
mod parser;
mod parsing;
#[macro_use]
mod symbol_table;
@@ -39,6 +38,7 @@ mod scope_resolution;
mod builtin;
mod reduced_ast;
mod eval;
mod source_map;
mod schala;

View File

@@ -0,0 +1,598 @@
extern crate nom;
use std::rc::Rc;
use std::str::FromStr;
use nom::IResult;
use nom::character::complete::{one_of, space0, alphanumeric0};
use nom::bytes::complete::{tag, take, take_while, take_while1, take_until};
use nom::combinator::{cut, cond, map, map_res, value, opt, verify};
use nom::multi::{separated_list, separated_nonempty_list, many1, many0};
use nom::error::{context, ParseError, VerboseError, ErrorKind, make_error};
use nom::branch::alt;
use nom::sequence::{pair, tuple, delimited, preceded};
use crate::ast::*;
use crate::builtin::Builtin;
type ParseResult<'a, T> = IResult<&'a str, T, VerboseError<&'a str>>;
pub fn ws<'a, O, E: ParseError<&'a str>, F>(parser: F) -> impl Fn(&'a str) -> IResult<&'a str, O, E>
where
F: Fn(&'a str) -> IResult<&'a str, O, E>,
{
delimited(space0, parser, space0)
}
fn statement_sep(text: &str) -> ParseResult<()> {
value((), one_of("\n;"))(text)
}
fn single_alphabetic_character(text: &str) -> ParseResult<char> {
let p = verify(take(1usize), |s: &str| s.chars().nth(0).map(|c| c.is_alphabetic()).unwrap_or(false));
map(p, |s: &str| s.chars().nth(0).unwrap())(text)
}
fn single_alphanumeric_character(text: &str) -> ParseResult<char> {
let p = verify(take(1usize), |s: &str| s.chars().nth(0).map(|c| c.is_alphanumeric() || c == '_').unwrap_or(false));
map(p, |s: &str| s.chars().nth(0).unwrap())(text)
}
fn identifier(text: &str) -> ParseResult<Rc<String>> {
use nom::character::complete::char;
map(alt((
pair(char('_'), many1(single_alphanumeric_character)),
pair(single_alphabetic_character, many0(single_alphanumeric_character))
)),
|(first, rest): (char, Vec<char>)| Rc::new(format!("{}{}", first, rest.into_iter().collect::<String>()))
)(text)
}
const OPERATOR_CHARS: &'static str = "~`!@#$%^&*-+=<>?/|";
fn operator(text: &str) -> ParseResult<Vec<char>> {
many1(one_of(OPERATOR_CHARS))(text)
}
fn binop(text: &str) -> ParseResult<BinOp> {
context("Binop", map(
operator,
|op| BinOp::from_sigil(&op.into_iter().collect::<String>())
))(text)
}
fn bool_literal(text: &str) -> ParseResult<ExpressionKind> {
let p = alt((
value(true, tag("true")),
value(false, tag("false"))
));
context("Bool literal", map(p, ExpressionKind::BoolLiteral))(text)
}
fn number_literal(text: &str) -> ParseResult<ExpressionKind> {
let num_lit = many1(alt((
map(one_of("1234567890"), |s: char| Some(s)),
value(None, nom::character::complete::char('_')),
)));
let (text, n) = map_res(num_lit,
|digits: Vec<Option<char>>| {
let num_str: String = digits.into_iter().filter_map(|x| x).collect();
u64::from_str_radix(&num_str, 10)
})(text)?;
Ok((text, ExpressionKind::NatLiteral(n)))
}
fn binary_literal(text: &str) -> ParseResult<ExpressionKind> {
let p = preceded(tag("0b"), cut(take_while1(|c: char| c == '0' || c == '1')));
let (rest, n): (&str, u64) = map_res(
p, |hex_str: &str| u64::from_str_radix(hex_str, 2)
)(text)?;
let expr = ExpressionKind::NatLiteral(n);
Ok((rest, expr))
}
fn hex_literal(text: &str) -> ParseResult<ExpressionKind> {
let p = preceded(tag("0x"), cut(take_while1(|c: char| c.is_digit(16))));
let (rest, n): (&str, u64) = map_res(
p, |hex_str: &str| u64::from_str_radix(hex_str, 16)
)(text)?;
let expr = ExpressionKind::NatLiteral(n);
Ok((rest, expr))
}
fn string_literal(text: &str) -> ParseResult<ExpressionKind> {
use nom::character::complete::char;
let (text, string_output) = delimited(
char('"'), take_until("\""), char('"')
)(text)?;
let expr = ExpressionKind::StringLiteral(Rc::new(string_output.to_string()));
Ok((text, expr))
}
fn literal(text: &str) -> ParseResult<ExpressionKind> {
alt((
string_literal,
hex_literal,
binary_literal,
number_literal,
bool_literal,
))(text)
}
fn paren_expr(text: &str) -> ParseResult<ExpressionKind> {
context("Paren expression", delimited(tag("("), ws(expression_kind), tag(")")))(text)
}
fn prefix_op(text: &str) -> ParseResult<PrefixOp> {
use nom::character::complete::char;
let p = alt((char('+'), char('-'), char('!')));
map(p, |sigil| PrefixOp::from_str(&sigil.to_string()).unwrap())(text)
}
fn qualified_name(text: &str) -> ParseResult<QualifiedName> {
map(
separated_nonempty_list(tag("::"), identifier),
|components| QualifiedName { id: ItemId::new(0), components }
)(text)
}
fn identifier_expr(text: &str) -> ParseResult<ExpressionKind> {
map(qualified_name, ExpressionKind::Value)(text)
}
fn primary_expr(text: &str) -> ParseResult<ExpressionKind> {
// primary := literal | paren_expr | if_expr | for_expr | while_expr | identifier_expr | lambda_expr | anonymous_struct | list_expr
alt((
if_expr,
for_expr,
while_expr,
literal,
paren_expr,
lambda_expr,
identifier_expr,
))(text)
}
fn lambda_expr(text: &str) -> ParseResult<ExpressionKind> {
let p = preceded(ws(tag("\\")),
tuple((ws(lambda_param_list), ws(opt(type_anno)), ws(block))));
context("Lambda expression",
map(p, |(params, type_anno, body)| ExpressionKind::Lambda { params, type_anno, body })
)(text)
}
fn lambda_param_list(text: &str) -> ParseResult<Vec<FormalParam>> {
alt((
map(formal_param, |x| vec![x]),
formal_params
))(text)
}
fn while_expr(text: &str) -> ParseResult<ExpressionKind> {
let p = preceded(tag("while"), tuple((ws(while_cond), ws(block))));
let m = map(p, |(condition, body)| {
let condition = condition.map(Box::new);
ExpressionKind::WhileExpression {condition, body}
});
context("While expression", m)(text)
}
fn while_cond(text: &str) -> ParseResult<Option<Expression>> {
//TODO support is constructs?
context("While condition",
map(opt(ws(expression_kind)),
|maybe_expr_kind| maybe_expr_kind.map(|kind| Expression::new(ItemId::new(0), kind)))
)(text)
}
fn for_expr(text: &str) -> ParseResult<ExpressionKind> {
//TODO do I need something like no struct literal here?
let en = alt((
map(enumerator, |e| vec![e]),
delimited(tag("{"), enumerators, tag("}"))
));
context("For expression",
preceded(tag("for"),
cut(
map(tuple((ws(en), for_expr_body)),
|(enumerators, body)| ExpressionKind::ForExpression { enumerators, body: Box::new(body) }
))))(text)
}
fn enumerators(text: &str) -> ParseResult<Vec<Enumerator>> {
separated_nonempty_list(alt((value((), tag(",")), statement_sep)),
enumerator)(text)
}
fn enumerator(text: &str) -> ParseResult<Enumerator> {
map(
tuple((ws(identifier), ws(tag("<-")), ws(expression))),
|(id, _, generator)| Enumerator { id, generator }
)(text)
}
fn for_expr_body(text: &str) -> ParseResult<ForBody> {
context("For expression body",
alt((
map(preceded(ws(tag("return")), expression), ForBody::MonadicReturn),
map(block, ForBody::StatementBlock),
)))(text)
}
fn invocation_argument(text: &str) -> ParseResult<InvocationArgument> {
use nom::character::complete::char;
alt((
value(InvocationArgument::Ignored, pair(char('_'), alphanumeric0)),
map(expression_kind, |kind: ExpressionKind| InvocationArgument::Positional(
Expression { id: ItemId::new(0), kind, type_anno: None }))
//map(identifier, |id: Rc<String>|
))(text)
}
fn if_expr(text: &str) -> ParseResult<ExpressionKind> {
let p = preceded(tag("if"), pair(ws(discriminator), ws(if_expr_body)));
map(p, |(discriminator, body)| {
let discriminator = discriminator.map(Box::new);
let body = Box::new(body);
ExpressionKind::IfExpression { discriminator, body }
}) (text)
}
fn discriminator(text: &str) -> ParseResult<Option<Expression>> {
use nom::combinator::verify;
cond(text.chars().next().map(|c| c != '{').unwrap_or(true),
expression
)(text)
}
fn if_expr_body(text: &str) -> ParseResult<IfExpressionBody> {
alt((
preceded(tag("then"), simple_conditional),
preceded(tag("is"), simple_pattern_match),
cond_block,
))(text)
}
fn simple_conditional(text: &str) -> ParseResult<IfExpressionBody> {
map(
pair(expr_or_block, else_case),
|(then_case, else_case)| IfExpressionBody::SimpleConditional { then_case, else_case }
)(text)
}
fn else_case(text: &str) -> ParseResult<Option<Block>> {
opt(preceded(tag("else"), expr_or_block))(text)
}
fn simple_pattern_match(text: &str) -> ParseResult<IfExpressionBody> {
let p = tuple((pattern, tag("then"), expr_or_block, else_case));
map(p, |(pattern, _, then_case, else_case)|
IfExpressionBody::SimplePatternMatch { pattern, then_case, else_case }
)(text)
}
fn pattern(text: &str) -> ParseResult<Pattern> {
use nom::character::complete::char;
let t = delimited(char('('),
separated_nonempty_list(char(','), pattern),
char(')')
);
alt((
map(t, |patterns| Pattern::TuplePattern(patterns)),
simple_pattern,
))(text)
}
fn simple_pattern(text: &str) -> ParseResult<Pattern> {
alt((
value(Pattern::Ignored, tag("_")),
tuple_struct_pattern,
record_pattern,
map(pattern_literal, Pattern::Literal),
map(qualified_name, Pattern::VarOrName),
))(text)
}
fn tuple_struct_pattern(text: &str) -> ParseResult<Pattern> {
let p = tuple((
qualified_name,
delimited(ws(tag("(")),
separated_nonempty_list(ws(tag(",")), ws(pattern)),
ws(tag(")"))
)
));
map(p, |(name, patterns)| Pattern::TupleStruct(name, patterns))(text)
}
fn record_pattern(text: &str) -> ParseResult<Pattern> {
let p = tuple((
qualified_name,
delimited(ws(tag("{")),
separated_nonempty_list(ws(tag(",")), ws(record_pattern_entry)), //TODO support newlines?
ws(tag("}")))
));
map(p, |(name, members)| Pattern::Record(name, members))(text)
}
fn record_pattern_entry(text: &str) -> ParseResult<(Rc<String>, Pattern)> {
alt((
map(tuple((ws(identifier), ws(tag(":")), ws(pattern))),
|(name, _, pattern)| (name, pattern)),
map(identifier, |name|
(name.clone(), Pattern::Literal(PatternLiteral::StringPattern(name.clone())))
)
))(text)
}
fn pattern_literal(text: &str) -> ParseResult<PatternLiteral> {
use PatternLiteral::*;
use nom::character::complete::char;
alt((
value(BoolPattern(true), tag("true")),
value(BoolPattern(false), tag("false")),
map(delimited(char('"'), take_until("\""), char('"')), |s: &str| StringPattern(Rc::new(s.to_string()))),
))(text)
//TODO handle signed_number_literal
}
fn cond_block(text: &str) -> ParseResult<IfExpressionBody> {
use nom::character::complete::char;
//TODO maybe change this bit of syntax
let comma_or_delimitor = alt((value((), char(',')), statement_sep));
let p = delimited(char('{'),
separated_nonempty_list(comma_or_delimitor, cond_arm),
char('}'));
map(p, IfExpressionBody::CondList)(text)
}
fn cond_arm(text: &str) -> ParseResult<ConditionArm> {
let variant_1 = map(
tuple((condition, guard, tag("then"), expr_or_block)),
|(condition, guard, _, body)| ConditionArm { condition, guard, body }
);
let variant_2 = map(
preceded(tag("else"), expr_or_block),
|body| ConditionArm { condition: Condition::Else, guard: None, body }
);
alt((variant_1, variant_2))(text)
}
fn condition(text: &str) -> ParseResult<Condition> {
alt((
map(preceded(tag("is"), pattern), Condition::Pattern),
map(tuple((binop, expression)), |(op, expr)|
Condition::TruncatedOp(op, expr)),
map(expression, Condition::Expression),
))(text)
}
fn guard(text: &str) -> ParseResult<Option<Expression>> {
opt(preceded(tag("if"), expression))(text)
}
fn expr_or_block(text: &str) -> ParseResult<Block> {
//TODO fix
alt((block, map(expression, |expr| vec![Statement { id: ItemId::new(0), kind: StatementKind::Expression(expr)}])))(text)
}
fn block(text: &str) -> ParseResult<Block> {
//TODO fix this so it can handle nested statements
let make_expr = |e| Statement { id: ItemId::new(0), kind: StatementKind::Expression(e) };
delimited(ws(tag("{")),
delimited(opt(many0(statement_sep)),
separated_list(many1(statement_sep),
map(expression, make_expr)
),
opt(many0(statement_sep))
),
ws(tag("}")))(text)
}
fn call_expr(text: &str) -> ParseResult<ExpressionKind> {
use nom::character::complete::char;
let parse_call = opt(
delimited(char('('), separated_list(char(','), invocation_argument), char(')'))
);
let p = pair(primary_expr, parse_call);
map(p, |(expr, call_part)| if let Some(arguments) = call_part {
let f = bx!(Expression { id: ItemId::new(0), kind: expr, type_anno: None });
ExpressionKind::Call { f, arguments }
} else {
expr
})(text)
}
fn prefix_expr(text: &str) -> ParseResult<ExpressionKind> {
let (text, pfx) = ws(opt(prefix_op))(text)?;
let (text, result) = call_expr(text)?;
match pfx {
None => Ok((text, result)),
Some(pfx) => {
let exp = Expression { id: ItemId::new(0), kind: result, type_anno: None };
Ok((text, ExpressionKind::PrefixExp(pfx, Box::new(exp))))
}
}
}
// this implements Pratt parsing, see http://journal.stuffwithstuff.com/2011/03/19/pratt-parsers-expression-parsing-made-easy/
fn precedence_expr(text: &str) -> ParseResult<ExpressionKind> {
fn inner_precedence_expr(input: &str, precedence: i32) -> ParseResult<ExpressionKind> {
let (mut outer_rest, mut lhs) = prefix_expr(input)?;
loop {
let (rest, _) = space0(outer_rest)?;
let (rest, maybe_binop) = opt(binop)(rest)?;
let (new_precedence, binop) = match maybe_binop {
Some(binop) => (binop.precedence(), binop),
None => break,
};
if precedence >= new_precedence {
break;
}
let (rest, _) = space0(rest)?;
let (rest, rhs) = inner_precedence_expr(rest, new_precedence)?;
outer_rest = rest;
lhs = ExpressionKind::BinExp(binop,
bx!(Expression::new(ItemId::new(0), lhs)),
bx!(Expression::new(ItemId::new(0), rhs))
);
}
Ok((outer_rest, lhs))
}
context("Precedence expression",
|input| inner_precedence_expr(input, BinOp::min_precedence())
)(text)
}
fn expression_kind(text: &str) -> ParseResult<ExpressionKind> {
context("Expression kind", ws(precedence_expr))(text)
}
fn type_anno(text: &str) -> ParseResult<TypeIdentifier> {
preceded(ws(tag(":")), ws(type_name))(text)
}
fn type_name(text: &str) -> ParseResult<TypeIdentifier> {
//TODO incomplete
let (text, name) = identifier(text)?;
let id = TypeIdentifier::Singleton(TypeSingletonName { name, params: vec![] });
Ok((text, id))
}
pub fn expression(text: &str) -> ParseResult<Expression> {
let (rest, (kind, type_anno)) = ws(pair(expression_kind, opt(type_anno)))(text)?;
let expr = Expression { id: ItemId::new(0), kind, type_anno };
Ok((rest, expr))
}
fn import(text: &str) -> ParseResult<ImportSpecifier> {
let p = preceded(
tag("import"),
separated_nonempty_list(tag("::"), identifier)
);
map(p, |path_components| ImportSpecifier {
id: ItemId::new(0),
path_components,
imported_names: ImportedNames::LastOfPath, //TODO finish
})(text)
}
fn module(text: &str) -> ParseResult<ModuleSpecifier> {
let p = tuple((tag("module"), ws(identifier), ws(block)));
map(p, |(_, name, contents)| ModuleSpecifier { name, contents })
(text)
}
fn declaration(text: &str) -> ParseResult<Declaration> {
alt((
func_declaration,
type_declaration,
))(text)
}
fn func_declaration(text: &str) -> ParseResult<Declaration> {
use Declaration::*;
let p = tuple((func_signature, ws(opt(block))));
map(p, |(signature, maybe_block)| match maybe_block {
Some(block) => FuncDecl(signature, block),
None => FuncSig(signature),
})(text)
}
fn func_signature(text: &str) -> ParseResult<Signature> {
let p = preceded(tag("fn"), cut(tuple((ws(identifier), ws(formal_params), opt(ws(type_anno))))));
//TODO fix op
map(p, |(name, params, type_anno)| Signature { name, params, type_anno, operator: false })
(text)
}
fn formal_params(text: &str) -> ParseResult<Vec<FormalParam>> {
delimited(tag("("), ws(separated_list(ws(tag(",")), formal_param)), tag(")"))(text)
}
fn formal_param(text: &str) -> ParseResult<FormalParam> {
let default = opt(preceded(ws(tag("=")), ws(expression)));
let p = tuple((ws(identifier), opt(ws(type_anno)), default));
map(p, |(name, anno, default)|
FormalParam { name, anno, default })(text)
}
fn type_declaration(text: &str) -> ParseResult<Declaration> {
preceded(tag("type"), ws(type_declaration_body))(text)
}
fn type_declaration_body(text: &str) -> ParseResult<Declaration> {
let t = tuple((opt(tag("mut")), ws(type_singleton_name), ws(tag("=")), ws(type_body)));
alt((
preceded(tag("alias"), ws(type_alias)),
map(t, |(mut_kw, name, _, body)| {
Declaration::TypeDecl { name, body, mutable: mut_kw.is_some() }
})
))(text)
}
fn type_body(text: &str) -> ParseResult<TypeBody> {
let p = separated_nonempty_list(ws(tag("|")), variant_specifier);
map(p, TypeBody)(text)
}
fn variant_specifier(text: &str) -> ParseResult<Variant> {
use self::Variant::*;
let tuple_struct =
delimited(tag("("), separated_nonempty_list(ws(tag(",")), type_name), ws(tag(")")));
//TODO record
let p = tuple((identifier, opt(tuple_struct)));
map(p, |(name, maybe_tuple_members)| match maybe_tuple_members {
Some(members) => TupleStruct(name, members),
None => UnitStruct(name),
})(text)
}
fn type_singleton_name(text: &str) -> ParseResult<TypeSingletonName> {
let p = tuple((identifier, opt(delimited(tag("<"),
separated_nonempty_list(tag(","), ws(type_name)),
tag(">")))));
map(p, |(name, params)| TypeSingletonName { name, params: params.unwrap_or(vec![]) })(text)
}
fn type_alias(text: &str) -> ParseResult<Declaration> {
let p = tuple((ws(identifier), ws(tag("=")), ws(identifier)));
map(p, |(alias, _, original)| Declaration::TypeAlias { alias, original })
(text)
}
fn statement(text: &str) -> ParseResult<Statement> {
let p = alt((
map(import, StatementKind::Import),
map(module, StatementKind::Module),
map(declaration, StatementKind::Declaration),
map(expression, StatementKind::Expression),
));
map(p, |kind| Statement { id: ItemId::new(0), kind })(text)
}
pub fn parse_ast(text: &str) -> ParseResult<AST> {
map(separated_list(statement_sep, statement),
|statements| AST { id: ItemId::new(0), statements }
)(text)
}
pub fn perform_parsing(input: &str) -> Result<String, String> {
let output = match parse_ast(input) {
Ok((rest, ast)) => format!("{:?} (rest: {})", ast, rest),
Err(nom::Err::Incomplete(needed)) => format!("Incomplete: {:?}" ,needed),
Err(nom::Err::Error(verbose_error) | nom::Err::Failure(verbose_error)) => {
format!("Verbose Error: ` {:?} `", verbose_error)
//nom::error::convert_error(input, verbose_error)
}
};
Ok(output)
}

View File

@@ -13,7 +13,7 @@
//! ```text
//! program := (statement delimiter)* EOF
//! delimiter := NEWLINE | ";"
//! statement := expression | declaration | import
//! statement := expression | declaration | import | module
//! block := "{" (statement delimiter)* "}"
//! declaration := type_declaration | func_declaration | binding_declaration | impl_declaration
//! ```
@@ -153,6 +153,10 @@
//! ```text
//! import := 'import' IDENTIFIER (:: IDENTIFIER)* import_suffix
//! import_suffix := ε | '::{' IDENTIFIER (, IDENTIFIER)* '}' | '*' //TODO add qualified, exclusions, etc.
//!
//! ## Modules
//!
//! module := 'module' IDENTIFIER '{' statement* '}'
//! ```
mod test;
@@ -163,7 +167,9 @@ use crate::tokenizing::*;
use crate::tokenizing::Kw::*;
use crate::tokenizing::TokenKind::*;
use crate::source_map::Location;
use crate::ast::*;
use crate::schala::SourceMapHandle;
/// Represents a parsing error
#[derive(Debug)]
@@ -196,8 +202,10 @@ pub struct Parser {
parse_level: u32,
restrictions: ParserRestrictions,
id_store: ItemIdStore,
source_map: SourceMapHandle
}
struct ParserRestrictions {
no_struct_literal: bool
}
@@ -205,14 +213,14 @@ struct ParserRestrictions {
struct TokenHandler {
tokens: Vec<Token>,
idx: usize,
end_of_file: (usize, usize),
end_of_file: Location
}
impl TokenHandler {
fn new(tokens: Vec<Token>) -> TokenHandler {
let end_of_file = match tokens.last() {
None => (0, 0),
Some(t) => (t.line_num, t.char_num)
None => Location { line_num: 0, char_num : 0 },
Some(t) => t.location,
};
TokenHandler { idx: 0, tokens, end_of_file }
}
@@ -225,49 +233,56 @@ impl TokenHandler {
self.peek_n(n).kind
}
fn peek(&mut self) -> Token {
self.tokens.get(self.idx).map(|t: &Token| { t.clone()}).unwrap_or(Token { kind: TokenKind::EOF, line_num: self.end_of_file.0, char_num: self.end_of_file.1})
self.tokens.get(self.idx).map(|t: &Token| { t.clone()}).unwrap_or(Token { kind: TokenKind::EOF, location: self.end_of_file })
}
/// calling peek_n(0) is the same thing as peek()
fn peek_n(&mut self, n: usize) -> Token {
self.tokens.get(self.idx + n).map(|t: &Token| { t.clone()}).unwrap_or(Token { kind: TokenKind::EOF, line_num: self.end_of_file.0, char_num: self.end_of_file.1})
self.tokens.get(self.idx + n).map(|t: &Token| { t.clone()}).unwrap_or(Token { kind: TokenKind::EOF, location: self.end_of_file })
}
fn next(&mut self) -> Token {
self.idx += 1;
self.tokens.get(self.idx - 1).map(|t: &Token| { t.clone() }).unwrap_or(Token { kind: TokenKind::EOF, line_num: self.end_of_file.0, char_num: self.end_of_file.1})
self.tokens.get(self.idx - 1).map(|t: &Token| { t.clone() }).unwrap_or(Token { kind: TokenKind::EOF, location: self.end_of_file })
}
}
impl Parser {
/// Create a new parser initialized with some tokens.
pub fn new(initial_input: Vec<Token>) -> Parser {
pub fn new(source_map: SourceMapHandle) -> Parser {
Parser {
token_handler: TokenHandler::new(initial_input),
token_handler: TokenHandler::new(vec![]),
parse_record: vec![],
parse_level: 0,
restrictions: ParserRestrictions { no_struct_literal: false },
id_store: ItemIdStore::new(),
source_map,
}
}
pub fn add_new_tokens(&mut self, new_tokens: Vec<Token>) {
self.token_handler = TokenHandler::new(new_tokens);
}
/// Parse all loaded tokens up to this point.
pub fn parse(&mut self) -> ParseResult<AST> {
self.program()
}
/*
pub fn parse_with_new_tokens(&mut self, new_tokens: Vec<Token>) -> ParseResult<AST> {
}
*/
pub fn format_parse_trace(&self) -> String {
let mut buf = String::new();
buf.push_str("Parse productions:\n");
let mut next_token = None;
for r in self.parse_record.iter() {
let mut indent = String::new();
for _ in 0..r.level {
indent.push(' ');
indent.push('.');
}
buf.push_str(&format!("{}Production `{}`, token: {}\n", indent, r.production_name, r.next_token))
let effective_token = if next_token == Some(&r.next_token) {
"".to_string()
} else {
next_token = Some(&r.next_token);
format!(", next token: {}", r.next_token)
};
buf.push_str(&format!("{}`{}`{}\n", indent, r.production_name, effective_token));
}
buf
}
@@ -355,16 +370,20 @@ impl Parser {
#[recursive_descent_method]
fn statement(&mut self) -> ParseResult<Statement> {
//TODO handle error recovery here
let kind = match self.token_handler.peek().get_kind() {
let tok = self.token_handler.peek();
let kind = match tok.get_kind() {
Keyword(Type) => self.type_declaration().map(|decl| { StatementKind::Declaration(decl) }),
Keyword(Func)=> self.func_declaration().map(|func| { StatementKind::Declaration(func) }),
Keyword(Let) => self.binding_declaration().map(|decl| StatementKind::Declaration(decl)),
Keyword(Interface) => self.interface_declaration().map(|decl| StatementKind::Declaration(decl)),
Keyword(Impl) => self.impl_declaration().map(|decl| StatementKind::Declaration(decl)),
Keyword(Import) => self.import_declaration().map(|spec| StatementKind::Import(spec)),
Keyword(Module) => self.module_declaration().map(|spec| StatementKind::Module(spec)),
_ => self.expression().map(|expr| { StatementKind::Expression(expr) } ),
}?;
Ok(Statement { kind, id: self.id_store.fresh() })
let id = self.id_store.fresh();
self.source_map.borrow_mut().add_location(&id, tok.location);
Ok(Statement { kind, id })
}
#[recursive_descent_method]
@@ -482,7 +501,6 @@ impl Parser {
Ok(delimited!(self, LParen, formal_param, Comma, RParen))
}
//TODO needs to support default values
#[recursive_descent_method]
fn formal_param(&mut self) -> ParseResult<FormalParam> {
let name = self.identifier()?;
@@ -973,7 +991,7 @@ impl Parser {
self.token_handler.next();
Pattern::Literal(PatternLiteral::BoolPattern(false))
},
StrLiteral(s) => {
StrLiteral { s, .. } => {
self.token_handler.next();
Pattern::Literal(PatternLiteral::StringPattern(s))
},
@@ -1122,7 +1140,7 @@ impl Parser {
let id = self.id_store.fresh();
Ok(Expression::new(id, BoolLiteral(false)))
},
StrLiteral(s) => {
StrLiteral {s, ..} => {
self.token_handler.next();
let id = self.id_store.fresh();
Ok(Expression::new(id, StringLiteral(s.clone())))
@@ -1245,6 +1263,14 @@ impl Parser {
_ => return ParseError::new_with_token("Expected '{{' or '*'", self.token_handler.peek()),
})
}
#[recursive_descent_method]
fn module_declaration(&mut self) -> ParseResult<ModuleSpecifier> {
expect!(self, Keyword(Kw::Module));
let name = self.identifier()?;
let contents = delimited!(self, LCurlyBrace, statement, Newline | Semicolon, RCurlyBrace, nonstrict);
Ok(ModuleSpecifier { name, contents })
}
}
fn parse_binary(digits: String, tok: Token) -> ParseResult<u64> {

View File

@@ -1,10 +1,10 @@
#![cfg(test)]
use ::std::rc::Rc;
use std::cell::RefCell;
use std::rc::Rc;
use std::str::FromStr;
use super::tokenize;
use super::ParseResult;
use crate::ast::{ItemIdStore, AST, Expression, Statement, StatementKind, IfExpressionBody, Pattern, PatternLiteral, TypeBody, Enumerator, ForBody, InvocationArgument, FormalParam, PrefixOp, BinOp, QualifiedName, ImportSpecifier, ImportedNames, Condition, ConditionArm};
use super::{Parser, ParseResult, tokenize, ParseError};
use crate::ast::*;
use super::Declaration::*;
use super::Signature;
use super::TypeIdentifier::*;
@@ -13,10 +13,28 @@ use super::ExpressionKind::*;
use super::Variant::*;
use super::ForBody::*;
fn parse(input: &str) -> ParseResult<AST> {
/*
fn make_parser(input: &str) -> Parser {
let source_map = crate::source_map::SourceMap::new();
let source_map_handle = Rc::new(RefCell::new(source_map));
let tokens: Vec<crate::tokenizing::Token> = tokenize(input);
let mut parser = super::Parser::new(tokens);
let mut parser = super::Parser::new(source_map_handle);
parser.add_new_tokens(tokens);
parser
}
*/
fn parse(input: &str) -> ParseResult<AST> {
use crate::tokenizing::*;
crate::parser::parse_ast(input).map_err(|err| {
let token = Token { kind: TokenKind::Newline, location: crate::source_map::Location { line_num: 0, char_num: 0 } };
ParseError { production_name: None, msg: "".to_string(), token }
})
.map(|(rest, s)| s)
/*
let mut parser = make_parser(input);
parser.parse()
*/
}
macro_rules! parse_test {
@@ -63,14 +81,22 @@ macro_rules! import {
}
}
macro_rules! module {
($module_spec:expr) => {
Statement { id: ItemIdStore::new_id(), kind: StatementKind::Module($module_spec) }
}
}
macro_rules! ex {
($expr_type:expr) => { Expression::new(ItemIdStore::new_id(), $expr_type) };
($expr_type:expr, $type_anno:expr) => { Expression::with_anno(ItemIdStore::new_id(), $expr_type, $type_anno) };
(s $expr_text:expr) => {
{
let tokens: Vec<crate::tokenizing::Token> = tokenize($expr_text);
let mut parser = super::Parser::new(tokens);
/*
let mut parser = make_parser($expr_text);
parser.expression().unwrap()
*/
crate::parser::expression($expr_text).unwrap().1
}
};
}
@@ -92,9 +118,16 @@ macro_rules! exst {
};
(s $statement_text:expr) => {
{
let tokens: Vec<crate::tokenizing::Token> = tokenize($statement_text);
let mut parser = super::Parser::new(tokens);
/*
let mut parser = make_parser($statement_text);
parser.statement().unwrap()
*/
Statement {
kind: StatementKind::Expression(
crate::parser::expression($statement_text).unwrap().1
),
id: ItemIdStore::new_id()
}
}
}
}
@@ -585,13 +618,13 @@ fn more_advanced_lambdas() {
#[test]
fn while_expr() {
parse_test_wrap_ast! {
"while { }",
exst!(WhileExpression { condition: None, body: vec![] })
"while { 3 }",
exst!(WhileExpression { condition: None, body: vec![ exst!(s "3")] })
}
parse_test_wrap_ast! {
"while a == b { }",
exst!(WhileExpression { condition: Some(bx![ex![binexp!("==", val!("a"), val!("b"))]]), body: vec![] })
"while a == b { 3 }",
exst!(WhileExpression { condition: Some(bx![ex![binexp!("==", val!("a"), val!("b"))]]), body: vec![ exst!(s "3")] })
}
}
@@ -796,3 +829,21 @@ fn if_expr() {
}
}
}
#[test]
fn modules() {
parse_test_wrap_ast! {
r#"
module ephraim {
let a = 10
fn nah() { 33 }
}
"#,
module!(
ModuleSpecifier { name: rc!(ephraim), contents: vec![
decl!(Binding { name: rc!(a), constant: true, type_anno: None, expr: ex!(s "10") }),
decl!(FuncDecl(Signature { name: rc!(nah), operator: false, params: vec![], type_anno: None }, vec![exst!(NatLiteral(33))])),
] }
)
}
}

View File

@@ -6,8 +6,8 @@ type Ord = LT | EQ | GT
fn map(input: Option<T>, func: Func): Option<T> {
if input {
is Option::Some(x) -> Option::Some(func(x)),
is Option::None -> Option::None,
is Option::Some(x) then Option::Some(func(x)),
is Option::None then Option::None,
}
}

View File

@@ -42,9 +42,9 @@ pub enum Stmt {
pub enum Expr {
Unit,
Lit(Lit),
Sym(Rc<String>), //a Sym is anything that can be looked up by name at runtime - i.e. a function or variable address
Tuple(Vec<Expr>),
Func(Func),
Sym(Rc<String>),
Constructor {
type_name: Rc<String>,
name: Rc<String>,
@@ -56,7 +56,7 @@ pub enum Expr {
args: Vec<Expr>,
},
Assign {
val: Box<Expr>,
val: Box<Expr>, //TODO this probably can't be a val
expr: Box<Expr>,
},
Conditional {
@@ -131,6 +131,12 @@ impl<'a> Reducer<'a> {
StatementKind::Expression(expr) => Stmt::Expr(self.expression(&expr)),
StatementKind::Declaration(decl) => self.declaration(&decl),
StatementKind::Import(_) => Stmt::Noop,
StatementKind::Module(modspec) => {
for statement in modspec.contents.iter() {
self.statement(&statement);
}
Stmt::Noop
}
}
}
@@ -158,25 +164,7 @@ impl<'a> Reducer<'a> {
BoolLiteral(b) => Expr::Lit(Lit::Bool(*b)),
BinExp(binop, lhs, rhs) => self.binop(binop, lhs, rhs),
PrefixExp(op, arg) => self.prefix(op, arg),
Value(qualified_name) => {
let ref id = qualified_name.id;
let ref sym_name = match symbol_table.get_fqsn_from_id(id) {
Some(fqsn) => fqsn,
None => return Expr::ReductionError(format!("FQSN lookup for Value {:?} failed", qualified_name)),
};
//TODO this probably needs to change
let FullyQualifiedSymbolName(ref v) = sym_name;
let name = v.last().unwrap().name.clone();
match symbol_table.lookup_by_fqsn(&sym_name) {
Some(Symbol { spec: SymbolSpec::DataConstructor { index, type_args, type_name}, .. }) => Expr::Constructor {
type_name: type_name.clone(),
name: name.clone(),
tag: index.clone(),
arity: type_args.len(),
},
_ => Expr::Sym(name.clone()),
}
},
Value(qualified_name) => self.value(qualified_name),
Call { f, arguments } => self.reduce_call_expression(f, arguments),
TupleLiteral(exprs) => Expr::Tuple(exprs.iter().map(|e| self.expression(e)).collect()),
IfExpression { discriminator, body } => self.reduce_if_expression(deref_optional_box(discriminator), body),
@@ -189,6 +177,38 @@ impl<'a> Reducer<'a> {
}
}
fn value(&mut self, qualified_name: &QualifiedName) -> Expr {
let symbol_table = self.symbol_table;
let ref id = qualified_name.id;
let ref sym_name = match symbol_table.get_fqsn_from_id(id) {
Some(fqsn) => fqsn,
None => return Expr::ReductionError(format!("FQSN lookup for Value {:?} failed", qualified_name)),
};
//TODO this probably needs to change
let FullyQualifiedSymbolName(ref v) = sym_name;
let name = v.last().unwrap().name.clone();
let Symbol { local_name, spec, .. } = match symbol_table.lookup_by_fqsn(&sym_name) {
Some(s) => s,
//None => return Expr::ReductionError(format!("Symbol {:?} not found", sym_name)),
None => return Expr::Sym(name.clone())
};
match spec {
SymbolSpec::RecordConstructor { .. } => Expr::ReductionError(format!("AST reducer doesn't expect a RecordConstructor here")),
SymbolSpec::DataConstructor { index, type_args, type_name } => Expr::Constructor {
type_name: type_name.clone(),
name: name.clone(),
tag: index.clone(),
arity: type_args.len(),
},
SymbolSpec::Func(_) => Expr::Sym(local_name.clone()),
SymbolSpec::Binding => Expr::Sym(local_name.clone()), //TODO not sure if this is right, probably needs to eventually be fqsn
SymbolSpec::Type { .. } => Expr::ReductionError("AST reducer doesnt expect a type here".to_string())
}
}
fn reduce_lambda(&mut self, params: &Vec<FormalParam>, body: &Block) -> Expr {
Expr::Func(Func::UserDefined {
name: None,
@@ -444,7 +464,6 @@ impl Pattern {
VarOrName(QualifiedName { components, id }) => {
// if fqsn is Some, treat this as a symbol pattern. If it's None, treat it
// as a variable.
println!("Calling VarOrName reduction with : {:?}", components);
let fqsn = symbol_table.get_fqsn_from_id(&id);
match fqsn.and_then(|fqsn| symbol_table.lookup_by_fqsn(&fqsn)) {
Some(symbol) => handle_symbol(Some(symbol), &vec![], symbol_table),

View File

@@ -10,17 +10,22 @@ use schala_repl::{ProgrammingLanguageInterface,
ComputationRequest, ComputationResponse,
LangMetaRequest, LangMetaResponse, GlobalOutputStats,
DebugResponse, DebugAsk};
use crate::{ast, reduced_ast, tokenizing, parsing, eval, typechecking, symbol_table};
use crate::{ast, reduced_ast, tokenizing, parsing, parser, eval, typechecking, symbol_table, source_map};
pub type SymbolTableHandle = Rc<RefCell<symbol_table::SymbolTable>>;
pub type SourceMapHandle = Rc<RefCell<source_map::SourceMap>>;
/// All the state necessary to parse and execute a Schala program are stored in this struct.
/// `state` represents the execution state for the AST-walking interpreter, the other fields
/// should be self-explanatory.
pub struct Schala {
source_reference: SourceReference,
source_map: SourceMapHandle,
state: eval::State<'static>,
symbol_table: Rc<RefCell<symbol_table::SymbolTable>>,
symbol_table: SymbolTableHandle,
resolver: crate::scope_resolution::ScopeResolver<'static>,
type_context: typechecking::TypeContext<'static>,
active_parser: Option<parsing::Parser>,
active_parser: parsing::Parser,
}
impl Schala {
@@ -34,13 +39,17 @@ impl Schala {
impl Schala {
/// Creates a new Schala environment *without* any prelude.
fn new_blank_env() -> Schala {
let symbols = Rc::new(RefCell::new(symbol_table::SymbolTable::new()));
let source_map = Rc::new(RefCell::new(source_map::SourceMap::new()));
let symbols = Rc::new(RefCell::new(symbol_table::SymbolTable::new(source_map.clone())));
Schala {
//TODO maybe these can be the same structure
source_reference: SourceReference::new(),
symbol_table: symbols.clone(),
state: eval::State::new(symbols),
source_map: source_map.clone(),
resolver: crate::scope_resolution::ScopeResolver::new(symbols.clone()),
state: eval::State::new(),
type_context: typechecking::TypeContext::new(),
active_parser: None,
active_parser: parsing::Parser::new(source_map)
}
}
@@ -51,7 +60,10 @@ impl Schala {
let mut s = Schala::new_blank_env();
let request = ComputationRequest { source: prelude, debug_requests: HashSet::default() };
s.run_computation(request);
let response = s.run_computation(request);
if let Err(msg) = response.main_output {
panic!("Error in prelude, panicking: {}", msg);
}
s
}
@@ -94,10 +106,10 @@ fn tokenizing(input: &str, _handle: &mut Schala, comp: Option<&mut PassDebugArti
}
fn parsing(input: Vec<tokenizing::Token>, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<ast::AST, String> {
use crate::parsing::Parser;
use ParsingDebugType::*;
let mut parser = handle.active_parser.take().unwrap_or_else(|| Parser::new(input));
let ref mut parser = handle.active_parser;
parser.add_new_tokens(input);
let ast = parser.parse();
comp.map(|comp| {
@@ -115,13 +127,13 @@ fn parsing(input: Vec<tokenizing::Token>, handle: &mut Schala, comp: Option<&mut
};
comp.add_artifact(debug_info);
});
ast.map_err(|err| format_parse_error(err, handle))
ast.map_err(|err| format_parse_error(err, &handle.source_reference))
}
fn format_parse_error(error: parsing::ParseError, handle: &mut Schala) -> String {
let line_num = error.token.line_num;
let ch = error.token.char_num;
let line_from_program = handle.source_reference.get_line(line_num);
fn format_parse_error(error: parsing::ParseError, source_reference: &SourceReference) -> String {
let line_num = error.token.location.line_num;
let ch = error.token.location.char_num;
let line_from_program = source_reference.get_line(line_num);
let location_pointer = format!("{}^", " ".repeat(ch));
let line_num_digits = format!("{}", line_num).chars().count();
@@ -151,9 +163,7 @@ fn symbol_table(input: ast::AST, handle: &mut Schala, comp: Option<&mut PassDebu
}
fn scope_resolution(mut input: ast::AST, handle: &mut Schala, _com: Option<&mut PassDebugArtifact>) -> Result<ast::AST, String> {
let mut symbol_table = handle.symbol_table.borrow_mut();
let mut resolver = crate::scope_resolution::ScopeResolver::new(&mut symbol_table);
let () = resolver.resolve(&mut input)?;
let () = handle.resolver.resolve(&mut input)?;
Ok(input)
}
@@ -309,6 +319,8 @@ impl ProgrammingLanguageInterface for Schala {
total_duration, stage_durations
};
let main_output = parser::perform_parsing(source);
ComputationResponse {
main_output,
global_output_stats,

View File

@@ -1,32 +1,43 @@
use std::rc::Rc;
use crate::symbol_table::{SymbolTable, ScopeSegment, ScopeSegmentKind, FullyQualifiedSymbolName};
use crate::schala::SymbolTableHandle;
use crate::symbol_table::{ScopeSegment, FullyQualifiedSymbolName};
use crate::ast::*;
use crate::util::ScopeStack;
type FQSNPrefix = Vec<ScopeSegment>;
type NameScopeStack<'t> = ScopeStack<'t, Rc<String>, FQSNPrefix>;
pub struct ScopeResolver<'a> {
symbol_table: &'a mut SymbolTable,
symbol_table_handle: SymbolTableHandle,
name_scope_stack: ScopeStack<'a, Rc<String>, FQSNPrefix>,
}
impl<'a> ASTVisitor for ScopeResolver<'a> {
//TODO need to un-insert these - maybe need to rethink visitor
fn import(&mut self, import_spec: &ImportSpecifier) {
let ref symbol_table = self.symbol_table_handle.borrow();
let ImportSpecifier { ref path_components, ref imported_names, .. } = &import_spec;
match imported_names {
ImportedNames::All => unimplemented!(),
ImportedNames::All => {
let prefix = FullyQualifiedSymbolName(path_components.iter().map(|c| ScopeSegment {
name: c.clone(),
}).collect());
let members = symbol_table.lookup_children_of_fqsn(&prefix);
for member in members.into_iter() {
let local_name = member.0.last().unwrap().name.clone();
self.name_scope_stack.insert(local_name.clone(), member.0);
}
},
ImportedNames::LastOfPath => {
let name = path_components.last().unwrap(); //TODO handle better
let fqsn_prefix = path_components.iter().map(|c| ScopeSegment {
name: c.clone(), kind: ScopeSegmentKind::Type
name: c.clone(),
}).collect();
self.name_scope_stack.insert(name.clone(), fqsn_prefix);
}
ImportedNames::List(ref names) => {
let fqsn_prefix: FQSNPrefix = path_components.iter().map(|c| ScopeSegment {
name: c.clone(), kind: ScopeSegmentKind::Type
name: c.clone(),
}).collect();
for name in names.iter() {
self.name_scope_stack.insert(name.clone(), fqsn_prefix.clone());
@@ -36,15 +47,17 @@ impl<'a> ASTVisitor for ScopeResolver<'a> {
}
fn qualified_name(&mut self, qualified_name: &QualifiedName) {
let ref mut symbol_table = self.symbol_table_handle.borrow_mut();
let fqsn = self.lookup_name_in_scope(&qualified_name);
let ref id = qualified_name.id;
self.symbol_table.map_id_to_fqsn(id, fqsn);
symbol_table.map_id_to_fqsn(id, fqsn);
}
fn named_struct(&mut self, name: &QualifiedName, _fields: &Vec<(Rc<String>, Expression)>) {
let ref mut symbol_table = self.symbol_table_handle.borrow_mut();
let ref id = name.id;
let fqsn = self.lookup_name_in_scope(&name);
self.symbol_table.map_id_to_fqsn(id, fqsn);
symbol_table.map_id_to_fqsn(id, fqsn);
}
fn pattern(&mut self, pat: &Pattern) {
@@ -61,36 +74,38 @@ impl<'a> ASTVisitor for ScopeResolver<'a> {
}
impl<'a> ScopeResolver<'a> {
pub fn new(symbol_table: &'a mut SymbolTable) -> ScopeResolver {
pub fn new(symbol_table_handle: SymbolTableHandle) -> ScopeResolver<'static> {
let name_scope_stack = ScopeStack::new(None);
ScopeResolver { symbol_table, name_scope_stack }
ScopeResolver { symbol_table_handle, name_scope_stack }
}
pub fn resolve(&mut self, ast: &mut AST) -> Result<(), String> {
walk_ast(self, ast);
Ok(())
}
//TODO this is incomplete
fn lookup_name_in_scope(&self, sym_name: &QualifiedName) -> FullyQualifiedSymbolName {
let QualifiedName { components: vec, .. } = sym_name;
let len = vec.len();
let new_vec: Vec<ScopeSegment> = vec.iter().enumerate().map(|(i, name)| {
let kind = if i == (len - 1) {
ScopeSegmentKind::Terminal
} else {
ScopeSegmentKind::Type
};
ScopeSegment { name: name.clone(), kind }
}).collect();
FullyQualifiedSymbolName(new_vec)
let QualifiedName { components, .. } = sym_name;
let first_component = &components[0];
match self.name_scope_stack.lookup(first_component) {
None => {
FullyQualifiedSymbolName(components.iter().map(|name| ScopeSegment { name: name.clone() }).collect())
},
Some(fqsn_prefix) => {
let mut full_name = fqsn_prefix.clone();
let rest_of_name: FQSNPrefix = components[1..].iter().map(|name| ScopeSegment { name: name.clone() }).collect();
full_name.extend_from_slice(&rest_of_name);
FullyQualifiedSymbolName(full_name)
}
}
}
/// this might be a variable or a pattern. if a variable, set to none
fn qualified_name_in_pattern(&mut self, qualified_name: &QualifiedName) {
let ref mut symbol_table = self.symbol_table_handle.borrow_mut();
let ref id = qualified_name.id;
let fqsn = self.lookup_name_in_scope(qualified_name);
if self.symbol_table.lookup_by_fqsn(&fqsn).is_some() {
self.symbol_table.map_id_to_fqsn(&id, fqsn);
if symbol_table.lookup_by_fqsn(&fqsn).is_some() {
symbol_table.map_id_to_fqsn(&id, fqsn);
}
}
}

View File

@@ -0,0 +1,39 @@
use std::collections::HashMap;
use std::fmt;
use crate::ast::ItemId;
pub type LineNumber = usize;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Location {
pub line_num: LineNumber,
pub char_num: usize,
}
impl fmt::Display for Location {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}:{}", self.line_num, self.char_num)
}
}
pub struct SourceMap {
map: HashMap<ItemId, Location>
}
impl SourceMap {
pub fn new() -> SourceMap {
SourceMap { map: HashMap::new() }
}
pub fn add_location(&mut self, id: &ItemId, loc: Location) {
self.map.insert(id.clone(), loc);
}
pub fn lookup(&self, id: &ItemId) -> Option<Location> {
match self.map.get(id) {
Some(loc) => Some(loc.clone()),
None => None
}
}
}

View File

@@ -4,12 +4,59 @@ use std::rc::Rc;
use std::fmt;
use std::fmt::Write;
use crate::schala::SourceMapHandle;
use crate::source_map::{SourceMap, LineNumber};
use crate::ast;
use crate::ast::{ItemId, TypeBody, TypeSingletonName, Signature, Statement, StatementKind};
use crate::ast::{ItemId, TypeBody, TypeSingletonName, Signature, Statement, StatementKind, ModuleSpecifier};
use crate::typechecking::TypeName;
type LineNumber = u32;
type SymbolTrackTable = HashMap<Rc<String>, LineNumber>;
#[allow(unused_macros)]
macro_rules! fqsn {
( $( $name:expr ; $kind:tt),* ) => {
{
let mut vec = vec![];
$(
vec.push(crate::symbol_table::ScopeSegment::new(std::rc::Rc::new($name.to_string())));
)*
FullyQualifiedSymbolName(vec)
}
};
}
mod symbol_trie;
use symbol_trie::SymbolTrie;
mod test;
/// Keeps track of what names were used in a given namespace. Call try_register to add a name to
/// the table, or report an error if a name already exists.
struct DuplicateNameTrackTable {
table: HashMap<Rc<String>, LineNumber>,
}
impl DuplicateNameTrackTable {
fn new() -> DuplicateNameTrackTable {
DuplicateNameTrackTable { table: HashMap::new() }
}
fn try_register(&mut self, name: &Rc<String>, id: &ItemId, source_map: &SourceMap) -> Result<(), LineNumber> {
match self.table.entry(name.clone()) {
Entry::Occupied(o) => {
let line_number = o.get();
Err(*line_number)
},
Entry::Vacant(v) => {
let line_number = if let Some(loc) = source_map.lookup(id) {
loc.line_num
} else {
0
};
v.insert(line_number);
Ok(())
}
}
}
}
#[derive(PartialEq, Eq, Hash, Debug, Clone, PartialOrd, Ord)]
pub struct FullyQualifiedSymbolName(pub Vec<ScopeSegment>);
@@ -27,71 +74,37 @@ impl fmt::Display for FullyQualifiedSymbolName {
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct ScopeSegment {
pub name: Rc<String>, //TODO maybe this could be a &str, for efficiency?
pub kind: ScopeSegmentKind,
}
impl fmt::Display for ScopeSegment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use colored::*;
use ScopeSegmentKind::*;
let kind = match self.kind {
Function => "[fn]".green(),
Type => "[ty]".red(),
Terminal => "[tr]".blue(),
};
let kind = ""; //TODO implement some kind of kind-tracking here
write!(f, "{}{}", self.name, kind)
}
}
impl ScopeSegment {
pub fn new(name: Rc<String>, kind: ScopeSegmentKind) -> ScopeSegment {
ScopeSegment { name, kind }
pub fn new(name: Rc<String>) -> ScopeSegment {
ScopeSegment { name }
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub enum ScopeSegmentKind {
Function,
Type,
Terminal,
//Module
}
#[allow(unused_macros)]
macro_rules! fqsn {
( $( $name:expr ; $kind:tt),* ) => {
{
let mut vec = vec![];
$(
vec.push(ScopeSegment::new(
Rc::new($name.to_string()),
sym_path_kind!($kind),
));
)*
FullyQualifiedSymbolName(vec)
}
};
}
#[allow(unused_macros)]
macro_rules! sym_path_kind {
(fn) => { ScopeSegmentKind::Function };
(ty) => { ScopeSegmentKind::Type };
(tr) => { ScopeSegmentKind::Terminal };
}
//cf. p. 150 or so of Language Implementation Patterns
pub struct SymbolTable {
source_map_handle: SourceMapHandle,
symbol_path_to_symbol: HashMap<FullyQualifiedSymbolName, Symbol>,
id_to_fqsn: HashMap<ItemId, FullyQualifiedSymbolName>,
symbol_trie: SymbolTrie,
}
//TODO add various types of lookups here, maybe multiple hash tables internally?
impl SymbolTable {
pub fn new() -> SymbolTable {
pub fn new(source_map_handle: SourceMapHandle) -> SymbolTable {
SymbolTable {
source_map_handle,
symbol_path_to_symbol: HashMap::new(),
id_to_fqsn: HashMap::new(),
symbol_trie: SymbolTrie::new()
}
}
@@ -105,15 +118,20 @@ impl SymbolTable {
fn add_new_symbol(&mut self, local_name: &Rc<String>, scope_path: &Vec<ScopeSegment>, spec: SymbolSpec) {
let mut vec: Vec<ScopeSegment> = scope_path.clone();
vec.push(ScopeSegment { name: local_name.clone(), kind: ScopeSegmentKind::Terminal });
vec.push(ScopeSegment { name: local_name.clone() });
let fully_qualified_name = FullyQualifiedSymbolName(vec);
let symbol = Symbol { local_name: local_name.clone(), fully_qualified_name: fully_qualified_name.clone(), spec };
self.symbol_trie.insert(&fully_qualified_name);
self.symbol_path_to_symbol.insert(fully_qualified_name, symbol);
}
pub fn lookup_by_fqsn(&self, fully_qualified_path: &FullyQualifiedSymbolName) -> Option<&Symbol> {
self.symbol_path_to_symbol.get(fully_qualified_path)
}
pub fn lookup_children_of_fqsn(&self, path: &FullyQualifiedSymbolName) -> Vec<FullyQualifiedSymbolName> {
self.symbol_trie.get_children(path)
}
}
#[derive(Debug)]
@@ -142,7 +160,10 @@ pub enum SymbolSpec {
members: HashMap<Rc<String>, TypeName>,
type_name: TypeName,
},
Binding
Binding,
Type {
name: TypeName
},
}
impl fmt::Display for SymbolSpec {
@@ -153,6 +174,7 @@ impl fmt::Display for SymbolSpec {
DataConstructor { index, type_name, type_args } => write!(f, "DataConstructor(idx: {})({:?} -> {})", index, type_args, type_name),
RecordConstructor { type_name, index, ..} => write!(f, "RecordConstructor(idx: {})(<members> -> {})", index, type_name),
Binding => write!(f, "Binding"),
Type { name } => write!(f, "Type <{}>", name),
}
}
}
@@ -169,51 +191,51 @@ impl SymbolTable {
fn add_symbols_from_scope<'a>(&'a mut self, statements: &Vec<Statement>, scope_name_stack: &mut Vec<ScopeSegment>) -> Result<(), String> {
use self::ast::Declaration::*;
fn insert_and_check_duplicate_symbol(table: &mut SymbolTrackTable, name: &Rc<String>) -> Result<(), String> {
match table.entry(name.clone()) {
Entry::Occupied(o) => {
let line_number = o.get(); //TODO make this actually work
Err(format!("Duplicate definition: {}. It's already defined at {}", name, line_number))
},
Entry::Vacant(v) => {
let line_number = 0; //TODO should work
v.insert(line_number);
Ok(())
}
}
}
let mut seen_identifiers: SymbolTrackTable = HashMap::new();
let mut seen_identifiers = DuplicateNameTrackTable::new();
let mut seen_modules = DuplicateNameTrackTable::new();
for statement in statements.iter() {
if let Statement { kind: StatementKind::Declaration(decl), .. } = statement {
match decl {
FuncSig(ref signature) => {
insert_and_check_duplicate_symbol(&mut seen_identifiers, &signature.name)?;
self.add_function_signature(signature, scope_name_stack)?
match statement {
Statement { kind: StatementKind::Declaration(decl), id } => {
match decl {
FuncSig(ref signature) => {
seen_identifiers.try_register(&signature.name, &id, &self.source_map_handle.borrow())
.map_err(|line| format!("Duplicate function definition: {}. It's already defined at {}", signature.name, line))?;
self.add_function_signature(signature, scope_name_stack)?
}
FuncDecl(ref signature, ref body) => {
seen_identifiers.try_register(&signature.name, &id, &self.source_map_handle.borrow())
.map_err(|line| format!("Duplicate function definition: {}. It's already defined at {}", signature.name, line))?;
self.add_function_signature(signature, scope_name_stack)?;
scope_name_stack.push(ScopeSegment{
name: signature.name.clone(),
});
let output = self.add_symbols_from_scope(body, scope_name_stack);
scope_name_stack.pop();
output?
},
TypeDecl { name, body, mutable } => {
seen_identifiers.try_register(&name.name, &id, &self.source_map_handle.borrow())
.map_err(|line| format!("Duplicate type definition: {}. It's already defined at {}", name.name, line))?;
self.add_type_decl(name, body, mutable, scope_name_stack)?
},
Binding { name, .. } => {
seen_identifiers.try_register(&name, &id, &self.source_map_handle.borrow())
.map_err(|line| format!("Duplicate variable definition: {}. It's already defined at {}", name, line))?;
self.add_new_symbol(name, scope_name_stack, SymbolSpec::Binding);
}
_ => ()
}
FuncDecl(ref signature, ref body) => {
insert_and_check_duplicate_symbol(&mut seen_identifiers, &signature.name)?;
self.add_function_signature(signature, scope_name_stack)?;
scope_name_stack.push(ScopeSegment{
name: signature.name.clone(),
kind: ScopeSegmentKind::Function,
});
let output = self.add_symbols_from_scope(body, scope_name_stack);
let _ = scope_name_stack.pop();
output?
},
TypeDecl { name, body, mutable } => {
insert_and_check_duplicate_symbol(&mut seen_identifiers, &name.name)?;
//TODO add ScopeSegmentKind::Type here
self.add_type_decl(name, body, mutable, scope_name_stack)?
},
Binding { name, .. } => {
insert_and_check_duplicate_symbol(&mut seen_identifiers, name)?;
self.add_new_symbol(name, scope_name_stack, SymbolSpec::Binding);
}
_ => ()
}
},
Statement { kind: StatementKind::Module(ModuleSpecifier { name, contents}), id } => {
seen_modules.try_register(&name, &id, &self.source_map_handle.borrow())
.map_err(|line| format!("Duplicate module definition: {}. It's already defined at {}", name, line))?;
scope_name_stack.push(ScopeSegment { name: name.clone() });
let output = self.add_symbols_from_scope(contents, scope_name_stack);
scope_name_stack.pop();
output?
},
_ => ()
}
}
Ok(())
@@ -243,9 +265,15 @@ impl SymbolTable {
use crate::ast::{TypeIdentifier, Variant};
let TypeBody(variants) = body;
let ref type_name = type_name.name;
let type_spec = SymbolSpec::Type {
name: type_name.clone(),
};
self.add_new_symbol(type_name, &scope_name_stack, type_spec);
scope_name_stack.push(ScopeSegment{
name: type_name.clone(),
kind: ScopeSegmentKind::Type,
});
//TODO figure out why _params isn't being used here
for (index, var) in variants.iter().enumerate() {
@@ -313,156 +341,3 @@ impl LocalTypeContext {
}
}
#[cfg(test)]
mod symbol_table_tests {
use super::*;
use crate::util::quick_ast;
macro_rules! values_in_table {
//TODO multiple values
($source:expr, $single_value:expr) => {
{
let mut symbol_table = SymbolTable::new();
let ast = quick_ast($source);
symbol_table.add_top_level_symbols(&ast).unwrap();
match symbol_table.lookup_by_fqsn($single_value) {
Some(_spec) => (),
None => panic!(),
};
}
}
}
#[test]
fn basic_symbol_table() {
values_in_table! { "let a = 10; fn b() { 20 }", &fqsn!("b"; tr) }
}
#[test]
fn no_duplicates() {
let source = r#"
fn a() { 1 }
fn b() { 2 }
fn a() { 3 }
"#;
let mut symbol_table = SymbolTable::new();
let ast = quick_ast(source);
let output = symbol_table.add_top_level_symbols(&ast).unwrap_err();
assert!(output.contains("Duplicate"))
}
#[test]
fn no_duplicates_2() {
let source = r#"
let a = 20;
let q = 39;
let a = 30;
"#;
let mut symbol_table = SymbolTable::new();
let ast = quick_ast(source);
let output = symbol_table.add_top_level_symbols(&ast).unwrap_err();
assert!(output.contains("Duplicate"))
}
#[test]
fn no_duplicates_3() {
let source = r#"
fn a() {
let a = 20
let b = 40
a + b
}
fn q() {
let x = 30
let x = 33
}
"#;
let mut symbol_table = SymbolTable::new();
let ast = quick_ast(source);
let output = symbol_table.add_top_level_symbols(&ast).unwrap_err();
assert!(output.contains("Duplicate"))
}
#[test]
fn dont_falsely_detect_duplicates() {
let source = r#"
let a = 20;
fn some_func() {
let a = 40;
77
}
let q = 39;
"#;
let mut symbol_table = SymbolTable::new();
let ast = quick_ast(source);
symbol_table.add_top_level_symbols(&ast).unwrap();
assert!(symbol_table.lookup_by_fqsn(&fqsn!["a"; tr]).is_some());
assert!(symbol_table.lookup_by_fqsn(&fqsn!["some_func"; fn, "a";tr]).is_some());
}
#[test]
fn enclosing_scopes() {
let source = r#"
fn outer_func(x) {
fn inner_func(arg) {
arg
}
x + inner_func(x)
}"#;
let mut symbol_table = SymbolTable::new();
let ast = quick_ast(source);
symbol_table.add_top_level_symbols(&ast).unwrap();
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; tr)).is_some());
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "inner_func"; tr)).is_some());
}
#[test]
fn enclosing_scopes_2() {
let source = r#"
fn outer_func(x) {
fn inner_func(arg) {
arg
}
fn second_inner_func() {
fn another_inner_func() {
}
}
inner_func(x)
}"#;
let mut symbol_table = SymbolTable::new();
let ast = quick_ast(source);
symbol_table.add_top_level_symbols(&ast).unwrap();
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; tr)).is_some());
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "inner_func"; tr)).is_some());
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "second_inner_func"; tr)).is_some());
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "second_inner_func"; fn, "another_inner_func"; tr)).is_some());
}
#[test]
fn enclosing_scopes_3() {
let source = r#"
fn outer_func(x) {
fn inner_func(arg) {
arg
}
fn second_inner_func() {
fn another_inner_func() {
}
fn another_inner_func() {
}
}
inner_func(x)
}"#;
let mut symbol_table = SymbolTable::new();
let ast = quick_ast(source);
let output = symbol_table.add_top_level_symbols(&ast).unwrap_err();
assert!(output.contains("Duplicate"))
}
}

View File

@@ -0,0 +1,51 @@
use radix_trie::{Trie, TrieCommon, TrieKey};
use super::FullyQualifiedSymbolName;
use std::hash::{Hasher, Hash};
use std::collections::hash_map::DefaultHasher;
#[derive(Debug)]
pub struct SymbolTrie(Trie<FullyQualifiedSymbolName, ()>);
impl TrieKey for FullyQualifiedSymbolName {
fn encode_bytes(&self) -> Vec<u8> {
let mut hasher = DefaultHasher::new();
let mut output = vec![];
let FullyQualifiedSymbolName(scopes) = self;
for segment in scopes.iter() {
segment.name.as_bytes().hash(&mut hasher);
output.extend_from_slice(&hasher.finish().to_be_bytes());
}
output
}
}
impl SymbolTrie {
pub fn new() -> SymbolTrie {
SymbolTrie(Trie::new())
}
pub fn insert(&mut self, fqsn: &FullyQualifiedSymbolName) {
self.0.insert(fqsn.clone(), ());
}
pub fn get_children(&self, fqsn: &FullyQualifiedSymbolName) -> Vec<FullyQualifiedSymbolName> {
let subtrie = match self.0.subtrie(fqsn) {
Some(s) => s,
None => return vec![]
};
let output: Vec<FullyQualifiedSymbolName> = subtrie.keys().filter(|cur_key| **cur_key != *fqsn).map(|fqsn| fqsn.clone()).collect();
output
}
}
#[test]
fn test_trie_insertion() {
let mut trie = SymbolTrie::new();
trie.insert(&fqsn!("unrelated"; ty, "thing"; tr));
trie.insert(&fqsn!("outer"; ty, "inner"; tr));
trie.insert(&fqsn!("outer"; ty, "inner"; ty, "still_inner"; tr));
let children = trie.get_children(&fqsn!("outer"; ty, "inner"; tr));
assert_eq!(children.len(), 1);
}

View File

@@ -0,0 +1,193 @@
#![cfg(test)]
use std::cell::RefCell;
use std::rc::Rc;
use super::*;
use crate::util::quick_ast;
fn add_symbols_from_source(src: &str) -> (SymbolTable, Result<(), String>) {
let (ast, source_map) = quick_ast(src);
let source_map = Rc::new(RefCell::new(source_map));
let mut symbol_table = SymbolTable::new(source_map);
let result = symbol_table.add_top_level_symbols(&ast);
(symbol_table, result)
}
macro_rules! values_in_table {
($source:expr, $single_value:expr) => {
values_in_table!($source => $single_value);
};
($source:expr => $( $value:expr ),* ) => {
{
let (symbol_table, _) = add_symbols_from_source($source);
$(
match symbol_table.lookup_by_fqsn($value) {
Some(_spec) => (),
None => panic!(),
};
)*
}
};
}
#[test]
fn basic_symbol_table() {
values_in_table! { "let a = 10; fn b() { 20 }", &fqsn!("b"; tr) };
values_in_table! { "type Option<T> = Some(T) | None" =>
&fqsn!("Option"; tr),
&fqsn!("Option"; ty, "Some"; tr),
&fqsn!("Option"; ty, "None"; tr) };
}
#[test]
fn no_function_definition_duplicates() {
let source = r#"
fn a() { 1 }
fn b() { 2 }
fn a() { 3 }
"#;
let (_, output) = add_symbols_from_source(source);
assert!(output.unwrap_err().contains("Duplicate function definition: a"))
}
#[test]
fn no_variable_definition_duplicates() {
let source = r#"
let x = 9
let a = 20
let q = 39
let a = 30
"#;
let (_, output) = add_symbols_from_source(source);
let output = output.unwrap_err();
assert!(output.contains("Duplicate variable definition: a"));
assert!(output.contains("already defined at 2"));
}
#[test]
fn no_variable_definition_duplicates_in_function() {
let source = r#"
fn a() {
let a = 20
let b = 40
a + b
}
fn q() {
let a = 29
let x = 30
let x = 33
}
"#;
let (_, output) = add_symbols_from_source(source);
assert!(output.unwrap_err().contains("Duplicate variable definition: x"))
}
#[test]
fn dont_falsely_detect_duplicates() {
let source = r#"
let a = 20;
fn some_func() {
let a = 40;
77
}
let q = 39;
"#;
let (symbol_table, _) = add_symbols_from_source(source);
assert!(symbol_table.lookup_by_fqsn(&fqsn!["a"; tr]).is_some());
assert!(symbol_table.lookup_by_fqsn(&fqsn!["some_func"; fn, "a";tr]).is_some());
}
#[test]
fn enclosing_scopes() {
let source = r#"
fn outer_func(x) {
fn inner_func(arg) {
arg
}
x + inner_func(x)
}"#;
let (symbol_table, _) = add_symbols_from_source(source);
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; tr)).is_some());
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "inner_func"; tr)).is_some());
}
#[test]
fn enclosing_scopes_2() {
let source = r#"
fn outer_func(x) {
fn inner_func(arg) {
arg
}
fn second_inner_func() {
fn another_inner_func() {
}
}
inner_func(x)
}"#;
let (symbol_table, _) = add_symbols_from_source(source);
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; tr)).is_some());
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "inner_func"; tr)).is_some());
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "second_inner_func"; tr)).is_some());
assert!(symbol_table.lookup_by_fqsn(&fqsn!("outer_func"; fn, "second_inner_func"; fn, "another_inner_func"; tr)).is_some());
}
#[test]
fn enclosing_scopes_3() {
let source = r#"
fn outer_func(x) {
fn inner_func(arg) {
arg
}
fn second_inner_func() {
fn another_inner_func() {
}
fn another_inner_func() {
}
}
inner_func(x)
}"#;
let (_, output) = add_symbols_from_source(source);
assert!(output.unwrap_err().contains("Duplicate"))
}
#[test]
fn modules() {
let source = r#"
module stuff {
fn item() {
}
}
fn item()
"#;
values_in_table! { source =>
&fqsn!("item"; tr),
&fqsn!("stuff"; tr, "item"; tr)
};
}
#[test]
fn duplicate_modules() {
let source = r#"
module q {
fn foo() { 4 }
}
module a {
fn foo() { 334 }
}
module a {
fn foo() { 256.1 }
}
"#;
let (_, output) = add_symbols_from_source(source);
let output = output.unwrap_err();
assert!(output.contains("Duplicate module"));
assert!(output.contains("already defined at 5"));
}

View File

@@ -4,6 +4,8 @@ use std::rc::Rc;
use std::iter::{Iterator, Peekable};
use std::fmt;
use crate::source_map::Location;
#[derive(Debug, PartialEq, Clone)]
pub enum TokenKind {
Newline, Semicolon,
@@ -19,7 +21,10 @@ pub enum TokenKind {
Operator(Rc<String>),
DigitGroup(Rc<String>), HexLiteral(Rc<String>), BinNumberSigil,
StrLiteral(Rc<String>),
StrLiteral {
s: Rc<String>,
prefix: Option<Rc<String>>
},
Identifier(Rc<String>),
Keyword(Kw),
@@ -35,7 +40,7 @@ impl fmt::Display for TokenKind {
&Operator(ref s) => write!(f, "Operator({})", **s),
&DigitGroup(ref s) => write!(f, "DigitGroup({})", s),
&HexLiteral(ref s) => write!(f, "HexLiteral({})", s),
&StrLiteral(ref s) => write!(f, "StrLiteral({})", s),
&StrLiteral {ref s, .. } => write!(f, "StrLiteral({})", s),
&Identifier(ref s) => write!(f, "Identifier({})", s),
&Error(ref s) => write!(f, "Error({})", s),
other => write!(f, "{:?}", other),
@@ -86,11 +91,10 @@ lazy_static! {
};
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq)]
pub struct Token {
pub kind: TokenKind,
pub line_num: usize,
pub char_num: usize
pub location: Location,
}
impl Token {
@@ -101,7 +105,7 @@ impl Token {
}
}
pub fn to_string_with_metadata(&self) -> String {
format!("{}(L:{},c:{})", self.kind, self.line_num, self.char_num)
format!("{}({})", self.kind, self.location)
}
pub fn get_kind(&self) -> TokenKind {
@@ -162,14 +166,15 @@ pub fn tokenize(input: &str) -> Vec<Token> {
'(' => LParen, ')' => RParen,
'{' => LCurlyBrace, '}' => RCurlyBrace,
'[' => LSquareBracket, ']' => RSquareBracket,
'"' => handle_quote(&mut input),
'"' => handle_quote(&mut input, None),
'\\' => Backslash,
c if c.is_digit(10) => handle_digit(c, &mut input),
c if c.is_alphabetic() || c == '_' => handle_alphabetic(c, &mut input),
c if is_operator(&c) => handle_operator(c, &mut input),
unknown => Error(format!("Unexpected character: {}", unknown)),
};
tokens.push(Token { kind: cur_tok_kind, line_num, char_num });
let location = Location { line_num, char_num };
tokens.push(Token { kind: cur_tok_kind, location });
}
tokens
}
@@ -189,7 +194,7 @@ fn handle_digit(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) ->
}
}
fn handle_quote(input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
fn handle_quote(input: &mut Peekable<impl Iterator<Item=CharData>>, quote_prefix: Option<&str>) -> TokenKind {
let mut buf = String::new();
loop {
match input.next().map(|(_, _, c)| { c }) {
@@ -211,7 +216,7 @@ fn handle_quote(input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind
None => return TokenKind::Error(format!("Unclosed string")),
}
}
TokenKind::StrLiteral(Rc::new(buf))
TokenKind::StrLiteral { s: Rc::new(buf), prefix: quote_prefix.map(|s| Rc::new(s.to_string())) }
}
fn handle_alphabetic(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
@@ -223,6 +228,10 @@ fn handle_alphabetic(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>
loop {
match input.peek().map(|&(_, _, c)| { c }) {
Some(c) if c == '"' => {
input.next();
return handle_quote(input, Some(&buf));
},
Some(c) if c.is_alphanumeric() || c == '_' => {
input.next();
buf.push(c);
@@ -323,4 +332,13 @@ mod schala_tokenizer_tests {
let token_kinds: Vec<TokenKind> = tokenize("1 `plus` 2").into_iter().map(move |t| t.kind).collect();
assert_eq!(token_kinds, vec![digit!("1"), op!("plus"), digit!("2")]);
}
#[test]
fn string_literals() {
let token_kinds: Vec<TokenKind> = tokenize(r#""some string""#).into_iter().map(move |t| t.kind).collect();
assert_eq!(token_kinds, vec![StrLiteral { s: Rc::new("some string".to_string()), prefix: None }]);
let token_kinds: Vec<TokenKind> = tokenize(r#"b"some bytestring""#).into_iter().map(move |t| t.kind).collect();
assert_eq!(token_kinds, vec![StrLiteral { s: Rc::new("some bytestring".to_string()), prefix: Some(Rc::new("b".to_string())) }]);
}
}

View File

@@ -14,6 +14,7 @@ pub struct TypeData {
}
impl TypeData {
#[allow(dead_code)]
pub fn new() -> TypeData {
TypeData { ty: None }
}
@@ -276,6 +277,7 @@ impl<'a> TypeContext<'a> {
StatementKind::Expression(e) => self.expr(e),
StatementKind::Declaration(decl) => self.decl(&decl),
StatementKind::Import(_) => Ok(ty!(Unit)),
StatementKind::Module(_) => Ok(ty!(Unit)),
}
}
@@ -460,7 +462,7 @@ mod typechecking_tests {
macro_rules! assert_type_in_fresh_context {
($string:expr, $type:expr) => {
let mut tc = TypeContext::new();
let ref ast = crate::util::quick_ast($string);
let (ref ast, _) = crate::util::quick_ast($string);
let ty = tc.typecheck(ast).unwrap();
assert_eq!(ty, $type)
}

View File

@@ -48,10 +48,18 @@ impl<'a, T, V> ScopeStack<'a, T, V> where T: Hash + Eq {
/// this is intended for use in tests, and does no error-handling whatsoever
#[allow(dead_code)]
pub fn quick_ast(input: &str) -> crate::ast::AST {
pub fn quick_ast(input: &str) -> (crate::ast::AST, crate::source_map::SourceMap) {
use std::cell::RefCell;
use std::rc::Rc;
let source_map = crate::source_map::SourceMap::new();
let source_map_handle = Rc::new(RefCell::new(source_map));
let tokens = crate::tokenizing::tokenize(input);
let mut parser = crate::parsing::Parser::new(tokens);
parser.parse().unwrap()
let mut parser = crate::parsing::Parser::new(source_map_handle.clone());
parser.add_new_tokens(tokens);
let output = parser.parse();
std::mem::drop(parser);
(output.unwrap(), Rc::try_unwrap(source_map_handle).map_err(|_| ()).unwrap().into_inner())
}
#[allow(unused_macros)]

View File

@@ -1,5 +1,4 @@
#![feature(link_args)]
#![feature(slice_patterns, box_patterns, box_syntax, proc_macro_hygiene, decl_macro)]
#![feature(link_args, box_patterns, box_syntax, proc_macro_hygiene, decl_macro)]
#![feature(plugin)]
extern crate getopts;
extern crate linefeed;

View File

@@ -49,8 +49,8 @@ impl CommandTree {
}
pub fn get_help(&self) -> &str {
match self {
CommandTree::Terminal { help_msg, ..} => help_msg.as_ref().map(|s| s.as_str()).unwrap_or(""),
CommandTree::NonTerminal { help_msg, .. } => help_msg.as_ref().map(|s| s.as_str()).unwrap_or(""),
CommandTree::Terminal { help_msg, ..} => help_msg.as_ref().map(|s| s.as_str()).unwrap_or("<no help text provided>"),
CommandTree::NonTerminal { help_msg, .. } => help_msg.as_ref().map(|s| s.as_str()).unwrap_or("<no help text provided>"),
CommandTree::Top(_) => ""
}
}

View File

@@ -13,7 +13,12 @@ pub fn help(repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput {
None => format!("Directive `{}` not found", commands.last().unwrap()),
Some(dir) => {
let mut buf = String::new();
writeln!(buf, "`{}` - {}", dir.get_cmd(), dir.get_help()).unwrap();
let cmd = dir.get_cmd();
let children = dir.get_children();
writeln!(buf, "`{}` - {}", cmd, dir.get_help()).unwrap();
for sub in children.iter() {
writeln!(buf, "\t`{} {}` - {}", cmd, sub.get_cmd(), sub.get_help()).unwrap();
}
buf
}
})