358 Commits

Author SHA1 Message Date
greg
5147a32c97 Handle FQSNs at reduced_eval level 2019-10-25 19:28:29 -07: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
greg
92ad4767c8 Remove some extraneous code 2019-10-16 10:39:48 -07:00
greg
7cabca2987 Got all tests passing with visitor scope-resolver 2019-10-16 02:46:32 -07:00
greg
98e53a6d0f Start porting ScopeResolution to use Visitor pattern 2019-10-15 19:06:07 -07:00
greg
77cc1f3824 ASTVisitor imports 2019-10-15 19:03:27 -07:00
greg
9e64a22328 Invocation argument in visitor 2019-10-15 18:58:51 -07:00
greg
5afdc16f2e Still more visitor work 2019-10-15 03:51:36 -07:00
greg
f818e86f48 More visitor work 2019-10-15 00:53:21 -07:00
greg
5a01b12d9b Add note about pattern synonyms 2019-10-13 16:50:54 -07:00
greg
7c75f9b2a8 Extraneous comment 2019-10-11 18:45:52 -07:00
greg
2c34ab52c4 Make this test conform to new if syntax 2019-10-11 09:13:09 -07:00
greg
44d1f4692f Add back parser restrictions 2019-10-11 09:11:14 -07:00
greg
3cf3fce72d Fixed some code in scope resolver 2019-10-10 18:33:34 -07:00
greg
ddea470ba8 Parsing tests pass, eval ones fail 2019-10-10 18:17:59 -07:00
greg
745afe981a Got compilation working again 2019-10-10 17:50:20 -07:00
greg
a6c86d6447 Some work 2019-10-10 17:06:41 -07:00
greg
8d3639ab8e Fix everything if-refactor-related save reduced_ast 2019-10-10 14:38:48 -07:00
greg
3bca82a8c8 Still more refactoring work 2019-10-10 10:34:54 -07:00
greg
811c52c8d3 More if-expr refactoring work
Think I finished all the parsing stuff, just need to fix the types
everywhere else
2019-10-10 03:56:35 -07:00
greg
95e278d1b5 Chunk of work on if-expr AST
don't expect this to compile yet
2019-10-10 03:29:28 -07:00
greg
61b757313d Alter grammar of if-blocks 2019-10-10 02:34:56 -07:00
greg
24b48551dc More playing around with syntax for if 2019-10-09 02:32:41 -07:00
greg
2ed84de641 Introduce bare else clause in if exprs
With a non-passing test
2019-10-09 01:50:32 -07:00
greg
22efd39114 Change if-expr syntax
use else instead of ->
2019-10-08 18:23:16 -07:00
greg
a48bb61eb3 Get rid of this test
need to rethink how if-expressions should work
2019-10-05 16:41:51 -07:00
greg
904d5c4431 Add "production" line to parse debug output
And also add a .next() in the parser that should've been there
2019-10-04 03:12:09 -07:00
greg
28056b1f89 Add production name in ParseError
for debugging
2019-10-04 03:12:00 -07:00
greg
f9a59838b0 Get rid of .into()'s in parser 2019-10-01 02:19:12 -07:00
greg
f02d7cb924 Add test for failing if expression 2019-09-28 17:42:22 -07:00
greg
489819a28e Multiline prompt 2019-09-28 17:31:37 -07:00
greg
c427646e75 Change type alias 2019-09-28 02:42:18 -07:00
greg
f06b5922de Visitor cleanup 2019-09-28 02:37:36 -07:00
greg
253b5d88f0 Finish cleaning up visitor logic 2019-09-28 01:58:22 -07:00
greg
f654cd6b50 Start moving all walking logic out of visitor 2019-09-28 01:01:56 -07:00
greg
89649273d8 Still more visitor stuff 2019-09-27 22:34:00 -07:00
greg
9fa4e3797c More visitor stuff 2019-09-27 09:54:24 -07:00
greg
c8804eeefb More visitor stuff 2019-09-26 03:26:37 -07:00
greg
d80a0036b1 Enough of ASTVisitor to test something 2019-09-26 02:29:35 -07:00
greg
7533c69c49 Add note on visitors 2019-09-26 01:32:33 -07:00
greg
39bb175722 Initial WIP code 2019-09-26 01:31:39 -07:00
greg
ae65455374 Add type alias for name scope data structure 2019-09-25 03:26:31 -07:00
greg
1fc028c9fc Make lookup_name_in_scope a method 2019-09-25 03:18:54 -07:00
greg
031ff9fe7e Add top-level variable to schala prelude 2019-09-25 02:54:56 -07:00
greg
5a9f3c1850 Sort symbols in debug 2019-09-25 02:43:07 -07:00
greg
58251d3f28 Use colored in symbol table debug 2019-09-25 02:28:24 -07:00
greg
2e42313991 add_new_symbol clarification 2019-09-25 02:18:36 -07:00
greg
355604d911 Cargo.lock should be version-controlled 2019-09-25 01:54:14 -07:00
greg
0b57561114 Use block in scope resolution 2019-09-25 01:45:02 -07:00
greg
dbd81ca83d names 2019-09-24 19:24:07 -07:00
greg
6368d10d92 Rename Symbol.name -> Symbol.local_name
to make it clearer what this means
2019-09-24 18:56:53 -07:00
greg
9cd64d97a5 Isolate import handling code 2019-09-24 18:42:01 -07:00
greg
41cad61e34 Start work on name resolution 2019-09-24 03:28:59 -07:00
greg
a054de56a2 Import statement syntax 2019-09-21 02:30:28 -07:00
greg
603ea89b98 Start adding import keyword 2019-09-20 18:19:29 -07:00
greg
06026604cc Fix test 2019-09-20 12:14:15 -07:00
greg
03f8abac6a Remove Meta type 2019-09-20 12:03:42 -07:00
greg
fd3922d866 Get rid of Meta from tests 2019-09-20 10:10:57 -07:00
greg
71b3365de2 Remove all the rest of the instances of Meta from the AST
Still need to do tests
2019-09-20 02:21:39 -07:00
greg
cf9ce74394 still more meta's 2019-09-20 02:05:57 -07:00
greg
f5d1c89574 Kill more Meta's 2019-09-20 02:03:10 -07:00
greg
8d1e0ebdea Start to get rid of Meta 2019-09-20 01:57:48 -07:00
greg
69c215eac9 Get rid of Meta elsewhere 2019-09-20 01:44:20 -07:00
greg
8a34034819 Symbol table map for NamedStruct 2019-09-20 01:36:58 -07:00
greg
403b171c72 remove another meta-use 2019-09-20 01:08:00 -07:00
greg
e5a09a6ee8 Get rid of Meta use in reduce_named_struct 2019-09-19 18:38:15 -07:00
greg
e1a83b5de3 Start to use table lookups instead of Meta
For fqsn
2019-09-19 03:34:09 -07:00
greg
8b1dd561f2 Add get_fqsn_from_id opposite lookup method 2019-09-19 03:06:49 -07:00
greg
6ebe893acb Add id_to_fqsn table on symbol table 2019-09-19 02:58:52 -07:00
greg
c9052e0a3b QualifiedName with id 2019-09-19 01:34:21 -07:00
greg
56e6eb44f9 Finish adding ItemId to Expression 2019-09-18 14:15:05 -07:00
greg
642f21d298 WIP commit - adding ItemId to Expression 2019-09-18 10:09:33 -07:00
greg
c12cb99b24 ItemId on statement 2019-09-18 10:07:20 -07:00
greg
8dc8833eb3 Item Id store 2019-09-18 09:56:11 -07:00
greg
b517bc2366 Add ItemId type to AST 2019-09-18 02:15:45 -07:00
greg
73519d5be5 Add derivative crate 2019-09-18 01:58:38 -07:00
greg
8b6de6961f ItemId type 2019-09-18 01:52:43 -07:00
greg
3eaeeb5509 Begin deprecating Meta in favor of an ItemId 2019-09-17 14:32:15 -07:00
greg
b91c3c9da5 Change design of Statement AST node 2019-09-17 02:25:11 -07:00
greg
08da787aae Make AST a struct 2019-09-11 19:25:12 -07:00
greg
d6f2fe6e02 Mark TODO done 2019-09-11 01:28:33 -07:00
greg
a85d3c46bd Finish conversion of AST Reducer 2019-09-11 01:27:52 -07:00
greg
25f51a314d Start transitioning design of ast reduction
to method-on-struct based system
2019-09-10 09:27:33 -07:00
greg
6c3a4f907b Warning cleanup, TODOs 2019-09-10 03:40:41 -07:00
greg
22887678bd Remove lookup_by_name 2019-09-10 03:35:11 -07:00
greg
1ecf1e506c Update more notes 2019-09-10 03:33:28 -07:00
greg
72944ded1b Fixed all broken tests 2019-09-10 03:31:23 -07:00
greg
b65779fb93 Add symbol_table to scope_resolution 2019-09-09 18:12:14 -07:00
greg
418d77770f Start adding symbol_table to scope resolution 2019-09-09 17:45:34 -07:00
greg
5572e0eebb Make some notes about what to do next 2019-09-09 10:17:46 -07:00
greg
65bc32b033 Fixed many of the broken tests 2019-09-09 01:04:46 -07:00
greg
29f4060a71 VarOrName fix in reduced ast 2019-09-08 17:01:07 -07:00
greg
09dbe5b736 Rename function 2019-09-08 04:27:04 -07:00
greg
cfa65e5339 Wire up all the qualified names 2019-09-08 02:11:15 -07:00
greg
9a28ccfd85 Tests compile again 2019-09-07 19:08:50 -07:00
greg
ea542192be Temp qualified names work 2019-09-06 17:19:41 -07:00
greg
79635f2f86 Add Meta annotation to QualifiedName 2019-09-06 10:03:50 -07:00
greg
2b5b1589b0 tests compile, 15 fail 2019-09-06 02:30:18 -07:00
greg
44c073320b Code builds, tests don't 2019-09-06 02:23:04 -07:00
greg
c04e4356a1 Changing how patterns work
to support qualified names in patterns
2019-09-04 10:53:52 -07:00
greg
24e0ecbe73 partial work 2019-09-03 21:14:12 -07:00
greg
fd66a9711d More work on fully-qualified names 2019-09-03 10:23:38 -07:00
greg
a5c9aca4d7 Halfway done with fqsn lookup pass initial work 2019-09-03 03:20:17 -07:00
greg
cefaeb1180 Make ScopeResolver struct 2019-09-03 02:59:19 -07:00
greg
724237545f Start work on scope resolver 2019-09-03 02:19:37 -07:00
greg
0f7f5cb416 Add new stage scope-resolution 2019-09-03 01:42:28 -07:00
greg
b4da57f5c5 Make Meta<Expression> exist everywhere it needs to 2019-09-02 14:41:09 -07:00
greg
8b87945bee Wrap remaining Expressions in Meta 2019-09-02 14:13:53 -07:00
greg
f96469178d Tests for qualified names 2019-09-01 01:07:00 -07:00
greg
34abb9b081 Start work on qualified names 2019-08-31 23:39:01 -07:00
greg
89d967aee4 FullyQualifiedSymbolName string representation 2019-08-30 22:55:59 -07:00
greg
0540df4024 Rename Val -> Sym 2019-08-30 19:10:16 -07:00
greg
61182a847f Rename lookup_by_path -> lookup_by_fqsn 2019-08-30 19:05:01 -07:00
greg
f6dcd7f0b8 Use proper symbol_table lookup in eval 2019-08-30 19:03:52 -07:00
greg
16dc973aa6 Remove one use of symbol_table.lookup_by_name
Should aim to remove it entirely
2019-08-30 18:56:16 -07:00
greg
611e46938d Make symbol names better
Refactor of symbol table to make name lookups
more precise, necessary for struct member lookups
2019-08-30 18:41:47 -07:00
greg
3d6447abb4 Start work on symbol table lookup by type name 2019-08-21 10:10:57 -07:00
greg
a74027bb1f Start adding object access 2019-08-20 00:20:07 -07:00
greg
583e87c19a Make apply_builtin compatible with Node 2019-08-19 21:49:46 -07:00
greg
12ed2f5c8e Pass symbol table reference to to_repl 2019-08-19 19:38:24 -07:00
greg
3caf9c763c Move eval tests 2019-08-16 10:39:21 -07:00
greg
cd20afc3c7 Add note about Nodes 2019-08-15 08:07:52 -07:00
greg
063a13f7ff Move BinOp into ast subcrate
now builtins is only builtin semantics and has nothing to do with
operators
2019-08-15 06:28:40 -07:00
greg
b0a1f3337c Clean up some operator code 2019-08-14 10:31:07 -07:00
greg
2e147e141e Update a bunch of schala-lang libraries 2019-08-14 10:18:35 -07:00
greg
44938aa4e6 Starting to refactor binop 2019-08-14 09:26:08 -07:00
greg
44ae10b7ae Add todo note 2019-08-14 07:54:39 -07:00
greg
fa1544c71f Fix eval of negatives 2019-08-14 07:31:59 -07:00
greg
fde169b623 Make operators live in a submodule of ast
Starting with PrefixOp, BinOp happens next
2019-08-14 07:25:45 -07:00
greg
6e92b03f81 Add types for (some) builtins 2019-08-13 04:28:21 -07:00
greg
0dd6b26e5a Move where PrefixOp lives 2019-08-13 04:17:17 -07:00
greg
a3bb3ee514 Note a bug 2019-08-12 14:13:20 -07:00
greg
7ae41e717d Switch away from string builtins 2019-08-12 14:10:07 -07:00
greg
24089da788 Mapping names to builtins 2019-08-12 13:49:39 -07:00
greg
bfb36b90e4 Start refactoring how builtins work
Create an enum of builtin operations to start with
2019-08-12 13:10:22 -07:00
greg
e750247134 Successfully constructing a record
Not yet destructing it
2019-08-12 12:46:18 -07:00
greg
a8efe40b57 Add some documentation for the reduced AST 2019-08-12 11:55:35 -07:00
greg
dae619c6fa Add notes 2019-08-12 11:40:31 -07:00
greg
c9bfa2b540 More named struct reduction work 2019-08-12 11:40:16 -07:00
greg
e708c728d2 Add a type to the prelude to test records 2019-08-12 11:33:03 -07:00
greg
b65d6e4c8e Symbol table notes to self 2019-08-12 11:27:16 -07:00
greg
d9eca8ffb3 Handle records more properly in symbol table 2019-08-12 11:18:03 -07:00
greg
a600d34712 More work on named struct
commented for now becuase I need to fix things in the symbol table
2019-08-12 10:59:04 -07:00
greg
aae2ee53cd More parsing debugging changes 2019-08-12 09:51:36 -07:00
greg
bf3dcc18d0 Fixed trace parsing debug output 2019-08-12 09:34:36 -07:00
greg
baf499ee5a Fix symbol-table debugging 2019-08-05 03:37:37 -07:00
greg
3b19fc5aa9 Barest beginning of named struct implementation 2019-08-05 03:35:10 -07:00
greg
16bf166fa9 Fix bug with debug specifications 2019-08-05 03:31:10 -07:00
greg
d832583ed9 Fix pluralization wording 2019-08-05 01:11:01 -07:00
greg
87ecc6f0cb Don't print out bare constructor
Instead convert to PrimObject
2019-08-05 01:07:48 -07:00
greg
ee87695626 Simplify Alternative data structure
Most of the subfields are duplicated on Subpattern so just use that
directly
2019-07-30 01:33:09 -07:00
greg
37c77d93d7 Fix off-by-one error in show-immediate parsing 2019-07-28 11:26:13 -07:00
greg
b62968379a Replace matches with functional constructs 2019-07-28 11:15:28 -07:00
greg
aa705b4eee Break out actual lib.rs functionality
To minimize the amount of meaningful text in files with generic names
2019-07-11 19:21:23 -07:00
greg
d67ccf5c7a Refactor Expression struct
to have explicit kind and type_anno fields, to make it clearer
that this represents source-code level annotation and not any kind
of type inference intermediate product
2019-07-10 18:52:25 -07:00
greg
d9330bed26 Upgrade linefeed version 2019-07-09 01:49:07 -07:00
greg
efe65edfe6 Put color into debug output 2019-07-09 01:32:38 -07:00
greg
7c9154de53 Refactor computation responses 2019-07-08 21:02:07 -07:00
greg
10e40669b5 Fix parsing debug options again 2019-06-22 12:33:28 -07:00
greg
ca37e006b9 Fix some dyn's 2019-06-21 02:01:46 -07:00
greg
6d3f5f4b81 Got things compiling again
But this is a bad design for the DebugAsk
2019-06-19 10:41:20 -07:00
greg
e3bd108e6c Debug stuff 2019-06-19 03:27:18 -07:00
greg
2ec3b21ebf Make output_wrapper more concise 2019-06-18 18:11:17 -07:00
greg
b6e3469573 Default argument to function 2019-06-16 21:36:59 -07:00
greg
32fe7430a4 Equals should be a token type 2019-06-16 16:07:27 -07:00
greg
c332747c3e Move parse test code into separate module 2019-06-16 15:03:34 -07:00
greg
33c2786ea1 More complicated FormalParam type 2019-06-16 14:56:52 -07:00
greg
30498d5c98 Add reference work 2019-06-16 00:22:18 -07:00
greg
bc01a5ded8 Make reduced ast call handler be a separate method 2019-06-16 00:21:39 -07:00
greg
71386be80e Make tests pass by using multiple-k lookahead 2019-06-14 02:28:14 -07:00
greg
ccdc02bbd0 Peek multiple tokens ahead 2019-06-14 01:30:53 -07:00
greg
3a207cf7a7 Make TokenHandler use an array and index
Instead of a peekable iterator, so I can implement LL(k) parsing
2019-06-14 00:44:54 -07:00
greg
66f71606ef Add back some debugging for parsing 2019-06-14 00:23:47 -07:00
greg
53ce31ea8c Start creating new TokenHandler infra
on top of old stuff
2019-06-14 07:21:32 +00:00
greg
4c688ce8b2 Lol grammar is no longer LL(1)
need to fix
2019-06-13 02:27:11 -07:00
greg
40579d80ce More work on args
not quite done
2019-06-12 03:28:46 -07:00
greg
fa1257e2cd Starting work on more complicated call expressions
Probably won't build yet
2019-06-12 00:20:20 +00:00
greg
e9fd20bfe5 A few more fixes to EBNF 2019-06-09 01:12:19 -07:00
greg
dfbd951aaf Some fixes to the EBNF grammar 2019-06-09 01:08:32 -07:00
greg
6b47ecf2d7 First pass at putting EBNF grammar into rustdoc 2019-06-09 00:01:11 -07:00
greg
a8b9f5046e Mark that I changed trait to interface 2019-06-07 18:57:55 +00:00
greg
83e05fe382 Remove unneeded directives field 2019-06-06 23:52:41 -07:00
greg
5271429715 Make help a bit nicer 2019-06-06 23:50:08 -07:00
greg
f88f2e8550 More help cleanup 2019-06-06 22:36:44 -07:00
greg
7097775a4a :help command working 2019-06-06 22:21:50 -07:00
greg
32d082e119 Kill duplicate code 2019-06-05 02:54:13 -07:00
greg
376fa1d1d1 Tab completion for help 2019-06-05 02:48:45 -07:00
greg
6fb9b4c2d3 Actually the Top variant is doing something useful 2019-06-05 02:42:34 -07:00
greg
f1d1042916 Add help text 2019-06-05 02:36:08 -07:00
greg
207f73d607 Moving help code around 2019-06-04 22:02:51 +00:00
greg
8dc0ad2348 Help function work 2019-06-03 22:18:53 +00:00
greg
bb39c59db2 directive-related cleanup 2019-06-02 00:48:59 -07:00
greg
10bfeab7e4 Switch over everything to new directive paradigm 2019-06-02 00:43:55 -07:00
greg
fe08e64860 More DirectiveAction conversion work 2019-06-02 00:27:12 -07:00
greg
fd517351de Start converting over directives to new format 2019-06-02 00:19:26 -07:00
greg
e12ff6f30b Start adding infrastructure to pay attention to actions 2019-06-01 22:17:20 -07:00
greg
176b286332 Add new ReplAction type 2019-06-01 18:41:55 -07:00
greg
3987360f8e Working on directives 2019-06-01 16:56:56 -07:00
greg
78d1e93e4b Put back rudimentary debug output 2019-05-28 03:41:49 -07:00
greg
856c0f95ce Wrap schala pass inputs in token struct 2019-05-28 03:07:35 -07:00
greg
3fa624bef4 Paramaterize debugging steps 2019-05-27 15:06:50 -07:00
greg
f27a65018d Fix prompt 2019-05-26 15:22:36 -07:00
greg
548a7b5f36 DebugRequests should be set 2019-05-26 04:16:40 -07:00
greg
d80d0d0904 Add some notes 2019-05-26 00:23:54 -07:00
greg
6162bae1ac Per-stage computation times 2019-05-25 22:21:52 -07:00
greg
fe7ba339b5 Per-stage timing output 2019-05-25 20:09:11 -07:00
greg
6a232907c5 Kill useless DebugRequest type 2019-05-25 19:31:41 -07:00
greg
a8583f6bc4 Separate command_tree module 2019-05-22 03:32:00 -07:00
greg
bdee4fe7c6 Fix commandtree debug processing 2019-05-22 03:19:12 -07:00
greg
5cdc2f3d07 Some total-time stuff 2019-05-21 02:52:26 -07:00
greg
eb2adb5b79 Moving options around
Showing time
2019-05-21 02:46:07 -07:00
greg
2b407a4a83 Total duration Timing 2019-05-21 02:06:34 -07:00
greg
6da6f6312d Remove more unused code 2019-05-20 22:04:14 -07:00
greg
ce2a65b044 Clean up some unused code 2019-05-20 16:10:50 -07:00
greg
ffdae14a88 Hook up docs 2019-05-17 18:23:03 -07:00
greg
94ea7bcd09 Starting to use more advanced error output 2019-05-15 03:32:57 -07:00
greg
4ebf7fe879 Remove more unused variables 2019-05-14 11:15:12 -07:00
greg
efbeff916a Allow this unused macro
for tests only
2019-05-14 10:51:32 -07:00
greg
e9ea7811df Kill unused 2019-05-14 02:12:18 -07:00
greg
198f93c533 Make non-interactive code work again 2019-05-14 01:57:31 -07:00
greg
694c152fcd Kill webapp for now
I might add this back in later but for now I'd have to catch up to so
much rocket that it's easier to just leave it out
2019-05-14 01:51:41 -07:00
greg
f8f3095f89 Remove dead code 2019-05-14 00:45:45 -07:00
greg
c68c23ed68 Restore option-saving 2019-05-14 00:40:38 -07:00
greg
4f972f20a7 Remove some unused code 2019-05-13 19:55:18 -07:00
greg
9d2e5918af Add some more hindley-milner-relatd blogs to README 2019-05-08 16:24:13 -07:00
greg
14fc2a5d10 Update TODO 2019-04-29 23:57:59 -07:00
greg
2b8e2749a4 Get rid of unneeded mut's 2019-04-29 23:57:38 -07:00
greg
6c369b072f Debug immediate working for symbol table 2019-03-31 01:13:40 -07:00
greg
938c0401d1 Some various work 2019-03-27 02:20:43 -07:00
greg
a829fb6cd8 Initial debug-handler function 2019-03-26 19:55:47 -07:00
greg
004b056232 Rearchitect CommandTree
- CommandTree::Term is now terminal only with respect to searching for
the debug-handling function to execute, it can have children that only
the tab-completion code cares about

- CommandTree::NonTerminal never has a function associated with it, if the
processing of a list of repl debug commands gets to a NonTerminal leaf,
there's just nothing to do
2019-03-26 19:43:11 -07:00
greg
8e9b410e02 Clean up TODO list 2019-03-20 01:15:35 -07:00
greg
b82eebdeec Fix up readme 2019-03-20 01:09:38 -07:00
greg
153e7977d3 Make function more concise 2019-03-20 00:24:46 -07:00
greg
5b5368ce6f Delete a bunch of comments 2019-03-20 00:18:02 -07:00
greg
7a67890227 List-passes 2019-03-20 00:04:02 -07:00
greg
04253543e9 Move where help is computed 2019-03-19 21:12:10 -07:00
greg
3a98096b61 Add back debug passes command completion support 2019-03-19 19:37:29 -07:00
greg
9476e7039b Doc requests in type system 2019-03-19 19:26:05 -07:00
greg
c767402865 Remove some no-longer-necessary indirection 2019-03-19 19:16:41 -07:00
greg
61972410ea Functionality to request/respond to meta items 2019-03-19 19:12:32 -07:00
greg
d3f9430a18 Avoid unnecessary String 2019-03-19 19:01:04 -07:00
greg
81323cafd4 Change wording of default repl_request handler 2019-03-19 18:46:24 -07:00
greg
14c08bbcdb Get rid of EvalOptions
and associated types
2019-03-19 18:40:21 -07:00
greg
4319c802f5 Add nonterminal with function
For making tab completion work properly
2019-03-19 04:28:54 -07:00
greg
9e58e3d7de Remove some warnings 2019-03-16 18:45:40 -07:00
greg
ac0050e5d1 Truncate command list passed to command function
Only pass it the arguments after its own path, if any exist
2019-03-16 18:33:31 -07:00
greg
d06cf90fce Help message 2019-03-16 11:34:52 -07:00
greg
712da62d35 Make new CommandTree paradigm work 2019-03-16 11:10:39 -07:00
greg
57f3d39ea1 Start adding commandtree abstraction 2019-03-14 21:25:37 -07:00
greg
6d88447458 Add func to command 2019-03-14 04:08:32 -07:00
greg
0451676ba7 start adding functions to command data structure 2019-03-14 03:47:56 -07:00
greg
2929362046 Change NewRepl -> Repl 2019-03-14 03:42:39 -07:00
greg
375db28ebb Remove support for non-Schala languages
I may come back to these, but not until after Schala is much better
developed
2019-03-14 01:04:46 -07:00
greg
1622a6ce44 Grand culling
Deleting a bunch of old code related to the old way the interpreter
worked
2019-03-14 00:51:33 -07:00
greg
7e899246e9 More refactoring in main Schala driver 2019-03-14 00:15:13 -07:00
greg
8610bd7a87 Port Schala to new framework
Evaluating a Schala function in the REPL works again with no debug info
2019-03-13 22:43:44 -07:00
greg
70f715fbb2 Fix bugs 2019-03-13 20:07:41 -07:00
greg
7360e698dd More work 2019-03-13 10:10:42 -07:00
greg
5b35c2a036 Add new types for ProgrammingLanguageInterface 2019-03-13 00:13:39 -07:00
greg
8d8d7d8bf8 More misc changes including edition 2018 2019-03-12 02:39:25 -07:00
greg
981d4f88bf Changes 2019-03-12 01:14:41 -07:00
greg
42aa316a23 Fix custom attribute thing
Upon updating rust version, the unrestricted_attribute_token thing
broke, but I'm changing this anyway so whatever
2019-03-12 01:05:10 -07:00
greg
58b37e56ae Correct typo in TODO 2019-03-11 20:05:45 -07:00
greg
2bf777f37b Add this note to self 2019-03-11 19:36:10 -07:00
greg
bdcae36b60 More cleaning up of how scopes are stored
on Symbol
2019-03-11 02:47:47 -07:00
greg
dbcd2278a6 Renamings 2019-03-11 02:35:42 -07:00
greg
2490aaf3f4 Add types necessary for refactor of Symbol table 2019-03-11 01:36:11 -07:00
greg
d4ad97b39a start preparing to get rid of symbol_table.lookup_by_name 2019-03-10 17:32:47 -07:00
greg
24213070a3 Delete useless comment 2019-03-10 17:29:02 -07:00
greg
051669b4cc Stuff pertaining to variant scoping 2019-03-10 17:24:58 -07:00
greg
c64f53a050 Detect duplicate variable declarations correctly
Later I'll probably want to make it so that you can explicitly override
the value of a declared variable
2019-03-10 17:02:01 -07:00
greg
8f176543c7 Nested scopes in symbol table 2019-03-10 16:04:20 -07:00
greg
9716b5e55b Symbol table detects some duplicate symbols 2019-03-08 03:57:32 -08:00
greg
956353cd80 Move rc! macro to util
So it can be used anywhere
2019-03-08 01:15:19 -08:00
greg
98db60498a Add very basic symbol table test shim 2019-03-07 23:51:31 -08:00
greg
7694afc9e2 Add type for talking about symbol paths
to symbol table
2019-03-07 20:45:12 -08:00
greg
0bcd7e6f41 Add new_env method
This is basically the same as the one on the evaluator and makes use of
the ScopeStack - maybe need to generalize this more?
2019-02-27 02:15:19 -08:00
greg
d515b1658a Some fixes 2019-02-24 16:24:45 -08:00
greg
e501f4bd10 Various cleanup of comments, stringifying types 2019-02-23 09:59:41 -08:00
greg
5bac01cf20 More boilerplate for apply 2019-02-23 03:55:46 -08:00
greg
0e9b3229e9 Refactor Arrow; add general handle_apply 2019-02-23 03:33:56 -08:00
greg
b709cfd51a Start adding call 2019-02-23 02:50:11 -08:00
greg
e34295a6f7 Starting on lambda typechecking 2019-02-23 02:45:11 -08:00
greg
8dc34e4b49 Fresh type var 2019-02-23 01:27:32 -08:00
greg
2cc3367666 Unify var-var 2019-02-23 01:20:19 -08:00
greg
452f2ab188 Unify var-const 2019-02-23 01:18:15 -08:00
greg
be175a2b75 Add more infrastructure for unify 2019-02-23 00:59:58 -08:00
greg
00a0de4431 Add ena crate for unification 2019-02-23 00:34:44 -08:00
greg
f041cc17d2 Wrap all Expression nodes in Meta<Expression> 2019-02-21 23:35:18 -08:00
greg
95fe1941a1 Kill some unused items 2019-02-21 18:39:41 -08:00
greg
b35262c444 Rename Node -> Meta 2019-02-21 01:49:15 -08:00
greg
9bb3a2be88 Add type data handle on Node
I think the way I want to handle this is a two-step process: first infer and
fill in variables, then unfiy in a separate step. Storing the data in
the AST is handy.
2019-02-21 01:46:27 -08:00
greg
9fa0576547 Rename ExpressionType -> ExpressionKind 2019-02-21 01:26:51 -08:00
greg
6fba0cc5b4 Add variables 2019-02-21 01:17:34 -08:00
greg
a6eb2b4020 Allow type annotations in let expressions 2019-02-20 22:44:45 -08:00
greg
03793e08d3 Typechecking operators 2019-02-20 03:27:46 -08:00
greg
2be55958f4 add Into<String> arg for error constructors 2019-02-20 02:06:58 -08:00
greg
bcf48d0ecb First tests for typechecking 2019-02-20 01:33:45 -08:00
greg
f0ed63ccf3 Basic if-statement checking 2019-02-19 23:00:41 -08:00
greg
6012bd1087 Variables 2019-02-19 21:41:07 -08:00
greg
866c9211f9 Add resources to README 2019-02-18 23:49:34 -08:00
greg
df7e74c79d Types with arguments 2019-02-17 04:31:02 -08:00
greg
abbd02eaef Use ty! macro 2019-02-17 04:25:38 -08:00
greg
993741e67f Get rid of typecheck_ 2019-02-17 04:08:49 -08:00
greg
fbb7b995b8 Rename mk_type! to ty!
Doesn't seem to conflict with the same macro in the parser tests, so should be
ok
2019-02-17 03:38:15 -08:00
greg
9d4f086a04 Put mk_type! in typechecking module 2019-02-17 03:36:12 -08:00
greg
e38ae1c3f1 Fix type to make it compile 2019-02-15 21:11:50 -08:00
greg
d969d573fa Starting work on values 2019-02-12 21:14:13 -08:00
greg
35da1748f0 Some more type work 2019-02-10 12:21:12 -08:00
greg
5e1799268d Unification works with bad annotations 2019-02-10 07:32:12 -08:00
greg
42a801d346 Rename Order -> Ordering 2019-02-10 07:06:30 -08:00
greg
a80e1bd706 Type name infra 2019-02-10 07:05:01 -08:00
greg
afd9aa52c5 More infra around unify 2019-02-10 06:53:11 -08:00
greg
5a70784346 Adding unify stub 2019-02-10 06:48:25 -08:00
greg
0dff177e8f Add more literals kill errors 2019-02-10 05:33:55 -08:00
greg
cf91f74912 Pass through type info to repl 2019-02-10 05:31:58 -08:00
greg
06e9452718 More type infrastructure
From here on out, I can start playing with concrete code that attempts
to actually typecheck limited syntactic constructs, and see what I end
up with.
2019-02-10 05:24:11 -08:00
greg
7d3ae36058 AST-walking infrastructure 2019-02-10 04:42:30 -08:00
greg
e8f1f51639 Move (most of) the type definitions back to typechecking module
Still need to figure out the macro export thing
2019-02-10 04:30:37 -08:00
greg
170cf349d7 Starting typechecking work again 2019-02-09 00:25:12 -08:00
48 changed files with 6141 additions and 3050 deletions

1
.gitignore vendored
View File

@@ -1,4 +1,3 @@
Cargo.lock
target
.schala_repl
.schala_history

1124
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,12 +6,10 @@ authors = ["greg <greg.shuflin@protonmail.com>"]
[dependencies]
schala-repl = { path = "schala-repl" }
schala-repl-codegen = { path = "schala-repl-codegen" }
maaru-lang = { path = "maaru" }
rukka-lang = { path = "rukka" }
robo-lang = { path = "robo" }
schala-lang = { path = "schala-lang/language" }
schala-lang-codegen = { path = "schala-lang/codegen" }
# maaru-lang = { path = "maaru" }
# rukka-lang = { path = "rukka" }
# robo-lang = { path = "robo" }
[build-dependencies]
includedir_codegen = "0.2.0"

View File

@@ -1,19 +1,22 @@
# Schala - a programming language meta-interpreter
Schala is a Rust framework written to make it easy to create and experiment
with toy programming languages. It provides a cross-language REPL and
with multipl toy programming languages. It provides a cross-language REPL and
provisions for tokenizing text, parsing tokens, evaluating an abstract syntax
tree, and other tasks that are common to all programming languages.
tree, and other tasks that are common to all programming languages, as well as sharing state
between multiple programming languages.
Schala is implemented as a Rust library `schala-repl`, which provides a
function `repl_main` meant to be used as the equivalent of main() for library
users. This function parses command-line arguments and either runs an interactive
REPL or interprets a program non-interactively.
function `start_repl`, meant to be used as entry point into a common REPL or
non-interactive environment. Clients are expected to invoke `start_repl` with a
vector of programming languages. Individual programming language
implementations are Rust types that implement the
`ProgrammingLanguageInterface` trait and store whatever persistent state is
relevant to that language.
Individual programming language implementations are Rust types that implement
the `ProgrammingLanguageInterface` trait and store whatever persistent state is
relevant to that language. The ability to share state between different
programming languages is in the works.
Run schala with: `cargo run`. This will drop you into a REPL environment. Type
`:help` for more information, or type in text in any supported programming
language (currently only schala-lang) to evaluate it in the REPL.
## History
@@ -33,18 +36,18 @@ creating a language name confusingly close to Scala. The naming scheme for
languages implemented with the Schala meta-interpreter is Chrono Trigger
characters.
Schala is incomplete alpha software and is not ready for public release.
Schala and languages implemented with it are incomplete alpha software and are
not ready for public release.
## Languages implemented using the meta-interpreter
* The eponymous *Schala* language is an interpreted/compiled scripting langauge,
designed to be relatively simple, but with a reasonably sophisticated type
system.
* The eponymous *Schala* language is a work-in-progress general purpose
programming language with static typing and algebraic data types. Its design
goals include having a very straightforward implemenation and being syntactically
minimal.
* *Maaru* was the original Schala (since renamed to free up the name *Schala*
for the above language), a very simple dynamically-typed scripting language
such that all possible runtime errors result in null rather than program
failure.
* *Maaru* is a very simple dynamically-typed scripting language, with the semantics
that all runtime errors return a `null` value rather than fail.
* *Robo* is an experiment in creating a lazy, functional, strongly-typed language
much like Haskell
@@ -56,12 +59,22 @@ much like Haskell
Here's a partial list of resources I've made use of in the process
of learning how to write a programming language.
### General
http://thume.ca/2019/04/18/writing-a-compiler-in-rust/
### Type-checking
https://skillsmatter.com/skillscasts/10868-inside-the-rust-compiler
https://www.youtube.com/watch?v=il3gD7XMdmA
http://dev.stephendiehl.com/fun/006_hindley_milner.html
https://rust-lang-nursery.github.io/rustc-guide/type-inference.html
https://eli.thegreenplace.net/2018/unification/
https://eli.thegreenplace.net/2018/type-inference/
http://smallcultfollowing.com/babysteps/blog/2017/03/25/unification-in-chalk-part-1/
http://reasonableapproximation.net/2019/05/05/hindley-milner.html
https://rickyhan.com/jekyll/update/2018/05/26/hindley-milner-tutorial-rust.html
### Evaluation
*Understanding Computation*, Tom Stuart, O'Reilly 2013

199
TODO.md
View File

@@ -1,58 +1,129 @@
#Typechecking Notes
# TODO items
(cf. cardelli paper)
-use 'let' sigil in patterns for variables :
```
q is MyStruct(let a, Chrono::Trigga) then {
Given a length function def:
````
fn length(x) {
if x.is_null {
0
} else {
succ(length(x.tail))
}
}
````
Constraints:
.null: List a -> bool
.tail: List a -> List a
0: Nat
succ: Nat -> Nat
```
## 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 )
-implement and test open/use statements
-implement field access
- standardize on an error type that isn't String
-implement a visitor pattern for the use of scope_resolver
- maybe implement this twice: 1) the value-returning, no-default one in the haoyi blogpost,
-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)
## Reduction
- make a good type for actual language builtins to avoid string comparisons
## Typechecking
# TODO Items
- make a type to represent types rather than relying on string comparisons
-make the REPL more advanced!
- look at https://rickyhan.com/jekyll/update/2018/05/26/hindley-milner-tutorial-rust.html
-Plan of attack:
-write a visitor pattern for AST
-convert AST type to including SourceMap'd wrappers (w/ .into())
-at the same time, amke sure the visitor pattern "skips over" the SourceMap'd stuff
so it can just care about AST structure
- cf. the notation mentioned in the cardelli paper, the debug information for the `typechecking` pass should
print the generated type variable for every subexpression in an expression
- AST : maybe replace the Expression type with "Ascription(TypeName, Box<Expression>) nodes??
- parser: add a "debug" field to the Parser struct for all debug-related things
- think about idris-related ideas of multiple implementations of a type for an interface (+ vs * impl for monoids, for preorder/inorder/postorder for Foldable)
-scala-style html"dfasfsadf${}" string interpolations!
-should have an Idris-like `cast To From` function
-fuzz test schala
## Schala-lang syntax
-look into Inkwell for LLVM
-idea: the `type` declaration should have some kind of GADT-like syntax
- Idea: if you have a pattern-match where one variant has a variable and the other lacks it
instead of treating this as a type error, promote the bound variable to an option type
*A neat idea for pattern matching optimization would be if you could match on one of several things in a list
- Include extensible scala-style html"string ${var}" string interpolations
- A neat idea for pattern matching optimization would be if you could match on one of several things in a list
ex:
if x {
```if x {
is (comp, LHSPat, RHSPat) if comp in ["==, "<"] -> ...
}
}```
- Schala should have both currying *and* default arguments!
```fn a(b: Int, c:Int, d:Int = 1) -> Int
a(1,2) : Int
a(1,2,d=2): Int
a(_,1,3) : Int -> Int
a(1,2, c=_): Int -> Int
a(_,_,_) : Int -> Int -> Int -> Int
```
- scoped types - be able to define a quick enum type scoped to a function or other type for
something, that only is meant to be used as a quick bespoke interface between
two other things
ex.
```type enum {
type enum MySubVariant {
SubVariant1, SubVariant2, etc.
}
Variant1(MySubVariant),
Variant2(...),
}```
- inclusive/exclusive range syntax like .. vs ..=
## Compilation
-look into Inkwell for rust LLVM bindings
- https://nshipster.com/never/
-https://cranelift.readthedocs.io/en/latest/?badge=latest<Paste>
## Other links of note
- https://nshipster.com/never/
-consult http://gluon-lang.org/book/embedding-api.html
## Trying if-syntax again
//simple if expr
if x == 10 then "a" else "z"
//complex if expr
if x == 10 then {
let a = 1
let b = 2
a + b
} else {
55
}
// different comparison ops
if x {
== 1 then "a"
.isPrime() then "b"
else "c"
}
/* for now disallow `if x == { 1 then ... }`, b/c hard to parse
//simple pattern-matching
if x is Person("Ivan", age) then age else 0
//match-block equivalent
if x {
is Person("Ivan", _) then "Ivan"
is Person(_, age) if age > 13 then "barmitzvah'd"
else "foo"
}
## (OLD) Playing around with conditional syntax ideas
- if/match playground
simple if
@@ -91,67 +162,3 @@ if the only two guard patterns are true and false, then the abbreviated syntax:
`'if' discriminator 'then' block_or_expr 'else' block_or_expr`
can replace `'if' discriminator '{' 'true' 'then' block_or_expr; 'false' 'then' block_or_expr '}'`
- Next priorities: - get ADTs working, get matches working
- inclusive/exclusive range syntax like .. vs ..=
- sketch of an idea for the REPL:
-each compiler pass should be a (procedural?) macro like
compiler_pass!("parse", dataproducts: ["ast", "parse_tree"], {
match parsing::parse(INPUT) {
Ok(
PASS.add_artifact(
}
-should have an Idris-like `cast To From` function
- REPL:
- want to be able to do things like `:doc Identifier`, and have the language load up these definitions to the REPL
* change 'trait' to 'interface'
-think about idris-related ideas of multiple implementations of a type for an interface (+ vs * impl for monoids, for preorder/inorder/postorder for Foldable)
* Share state between programming languages
* idea for Schala - scoped types - be able to define a quick enum type scoped to a function ro something, that only is meant to be used as a quick bespoke interface between two other things
* another idea, allow:
type enum {
type enum MySubVariant {
SubVariant1, SubVariant2, etc.
}
Variant1(MySubVariant),
Variant2(...),
}
* idea for Schala: both currying *and* default arguments!
ex. fn a(b: Int, c:Int, d:Int = 1) -> Int
a(1,2) : Int
a(1,2,d=2): Int
a(_,1,3) : Int -> Int
a(1,2, c=_): Int -> Int
a(_,_,_) : Int -> Int -> Int -> Int
*Compiler passes architecture
-ProgrammingLanguageInterface defines a evaluate_in_repl() and evaluate_no_repl() functions
-these take in a vec of CompilerPasses
struct CompilerPass {
name: String,
run: fn(PrevPass) -> NextPass
}
-change "Type...." names in parser.rs to "Anno..." for non-collision with names in typechecking.rs
-get rid of code pertaining to compilation specifically, have a more generation notion of "execution type"

View File

@@ -6,8 +6,6 @@ mod tokenizer;
mod parser;
mod eval;
use schala_repl::{ProgrammingLanguageInterface, EvalOptions, UnfinishedComputation, FinishedComputation, TraceArtifact};
#[derive(Debug)]
pub struct TokenError {
pub msg: String,
@@ -33,6 +31,42 @@ impl<'a> Maaru<'a> {
}
}
/*
fn execute_pipeline(&mut self, input: &str, options: &EvalOptions) -> Result<String, String> {
let mut output = UnfinishedComputation::default();
let tokens = match tokenizer::tokenize(input) {
Ok(tokens) => {
if let Some(_) = options.debug_passes.get("tokens") {
output.add_artifact(TraceArtifact::new("tokens", format!("{:?}", tokens)));
}
tokens
},
Err(err) => {
return output.finish(Err(format!("Tokenization error: {:?}\n", err.msg)))
}
};
let ast = match parser::parse(&tokens, &[]) {
Ok(ast) => {
if let Some(_) = options.debug_passes.get("ast") {
output.add_artifact(TraceArtifact::new("ast", format!("{:?}", ast)));
}
ast
},
Err(err) => {
return output.finish(Err(format!("Parse error: {:?}\n", err.msg)))
}
};
let mut evaluation_output = String::new();
for s in self.evaluator.run(ast).iter() {
evaluation_output.push_str(s);
}
Ok(evaluation_output)
}
*/
/*
impl<'a> ProgrammingLanguageInterface for Maaru<'a> {
fn get_language_name(&self) -> String {
"Maaru".to_string()
@@ -40,37 +74,5 @@ impl<'a> ProgrammingLanguageInterface for Maaru<'a> {
fn get_source_file_suffix(&self) -> String {
format!("maaru")
}
fn execute_pipeline(&mut self, input: &str, options: &EvalOptions) -> FinishedComputation {
let mut output = UnfinishedComputation::default();
let tokens = match tokenizer::tokenize(input) {
Ok(tokens) => {
if let Some(_) = options.debug_passes.get("tokens") {
output.add_artifact(TraceArtifact::new("tokens", format!("{:?}", tokens)));
}
tokens
},
Err(err) => {
return output.finish(Err(format!("Tokenization error: {:?}\n", err.msg)))
}
};
let ast = match parser::parse(&tokens, &[]) {
Ok(ast) => {
if let Some(_) = options.debug_passes.get("ast") {
output.add_artifact(TraceArtifact::new("ast", format!("{:?}", ast)));
}
ast
},
Err(err) => {
return output.finish(Err(format!("Parse error: {:?}\n", err.msg)))
}
};
let mut evaluation_output = String::new();
for s in self.evaluator.run(ast).iter() {
evaluation_output.push_str(s);
}
output.finish(Ok(evaluation_output))
}
}
*/

View File

@@ -4,7 +4,7 @@ extern crate itertools;
extern crate schala_repl;
use itertools::Itertools;
use schala_repl::{ProgrammingLanguageInterface, EvalOptions, FinishedComputation, UnfinishedComputation};
use schala_repl::{ProgrammingLanguageInterface, EvalOptions};
pub struct Robo {
}
@@ -154,17 +154,5 @@ impl ProgrammingLanguageInterface for Robo {
fn get_source_file_suffix(&self) -> String {
format!("robo")
}
fn execute_pipeline(&mut self, input: &str, _eval_options: &EvalOptions) -> FinishedComputation {
let output = UnfinishedComputation::default();
let tokens = match tokenize(input) {
Ok(tokens) => tokens,
Err(e) => {
return output.finish(Err(format!("Tokenize error: {:?}", e)));
}
};
output.finish(Ok(format!("{:?}", tokens)))
}
}

View File

@@ -4,7 +4,7 @@ extern crate itertools;
extern crate schala_repl;
use itertools::Itertools;
use schala_repl::{ProgrammingLanguageInterface, EvalOptions, UnfinishedComputation, FinishedComputation};
use schala_repl::{ProgrammingLanguageInterface, EvalOptions};
use std::iter::Peekable;
use std::vec::IntoIter;
use std::str::Chars;
@@ -72,24 +72,6 @@ impl ProgrammingLanguageInterface for Rukka {
fn get_source_file_suffix(&self) -> String {
format!("rukka")
}
fn execute_pipeline(&mut self, input: &str, _eval_options: &EvalOptions) -> FinishedComputation {
let output = UnfinishedComputation::default();
let sexps = match read(input) {
Err(err) => {
return output.finish(Err(format!("Error: {}", err)));
},
Ok(sexps) => sexps
};
let output_str: String = sexps.into_iter().enumerate().map(|(i, sexp)| {
match self.state.eval(sexp) {
Ok(result) => format!("{}: {}", i, result.print()),
Err(err) => format!("{} Error: {}", i, err),
}
}).intersperse(format!("\n")).collect();
output.finish(Ok(output_str))
}
}
impl EvaluatorState {

View File

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

View File

@@ -5,13 +5,16 @@ authors = ["greg <greg.shuflin@protonmail.com>"]
edition = "2018"
[dependencies]
itertools = "0.5.8"
take_mut = "0.1.3"
maplit = "*"
lazy_static = "0.2.8"
failure = "0.1.2"
itertools = "0.8.0"
take_mut = "0.2.2"
maplit = "1.0.1"
lazy_static = "1.3.0"
failure = "0.1.5"
ena = "0.11.0"
stopwatch = "0.0.7"
derivative = "1.0.3"
colored = "1.8"
radix_trie = "0.1.5"
schala-lang-codegen = { path = "../codegen" }
schala-repl = { path = "../../schala-repl" }
schala-repl-codegen = { path = "../../schala-repl-codegen" }

View File

@@ -1,47 +1,90 @@
use std::rc::Rc;
use std::convert::From;
use crate::builtin::{BinOp, PrefixOp};
use crate::derivative::Derivative;
#[derive(Clone, Debug, PartialEq)]
pub struct Node<T> {
n: T,
source_map: SourceMap
mod walker;
mod visitor;
mod visitor_test;
mod operators;
pub use operators::*;
pub use visitor::ASTVisitor;
pub use walker::walk_ast;
/// An abstract identifier for an AST node
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct ItemId {
idx: u32,
}
impl<T> Node<T> {
pub fn new(n: T) -> Node<T> {
Node { n, source_map: SourceMap::default() }
}
pub fn node(&self) -> &T {
&self.n
impl ItemId {
fn new(n: u32) -> ItemId {
ItemId { idx: n }
}
}
//TODO this PartialEq is here to make tests work - find a way to make it not necessary
#[derive(Clone, Debug, Default, PartialEq)]
struct SourceMap {
pub struct ItemIdStore {
last_idx: u32
}
impl From<Expression> for Node<Expression> {
fn from(expr: Expression) -> Node<Expression> {
Node { n: expr, source_map: SourceMap::default() }
impl ItemIdStore {
pub fn new() -> ItemIdStore {
ItemIdStore { last_idx: 0 }
}
/// Always returns an ItemId with internal value zero
#[cfg(test)]
pub fn new_id() -> ItemId {
ItemId { idx: 0 }
}
/// This limits the size of the AST to 2^32 tree elements
pub fn fresh(&mut self) -> ItemId {
let idx = self.last_idx;
self.last_idx += 1;
ItemId::new(idx)
}
}
#[derive(Debug, PartialEq)]
pub struct AST(pub Vec<Node<Statement>>);
#[derive(Derivative, Debug)]
#[derivative(PartialEq)]
pub struct AST {
#[derivative(PartialEq="ignore")]
pub id: ItemId,
pub statements: Vec<Statement>
}
#[derive(Derivative, Debug, Clone)]
#[derivative(PartialEq)]
pub struct Statement {
#[derivative(PartialEq="ignore")]
pub id: ItemId,
pub kind: StatementKind,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Statement {
ExpressionStatement(Node<Expression>),
pub enum StatementKind {
Expression(Expression),
Declaration(Declaration),
Import(ImportSpecifier),
Module(ModuleSpecifier),
}
pub type Block = Vec<Node<Statement>>;
pub type Block = Vec<Statement>;
pub type ParamName = Rc<String>;
pub type FormalParam = (ParamName, Option<TypeIdentifier>);
#[derive(Debug, Derivative, Clone)]
#[derivative(PartialEq)]
pub struct QualifiedName {
#[derivative(PartialEq="ignore")]
pub id: ItemId,
pub components: Vec<Rc<String>>,
}
#[derive(Debug, PartialEq, Clone)]
pub struct FormalParam {
pub name: ParamName,
pub default: Option<Expression>,
pub anno: Option<TypeIdentifier>
}
#[derive(Debug, PartialEq, Clone)]
pub enum Declaration {
@@ -52,10 +95,15 @@ pub enum Declaration {
body: TypeBody,
mutable: bool
},
TypeAlias(Rc<String>, Rc<String>), //should have TypeSingletonName in it, or maybe just String, not sure
//TODO this needs to be more sophisticated
TypeAlias {
alias: Rc<String>,
original: Rc<String>,
},
Binding {
name: Rc<String>,
constant: bool,
type_anno: Option<TypeIdentifier>,
expr: Expression,
},
Impl {
@@ -90,9 +138,24 @@ pub enum Variant {
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct Expression(pub ExpressionType, pub Option<TypeIdentifier>);
#[derive(Debug, Derivative, Clone)]
#[derivative(PartialEq)]
pub struct Expression {
#[derivative(PartialEq="ignore")]
pub id: ItemId,
pub kind: ExpressionKind,
pub type_anno: Option<TypeIdentifier>
}
impl Expression {
pub fn new(id: ItemId, kind: ExpressionKind) -> Expression {
Expression { id, kind, type_anno: None }
}
pub fn with_anno(id: ItemId, kind: ExpressionKind, type_anno: TypeIdentifier) -> Expression {
Expression { id, kind, type_anno: Some(type_anno) }
}
}
#[derive(Debug, PartialEq, Clone)]
pub enum TypeIdentifier {
@@ -107,29 +170,29 @@ pub struct TypeSingletonName {
}
#[derive(Debug, PartialEq, Clone)]
pub enum ExpressionType {
pub enum ExpressionKind {
NatLiteral(u64),
FloatLiteral(f64),
StringLiteral(Rc<String>),
BoolLiteral(bool),
BinExp(BinOp, Box<Node<Expression>>, Box<Node<Expression>>),
PrefixExp(PrefixOp, Box<Node<Expression>>),
TupleLiteral(Vec<Node<Expression>>),
Value(Rc<String>),
BinExp(BinOp, Box<Expression>, Box<Expression>),
PrefixExp(PrefixOp, Box<Expression>),
TupleLiteral(Vec<Expression>),
Value(QualifiedName),
NamedStruct {
name: Rc<String>,
name: QualifiedName,
fields: Vec<(Rc<String>, Expression)>,
},
Call {
f: Box<Expression>,
arguments: Vec<Node<Expression>>,
arguments: Vec<InvocationArgument>,
},
Index {
indexee: Box<Expression>,
indexers: Vec<Expression>,
},
IfExpression {
discriminator: Box<Discriminator>,
discriminator: Option<Box<Expression>>,
body: Box<IfExpressionBody>,
},
WhileExpression {
@@ -149,34 +212,42 @@ pub enum ExpressionType {
}
#[derive(Debug, PartialEq, Clone)]
pub enum Discriminator {
Simple(Expression),
BinOp(Expression, BinOp)
pub enum InvocationArgument {
Positional(Expression),
Keyword {
name: Rc<String>,
expr: Expression,
},
Ignored
}
#[derive(Debug, PartialEq, Clone)]
pub enum IfExpressionBody {
SimpleConditional(Block, Option<Block>),
SimplePatternMatch(Pattern, Block, Option<Block>),
GuardList(Vec<GuardArm>)
SimpleConditional {
then_case: Block,
else_case: Option<Block>
},
SimplePatternMatch {
pattern: Pattern,
then_case: Block,
else_case: Option<Block>
},
CondList(Vec<ConditionArm>)
}
#[derive(Debug, PartialEq, Clone)]
pub struct GuardArm {
pub guard: Guard,
pub struct ConditionArm {
pub condition: Condition,
pub guard: Option<Expression>,
pub body: Block,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Guard {
Pat(Pattern),
HalfExpr(HalfExpr)
}
#[derive(Debug, PartialEq, Clone)]
pub struct HalfExpr {
pub op: Option<BinOp>,
pub expr: ExpressionType,
pub enum Condition {
Pattern(Pattern),
TruncatedOp(BinOp, Expression),
Expression(Expression),
Else,
}
#[derive(Debug, PartialEq, Clone)]
@@ -184,19 +255,19 @@ pub enum Pattern {
Ignored,
TuplePattern(Vec<Pattern>),
Literal(PatternLiteral),
TupleStruct(Rc<String>, Vec<Pattern>),
Record(Rc<String>, Vec<(Rc<String>, Pattern)>),
TupleStruct(QualifiedName, Vec<Pattern>),
Record(QualifiedName, Vec<(Rc<String>, Pattern)>),
VarOrName(QualifiedName),
}
#[derive(Debug, PartialEq, Clone)]
pub enum PatternLiteral {
NumPattern {
neg: bool,
num: ExpressionType,
num: ExpressionKind,
},
StringPattern(Rc<String>),
BoolPattern(bool),
VarPattern(Rc<String>)
}
#[derive(Debug, PartialEq, Clone)]
@@ -210,3 +281,27 @@ pub enum ForBody {
MonadicReturn(Expression),
StatementBlock(Block),
}
#[derive(Debug, Derivative, Clone)]
#[derivative(PartialEq)]
pub struct ImportSpecifier {
#[derivative(PartialEq="ignore")]
pub id: ItemId,
pub path_components: Vec<Rc<String>>,
pub imported_names: ImportedNames
}
#[derive(Debug, PartialEq, Clone)]
pub enum ImportedNames {
All,
LastOfPath,
List(Vec<Rc<String>>)
}
#[derive(Debug, PartialEq, Clone)]
pub struct ModuleSpecifier {
pub name: Rc<String>,
pub contents: Vec<Statement>,
}

View File

@@ -0,0 +1,108 @@
use std::rc::Rc;
use std::str::FromStr;
use crate::tokenizing::TokenKind;
use crate::builtin::Builtin;
#[derive(Debug, PartialEq, Clone)]
pub struct PrefixOp {
sigil: Rc<String>,
pub builtin: Option<Builtin>,
}
impl PrefixOp {
#[allow(dead_code)]
pub fn sigil(&self) -> &Rc<String> {
&self.sigil
}
pub fn is_prefix(op: &str) -> bool {
match op {
"+" => true,
"-" => true,
"!" => true,
_ => false
}
}
}
impl FromStr for PrefixOp {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
use Builtin::*;
let builtin = match s {
"+" => Ok(Increment),
"-" => Ok(Negate),
"!" => Ok(BooleanNot),
_ => Err(())
};
builtin.map(|builtin| PrefixOp { sigil: Rc::new(s.to_string()), builtin: Some(builtin) })
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct BinOp {
sigil: Rc<String>,
pub builtin: Option<Builtin>,
}
impl BinOp {
pub fn from_sigil(sigil: &str) -> BinOp {
let builtin = Builtin::from_str(sigil).ok();
BinOp { sigil: Rc::new(sigil.to_string()), builtin }
}
pub fn sigil(&self) -> &Rc<String> {
&self.sigil
}
pub fn from_sigil_token(tok: &TokenKind) -> Option<BinOp> {
let s = token_kind_to_sigil(tok)?;
Some(BinOp::from_sigil(s))
}
pub fn min_precedence() -> i32 {
i32::min_value()
}
pub fn get_precedence_from_token(op_tok: &TokenKind) -> Option<i32> {
let s = token_kind_to_sigil(op_tok)?;
Some(binop_precedences(s))
}
}
fn token_kind_to_sigil<'a>(tok: &'a TokenKind) -> Option<&'a str> {
use self::TokenKind::*;
Some(match tok {
Operator(op) => op.as_str(),
Period => ".",
Pipe => "|",
Slash => "/",
LAngleBracket => "<",
RAngleBracket => ">",
Equals => "=",
_ => return None
})
}
fn binop_precedences(s: &str) -> i32 {
let default = 10_000_000;
match s {
"+" => 10,
"-" => 10,
"*" => 20,
"/" => 20,
"%" => 20,
"++" => 30,
"^" => 30,
"&" => 20,
"|" => 20,
">" => 20,
">=" => 20,
"<" => 20,
"<=" => 20,
"==" => 40,
"=" => 10,
"<=>" => 30,
_ => default,
}
}

View File

@@ -0,0 +1,41 @@
use std::rc::Rc;
use crate::ast::*;
//TODO maybe these functions should take closures that return a KeepRecursing | StopHere type,
//or a tuple of (T, <that type>)
pub trait ASTVisitor: Sized {
fn ast(&mut self, _ast: &AST) {}
fn block(&mut self, _statements: &Vec<Statement>) {}
fn statement(&mut self, _statement: &Statement) {}
fn declaration(&mut self, _declaration: &Declaration) {}
fn signature(&mut self, _signature: &Signature) {}
fn type_declaration(&mut self, _name: &TypeSingletonName, _body: &TypeBody, _mutable: bool) {}
fn type_alias(&mut self, _alias: &Rc<String>, _original: &Rc<String>) {}
fn binding(&mut self, _name: &Rc<String>, _constant: bool, _type_anno: Option<&TypeIdentifier>, _expr: &Expression) {}
fn implemention(&mut self, _type_name: &TypeIdentifier, _interface_name: Option<&TypeSingletonName>, _block: &Vec<Declaration>) {}
fn interface(&mut self, _name: &Rc<String>, _signatures: &Vec<Signature>) {}
fn expression(&mut self, _expression: &Expression) {}
fn expression_kind(&mut self, _kind: &ExpressionKind) {}
fn type_annotation(&mut self, _type_anno: Option<&TypeIdentifier>) {}
fn named_struct(&mut self, _name: &QualifiedName, _fields: &Vec<(Rc<String>, Expression)>) {}
fn call(&mut self, _f: &Expression, _arguments: &Vec<InvocationArgument>) {}
fn index(&mut self, _indexee: &Expression, _indexers: &Vec<Expression>) {}
fn if_expression(&mut self, _discrim: Option<&Expression>, _body: &IfExpressionBody) {}
fn condition_arm(&mut self, _arm: &ConditionArm) {}
fn while_expression(&mut self, _condition: Option<&Expression>, _body: &Block) {}
fn for_expression(&mut self, _enumerators: &Vec<Enumerator>, _body: &ForBody) {}
fn lambda(&mut self, _params: &Vec<FormalParam>, _type_anno: Option<&TypeIdentifier>, _body: &Block) {}
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) {}
fn string_literal(&mut self, _s: &Rc<String>) {}
fn bool_literal(&mut self, _b: bool) {}
fn binexp(&mut self, _op: &BinOp, _lhs: &Expression, _rhs: &Expression) {}
fn prefix_exp(&mut self, _op: &PrefixOp, _arg: &Expression) {}
fn pattern(&mut self, _pat: &Pattern) {}
}

View File

@@ -0,0 +1,41 @@
#![cfg(test)]
use crate::ast::visitor::ASTVisitor;
use crate::ast::walker;
use crate::util::quick_ast;
struct Tester {
count: u64,
float_count: u64
}
impl ASTVisitor for Tester {
fn nat_literal(&mut self, _n: u64) {
self.count += 1;
}
fn float_literal(&mut self, _f: f64) {
self.float_count += 1;
}
}
#[test]
fn foo() {
let mut tester = Tester { count: 0, float_count: 0 };
let (ast, _) = quick_ast(r#"
import gragh
let a = 20 + 84
let b = 28 + 1 + 2 + 2.0
fn heh() {
let m = 9
}
"#);
walker::walk_ast(&mut tester, &ast);
assert_eq!(tester.count, 6);
assert_eq!(tester.float_count, 1);
}

View File

@@ -0,0 +1,269 @@
#![allow(dead_code)]
use std::rc::Rc;
use crate::ast::*;
use crate::ast::visitor::ASTVisitor;
use crate::util::deref_optional_box;
pub fn walk_ast<V: ASTVisitor>(v: &mut V, ast: &AST) {
v.ast(ast);
walk_block(v, &ast.statements);
}
fn walk_block<V: ASTVisitor>(v: &mut V, block: &Vec<Statement>) {
for s in block {
v.statement(s);
statement(v, s);
}
}
fn statement<V: ASTVisitor>(v: &mut V, statement: &Statement) {
use StatementKind::*;
match statement.kind {
Expression(ref expr) => {
v.expression(expr);
expression(v, expr);
},
Declaration(ref decl) => {
v.declaration(decl);
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);
}
}
}
fn declaration<V: ASTVisitor>(v: &mut V, decl: &Declaration) {
use Declaration::*;
match decl {
FuncSig(sig) => {
v.signature(&sig);
signature(v, &sig);
},
FuncDecl(sig, block) => {
v.signature(&sig);
v.block(&block);
walk_block(v, block);
},
TypeDecl { name, body, mutable } => v.type_declaration(name, body, *mutable),
TypeAlias { alias, original} => v.type_alias(alias, original),
Binding { name, constant, type_anno, expr } => {
v.binding(name, *constant, type_anno.as_ref(), expr);
v.type_annotation(type_anno.as_ref());
v.expression(&expr);
expression(v, &expr);
},
Impl { type_name, interface_name, block } => {
v.implemention(type_name, interface_name.as_ref(), block);
}
Interface { name, signatures } => v.interface(name, signatures),
}
}
fn signature<V: ASTVisitor>(v: &mut V, signature: &Signature) {
for p in signature.params.iter() {
v.formal_param(p);
}
v.type_annotation(signature.type_anno.as_ref());
for p in signature.params.iter() {
formal_param(v, p);
}
}
fn expression<V: ASTVisitor>(v: &mut V, expression: &Expression) {
v.expression_kind(&expression.kind);
v.type_annotation(expression.type_anno.as_ref());
expression_kind(v, &expression.kind);
}
fn call<V: ASTVisitor>(v: &mut V, f: &Expression, args: &Vec<InvocationArgument>) {
v.expression(f);
expression(v, f);
for arg in args.iter() {
v.invocation_argument(arg);
invocation_argument(v, arg);
}
}
fn invocation_argument<V: ASTVisitor>(v: &mut V, arg: &InvocationArgument) {
use InvocationArgument::*;
match arg {
Positional(expr) => {
v.expression(expr);
expression(v, expr);
},
Keyword { expr, .. } => {
v.expression(expr);
expression(v, expr);
},
Ignored => (),
}
}
fn index<V: ASTVisitor>(v: &mut V, indexee: &Expression, indexers: &Vec<Expression>) {
v.expression(indexee);
for i in indexers.iter() {
v.expression(i);
}
}
fn named_struct<V: ASTVisitor>(v: &mut V, n: &QualifiedName, fields: &Vec<(Rc<String>, Expression)>) {
v.qualified_name(n);
for (_, expr) in fields.iter() {
v.expression(expr);
}
}
fn lambda<V: ASTVisitor>(v: &mut V, params: &Vec<FormalParam>, type_anno: Option<&TypeIdentifier>, body: &Block) {
for param in params {
v.formal_param(param);
formal_param(v, param);
}
v.type_annotation(type_anno);
v.block(body);
walk_block(v, body);
}
fn formal_param<V: ASTVisitor>(v: &mut V, param: &FormalParam) {
param.default.as_ref().map(|p| {
v.expression(p);
expression(v, p);
});
v.type_annotation(param.anno.as_ref());
}
fn expression_kind<V: ASTVisitor>(v: &mut V, expression_kind: &ExpressionKind) {
use ExpressionKind::*;
match expression_kind {
NatLiteral(n) => v.nat_literal(*n),
FloatLiteral(f) => v.float_literal(*f),
StringLiteral(s) => v.string_literal(s),
BoolLiteral(b) => v.bool_literal(*b),
BinExp(op, lhs, rhs) => {
v.binexp(op, lhs, rhs);
expression(v, lhs);
expression(v, rhs);
},
PrefixExp(op, arg) => {
v.prefix_exp(op, arg);
expression(v, arg);
}
TupleLiteral(exprs) => {
for expr in exprs {
v.expression(expr);
expression(v, expr);
}
},
Value(name) => v.qualified_name(name),
NamedStruct { name, fields } => {
v.named_struct(name, fields);
named_struct(v, name, fields);
}
Call { f, arguments } => {
v.call(f, arguments);
call(v, f, arguments);
},
Index { indexee, indexers } => {
v.index(indexee, indexers);
index(v, indexee, indexers);
},
IfExpression { discriminator, body } => {
v.if_expression(deref_optional_box(discriminator), body);
discriminator.as_ref().map(|d| expression(v, d));
if_expression_body(v, body);
},
WhileExpression { condition, body } => v.while_expression(deref_optional_box(condition), body),
ForExpression { enumerators, body } => v.for_expression(enumerators, body),
Lambda { params , type_anno, body } => {
v.lambda(params, type_anno.as_ref(), body);
lambda(v, params, type_anno.as_ref(), body);
},
ListLiteral(exprs) => {
for expr in exprs {
v.expression(expr);
expression(v, expr);
}
},
}
}
fn if_expression_body<V: ASTVisitor>(v: &mut V, body: &IfExpressionBody) {
use IfExpressionBody::*;
match body {
SimpleConditional { then_case, else_case } => {
walk_block(v, then_case);
else_case.as_ref().map(|block| walk_block(v, block));
},
SimplePatternMatch { pattern, then_case, else_case } => {
v.pattern(pattern);
walk_pattern(v, pattern);
walk_block(v, then_case);
else_case.as_ref().map(|block| walk_block(v, block));
},
CondList(arms) => {
for arm in arms {
v.condition_arm(arm);
condition_arm(v, arm);
}
}
}
}
fn condition_arm<V: ASTVisitor>(v: &mut V, arm: &ConditionArm) {
use Condition::*;
v.condition_arm(arm);
match arm.condition {
Pattern(ref pat) => {
v.pattern(pat);
walk_pattern(v, pat);
},
TruncatedOp(ref _binop, ref expr) => {
v.expression(expr);
expression(v, expr);
},
Expression(ref expr) => {
v.expression(expr);
expression(v, expr);
},
_ => ()
}
arm.guard.as_ref().map(|guard| {
v.expression(guard);
expression(v, guard);
});
v.block(&arm.body);
walk_block(v, &arm.body);
}
fn walk_pattern<V: ASTVisitor>(v: &mut V, pat: &Pattern) {
use Pattern::*;
match pat {
TuplePattern(patterns) => {
for pat in patterns {
v.pattern(pat);
walk_pattern(v, pat);
}
},
TupleStruct(qualified_name, patterns) => {
v.qualified_name(qualified_name);
for pat in patterns {
v.pattern(pat);
walk_pattern(v, pat);
}
},
Record(qualified_name, name_and_patterns) => {
v.qualified_name(qualified_name);
for (_, pat) in name_and_patterns {
v.pattern(pat);
walk_pattern(v, pat);
}
},
VarOrName(qualified_name) => {
v.qualified_name(qualified_name);
},
_ => ()
}
}

View File

@@ -1,145 +1,102 @@
use std::rc::Rc;
use std::collections::HashMap;
use std::fmt;
use std::str::FromStr;
use crate::tokenizing::TokenKind;
use self::BuiltinTypeSpecifier::*;
use self::BuiltinTConst::*;
use crate::typechecking::{TypeConst, Type};
#[derive(Debug, PartialEq, Clone)]
pub enum BuiltinTypeSpecifier {
Const(BuiltinTConst),
Func(Box<BuiltinTypeSpecifier>, Box<BuiltinTypeSpecifier>),
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Builtin {
Add,
Increment,
Subtract,
Negate,
Multiply,
Divide,
Quotient,
Modulo,
Exponentiation,
BitwiseAnd,
BitwiseOr,
BooleanAnd,
BooleanOr,
BooleanNot,
Equality,
LessThan,
LessThanOrEqual,
GreaterThan,
GreaterThanOrEqual,
Comparison,
FieldAccess,
IOPrint,
IOPrintLn,
IOGetLine,
Assignment,
Concatenate,
}
#[derive(Debug, PartialEq, Clone)]
pub enum BuiltinTConst {
Nat,
Int,
Float,
StringT,
Bool,
}
impl fmt::Display for BuiltinTypeSpecifier {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
impl Builtin {
pub fn get_type(&self) -> Type {
use Builtin::*;
match self {
Add => ty!(Nat -> Nat -> Nat),
Subtract => ty!(Nat -> Nat -> Nat),
Multiply => ty!(Nat -> Nat -> Nat),
Divide => ty!(Nat -> Nat -> Float),
Quotient => ty!(Nat -> Nat -> Nat),
Modulo => ty!(Nat -> Nat -> Nat),
Exponentiation => ty!(Nat -> Nat -> Nat),
BitwiseAnd => ty!(Nat -> Nat -> Nat),
BitwiseOr => ty!(Nat -> Nat -> Nat),
BooleanAnd => ty!(Bool -> Bool -> Bool),
BooleanOr => ty!(Bool -> Bool -> Bool),
BooleanNot => ty!(Bool -> Bool),
Equality => ty!(Nat -> Nat -> Bool),
LessThan => ty!(Nat -> Nat -> Bool),
LessThanOrEqual => ty!(Nat -> Nat -> Bool),
GreaterThan => ty!(Nat -> Nat -> Bool),
GreaterThanOrEqual => ty!(Nat -> Nat -> Bool),
Comparison => ty!(Nat -> Nat -> Ordering),
FieldAccess => ty!(Unit),
IOPrint => ty!(Unit),
IOPrintLn => ty!(Unit) ,
IOGetLine => ty!(StringT),
Assignment => ty!(Unit),
Concatenate => ty!(StringT -> StringT -> StringT),
Increment => ty!(Nat -> Int),
Negate => ty!(Nat -> Int)
}
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct BinOp {
sigil: Rc<String>
}
impl FromStr for Builtin {
type Err = ();
impl BinOp {
pub fn from_sigil(sigil: &str) -> BinOp {
BinOp { sigil: Rc::new(sigil.to_string()) }
}
pub fn sigil(&self) -> &Rc<String> {
&self.sigil
}
pub fn from_sigil_token(tok: &TokenKind) -> Option<BinOp> {
use self::TokenKind::*;
let s = match tok {
Operator(op) => op,
Period => ".",
Pipe => "|",
Slash => "/",
LAngleBracket => "<",
RAngleBracket => ">",
_ => return None
};
Some(BinOp::from_sigil(s))
}
/*
pub fn get_type(&self) -> Result<Type, String> {
let s = self.sigil.as_str();
BINOPS.get(s).map(|x| x.0.clone()).ok_or(format!("Binop {} not found", s))
}
*/
pub fn min_precedence() -> i32 {
i32::min_value()
}
pub fn get_precedence_from_token(op: &TokenKind) -> Option<i32> {
use self::TokenKind::*;
let s = match op {
Operator(op) => op,
Period => ".",
Pipe => "|",
Slash => "/",
LAngleBracket => "<",
RAngleBracket => ">",
_ => return None
};
let default = 10_000_000;
Some(BINOPS.get(s).map(|x| x.2.clone()).unwrap_or_else(|| {
default
}))
}
pub fn get_precedence(&self) -> i32 {
let s: &str = &self.sigil;
let default = 10_000_000;
BINOPS.get(s).map(|x| x.2.clone()).unwrap_or_else(|| {
default
fn from_str(s: &str) -> Result<Self, Self::Err> {
use Builtin::*;
Ok(match s {
"+" => Add,
"-" => Subtract,
"*" => Multiply,
"/" => Divide,
"quot" => Quotient,
"%" => Modulo,
"++" => Concatenate,
"^" => Exponentiation,
"&" => BitwiseAnd,
"&&" => BooleanAnd,
"|" => BitwiseOr,
"||" => BooleanOr,
"!" => BooleanNot,
">" => GreaterThan,
">=" => GreaterThanOrEqual,
"<" => LessThan,
"<=" => LessThanOrEqual,
"==" => Equality,
"=" => Assignment,
"<=>" => Comparison,
"." => FieldAccess,
"print" => IOPrint,
"println" => IOPrintLn,
"getline" => IOGetLine,
_ => return Err(())
})
}
}
#[derive(Debug, PartialEq, Clone)]
pub struct PrefixOp {
sigil: Rc<String>
}
impl PrefixOp {
pub fn from_sigil(sigil: &str) -> PrefixOp {
PrefixOp { sigil: Rc::new(sigil.to_string()) }
}
pub fn sigil(&self) -> &Rc<String> {
&self.sigil
}
pub fn is_prefix(op: &str) -> bool {
PREFIX_OPS.get(op).is_some()
}
/*
pub fn get_type(&self) -> Result<Type, String> {
let s = self.sigil.as_str();
PREFIX_OPS.get(s).map(|x| x.0.clone()).ok_or(format!("Prefix op {} not found", s))
}
*/
}
lazy_static! {
static ref PREFIX_OPS: HashMap<&'static str, (BuiltinTypeSpecifier, ())> =
hashmap! {
"+" => (Func(bx!(Const(Int)), bx!(Const(Int))), ()),
"-" => (Func(bx!(Const(Int)), bx!(Const(Int))), ()),
"!" => (Func(bx!(Const(Bool)), bx!(Const(Bool))), ()),
};
}
/* the second tuple member is a placeholder for when I want to make evaluation rules tied to the
* binop definition */
lazy_static! {
static ref BINOPS: HashMap<&'static str, (BuiltinTypeSpecifier, (), i32)> =
hashmap! {
"+" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 10),
"-" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 10),
"*" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
"/" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Float))))), (), 20),
"quot" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
"%" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
"++" => (Func(bx!(Const(StringT)), bx!(Func(bx!(Const(StringT)), bx!(Const(StringT))))), (), 30),
"^" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
"&" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
"|" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
">" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
">=" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
"<" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
"<=" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
"==" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
"=" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
"<=>" => (Func(bx!(Const(Nat)), bx!(Func(bx!(Const(Nat)), bx!(Const(Nat))))), (), 20),
};
}

View File

@@ -0,0 +1,10 @@
use crate::ast::*;
impl AST {
pub fn compact_debug(&self) -> String {
format!("{:?}", self)
}
pub fn expanded_debug(&self) -> String {
format!("{:#?}", self)
}
}

View File

@@ -1,32 +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};
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>>,
}
macro_rules! builtin_binding {
($name:expr, $values:expr) => {
$values.insert(Rc::new(format!($name)), ValueEntry::Binding { constant: true, val: Node::Expr(Expr::Func(Func::BuiltIn(Rc::new(format!($name))))) });
}
symbol_table_handle: SymbolTableHandle,
}
impl<'a> State<'a> {
pub fn new(symbol_table_handle: Rc<RefCell<SymbolTable>>) -> State<'a> {
let mut values = ScopeStack::new(Some(format!("global")));
builtin_binding!("print", values);
builtin_binding!("println", values);
builtin_binding!("getline", values);
pub fn new(symbol_table_handle: SymbolTableHandle) -> State<'a> {
let values = ScopeStack::new(Some(format!("global")));
State { values, symbol_table_handle }
}
@@ -76,12 +69,12 @@ fn paren_wrapped_vec(terms: impl Iterator<Item=String>) -> String {
impl Node {
fn to_repl(&self) -> String {
fn to_repl(&self, symbol_table: &SymbolTable) -> String {
match self {
Node::Expr(e) => e.to_repl(),
Node::Expr(e) => e.to_repl(symbol_table),
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()))),
Node::PrimTuple { items } => format!("{}", paren_wrapped_vec(items.iter().map(|x| x.to_repl()))),
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)))),
}
}
fn is_true(&self) -> bool {
@@ -106,10 +99,12 @@ impl Expr {
fn to_node(self) -> Node {
Node::Expr(self)
}
fn to_repl(&self) -> String {
fn to_repl(&self, symbol_table: &SymbolTable) -> String {
use self::Lit::*;
use self::Func::*;
let _ = symbol_table;
match self {
Expr::Lit(ref l) => match l {
Nat(n) => format!("{}", n),
@@ -119,18 +114,14 @@ impl Expr {
StringLit(s) => format!("\"{}\"", s),
},
Expr::Func(f) => match f {
BuiltIn(name) => format!("<built-in function '{}'>", name),
BuiltIn(builtin) => format!("<built-in function '{:?}'>", builtin),
UserDefined { name: None, .. } => format!("<function>"),
UserDefined { name: Some(name), .. } => format!("<function '{}'>", name),
},
Expr::Constructor {
type_name: _, name, arity, ..
} => if *arity == 0 {
format!("{}", name)
} else {
format!("<data constructor '{}'>", name)
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())),
Expr::Tuple(exprs) => paren_wrapped_vec(exprs.iter().map(|x| x.to_repl(symbol_table))),
_ => format!("{:?}", self),
}
}
@@ -140,8 +131,8 @@ impl Expr {
match self {
ConditionalTargetSigilValue => replacement.clone(),
Unit | Lit(_) | Func(_) | Val(_) | Constructor { .. } |
CaseMatch { .. } | UnimplementedSigilValue => self,
Unit | Lit(_) | Func(_) | Sym(_) | Constructor { .. } |
CaseMatch { .. } | UnimplementedSigilValue | ReductionError(_) => self,
Tuple(exprs) => Tuple(exprs.into_iter().map(|e| e.replace_conditional_target_sigil(replacement)).collect()),
Call { f, args } => {
let new_args = args.into_iter().map(|e| e.replace_conditional_target_sigil(replacement)).collect();
@@ -164,7 +155,10 @@ impl<'a> State<'a> {
for statement in ast.0 {
match self.statement(statement) {
Ok(Some(ref output)) if repl => acc.push(Ok(output.to_repl())),
Ok(Some(ref output)) if repl => {
let ref symbol_table = self.symbol_table_handle.borrow();
acc.push(Ok(output.to_repl(symbol_table)))
},
Ok(_) => (),
Err(error) => {
acc.push(Err(format!("Runtime error: {}", error)));
@@ -217,7 +211,7 @@ impl<'a> State<'a> {
Node::Expr(expr) => match expr {
literal @ Lit(_) => Ok(Node::Expr(literal)),
Call { box f, args } => self.call_expression(f, args),
Val(v) => self.value(v),
Sym(v) => self.handle_sym(v),
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)),
@@ -231,6 +225,7 @@ impl<'a> State<'a> {
CaseMatch { box cond, alternatives } => self.case_match_expression(cond, alternatives),
ConditionalTargetSigilValue => Ok(Node::Expr(ConditionalTargetSigilValue)),
UnimplementedSigilValue => Err(format!("Sigil value eval not implemented")),
ReductionError(err) => Err(format!("Reduction error: {}", err)),
}
}
}
@@ -246,7 +241,7 @@ impl<'a> State<'a> {
fn apply_data_constructor(&mut self, _type_name: Rc<String>, name: Rc<String>, tag: usize, arity: usize, args: Vec<Expr>) -> EvalResult<Node> {
if arity != args.len() {
return Err(format!("Data constructor {} requires {} args", name, arity));
return Err(format!("Data constructor {} requires {} arg(s)", name, arity));
}
let evaled_args = args.into_iter().map(|expr| self.expression(Node::Expr(expr))).collect::<Result<Vec<Node>,_>>()?;
@@ -260,7 +255,7 @@ impl<'a> State<'a> {
fn apply_function(&mut self, f: Func, args: Vec<Expr>) -> EvalResult<Node> {
match f {
Func::BuiltIn(sigil) => Ok(Node::Expr(self.apply_builtin(sigil, args)?)),
Func::BuiltIn(builtin) => Ok(self.apply_builtin(builtin, args)?),
Func::UserDefined { params, body, name } => {
if params.len() != args.len() {
@@ -280,81 +275,86 @@ impl<'a> State<'a> {
}
}
fn apply_builtin(&mut self, name: Rc<String>, args: Vec<Expr>) -> EvalResult<Expr> {
fn apply_builtin(&mut self, builtin: Builtin, args: Vec<Expr>) -> EvalResult<Node> {
use self::Expr::*;
use self::Lit::*;
let evaled_args: Result<Vec<Expr>, String> = args.into_iter().map(|arg| {
match self.expression(Node::Expr(arg)) {
Ok(Node::Expr(e)) => Ok(e),
Ok(Node::PrimTuple { .. }) => Err(format!("Trying to apply a builtin to a tuple")),
Ok(Node::PrimObject { .. }) => Err(format!("Trying to apply a builtin to a primitive object")),
Err(e) => Err(e)
}
}).collect();
use Builtin::*;
let evaled_args: Result<Vec<Node>, String> = args.into_iter().map(|arg| self.expression(arg.to_node()))
.collect();
let evaled_args = evaled_args?;
Ok(match (name.as_str(), evaled_args.as_slice()) {
/* binops */
("+", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l + r)),
("++", &[Lit(StringLit(ref s1)), Lit(StringLit(ref s2))]) => Lit(StringLit(Rc::new(format!("{}{}", s1, s2)))),
("-", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l - r)),
("*", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l * r)),
("/", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Float((l as f64)/ (r as f64))),
("quot", &[Lit(Nat(l)), Lit(Nat(r))]) => if r == 0 {
return Err(format!("divide by zero"));
} else {
Lit(Nat(l / r))
Ok(match (builtin, evaled_args.as_slice()) {
(FieldAccess, &[Node::PrimObject { .. }]) => {
//TODO implement field access
unimplemented!()
},
("%", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l % r)),
("^", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l ^ r)),
("&", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l & r)),
("|", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Nat(l | r)),
(binop, &[Node::Expr(ref lhs), Node::Expr(ref rhs)]) => match (binop, lhs, rhs) {
/* binops */
(Add, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l + r)),
(Concatenate, Lit(StringLit(ref s1)), Lit(StringLit(ref s2))) => Lit(StringLit(Rc::new(format!("{}{}", s1, s2)))),
(Subtract, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l - r)),
(Multiply, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l * r)),
(Divide, Lit(Nat(l)), Lit(Nat(r))) => Lit(Float((*l as f64)/ (*r as f64))),
(Quotient, Lit(Nat(l)), Lit(Nat(r))) => if *r == 0 {
return Err(format!("divide by zero"));
} else {
Lit(Nat(l / r))
},
(Modulo, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l % r)),
(Exponentiation, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l ^ r)),
(BitwiseAnd, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l & r)),
(BitwiseOr, Lit(Nat(l)), Lit(Nat(r))) => Lit(Nat(l | r)),
/* comparisons */
("==", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l == r)),
("==", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l == r)),
("==", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l == r)),
("==", &[Lit(Bool(l)), Lit(Bool(r))]) => Lit(Bool(l == r)),
("==", &[Lit(StringLit(ref l)), Lit(StringLit(ref r))]) => Lit(Bool(l == r)),
/* comparisons */
(Equality, Lit(Nat(l)), Lit(Nat(r))) => Lit(Bool(l == r)),
(Equality, Lit(Int(l)), Lit(Int(r))) => Lit(Bool(l == r)),
(Equality, Lit(Float(l)), Lit(Float(r))) => Lit(Bool(l == r)),
(Equality, Lit(Bool(l)), Lit(Bool(r))) => Lit(Bool(l == r)),
(Equality, Lit(StringLit(ref l)), Lit(StringLit(ref r))) => Lit(Bool(l == r)),
("<", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l < r)),
("<", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l < r)),
("<", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l < r)),
(LessThan, Lit(Nat(l)), Lit(Nat(r))) => Lit(Bool(l < r)),
(LessThan, Lit(Int(l)), Lit(Int(r))) => Lit(Bool(l < r)),
(LessThan, Lit(Float(l)), Lit(Float(r))) => Lit(Bool(l < r)),
("<=", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l <= r)),
("<=", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l <= r)),
("<=", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l <= r)),
(LessThanOrEqual, Lit(Nat(l)), Lit(Nat(r))) => Lit(Bool(l <= r)),
(LessThanOrEqual, Lit(Int(l)), Lit(Int(r))) => Lit(Bool(l <= r)),
(LessThanOrEqual, Lit(Float(l)), Lit(Float(r))) => Lit(Bool(l <= r)),
(">", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l > r)),
(">", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l > r)),
(">", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l > r)),
(">=", &[Lit(Nat(l)), Lit(Nat(r))]) => Lit(Bool(l >= r)),
(">=", &[Lit(Int(l)), Lit(Int(r))]) => Lit(Bool(l >= r)),
(">=", &[Lit(Float(l)), Lit(Float(r))]) => Lit(Bool(l >= r)),
/* prefix ops */
("!", &[Lit(Bool(true))]) => Lit(Bool(false)),
("!", &[Lit(Bool(false))]) => Lit(Bool(true)),
("-", &[Lit(Nat(n))]) => Lit(Int(-1*(n as i64))),
("-", &[Lit(Int(n))]) => Lit(Int(-1*(n as i64))),
("+", &[Lit(Int(n))]) => Lit(Int(n)),
("+", &[Lit(Nat(n))]) => Lit(Nat(n)),
(GreaterThan, Lit(Nat(l)), Lit(Nat(r))) => Lit(Bool(l > r)),
(GreaterThan, Lit(Int(l)), Lit(Int(r))) => Lit(Bool(l > r)),
(GreaterThan, Lit(Float(l)), Lit(Float(r))) => Lit(Bool(l > r)),
(GreaterThanOrEqual, Lit(Nat(l)), Lit(Nat(r))) => Lit(Bool(l >= r)),
(GreaterThanOrEqual, Lit(Int(l)), Lit(Int(r))) => Lit(Bool(l >= r)),
(GreaterThanOrEqual, Lit(Float(l)), Lit(Float(r))) => Lit(Bool(l >= r)),
_ => return Err("No valid binop".to_string())
}.to_node(),
(prefix, &[Node::Expr(ref arg)]) => match (prefix, arg) {
(BooleanNot, Lit(Bool(true))) => Lit(Bool(false)),
(BooleanNot, Lit(Bool(false))) => Lit(Bool(true)),
(Negate, Lit(Nat(n))) => Lit(Int(-1*(*n as i64))),
(Negate, Lit(Int(n))) => Lit(Int(-1*(*n as i64))),
(Increment, Lit(Int(n))) => Lit(Int(*n)),
(Increment, Lit(Nat(n))) => Lit(Nat(*n)),
_ => return Err("No valid prefix op".to_string())
}.to_node(),
/* builtin functions */
("print", &[ref anything]) => {
print!("{}", anything.to_repl());
Expr::Unit
(IOPrint, &[ref anything]) => {
let ref symbol_table = self.symbol_table_handle.borrow();
print!("{}", anything.to_repl(symbol_table));
Expr::Unit.to_node()
},
("println", &[ref anything]) => {
println!("{}", anything.to_repl());
Expr::Unit
(IOPrintLn, &[ref anything]) => {
let ref symbol_table = self.symbol_table_handle.borrow();
println!("{}", anything.to_repl(symbol_table));
Expr::Unit.to_node()
},
("getline", &[]) => {
(IOGetLine, &[]) => {
let mut buf = String::new();
io::stdin().read_line(&mut buf).expect("Error readling line in 'getline'");
Lit(StringLit(Rc::new(buf.trim().to_string())))
Lit(StringLit(Rc::new(buf.trim().to_string()))).to_node()
},
(x, args) => return Err(format!("bad or unimplemented builtin {:?} | {:?}", x, args)),
})
@@ -371,7 +371,7 @@ impl<'a> State<'a> {
fn assign_expression(&mut self, val: Expr, expr: Expr) -> EvalResult<Node> {
let name = match val {
Expr::Val(name) => name,
Expr::Sym(name) => name,
_ => return Err(format!("Trying to assign to a non-value")),
};
@@ -425,15 +425,15 @@ impl<'a> State<'a> {
let cond = self.expression(Node::Expr(cond))?;
for alt in alternatives {
// no matter what type of condition we have, ignore alternative if the guard evaluates false
if !self.guard_passes(&alt.guard, &cond)? {
if !self.guard_passes(&alt.matchable.guard, &cond)? {
continue;
}
match cond {
Node::PrimObject { ref tag, ref items, .. } => {
if alt.tag.map(|t| t == *tag).unwrap_or(true) {
let mut inner_state = self.new_frame(items, &alt.bound_vars);
if all_subpatterns_pass(&mut inner_state, &alt.subpatterns, items)? {
if alt.matchable.tag.map(|t| t == *tag).unwrap_or(true) {
let mut inner_state = self.new_frame(items, &alt.matchable.bound_vars);
if all_subpatterns_pass(&mut inner_state, &alt.matchable.subpatterns, items)? {
return inner_state.block(alt.item);
} else {
continue;
@@ -441,15 +441,15 @@ impl<'a> State<'a> {
}
},
Node::PrimTuple { ref items } => {
let mut inner_state = self.new_frame(items, &alt.bound_vars);
if all_subpatterns_pass(&mut inner_state, &alt.subpatterns, items)? {
let mut inner_state = self.new_frame(items, &alt.matchable.bound_vars);
if all_subpatterns_pass(&mut inner_state, &alt.matchable.subpatterns, items)? {
return inner_state.block(alt.item);
} else {
continue;
}
},
Node::Expr(ref _e) => {
if let None = alt.tag {
if let None = alt.matchable.tag {
return self.block(alt.item)
}
}
@@ -458,20 +458,21 @@ impl<'a> State<'a> {
Err(format!("{:?} failed pattern match", cond))
}
fn value(&mut self, name: Rc<String>) -> EvalResult<Node> {
//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_name(&name);
let value = symbol_table.lookup_by_fqsn(&fqsn!(name ; tr));
Ok(match value {
Some(Symbol { name, spec }) => match spec {
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: name.clone(), tag: 0, items: vec![] }
Node::PrimObject { name: local_name.clone(), tag: 0, items: vec![] }
} else {
return Err(format!("This data constructor thing not done"))
}
@@ -483,8 +484,15 @@ impl<'a> State<'a> {
_ => 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))
}
SymbolSpec::Type { name } => return Err(format!("Symbol {} not in scope", name)),
},
/* see if it's an ordinary variable TODO make variables go in symbol table */
//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)),
@@ -492,257 +500,3 @@ impl<'a> State<'a> {
})
}
}
#[cfg(test)]
mod eval_tests {
use std::cell::RefCell;
use std::rc::Rc;
use crate::tokenizing::{Token, tokenize};
use crate::parsing::ParseResult;
use crate::ast::AST;
use crate::symbol_table::SymbolTable;
use crate::eval::State;
fn parse(tokens: Vec<Token>) -> ParseResult<AST> {
let mut parser = crate::parsing::Parser::new(tokens);
parser.parse()
}
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 ast = parse(tokenize(input)).unwrap();
state.symbol_table_handle.borrow_mut().add_top_level_symbols(&ast).unwrap();
let reduced = ast.reduce(&state.symbol_table_handle.borrow());
let all_output = state.evaluate(reduced, true);
all_output
}
macro_rules! test_in_fresh_env {
($string:expr, $correct:expr) => {
{
let all_output = evaluate_all_outputs($string);
let ref output = all_output.last().unwrap();
assert_eq!(**output, Ok($correct.to_string()));
}
}
}
#[test]
fn test_basic_eval() {
test_in_fresh_env!("1 + 2", "3");
test_in_fresh_env!("let mut a = 1; a = 2", "Unit");
test_in_fresh_env!("let mut a = 1; a = 2; a", "2");
test_in_fresh_env!(r#"("a", 1 + 2)"#, r#"("a", 3)"#);
}
#[test]
fn function_eval() {
test_in_fresh_env!("fn oi(x) { x + 1 }; oi(4)", "5");
test_in_fresh_env!("fn oi(x) { x + 1 }; oi(1+2)", "4");
}
#[test]
fn scopes() {
let scope_ok = r#"
let a = 20
fn haha() {
let a = 10
a
}
haha()
"#;
test_in_fresh_env!(scope_ok, "10");
let scope_ok = r#"
let a = 20
fn haha() {
let a = 10
a
}
a
"#;
test_in_fresh_env!(scope_ok, "20");
}
#[test]
fn if_is_patterns() {
let source = r#"
type Option<T> = Some(T) | None
let x = Some(9); if x is Some(q) then { q } else { 0 }"#;
test_in_fresh_env!(source, "9");
let source = r#"
type Option<T> = Some(T) | None
let x = None; if x is Some(q) then { q } else { 0 }"#;
test_in_fresh_env!(source, "0");
}
#[test]
fn full_if_matching() {
let source = r#"
type Option<T> = Some(T) | None
let a = None
if a { is None -> 4, is Some(x) -> x }
"#;
test_in_fresh_env!(source, "4");
let source = r#"
type Option<T> = Some(T) | None
let a = Some(99)
if a { is None -> 4, is Some(x) -> x }
"#;
test_in_fresh_env!(source, "99");
let source = r#"
let a = 10
if a { is 10 -> "x", is 4 -> "y" }
"#;
test_in_fresh_env!(source, "\"x\"");
let source = r#"
let a = 10
if a { is 15 -> "x", is 10 -> "y" }
"#;
test_in_fresh_env!(source, "\"y\"");
}
#[test]
fn string_pattern() {
let source = r#"
let a = "foo"
if a { is "foo" -> "x", is _ -> "y" }
"#;
test_in_fresh_env!(source, "\"x\"");
}
#[test]
fn boolean_pattern() {
let source = r#"
let a = true
if a {
is true -> "x",
is false -> "y"
}
"#;
test_in_fresh_env!(source, "\"x\"");
}
#[test]
fn boolean_pattern_2() {
let source = r#"
let a = false
if a { is true -> "x", is false -> "y" }
"#;
test_in_fresh_env!(source, "\"y\"");
}
#[test]
fn ignore_pattern() {
let source = r#"
type Option<T> = Some(T) | None
if Some(10) {
is _ -> "hella"
}
"#;
test_in_fresh_env!(source, "\"hella\"");
}
#[test]
fn tuple_pattern() {
let source = r#"
if (1, 2) {
is (1, x) -> x,
is _ -> 99
}
"#;
test_in_fresh_env!(source, 2);
}
#[test]
fn tuple_pattern_2() {
let source = r#"
if (1, 2) {
is (10, x) -> x,
is (y, x) -> x + y
}
"#;
test_in_fresh_env!(source, 3);
}
#[test]
fn tuple_pattern_3() {
let source = r#"
if (1, 5) {
is (10, x) -> x,
is (1, x) -> x
}
"#;
test_in_fresh_env!(source, 5);
}
#[test]
fn tuple_pattern_4() {
let source = r#"
if (1, 5) {
is (10, x) -> x,
is (1, x) -> x,
}
"#;
test_in_fresh_env!(source, 5);
}
#[test]
fn prim_obj_pattern() {
let source = r#"
type Stuff = Mulch(Nat) | Jugs(Nat, String) | Mardok
let a = Mulch(20)
let b = Jugs(1, "haha")
let c = Mardok
let x = if a {
is Mulch(20) -> "x",
is _ -> "ERR"
}
let y = if b {
is Mulch(n) -> "ERR",
is Jugs(2, _) -> "ERR",
is Jugs(1, s) -> s,
is _ -> "ERR",
}
let z = if c {
is Jugs(_, _) -> "ERR",
is Mardok -> "NIGH",
is _ -> "ERR",
}
(x, y, z)
"#;
test_in_fresh_env!(source, r#"("x", "haha", "NIGH")"#);
}
#[test]
fn basic_lambda_syntax() {
let source = r#"
let q = \(x, y) { x * y }
let x = q(5,2)
let y = \(m, n, o) { m + n + o }(1,2,3)
(x, y)
"#;
test_in_fresh_env!(source, r"(10, 6)");
}
#[test]
fn lambda_syntax_2() {
let source = r#"
fn milta() {
\(x) { x + 33 }
}
milta()(10)
"#;
test_in_fresh_env!(source, "43");
}
}

View File

@@ -0,0 +1,269 @@
#![cfg(test)]
use std::cell::RefCell;
use std::rc::Rc;
use crate::symbol_table::SymbolTable;
use crate::scope_resolution::ScopeResolver;
use crate::reduced_ast::reduce;
use crate::eval::State;
fn evaluate_all_outputs(input: &str) -> Vec<Result<String, String>> {
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)));
let mut state = State::new(symbol_table);
state.symbol_table_handle.borrow_mut().add_top_level_symbols(&ast).unwrap();
{
let mut scope_resolver = ScopeResolver::new(state.symbol_table_handle.clone());
let _ = scope_resolver.resolve(&mut ast);
}
let reduced = reduce(&ast, &state.symbol_table_handle.borrow());
let all_output = state.evaluate(reduced, true);
all_output
}
macro_rules! test_in_fresh_env {
($string:expr, $correct:expr) => {
{
let all_output = evaluate_all_outputs($string);
let ref output = all_output.last().unwrap();
assert_eq!(**output, Ok($correct.to_string()));
}
}
}
#[test]
fn test_basic_eval() {
test_in_fresh_env!("1 + 2", "3");
test_in_fresh_env!("let mut a = 1; a = 2", "Unit");
/*
test_in_fresh_env!("let mut a = 1; a = 2; a", "2");
test_in_fresh_env!(r#"("a", 1 + 2)"#, r#"("a", 3)"#);
*/
}
#[test]
fn op_eval() {
test_in_fresh_env!("- 13", "-13");
test_in_fresh_env!("10 - 2", "8");
}
#[test]
fn function_eval() {
test_in_fresh_env!("fn oi(x) { x + 1 }; oi(4)", "5");
test_in_fresh_env!("fn oi(x) { x + 1 }; oi(1+2)", "4");
}
#[test]
fn scopes() {
let scope_ok = r#"
let a = 20
fn haha() {
let a = 10
a
}
haha()
"#;
test_in_fresh_env!(scope_ok, "10");
let scope_ok = r#"
let a = 20
fn queque() {
let a = 10
a
}
a
"#;
test_in_fresh_env!(scope_ok, "20");
}
#[test]
fn if_is_patterns() {
let source = r#"
type Option<T> = Some(T) | None
let x = Option::Some(9); if x is Option::Some(q) then { q } else { 0 }"#;
test_in_fresh_env!(source, "9");
let source = r#"
type Option<T> = Some(T) | None
let x = Option::None; if x is Option::Some(q) then { q } else { 0 }"#;
test_in_fresh_env!(source, "0");
}
#[test]
fn full_if_matching() {
let source = r#"
type Option<T> = Some(T) | None
let a = Option::None
if a { is Option::None then 4, is Option::Some(x) then x }
"#;
test_in_fresh_env!(source, "4");
let source = r#"
type Option<T> = Some(T) | None
let a = Option::Some(99)
if a { is Option::None then 4, is Option::Some(x) then x }
"#;
test_in_fresh_env!(source, "99");
let source = r#"
let a = 10
if a { is 10 then "x", is 4 then "y" }
"#;
test_in_fresh_env!(source, "\"x\"");
let source = r#"
let a = 10
if a { is 15 then "x", is 10 then "y" }
"#;
test_in_fresh_env!(source, "\"y\"");
}
#[test]
fn string_pattern() {
let source = r#"
let a = "foo"
if a { is "foo" then "x", is _ then "y" }
"#;
test_in_fresh_env!(source, "\"x\"");
}
#[test]
fn boolean_pattern() {
let source = r#"
let a = true
if a {
is true then "x",
is false then "y"
}
"#;
test_in_fresh_env!(source, "\"x\"");
}
#[test]
fn boolean_pattern_2() {
let source = r#"
let a = false
if a { is true then "x", is false then "y" }
"#;
test_in_fresh_env!(source, "\"y\"");
}
#[test]
fn ignore_pattern() {
let source = r#"
type Option<T> = Some(T) | None
if Option::Some(10) {
is _ then "hella"
}
"#;
test_in_fresh_env!(source, "\"hella\"");
}
#[test]
fn tuple_pattern() {
let source = r#"
if (1, 2) {
is (1, x) then x,
is _ then 99
}
"#;
test_in_fresh_env!(source, 2);
}
#[test]
fn tuple_pattern_2() {
let source = r#"
if (1, 2) {
is (10, x) then x,
is (y, x) then x + y
}
"#;
test_in_fresh_env!(source, 3);
}
#[test]
fn tuple_pattern_3() {
let source = r#"
if (1, 5) {
is (10, x) then x,
is (1, x) then x
}
"#;
test_in_fresh_env!(source, 5);
}
#[test]
fn tuple_pattern_4() {
let source = r#"
if (1, 5) {
is (10, x) then x,
is (1, x) then x,
}
"#;
test_in_fresh_env!(source, 5);
}
#[test]
fn prim_obj_pattern() {
let source = r#"
type Stuff = Mulch(Nat) | Jugs(Nat, String) | Mardok
let a = Stuff::Mulch(20)
let b = Stuff::Jugs(1, "haha")
let c = Stuff::Mardok
let x = if a {
is Stuff::Mulch(20) then "x",
is _ then "ERR"
}
let y = if b {
is Stuff::Mulch(n) then "ERR",
is Stuff::Jugs(2, _) then "ERR",
is Stuff::Jugs(1, s) then s,
is _ then "ERR",
}
let z = if c {
is Stuff::Jugs(_, _) then "ERR",
is Stuff::Mardok then "NIGH",
is _ then "ERR",
}
(x, y, z)
"#;
test_in_fresh_env!(source, r#"("x", "haha", "NIGH")"#);
}
#[test]
fn basic_lambda_syntax() {
let source = r#"
let q = \(x, y) { x * y }
let x = q(5,2)
let y = \(m, n, o) { m + n + o }(1,2,3)
(x, y)
"#;
test_in_fresh_env!(source, r"(10, 6)");
}
#[test]
fn lambda_syntax_2() {
let source = r#"
fn milta() {
\(x) { x + 33 }
}
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,6 +1,6 @@
#![feature(trace_macros)]
#![feature(custom_attribute)]
#![feature(unrestricted_attribute_tokens)]
//#![feature(unrestricted_attribute_tokens)]
#![feature(slice_patterns, box_patterns, box_syntax)]
//! `schala-lang` is where the Schala programming language is actually implemented.
@@ -14,200 +14,34 @@ extern crate lazy_static;
extern crate maplit;
extern crate schala_repl;
#[macro_use]
extern crate schala_repl_codegen;
#[macro_use]
extern crate schala_lang_codegen;
extern crate ena;
extern crate derivative;
extern crate colored;
extern crate radix_trie;
use std::cell::RefCell;
use std::rc::Rc;
use itertools::Itertools;
use schala_repl::{ProgrammingLanguageInterface, EvalOptions, TraceArtifact, UnfinishedComputation, FinishedComputation};
macro_rules! bx {
($e:expr) => { Box::new($e) }
}
#[macro_use]
mod util;
mod builtin;
#[macro_use]
mod typechecking;
mod debugging;
mod tokenizing;
mod ast;
mod parsing;
#[macro_use]
mod symbol_table;
mod typechecking;
mod scope_resolution;
mod builtin;
mod reduced_ast;
mod eval;
mod source_map;
//trace_macros!(true);
#[derive(ProgrammingLanguageInterface)]
#[LanguageName = "Schala"]
#[SourceFileExtension = "schala"]
#[PipelineSteps(load_source, tokenizing, parsing(compact,expanded,trace), symbol_table, typechecking, ast_reducing, eval)]
#[DocMethod = get_doc]
#[HandleCustomInterpreterDirectives = handle_custom_interpreter_directives]
/// All bits of 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,
state: eval::State<'static>,
symbol_table: Rc<RefCell<symbol_table::SymbolTable>>,
type_context: typechecking::TypeContext<'static>,
active_parser: Option<parsing::Parser>,
}
mod schala;
impl Schala {
fn get_doc(&self, commands: &Vec<&str>) -> Option<String> {
Some(format!("Documentation on commands: {:?}", commands))
}
fn handle_custom_interpreter_directives(&mut self, commands: &Vec<&str>) -> Option<String> {
Some(format!("Schala-lang command: {:?} not supported", commands.get(0)))
}
}
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()));
Schala {
source_reference: SourceReference::new(),
symbol_table: symbols.clone(),
state: eval::State::new(symbols),
type_context: typechecking::TypeContext::new(),
active_parser: None,
}
}
/// Creates a new Schala environment with the standard prelude, which is defined as ordinary
/// Schala code in the file `prelude.schala`
pub fn new() -> Schala {
let prelude = include_str!("prelude.schala");
let mut s = Schala::new_blank_env();
s.execute_pipeline(prelude, &EvalOptions::default());
s
}
}
fn load_source<'a>(input: &'a str, handle: &mut Schala, _comp: Option<&mut UnfinishedComputation>) -> Result<&'a str, String> {
handle.source_reference.load_new_source(input);
Ok(input)
}
fn tokenizing(input: &str, _handle: &mut Schala, comp: Option<&mut UnfinishedComputation>) -> Result<Vec<tokenizing::Token>, String> {
let tokens = tokenizing::tokenize(input);
comp.map(|comp| {
let token_string = tokens.iter().map(|t| t.to_string_with_metadata()).join(", ");
comp.add_artifact(TraceArtifact::new("tokens", token_string));
});
let errors: Vec<String> = tokens.iter().filter_map(|t| t.get_error()).collect();
if errors.len() == 0 {
Ok(tokens)
} else {
Err(format!("{:?}", errors))
}
}
fn parsing(input: Vec<tokenizing::Token>, handle: &mut Schala, comp: Option<&mut UnfinishedComputation>) -> Result<ast::AST, String> {
use crate::parsing::Parser;
let mut parser = match handle.active_parser.take() {
None => Parser::new(input),
Some(parser) => parser
};
let ast = parser.parse();
let trace = parser.format_parse_trace();
comp.map(|comp| {
//TODO need to control which of these debug stages get added
let opt = comp.cur_debug_options.get(0).map(|s| s.clone());
match opt {
None => comp.add_artifact(TraceArtifact::new("ast", format!("{:?}", ast))),
Some(ref s) if s == "compact" => comp.add_artifact(TraceArtifact::new("ast", format!("{:?}", ast))),
Some(ref s) if s == "expanded" => comp.add_artifact(TraceArtifact::new("ast", format!("{:#?}", ast))),
Some(ref s) if s == "trace" => comp.add_artifact(TraceArtifact::new_parse_trace(trace)),
Some(ref x) => println!("Bad parsing debug option: {}", x),
};
});
ast.map_err(|err| format_parse_error(err, handle))
}
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);
let location_pointer = format!("{}^", " ".repeat(ch));
let line_num_digits = format!("{}", line_num).chars().count();
let space_padding = " ".repeat(line_num_digits);
format!(r#"
{error_msg}
{space_padding} |
{line_num} | {}
{space_padding} | {}
"#, line_from_program, location_pointer, error_msg=error.msg, space_padding=space_padding, line_num=line_num)
}
fn symbol_table(input: ast::AST, handle: &mut Schala, comp: Option<&mut UnfinishedComputation>) -> Result<ast::AST, String> {
let add = handle.symbol_table.borrow_mut().add_top_level_symbols(&input);
match add {
Ok(()) => {
let artifact = TraceArtifact::new("symbol_table", handle.symbol_table.borrow().debug_symbol_table());
comp.map(|comp| comp.add_artifact(artifact));
Ok(input)
},
Err(msg) => Err(msg)
}
}
fn typechecking(input: ast::AST, handle: &mut Schala, comp: Option<&mut UnfinishedComputation>) -> Result<ast::AST, String> {
let result = handle.type_context.typecheck(&input);
comp.map(|comp| {
let artifact = TraceArtifact::new("type", format!("{:?}", result));
comp.add_artifact(artifact);
});
Ok(input)
}
fn ast_reducing(input: ast::AST, handle: &mut Schala, comp: Option<&mut UnfinishedComputation>) -> Result<reduced_ast::ReducedAST, String> {
let ref symbol_table = handle.symbol_table.borrow();
let output = input.reduce(symbol_table);
comp.map(|comp| comp.add_artifact(TraceArtifact::new("ast_reducing", format!("{:?}", output))));
Ok(output)
}
fn eval(input: reduced_ast::ReducedAST, handle: &mut Schala, comp: Option<&mut UnfinishedComputation>) -> Result<String, String> {
comp.map(|comp| comp.add_artifact(TraceArtifact::new("value_state", handle.state.debug_print())));
let evaluation_outputs = handle.state.evaluate(input, true);
let text_output: Result<Vec<String>, String> = evaluation_outputs
.into_iter()
.collect();
let eval_output: Result<String, String> = text_output
.map(|v| { v.into_iter().intersperse(format!("\n")).collect() });
eval_output
}
/// Represents lines of source code
struct SourceReference {
lines: Option<Vec<String>>
}
impl SourceReference {
fn new() -> SourceReference {
SourceReference { lines: None }
}
fn load_new_source(&mut self, source: &str) {
//TODO this is a lot of heap allocations - maybe there's a way to make it more efficient?
self.lines = Some(source.lines().map(|s| s.to_string()).collect()); }
fn get_line(&self, line: usize) -> String {
self.lines.as_ref().and_then(|x| x.get(line).map(|s| s.to_string())).unwrap_or(format!("NO LINE FOUND"))
}
}
pub use schala::Schala;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,828 @@
#![cfg(test)]
use std::cell::RefCell;
use std::rc::Rc;
use std::str::FromStr;
use super::{Parser, ParseResult, tokenize};
use crate::ast::*;
use super::Declaration::*;
use super::Signature;
use super::TypeIdentifier::*;
use super::TypeSingletonName;
use super::ExpressionKind::*;
use super::Variant::*;
use super::ForBody::*;
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(source_map_handle);
parser.add_new_tokens(tokens);
parser
}
fn parse(input: &str) -> ParseResult<AST> {
let mut parser = make_parser(input);
parser.parse()
}
macro_rules! parse_test {
($string:expr, $correct:expr) => {
assert_eq!(parse($string).unwrap(), $correct)
};
}
macro_rules! parse_test_wrap_ast {
($string:expr, $correct:expr) => { parse_test!($string, AST { id: ItemIdStore::new_id(), statements: vec![$correct] }) }
}
macro_rules! parse_error {
($string:expr) => { assert!(parse($string).is_err()) }
}
macro_rules! qname {
( $( $component:expr),* ) => {
{
let mut components = vec![];
$(
components.push(rc!($component));
)*
QualifiedName { components, id: ItemIdStore::new_id() }
}
};
}
macro_rules! val {
($var:expr) => { Value(QualifiedName { components: vec![Rc::new($var.to_string())], id: ItemIdStore::new_id() }) };
}
macro_rules! ty {
($name:expr) => { Singleton(tys!($name)) }
}
macro_rules! tys {
($name:expr) => { TypeSingletonName { name: Rc::new($name.to_string()), params: vec![] } };
}
macro_rules! decl {
($expr_type:expr) => {
Statement { id: ItemIdStore::new_id(), kind: StatementKind::Declaration($expr_type) }
};
}
macro_rules! import {
($import_spec:expr) => {
Statement { id: ItemIdStore::new_id(), kind: StatementKind::Import($import_spec) }
}
}
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 mut parser = make_parser($expr_text);
parser.expression().unwrap()
}
};
}
macro_rules! inv {
($expr_type:expr) => { InvocationArgument::Positional($expr_type) }
}
macro_rules! binexp {
($op:expr, $lhs:expr, $rhs:expr) => { BinExp(BinOp::from_sigil($op), bx!(Expression::new(ItemIdStore::new_id(), $lhs).into()), bx!(Expression::new(ItemIdStore::new_id(), $rhs).into())) }
}
macro_rules! prefexp {
($op:expr, $lhs:expr) => { PrefixExp(PrefixOp::from_str($op).unwrap(), bx!(Expression::new(ItemIdStore::new_id(), $lhs).into())) }
}
macro_rules! exst {
($expr_type:expr) => { Statement { id: ItemIdStore::new_id(), kind: StatementKind::Expression(Expression::new(ItemIdStore::new_id(), $expr_type).into())} };
($expr_type:expr, $type_anno:expr) => { Statement { id: ItemIdStore::new_id(), kind: StatementKind::Expression(Expression::with_anno(ItemIdStore::new_id(), $expr_type, $type_anno).into())} };
($op:expr, $lhs:expr, $rhs:expr) => { Statement { id: ItemIdStore::new_id(), ,kind: StatementKind::Expression(ex!(binexp!($op, $lhs, $rhs)))}
};
(s $statement_text:expr) => {
{
let mut parser = make_parser($statement_text);
parser.statement().unwrap()
}
}
}
#[test]
fn parsing_number_literals_and_binexps() {
parse_test_wrap_ast! { ".2", exst!(FloatLiteral(0.2)) };
parse_test_wrap_ast! { "8.1", exst!(FloatLiteral(8.1)) };
parse_test_wrap_ast! { "0b010", exst!(NatLiteral(2)) };
parse_test_wrap_ast! { "0b0_1_0_", exst!(NatLiteral(2)) }
parse_test_wrap_ast! {"0xff", exst!(NatLiteral(255)) };
parse_test_wrap_ast! {"0xf_f_", exst!(NatLiteral(255)) };
parse_test_wrap_ast! {"0xf_f_+1", exst!(binexp!("+", NatLiteral(255), NatLiteral(1))) };
parse_test! {"3; 4; 4.3",
AST {
id: ItemIdStore::new_id(),
statements: vec![exst!(NatLiteral(3)), exst!(NatLiteral(4)),
exst!(FloatLiteral(4.3))]
}
};
parse_test_wrap_ast!("1 + 2 * 3",
exst!(binexp!("+", NatLiteral(1), binexp!("*", NatLiteral(2), NatLiteral(3))))
);
parse_test_wrap_ast!("1 * 2 + 3",
exst!(binexp!("+", binexp!("*", NatLiteral(1), NatLiteral(2)), NatLiteral(3)))
) ;
parse_test_wrap_ast!("1 && 2", exst!(binexp!("&&", NatLiteral(1), NatLiteral(2))));
parse_test_wrap_ast!("1 + 2 * 3 + 4", exst!(
binexp!("+",
binexp!("+", NatLiteral(1), binexp!("*", NatLiteral(2), NatLiteral(3))),
NatLiteral(4))));
parse_test_wrap_ast!("(1 + 2) * 3",
exst!(binexp!("*", binexp!("+", NatLiteral(1), NatLiteral(2)), NatLiteral(3))));
parse_test_wrap_ast!(".1 + .2", exst!(binexp!("+", FloatLiteral(0.1), FloatLiteral(0.2))));
parse_test_wrap_ast!("1 / 2", exst!(binexp!("/", NatLiteral(1), NatLiteral(2))));
}
#[test]
fn parsing_tuples() {
parse_test_wrap_ast!("()", exst!(TupleLiteral(vec![])));
parse_test_wrap_ast!("(\"hella\", 34)", exst!(
TupleLiteral(
vec![ex!(s r#""hella""#).into(), ex!(s "34").into()]
)
));
parse_test_wrap_ast!("((1+2), \"slough\")", exst!(TupleLiteral(vec![
ex!(binexp!("+", NatLiteral(1), NatLiteral(2))).into(),
ex!(StringLiteral(rc!(slough))).into(),
])))
}
#[test]
fn parsing_identifiers() {
parse_test_wrap_ast!("a", exst!(val!("a")));
parse_test_wrap_ast!("some_value", exst!(val!("some_value")));
parse_test_wrap_ast!("a + b", exst!(binexp!("+", val!("a"), val!("b"))));
//parse_test!("a[b]", AST(vec![Expression(
//parse_test!("a[]", <- TODO THIS NEEDS TO FAIL
//parse_test("a()[b]()[d]")
//TODO fix this parsing stuff
/*
parse_test! { "perspicacity()[a]", AST(vec![
exst!(Index {
indexee: bx!(ex!(Call { f: bx!(ex!(val!("perspicacity"))), arguments: vec![] })),
indexers: vec![ex!(val!("a"))]
})
])
}
*/
parse_test_wrap_ast!("a[b,c]", exst!(Index { indexee: bx!(ex!(val!("a"))), indexers: vec![ex!(val!("b")), ex!(val!("c"))]} ));
parse_test_wrap_ast!("None", exst!(val!("None")));
parse_test_wrap_ast!("Pandas { a: x + y }",
exst!(NamedStruct { name: qname!(Pandas), fields: vec![(rc!(a), ex!(binexp!("+", val!("x"), val!("y"))))]})
);
parse_test_wrap_ast! { "Pandas { a: n, b: q, }",
exst!(NamedStruct { name: qname!(Pandas), fields:
vec![(rc!(a), ex!(val!("n"))), (rc!(b), ex!(val!("q")))]
}
)
};
}
#[test]
fn qualified_identifiers() {
parse_test_wrap_ast! {
"let q_q = Yolo::Swaggins",
decl!(Binding { name: rc!(q_q), constant: true, type_anno: None,
expr: Expression::new(ItemIdStore::new_id(), Value(qname!(Yolo, Swaggins))),
})
}
parse_test_wrap_ast! {
"thing::item::call()",
exst!(Call { f: bx![ex!(Value(qname!(thing, item, call)))], arguments: vec![] })
}
}
#[test]
fn reserved_words() {
parse_error!("module::item::call()");
}
#[test]
fn parsing_complicated_operators() {
parse_test_wrap_ast!("a <- b", exst!(binexp!("<-", val!("a"), val!("b"))));
parse_test_wrap_ast!("a || b", exst!(binexp!("||", val!("a"), val!("b"))));
parse_test_wrap_ast!("a<>b", exst!(binexp!("<>", val!("a"), val!("b"))));
parse_test_wrap_ast!("a.b.c.d", exst!(binexp!(".",
binexp!(".",
binexp!(".", val!("a"), val!("b")),
val!("c")),
val!("d"))));
parse_test_wrap_ast!("-3", exst!(prefexp!("-", NatLiteral(3))));
parse_test_wrap_ast!("-0.2", exst!(prefexp!("-", FloatLiteral(0.2))));
parse_test_wrap_ast!("!3", exst!(prefexp!("!", NatLiteral(3))));
parse_test_wrap_ast!("a <- -b", exst!(binexp!("<-", val!("a"), prefexp!("-", val!("b")))));
parse_test_wrap_ast!("a <--b", exst!(binexp!("<--", val!("a"), val!("b"))));
}
#[test]
fn parsing_functions() {
parse_test_wrap_ast!("fn oi()", decl!(FuncSig(Signature { name: rc!(oi), operator: false, params: vec![], type_anno: None })));
parse_test_wrap_ast!("oi()", exst!(Call { f: bx!(ex!(val!("oi"))), arguments: vec![] }));
parse_test_wrap_ast!("oi(a, 2 + 2)", exst!(Call
{ f: bx!(ex!(val!("oi"))),
arguments: vec![inv!(ex!(val!("a"))), inv!(ex!(binexp!("+", NatLiteral(2), NatLiteral(2)))).into()]
}));
parse_error!("a(b,,c)");
parse_test_wrap_ast!("fn a(b, c: Int): Int", decl!(
FuncSig(Signature { name: rc!(a), operator: false, params: vec![
FormalParam { name: rc!(b), anno: None, default: None },
FormalParam { name: rc!(c), anno: Some(ty!("Int")), default: None }
], type_anno: Some(ty!("Int")) })));
parse_test_wrap_ast!("fn a(x) { x() }", decl!(
FuncDecl(Signature { name: rc!(a), operator: false, params: vec![FormalParam { name: rc!(x), anno: None, default: None }], type_anno: None },
vec![exst!(Call { f: bx!(ex!(val!("x"))), arguments: vec![] })])));
parse_test_wrap_ast!("fn a(x) {\n x() }", decl!(
FuncDecl(Signature { name: rc!(a), operator: false, params: vec![FormalParam { name: rc!(x), anno: None, default: None }], type_anno: None },
vec![exst!(Call { f: bx!(ex!(val!("x"))), arguments: vec![] })])));
let multiline = r#"
fn a(x) {
x()
}
"#;
parse_test_wrap_ast!(multiline, decl!(
FuncDecl(Signature { name: rc!(a), operator: false, params: vec![FormalParam { name: rc!(x), default: None, anno: None }], type_anno: None },
vec![exst!(Call { f: bx!(ex!(val!("x"))), arguments: vec![] })])));
let multiline2 = r#"
fn a(x) {
x()
}
"#;
parse_test_wrap_ast!(multiline2, decl!(
FuncDecl(Signature { name: rc!(a), operator: false, params: vec![FormalParam { name: rc!(x), default: None, anno: None }], type_anno: None },
vec![exst!(s "x()")])));
}
#[test]
fn functions_with_default_args() {
parse_test_wrap_ast! {
"fn func(x: Int, y: Int = 4) { }",
decl!(
FuncDecl(Signature { name: rc!(func), operator: false, type_anno: None, params: vec![
FormalParam { name: rc!(x), default: None, anno: Some(ty!("Int")) },
FormalParam { name: rc!(y), default: Some(ex!(s "4")), anno: Some(ty!("Int")) }
]}, vec![])
)
};
}
#[test]
fn parsing_bools() {
parse_test_wrap_ast!("false", exst!(BoolLiteral(false)));
parse_test_wrap_ast!("true", exst!(BoolLiteral(true)));
}
#[test]
fn parsing_strings() {
parse_test_wrap_ast!(r#""hello""#, exst!(StringLiteral(rc!(hello))));
}
#[test]
fn parsing_types() {
parse_test_wrap_ast!("type Yolo = Yolo", decl!(TypeDecl { name: tys!("Yolo"), body: TypeBody(vec![UnitStruct(rc!(Yolo))]), mutable: false} ));
parse_test_wrap_ast!("type mut Yolo = Yolo", decl!(TypeDecl { name: tys!("Yolo"), body: TypeBody(vec![UnitStruct(rc!(Yolo))]), mutable: true} ));
parse_test_wrap_ast!("type alias Sex = Drugs", decl!(TypeAlias { alias: rc!(Sex), original: rc!(Drugs) }));
parse_test_wrap_ast!("type Sanchez = Miguel | Alejandro(Int, Option<a>) | Esperanza { a: Int, b: String }",
decl!(TypeDecl {
name: tys!("Sanchez"),
body: TypeBody(vec![
UnitStruct(rc!(Miguel)),
TupleStruct(rc!(Alejandro), vec![
Singleton(TypeSingletonName { name: rc!(Int), params: vec![] }),
Singleton(TypeSingletonName { name: rc!(Option), params: vec![Singleton(TypeSingletonName { name: rc!(a), params: vec![] })] }),
]),
Record{
name: rc!(Esperanza),
members: vec![
(rc!(a), Singleton(TypeSingletonName { name: rc!(Int), params: vec![] })),
(rc!(b), Singleton(TypeSingletonName { name: rc!(String), params: vec![] })),
]
}
]),
mutable: false
}));
parse_test_wrap_ast! {
"type Jorge<a> = Diego | Kike(a)",
decl!(TypeDecl{
name: TypeSingletonName { name: rc!(Jorge), params: vec![Singleton(TypeSingletonName { name: rc!(a), params: vec![] })] },
body: TypeBody(vec![UnitStruct(rc!(Diego)), TupleStruct(rc!(Kike), vec![Singleton(TypeSingletonName { name: rc!(a), params: vec![] })])]),
mutable: false
}
)
};
}
#[test]
fn parsing_bindings() {
parse_test_wrap_ast!("let mut a = 10", decl!(Binding { name: rc!(a), constant: false, type_anno: None, expr: ex!(NatLiteral(10)) } ));
parse_test_wrap_ast!("let a = 2 + 2", decl!(Binding { name: rc!(a), constant: true, type_anno: None, expr: ex!(binexp!("+", NatLiteral(2), NatLiteral(2))) }));
parse_test_wrap_ast!("let a: Nat = 2 + 2", decl!(
Binding { name: rc!(a), constant: true, type_anno: Some(Singleton(TypeSingletonName { name: rc!(Nat), params: vec![] })),
expr: ex!(binexp!("+", NatLiteral(2), NatLiteral(2))) }
));
}
#[test]
fn parsing_block_expressions() {
parse_test_wrap_ast! {
"if a() then { b(); c() }", exst!(
IfExpression {
discriminator: Some(bx! {
ex!(Call { f: bx!(ex!(val!("a"))), arguments: vec![]})
}),
body: bx! {
IfExpressionBody::SimpleConditional {
then_case: vec![exst!(Call { f: bx!(ex!(val!("b"))), arguments: vec![]}), exst!(Call { f: bx!(ex!(val!("c"))), arguments: vec![] })],
else_case: None,
}
}
}
)
};
parse_test_wrap_ast! {
"if a() then { b(); c() } else { q }", exst!(
IfExpression {
discriminator: Some(bx! {
ex!(Call { f: bx!(ex!(val!("a"))), arguments: vec![]})
}),
body: bx! {
IfExpressionBody::SimpleConditional {
then_case: vec![exst!(Call { f: bx!(ex!(val!("b"))), arguments: vec![]}), exst!(Call { f: bx!(ex!(val!("c"))), arguments: vec![] })],
else_case: Some(vec![exst!(val!("q"))]),
}
}
}
)
};
/*
parse_test!("if a() then { b(); c() }", AST(vec![exst!(
IfExpression(bx!(ex!(Call { f: bx!(ex!(val!("a"))), arguments: vec![]})),
vec![exst!(Call { f: bx!(ex!(val!("b"))), arguments: vec![]}), exst!(Call { f: bx!(ex!(val!("c"))), arguments: vec![] })],
None)
)]));
parse_test!(r#"
if true then {
const a = 10
b
} else {
c
}"#,
AST(vec![exst!(IfExpression(bx!(ex!(BoolLiteral(true))),
vec![decl!(Binding { name: rc!(a), constant: true, expr: ex!(NatLiteral(10)) }),
exst!(val!(rc!(b)))],
Some(vec![exst!(val!(rc!(c)))])))])
);
parse_test!("if a { b } else { c }", AST(vec![exst!(
IfExpression(bx!(ex!(val!("a"))),
vec![exst!(val!("b"))],
Some(vec![exst!(val!("c"))])))]));
parse_test!("if (A {a: 1}) { b } else { c }", AST(vec![exst!(
IfExpression(bx!(ex!(NamedStruct { name: rc!(A), fields: vec![(rc!(a), ex!(NatLiteral(1)))]})),
vec![exst!(val!("b"))],
Some(vec![exst!(val!("c"))])))]));
parse_error!("if A {a: 1} { b } else { c }");
*/
}
#[test]
fn parsing_interfaces() {
parse_test_wrap_ast!("interface Unglueable { fn unglue(a: Glue); fn mar(): Glue }",
decl!(Interface {
name: rc!(Unglueable),
signatures: vec![
Signature {
name: rc!(unglue),
operator: false,
params: vec![
FormalParam { name: rc!(a), anno: Some(Singleton(TypeSingletonName { name: rc!(Glue), params: vec![] })), default: None }
],
type_anno: None
},
Signature { name: rc!(mar), operator: false, params: vec![], type_anno: Some(Singleton(TypeSingletonName { name: rc!(Glue), params: vec![] })) },
]
})
);
}
#[test]
fn parsing_impls() {
parse_test_wrap_ast!("impl Heh { fn yolo(); fn swagg(); }",
decl!(Impl {
type_name: ty!("Heh"),
interface_name: None,
block: vec![
FuncSig(Signature { name: rc!(yolo), operator: false, params: vec![], type_anno: None }),
FuncSig(Signature { name: rc!(swagg), operator: false, params: vec![], type_anno: None })
] }));
parse_test_wrap_ast!("impl Mondai for Lollerino { fn yolo(); fn swagg(); }",
decl!(Impl {
type_name: ty!("Lollerino"),
interface_name: Some(TypeSingletonName { name: rc!(Mondai), params: vec![] }),
block: vec![
FuncSig(Signature { name: rc!(yolo), operator: false, params: vec![], type_anno: None}),
FuncSig(Signature { name: rc!(swagg), operator: false, params: vec![], type_anno: None })
] }));
parse_test_wrap_ast!("impl Hella<T> for (Alpha, Omega) { }",
decl!(Impl {
type_name: Tuple(vec![ty!("Alpha"), ty!("Omega")]),
interface_name: Some(TypeSingletonName { name: rc!(Hella), params: vec![ty!("T")] }),
block: vec![]
})
);
parse_test_wrap_ast!("impl Option<WTFMate> { fn oi() }",
decl!(Impl {
type_name: Singleton(TypeSingletonName { name: rc!(Option), params: vec![ty!("WTFMate")]}),
interface_name: None,
block: vec![
FuncSig(Signature { name: rc!(oi), operator: false, params: vec![], type_anno: None }),
]
}));
}
#[test]
fn parsing_type_annotations() {
parse_test_wrap_ast!("let a = b : Int",
decl!(Binding { name: rc!(a), constant: true, type_anno: None, expr:
ex!(val!("b"), ty!("Int")) }));
parse_test_wrap_ast!("a : Int",
exst!(val!("a"), ty!("Int"))
);
parse_test_wrap_ast!("a : Option<Int>",
exst!(val!("a"), Singleton(TypeSingletonName { name: rc!(Option), params: vec![ty!("Int")] }))
);
parse_test_wrap_ast!("a : KoreanBBQSpecifier<Kimchi, Option<Bulgogi> >",
exst!(val!("a"), Singleton(TypeSingletonName { name: rc!(KoreanBBQSpecifier), params: vec![
ty!("Kimchi"), Singleton(TypeSingletonName { name: rc!(Option), params: vec![ty!("Bulgogi")] })
] }))
);
parse_test_wrap_ast!("a : (Int, Yolo<a>)",
exst!(val!("a"), Tuple(
vec![ty!("Int"), Singleton(TypeSingletonName {
name: rc!(Yolo), params: vec![ty!("a")]
})])));
}
#[test]
fn parsing_lambdas() {
parse_test_wrap_ast! { r#"\(x) { x + 1}"#, exst!(
Lambda { params: vec![FormalParam { name: rc!(x), anno: None, default: None } ], type_anno: None, body: vec![exst!(s "x + 1")] }
)
}
parse_test_wrap_ast!(r#"\ (x: Int, y) { a;b;c;}"#,
exst!(Lambda {
params: vec![
FormalParam { name: rc!(x), anno: Some(ty!("Int")), default: None },
FormalParam { name: rc!(y), anno: None, default: None }
],
type_anno: None,
body: vec![exst!(s "a"), exst!(s "b"), exst!(s "c")]
})
);
parse_test_wrap_ast! { r#"\(x){y}(1)"#,
exst!(Call { f: bx!(ex!(
Lambda {
params: vec![
FormalParam { name: rc!(x), anno: None, default: None }
],
type_anno: None,
body: vec![exst!(s "y")] }
)),
arguments: vec![inv!(ex!(NatLiteral(1))).into()] })
};
parse_test_wrap_ast! {
r#"\(x: Int): String { "q" }"#,
exst!(Lambda {
params: vec![
FormalParam { name: rc!(x), anno: Some(ty!("Int")), default: None },
],
type_anno: Some(ty!("String")),
body: vec![exst!(s r#""q""#)]
})
}
}
#[test]
fn single_param_lambda() {
parse_test_wrap_ast! {
r"\x { x + 10 }",
exst!(Lambda {
params: vec![FormalParam { name: rc!(x), anno: None, default: None }],
type_anno: None,
body: vec![exst!(s r"x + 10")]
})
}
parse_test_wrap_ast! {
r"\x: Nat { x + 10 }",
exst!(Lambda {
params: vec![FormalParam { name: rc!(x), anno: Some(ty!("Nat")), default: None }],
type_anno: None,
body: vec![exst!(s r"x + 10")]
})
}
}
#[test]
fn more_advanced_lambdas() {
parse_test! {
r#"fn wahoo() { let a = 10; \(x) { x + a } };
wahoo()(3) "#,
AST {
id: ItemIdStore::new_id(),
statements: vec![
exst!(s r"fn wahoo() { let a = 10; \(x) { x + a } }"),
exst! {
Call {
f: bx!(ex!(Call { f: bx!(ex!(val!("wahoo"))), arguments: vec![] })),
arguments: vec![inv!(ex!(NatLiteral(3))).into()],
}
}
]
}
}
}
#[test]
fn list_literals() {
parse_test_wrap_ast! {
"[1,2]",
exst!(ListLiteral(vec![ex!(NatLiteral(1)), ex!(NatLiteral(2))]))
};
}
#[test]
fn while_expr() {
parse_test_wrap_ast! {
"while { }",
exst!(WhileExpression { condition: None, body: vec![] })
}
parse_test_wrap_ast! {
"while a == b { }",
exst!(WhileExpression { condition: Some(bx![ex![binexp!("==", val!("a"), val!("b"))]]), body: vec![] })
}
}
#[test]
fn for_expr() {
parse_test_wrap_ast! {
"for { a <- maybeValue } return 1",
exst!(ForExpression {
enumerators: vec![Enumerator { id: rc!(a), generator: ex!(val!("maybeValue")) }],
body: bx!(MonadicReturn(ex!(s "1")))
})
}
parse_test_wrap_ast! {
"for n <- someRange { f(n); }",
exst!(ForExpression { enumerators: vec![Enumerator { id: rc!(n), generator: ex!(val!("someRange"))}],
body: bx!(ForBody::StatementBlock(vec![exst!(s "f(n)")]))
})
}
}
#[test]
fn patterns() {
parse_test_wrap_ast! {
"if x is Some(a) then { 4 } else { 9 }", exst!(
IfExpression {
discriminator: Some(bx!(ex!(s "x"))),
body: bx!(IfExpressionBody::SimplePatternMatch {
pattern: Pattern::TupleStruct(qname!(Some), vec![Pattern::VarOrName(qname!(a))]),
then_case: vec![exst!(s "4")],
else_case: Some(vec![exst!(s "9")]) })
}
)
}
parse_test_wrap_ast! {
"if x is Some(a) then 4 else 9", exst!(
IfExpression {
discriminator: Some(bx!(ex!(s "x"))),
body: bx!(IfExpressionBody::SimplePatternMatch {
pattern: Pattern::TupleStruct(qname!(Some), vec![Pattern::VarOrName(qname!(a))]),
then_case: vec![exst!(s "4")],
else_case: Some(vec![exst!(s "9")]) }
)
}
)
}
parse_test_wrap_ast! {
"if x is Something { a, b: x } then { 4 } else { 9 }", exst!(
IfExpression {
discriminator: Some(bx!(ex!(s "x"))),
body: bx!(IfExpressionBody::SimplePatternMatch {
pattern: Pattern::Record(qname!(Something), vec![
(rc!(a),Pattern::Literal(PatternLiteral::StringPattern(rc!(a)))),
(rc!(b),Pattern::VarOrName(qname!(x)))
]),
then_case: vec![exst!(s "4")],
else_case: Some(vec![exst!(s "9")])
}
)
}
)
}
}
#[test]
fn pattern_literals() {
parse_test_wrap_ast! {
"if x is -1 then 1 else 2",
exst!(
IfExpression {
discriminator: Some(bx!(ex!(s "x"))),
body: bx!(IfExpressionBody::SimplePatternMatch {
pattern: Pattern::Literal(PatternLiteral::NumPattern { neg: true, num: NatLiteral(1) }),
then_case: vec![exst!(NatLiteral(1))],
else_case: Some(vec![exst!(NatLiteral(2))]),
})
}
)
}
parse_test_wrap_ast! {
"if x is 1 then 1 else 2",
exst!(
IfExpression {
discriminator: Some(bx!(ex!(s "x"))),
body: bx!(IfExpressionBody::SimplePatternMatch {
pattern: Pattern::Literal(PatternLiteral::NumPattern { neg: false, num: NatLiteral(1) }),
then_case: vec![exst!(s "1")],
else_case: Some(vec![exst!(s "2")]),
})
}
)
}
parse_test_wrap_ast! {
"if x is true then 1 else 2",
exst!(
IfExpression {
discriminator: Some(bx!(ex!(s "x"))),
body: bx!(
IfExpressionBody::SimplePatternMatch {
pattern: Pattern::Literal(PatternLiteral::BoolPattern(true)),
then_case: vec![exst!(NatLiteral(1))],
else_case: Some(vec![exst!(NatLiteral(2))]),
})
}
)
}
parse_test_wrap_ast! {
"if x is \"gnosticism\" then 1 else 2",
exst!(
IfExpression {
discriminator: Some(bx!(ex!(s "x"))),
body: bx!(IfExpressionBody::SimplePatternMatch {
pattern: Pattern::Literal(PatternLiteral::StringPattern(rc!(gnosticism))),
then_case: vec![exst!(s "1")],
else_case: Some(vec![exst!(s "2")]),
})
}
)
}
}
#[test]
fn imports() {
parse_test_wrap_ast! {
"import harbinger::draughts::Norgleheim",
import!(ImportSpecifier {
id: ItemIdStore::new_id(),
path_components: vec![rc!(harbinger), rc!(draughts), rc!(Norgleheim)],
imported_names: ImportedNames::LastOfPath
})
}
}
#[test]
fn imports_2() {
parse_test_wrap_ast! {
"import harbinger::draughts::{Norgleheim, Xraksenlaigar}",
import!(ImportSpecifier {
id: ItemIdStore::new_id(),
path_components: vec![rc!(harbinger), rc!(draughts)],
imported_names: ImportedNames::List(vec![
rc!(Norgleheim),
rc!(Xraksenlaigar)
])
})
}
}
#[test]
fn imports_3() {
parse_test_wrap_ast! {
"import bespouri::{}",
import!(ImportSpecifier {
id: ItemIdStore::new_id(),
path_components: vec![rc!(bespouri)],
imported_names: ImportedNames::List(vec![])
})
}
}
#[test]
fn imports_4() {
parse_test_wrap_ast! {
"import bespouri::*",
import!(ImportSpecifier {
id: ItemIdStore::new_id(),
path_components: vec![rc!(bespouri)],
imported_names: ImportedNames::All
})
}
}
#[test]
fn if_expr() {
parse_test_wrap_ast! {
"if x { is 1 then 5, else 20 }",
exst! {
IfExpression {
discriminator: Some(bx!(ex!(s "x"))),
body: bx!(IfExpressionBody::CondList(
vec![
ConditionArm {
condition: Condition::Pattern(Pattern::Literal(PatternLiteral::NumPattern { neg: false, num: NatLiteral(1)})),
guard: None,
body: vec![exst!(s "5")],
},
ConditionArm {
condition: Condition::Else,
guard: None,
body: vec![exst!(s "20")],
},
]
))
}
}
}
}
#[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

@@ -1,13 +1,14 @@
let _SCHALA_VERSION = "0.1.0"
type Option<T> = Some(T) | None
type Color = Red | Green | Blue
type Ord = LT | EQ | GT
fn map(input: Option<T>, func: Func): Option<T> {
if input {
is Some(x) -> Some(func(x)),
is None -> None,
is Option::Some(x) then Option::Some(func(x)),
is Option::None then Option::None,
}
}
type Complicated = Sunrise | Metal { black: bool, norwegian: bool } | Fella(String, Int)

View File

@@ -1,8 +1,24 @@
//! # Reduced AST
//! The reduced AST is a minimal AST designed to be built from the full AST after all possible
//! static checks have been done. Consequently, the AST reduction phase does very little error
//! checking itself - any errors should ideally be caught either by an earlier phase, or are
//! runtime errors that the evaluator should handle. That said, becuase it does do table lookups
//! that can in principle fail [especially at the moment with most static analysis not yet complete],
//! there is an Expr variant `ReductionError` to handle these cases.
//!
//! A design decision to make - should the ReducedAST types contain all information about
//! type/layout necessary for the evaluator to work? If so, then the evaluator should not
//! have access to the symbol table at all and ReducedAST should carry that information. If not,
//! then ReducedAST shouldn't be duplicating information that can be queried at runtime from the
//! symbol table. But I think the former might make sense since ultimately the bytecode will be
//! built from the ReducedAST.
use std::rc::Rc;
use std::str::FromStr;
use crate::ast::*;
use crate::symbol_table::{Symbol, SymbolSpec, SymbolTable};
use crate::builtin::{BinOp, PrefixOp};
use crate::symbol_table::{Symbol, SymbolSpec, SymbolTable, FullyQualifiedSymbolName};
use crate::builtin::Builtin;
use crate::util::deref_optional_box;
#[derive(Debug)]
pub struct ReducedAST(pub Vec<Stmt>);
@@ -28,12 +44,12 @@ pub enum Expr {
Lit(Lit),
Tuple(Vec<Expr>),
Func(Func),
Val(Rc<String>),
Sym(FullyQualifiedSymbolName),
Constructor {
type_name: Rc<String>,
name: Rc<String>,
tag: usize,
arity: usize,
arity: usize, // n.b. arity here is always the value from the symbol table - if it doesn't match what it's being called with, that's an eval error, eval will handle it
},
Call {
f: Box<Expr>,
@@ -53,17 +69,15 @@ pub enum Expr {
cond: Box<Expr>,
alternatives: Vec<Alternative>
},
UnimplementedSigilValue
UnimplementedSigilValue,
ReductionError(String),
}
pub type BoundVars = Vec<Option<Rc<String>>>; //remember that order matters here
#[derive(Debug, Clone)]
pub struct Alternative {
pub tag: Option<usize>,
pub subpatterns: Vec<Option<Subpattern>>,
pub guard: Option<Expr>,
pub bound_vars: BoundVars,
pub matchable: Subpattern,
pub item: Vec<Stmt>,
}
@@ -86,7 +100,7 @@ pub enum Lit {
#[derive(Debug, Clone)]
pub enum Func {
BuiltIn(Rc<String>),
BuiltIn(Builtin),
UserDefined {
name: Option<Rc<String>>,
params: Vec<Rc<String>>,
@@ -94,129 +108,256 @@ pub enum Func {
}
}
impl AST {
pub fn reduce(&self, symbol_table: &SymbolTable) -> ReducedAST {
pub fn reduce(ast: &AST, symbol_table: &SymbolTable) -> ReducedAST {
let mut reducer = Reducer { symbol_table };
reducer.ast(ast)
}
struct Reducer<'a> {
symbol_table: &'a SymbolTable
}
impl<'a> Reducer<'a> {
fn ast(&mut self, ast: &AST) -> ReducedAST {
let mut output = vec![];
for statement in self.0.iter() {
output.push(statement.node().reduce(symbol_table));
for statement in ast.statements.iter() {
output.push(self.statement(statement));
}
ReducedAST(output)
}
}
impl Statement {
fn reduce(&self, symbol_table: &SymbolTable) -> Stmt {
use crate::ast::Statement::*;
match self {
ExpressionStatement(expr) => Stmt::Expr(expr.node().reduce(symbol_table)),
Declaration(decl) => decl.reduce(symbol_table),
fn statement(&mut self, stmt: &Statement) -> Stmt {
match &stmt.kind {
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
}
}
}
}
fn reduce_block(block: &Block, symbol_table: &SymbolTable) -> Vec<Stmt> {
block.iter().map(|stmt| stmt.node().reduce(symbol_table)).collect()
}
fn block(&mut self, block: &Block) -> Vec<Stmt> {
block.iter().map(|stmt| self.statement(stmt)).collect()
}
impl Expression {
fn reduce(&self, symbol_table: &SymbolTable) -> Expr {
use crate::ast::ExpressionType::*;
let ref input = self.0;
fn invocation_argument(&mut self, invoc: &InvocationArgument) -> Expr {
use crate::ast::InvocationArgument::*;
match invoc {
Positional(ex) => self.expression(ex),
Keyword { .. } => Expr::UnimplementedSigilValue,
Ignored => Expr::UnimplementedSigilValue,
}
}
fn expression(&mut self, expr: &Expression) -> Expr {
use crate::ast::ExpressionKind::*;
let symbol_table = self.symbol_table;
let ref input = expr.kind;
match input {
NatLiteral(n) => Expr::Lit(Lit::Nat(*n)),
FloatLiteral(f) => Expr::Lit(Lit::Float(*f)),
StringLiteral(s) => Expr::Lit(Lit::StringLit(s.clone())),
BoolLiteral(b) => Expr::Lit(Lit::Bool(*b)),
BinExp(binop, lhs, rhs) => binop.reduce(symbol_table, lhs, rhs),
PrefixExp(op, arg) => op.reduce(symbol_table, arg),
Value(name) => match symbol_table.lookup_by_name(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::Val(name.clone()),
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 fqsn = 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
match symbol_table.lookup_by_fqsn(&fqsn) {
Some(Symbol { spec: SymbolSpec::DataConstructor { index, type_args, type_name}, local_name, .. }) => Expr::Constructor {
type_name: type_name.clone(),
name: local_name.clone(),
tag: index.clone(),
arity: type_args.len(),
},
_ => Expr::Sym(fqsn.clone()),
}
},
Call { f, arguments } => Expr::Call {
f: Box::new(f.reduce(symbol_table)),
args: arguments.iter().map(|arg| arg.node().reduce(symbol_table)).collect(),
},
TupleLiteral(exprs) => Expr::Tuple(exprs.iter().map(|e| e.node().reduce(symbol_table)).collect()),
IfExpression { discriminator, body } => reduce_if_expression(discriminator, body, symbol_table),
Lambda { params, body, .. } => reduce_lambda(params, body, symbol_table),
NamedStruct { .. } => Expr::UnimplementedSigilValue,
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),
Lambda { params, body, .. } => self.reduce_lambda(params, body),
NamedStruct { name, fields } => self.reduce_named_struct(name, fields),
Index { .. } => Expr::UnimplementedSigilValue,
WhileExpression { .. } => Expr::UnimplementedSigilValue,
ForExpression { .. } => Expr::UnimplementedSigilValue,
ListLiteral { .. } => Expr::UnimplementedSigilValue,
}
}
}
fn reduce_lambda(params: &Vec<FormalParam>, body: &Block, symbol_table: &SymbolTable) -> Expr {
Expr::Func(Func::UserDefined {
name: None,
params: params.iter().map(|param| param.0.clone()).collect(),
body: reduce_block(body, symbol_table),
})
}
fn reduce_lambda(&mut self, params: &Vec<FormalParam>, body: &Block) -> Expr {
Expr::Func(Func::UserDefined {
name: None,
params: params.iter().map(|param| param.name.clone()).collect(),
body: self.block(body),
})
}
fn reduce_if_expression(discriminator: &Discriminator, body: &IfExpressionBody, symbol_table: &SymbolTable) -> Expr {
let cond = Box::new(match *discriminator {
Discriminator::Simple(ref expr) => expr.reduce(symbol_table),
Discriminator::BinOp(ref _expr, ref _binop) => panic!("Can't yet handle binop discriminators")
});
match *body {
IfExpressionBody::SimpleConditional(ref then_clause, ref else_clause) => {
let then_clause = reduce_block(then_clause, symbol_table);
let else_clause = match else_clause {
None => vec![],
Some(stmts) => reduce_block(stmts, symbol_table),
};
Expr::Conditional { cond, then_clause, else_clause }
},
IfExpressionBody::SimplePatternMatch(ref pat, ref then_clause, ref else_clause) => {
let then_clause = reduce_block(then_clause, symbol_table);
let else_clause = match else_clause {
None => vec![],
Some(stmts) => reduce_block(stmts, symbol_table),
};
fn reduce_named_struct(&mut self, name: &QualifiedName, fields: &Vec<(Rc<String>, Expression)>) -> Expr {
let symbol_table = self.symbol_table;
let ref sym_name = match symbol_table.get_fqsn_from_id(&name.id) {
Some(fqsn) => fqsn,
None => return Expr::ReductionError(format!("FQSN lookup for name {:?} failed", name)),
};
let alternatives = vec![
pat.to_alternative(then_clause, symbol_table),
Alternative {
tag: None,
subpatterns: vec![],
bound_vars: vec![],
guard: None,
item: else_clause
},
];
let FullyQualifiedSymbolName(ref v) = sym_name;
let ref name = v.last().unwrap().name;
let (type_name, index, members_from_table) = match symbol_table.lookup_by_fqsn(&sym_name) {
Some(Symbol { spec: SymbolSpec::RecordConstructor { members, type_name, index }, .. }) => (type_name.clone(), index, members),
_ => return Expr::ReductionError("Not a record constructor".to_string()),
};
let arity = members_from_table.len();
Expr::CaseMatch {
cond,
alternatives,
}
},
IfExpressionBody::GuardList(ref guard_arms) => {
let mut alternatives = vec![];
for arm in guard_arms {
match arm.guard {
Guard::Pat(ref p) => {
let item = reduce_block(&arm.body, symbol_table);
let alt = p.to_alternative(item, symbol_table);
alternatives.push(alt);
let mut args: Vec<(Rc<String>, Expr)> = fields.iter()
.map(|(name, expr)| (name.clone(), self.expression(expr)))
.collect();
args.as_mut_slice()
.sort_unstable_by(|(name1, _), (name2, _)| name1.cmp(name2)); //arbitrary - sorting by alphabetical order
let args = args.into_iter().map(|(_, expr)| expr).collect();
//TODO make sure this sorting actually works
let f = box Expr::Constructor { type_name, name: name.clone(), tag: *index, arity, };
Expr::Call { f, args }
}
fn reduce_call_expression(&mut self, func: &Expression, arguments: &Vec<InvocationArgument>) -> Expr {
Expr::Call {
f: Box::new(self.expression(func)),
args: arguments.iter().map(|arg| self.invocation_argument(arg)).collect(),
}
}
fn reduce_if_expression(&mut self, discriminator: Option<&Expression>, body: &IfExpressionBody) -> Expr {
let symbol_table = self.symbol_table;
let cond = Box::new(match discriminator {
Some(expr) => self.expression(expr),
None => return Expr::ReductionError(format!("blank cond if-expr not supported")),
});
match body {
IfExpressionBody::SimpleConditional { then_case, else_case } => {
let then_clause = self.block(&then_case);
let else_clause = match else_case.as_ref() {
None => vec![],
Some(stmts) => self.block(&stmts),
};
Expr::Conditional { cond, then_clause, else_clause }
},
IfExpressionBody::SimplePatternMatch { pattern, then_case, else_case } => {
let then_clause = self.block(&then_case);
let else_clause = match else_case.as_ref() {
None => vec![],
Some(stmts) => self.block(&stmts),
};
let alternatives = vec![
pattern.to_alternative(then_clause, symbol_table),
Alternative {
matchable: Subpattern {
tag: None,
subpatterns: vec![],
bound_vars: vec![],
guard: None,
},
item: else_clause
},
Guard::HalfExpr(HalfExpr { op: _, expr: _ }) => {
return Expr::UnimplementedSigilValue
];
Expr::CaseMatch {
cond,
alternatives,
}
},
IfExpressionBody::CondList(ref condition_arms) => {
let mut alternatives = vec![];
for arm in condition_arms {
match arm.condition {
Condition::Expression(ref _expr) => {
return Expr::UnimplementedSigilValue
},
Condition::Pattern(ref p) => {
let item = self.block(&arm.body);
let alt = p.to_alternative(item, symbol_table);
alternatives.push(alt);
},
Condition::TruncatedOp(_, _) => {
return Expr::UnimplementedSigilValue
},
Condition::Else => {
return Expr::UnimplementedSigilValue
}
}
}
Expr::CaseMatch { cond, alternatives }
}
Expr::CaseMatch { cond, alternatives }
}
}
fn binop(&mut self, binop: &BinOp, lhs: &Box<Expression>, rhs: &Box<Expression>) -> Expr {
let operation = Builtin::from_str(binop.sigil()).ok();
match operation {
Some(Builtin::Assignment) => Expr::Assign {
val: Box::new(self.expression(&*lhs)),
expr: Box::new(self.expression(&*rhs)),
},
Some(op) => {
let f = Box::new(Expr::Func(Func::BuiltIn(op)));
Expr::Call { f, args: vec![self.expression(&*lhs), self.expression(&*rhs)] }
},
None => {
//TODO handle a user-defined operation
Expr::UnimplementedSigilValue
}
}
}
fn prefix(&mut self, prefix: &PrefixOp, arg: &Box<Expression>) -> Expr {
match prefix.builtin {
Some(op) => {
let f = Box::new(Expr::Func(Func::BuiltIn(op)));
Expr::Call { f, args: vec![self.expression(arg)] }
},
None => { //TODO need this for custom prefix ops
Expr::UnimplementedSigilValue
}
}
}
fn declaration(&mut self, declaration: &Declaration) -> Stmt {
use self::Declaration::*;
match declaration {
Binding {name, constant, expr, .. } => Stmt::Binding { name: name.clone(), constant: *constant, expr: self.expression(expr) },
FuncDecl(Signature { name, params, .. }, statements) => Stmt::PreBinding {
name: name.clone(),
func: Func::UserDefined {
name: Some(name.clone()),
params: params.iter().map(|param| param.name.clone()).collect(),
body: self.block(&statements),
}
},
TypeDecl { .. } => Stmt::Noop,
TypeAlias{ .. } => Stmt::Noop,
Interface { .. } => Stmt::Noop,
Impl { .. } => Stmt::Expr(Expr::UnimplementedSigilValue),
_ => Stmt::Expr(Expr::UnimplementedSigilValue)
}
}
}
/* ig var pat
* x is SomeBigOldEnum(_, x, Some(t))
*/
@@ -228,13 +369,26 @@ fn handle_symbol(symbol: Option<&Symbol>, inner_patterns: &Vec<Pattern>, symbol_
_ => panic!("Symbol is not a data constructor - this should've been caught in type-checking"),
});
let bound_vars = inner_patterns.iter().map(|p| match p {
Literal(PatternLiteral::VarPattern(var)) => Some(var.clone()),
VarOrName(qualified_name) => {
let fqsn = symbol_table.get_fqsn_from_id(&qualified_name.id);
let symbol_exists = fqsn.and_then(|fqsn| symbol_table.lookup_by_fqsn(&fqsn)).is_some();
if symbol_exists {
None
} else {
let QualifiedName { components, .. } = qualified_name;
if components.len() == 1 {
Some(components[0].clone())
} else {
panic!("Bad variable name in pattern");
}
}
},
_ => None,
}).collect();
let subpatterns = inner_patterns.iter().map(|p| match p {
Ignored => None,
Literal(PatternLiteral::VarPattern(_)) => None,
VarOrName(_) => None,
Literal(other) => Some(other.to_subpattern(symbol_table)),
tp @ TuplePattern(_) => Some(tp.to_subpattern(symbol_table)),
ts @ TupleStruct(_, _) => Some(ts.to_subpattern(symbol_table)),
@@ -263,10 +417,12 @@ impl Pattern {
fn to_alternative(&self, item: Vec<Stmt>, symbol_table: &SymbolTable) -> Alternative {
let s = self.to_subpattern(symbol_table);
Alternative {
tag: s.tag,
subpatterns: s.subpatterns,
bound_vars: s.bound_vars,
guard: s.guard,
matchable: Subpattern {
tag: s.tag,
subpatterns: s.subpatterns,
bound_vars: s.bound_vars,
guard: s.guard,
},
item
}
}
@@ -274,9 +430,14 @@ impl Pattern {
fn to_subpattern(&self, symbol_table: &SymbolTable) -> Subpattern {
use self::Pattern::*;
match self {
TupleStruct(name, inner_patterns) => {
let symbol = symbol_table.lookup_by_name(name).expect(&format!("Symbol {} not found", name));
handle_symbol(Some(symbol), inner_patterns, symbol_table)
TupleStruct(QualifiedName{ components, id }, inner_patterns) => {
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), inner_patterns, symbol_table),
None => {
panic!("Symbol {:?} not found", components);
}
}
},
TuplePattern(inner_patterns) => handle_symbol(None, inner_patterns, symbol_table),
Record(_name, _pairs) => {
@@ -284,24 +445,45 @@ impl Pattern {
},
Ignored => Subpattern { tag: None, subpatterns: vec![], guard: None, bound_vars: vec![] },
Literal(lit) => lit.to_subpattern(symbol_table),
VarOrName(QualifiedName { components, id }) => {
// if fqsn is Some, treat this as a symbol pattern. If it's None, treat it
// as a variable.
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),
None => {
let name = if components.len() == 1 {
components[0].clone()
} else {
panic!("check this line of code yo");
};
Subpattern {
tag: None,
subpatterns: vec![],
guard: None,
bound_vars: vec![Some(name.clone())],
}
}
}
},
}
}
}
impl PatternLiteral {
fn to_subpattern(&self, symbol_table: &SymbolTable) -> Subpattern {
fn to_subpattern(&self, _symbol_table: &SymbolTable) -> Subpattern {
use self::PatternLiteral::*;
match self {
NumPattern { neg, num } => {
let comparison = Expr::Lit(match (neg, num) {
(false, ExpressionType::NatLiteral(n)) => Lit::Nat(*n),
(false, ExpressionType::FloatLiteral(f)) => Lit::Float(*f),
(true, ExpressionType::NatLiteral(n)) => Lit::Int(-1*(*n as i64)),
(true, ExpressionType::FloatLiteral(f)) => Lit::Float(-1.0*f),
(false, ExpressionKind::NatLiteral(n)) => Lit::Nat(*n),
(false, ExpressionKind::FloatLiteral(f)) => Lit::Float(*f),
(true, ExpressionKind::NatLiteral(n)) => Lit::Int(-1*(*n as i64)),
(true, ExpressionKind::FloatLiteral(f)) => Lit::Float(-1.0*f),
_ => panic!("This should never happen")
});
let guard = Some(Expr::Call {
f: Box::new(Expr::Func(Func::BuiltIn(Rc::new("==".to_string())))),
f: Box::new(Expr::Func(Func::BuiltIn(Builtin::Equality))),
args: vec![comparison, Expr::ConditionalTargetSigilValue],
});
Subpattern {
@@ -313,7 +495,7 @@ impl PatternLiteral {
},
StringPattern(s) => {
let guard = Some(Expr::Call {
f: Box::new(Expr::Func(Func::BuiltIn(Rc::new("==".to_string())))),
f: Box::new(Expr::Func(Func::BuiltIn(Builtin::Equality))),
args: vec![Expr::Lit(Lit::StringLit(s.clone())), Expr::ConditionalTargetSigilValue]
});
@@ -329,7 +511,7 @@ impl PatternLiteral {
Expr::ConditionalTargetSigilValue
} else {
Expr::Call {
f: Box::new(Expr::Func(Func::BuiltIn(Rc::new("!".to_string())))),
f: Box::new(Expr::Func(Func::BuiltIn(Builtin::BooleanNot))),
args: vec![Expr::ConditionalTargetSigilValue]
}
});
@@ -340,59 +522,7 @@ impl PatternLiteral {
bound_vars: vec![],
}
},
VarPattern(var) => match symbol_table.lookup_by_name(var) {
Some(symbol) => handle_symbol(Some(symbol), &vec![], symbol_table),
None => Subpattern {
tag: None,
subpatterns: vec![],
guard: None,
bound_vars: vec![Some(var.clone())],
}
}
}
}
}
impl Declaration {
fn reduce(&self, symbol_table: &SymbolTable) -> Stmt {
use self::Declaration::*;
use crate::ast::Signature;
match self {
Binding {name, constant, expr } => Stmt::Binding { name: name.clone(), constant: *constant, expr: expr.reduce(symbol_table) },
FuncDecl(Signature { name, params, .. }, statements) => Stmt::PreBinding {
name: name.clone(),
func: Func::UserDefined {
name: Some(name.clone()),
params: params.iter().map(|param| param.0.clone()).collect(),
body: reduce_block(&statements, symbol_table),
}
},
TypeDecl { .. } => Stmt::Noop,
TypeAlias(_, _) => Stmt::Noop,
Interface { .. } => Stmt::Noop,
Impl { .. } => Stmt::Expr(Expr::UnimplementedSigilValue),
_ => Stmt::Expr(Expr::UnimplementedSigilValue)
}
}
}
impl BinOp {
fn reduce(&self, symbol_table: &SymbolTable, lhs: &Box<Node<Expression>>, rhs: &Box<Node<Expression>>) -> Expr {
if **self.sigil() == "=" {
Expr::Assign {
val: Box::new(lhs.node().reduce(symbol_table)),
expr: Box::new(rhs.node().reduce(symbol_table)),
}
} else {
let f = Box::new(Expr::Func(Func::BuiltIn(self.sigil().clone())));
Expr::Call { f, args: vec![lhs.node().reduce(symbol_table), rhs.node().reduce(symbol_table)]}
}
}
}
impl PrefixOp {
fn reduce(&self, symbol_table: &SymbolTable, arg: &Box<Node<Expression>>) -> Expr {
let f = Box::new(Expr::Func(Func::BuiltIn(self.sigil().clone())));
Expr::Call { f, args: vec![arg.node().reduce(symbol_table)]}
}
}

View File

@@ -0,0 +1,338 @@
use stopwatch::Stopwatch;
use std::time::Duration;
use std::cell::RefCell;
use std::rc::Rc;
use std::collections::HashSet;
use itertools::Itertools;
use schala_repl::{ProgrammingLanguageInterface,
ComputationRequest, ComputationResponse,
LangMetaRequest, LangMetaResponse, GlobalOutputStats,
DebugResponse, DebugAsk};
use crate::{ast, reduced_ast, tokenizing, parsing, 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: SymbolTableHandle,
resolver: crate::scope_resolution::ScopeResolver<'static>,
type_context: typechecking::TypeContext<'static>,
active_parser: parsing::Parser,
}
impl Schala {
fn handle_docs(&self, source: String) -> LangMetaResponse {
LangMetaResponse::Docs {
doc_string: format!("Schala item `{}` : <<Schala-lang documentation not yet implemented>>", source)
}
}
}
impl Schala {
/// Creates a new Schala environment *without* any prelude.
fn new_blank_env() -> Schala {
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(),
source_map: source_map.clone(),
resolver: crate::scope_resolution::ScopeResolver::new(symbols.clone()),
state: eval::State::new(symbols),
type_context: typechecking::TypeContext::new(),
active_parser: parsing::Parser::new(source_map)
}
}
/// Creates a new Schala environment with the standard prelude, which is defined as ordinary
/// Schala code in the file `prelude.schala`
pub fn new() -> Schala {
let prelude = include_str!("prelude.schala");
let mut s = Schala::new_blank_env();
let request = ComputationRequest { source: prelude, debug_requests: HashSet::default() };
let response = s.run_computation(request);
if let Err(msg) = response.main_output {
panic!("Error in prelude, panicking: {}", msg);
}
s
}
fn handle_debug_immediate(&self, request: DebugAsk) -> DebugResponse {
use DebugAsk::*;
match request {
Timing => DebugResponse { ask: Timing, value: format!("Invalid") },
ByStage { stage_name, token } => match &stage_name[..] {
"symbol-table" => {
let value = self.symbol_table.borrow().debug_symbol_table();
DebugResponse {
ask: ByStage { stage_name: format!("symbol-table"), token },
value
}
},
s => {
DebugResponse {
ask: ByStage { stage_name: s.to_string(), token: None },
value: format!("Not-implemented")
}
}
}
}
}
}
fn tokenizing(input: &str, _handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<Vec<tokenizing::Token>, String> {
let tokens = tokenizing::tokenize(input);
comp.map(|comp| {
let token_string = tokens.iter().map(|t| t.to_string_with_metadata()).join(", ");
comp.add_artifact(token_string);
});
let errors: Vec<String> = tokens.iter().filter_map(|t| t.get_error()).collect();
if errors.len() == 0 {
Ok(tokens)
} else {
Err(format!("{:?}", errors))
}
}
fn parsing(input: Vec<tokenizing::Token>, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<ast::AST, String> {
use ParsingDebugType::*;
let ref mut parser = handle.active_parser;
parser.add_new_tokens(input);
let ast = parser.parse();
comp.map(|comp| {
let debug_format = comp.parsing.as_ref().unwrap_or(&CompactAST);
let debug_info = match debug_format {
CompactAST => match ast{
Ok(ref ast) => ast.compact_debug(),
Err(_) => "Error - see output".to_string(),
},
ExpandedAST => match ast{
Ok(ref ast) => ast.expanded_debug(),
Err(_) => "Error - see output".to_string(),
},
Trace => parser.format_parse_trace(),
};
comp.add_artifact(debug_info);
});
ast.map_err(|err| format_parse_error(err, &handle.source_reference))
}
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();
let space_padding = " ".repeat(line_num_digits);
let production = match error.production_name {
Some(n) => format!("\n(from production \"{}\")", n),
None => "".to_string()
};
format!(r#"
{error_msg}{production}
{space_padding} |
{line_num} | {}
{space_padding} | {}
"#, line_from_program, location_pointer, error_msg=error.msg, space_padding=space_padding, line_num=line_num, production=production
)
}
fn symbol_table(input: ast::AST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<ast::AST, String> {
let () = handle.symbol_table.borrow_mut().add_top_level_symbols(&input)?;
comp.map(|comp| {
let debug = handle.symbol_table.borrow().debug_symbol_table();
comp.add_artifact(debug);
});
Ok(input)
}
fn scope_resolution(mut input: ast::AST, handle: &mut Schala, _com: Option<&mut PassDebugArtifact>) -> Result<ast::AST, String> {
let () = handle.resolver.resolve(&mut input)?;
Ok(input)
}
fn typechecking(input: ast::AST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<ast::AST, String> {
let result = handle.type_context.typecheck(&input);
comp.map(|comp| {
comp.add_artifact(match result {
Ok(ty) => ty.to_string(),
Err(err) => format!("Type error: {}", err.msg)
});
});
Ok(input)
}
fn ast_reducing(input: ast::AST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<reduced_ast::ReducedAST, String> {
let ref symbol_table = handle.symbol_table.borrow();
let output = reduced_ast::reduce(&input, symbol_table);
comp.map(|comp| comp.add_artifact(format!("{:?}", output)));
Ok(output)
}
fn eval(input: reduced_ast::ReducedAST, handle: &mut Schala, comp: Option<&mut PassDebugArtifact>) -> Result<String, String> {
comp.map(|comp| comp.add_artifact(handle.state.debug_print()));
let evaluation_outputs = handle.state.evaluate(input, true);
let text_output: Result<Vec<String>, String> = evaluation_outputs
.into_iter()
.collect();
let eval_output: Result<String, String> = text_output
.map(|v| { v.into_iter().intersperse(format!("\n")).collect() });
eval_output
}
/// Represents lines of source code
struct SourceReference {
lines: Option<Vec<String>>
}
impl SourceReference {
fn new() -> SourceReference {
SourceReference { lines: None }
}
fn load_new_source(&mut self, source: &str) {
//TODO this is a lot of heap allocations - maybe there's a way to make it more efficient?
self.lines = Some(source.lines().map(|s| s.to_string()).collect()); }
fn get_line(&self, line: usize) -> String {
self.lines.as_ref().and_then(|x| x.get(line).map(|s| s.to_string())).unwrap_or(format!("NO LINE FOUND"))
}
}
enum ParsingDebugType {
CompactAST,
ExpandedAST,
Trace
}
#[derive(Default)]
struct PassDebugArtifact {
parsing: Option<ParsingDebugType>,
artifacts: Vec<String>
}
impl PassDebugArtifact {
fn add_artifact(&mut self, artifact: String) {
self.artifacts.push(artifact)
}
}
fn stage_names() -> Vec<&'static str> {
vec![
"tokenizing",
"parsing",
"symbol-table",
"scope-resolution",
"typechecking",
"ast-reduction",
"ast-walking-evaluation"
]
}
impl ProgrammingLanguageInterface for Schala {
fn get_language_name(&self) -> String { format!("Schala") }
fn get_source_file_suffix(&self) -> String { format!("schala") }
fn run_computation(&mut self, request: ComputationRequest) -> ComputationResponse {
struct PassToken<'a> {
schala: &'a mut Schala,
stage_durations: &'a mut Vec<(String, Duration)>,
sw: &'a Stopwatch,
debug_requests: &'a HashSet<DebugAsk>,
debug_responses: &'a mut Vec<DebugResponse>,
}
fn output_wrapper<Input, Output, F>(n: usize, func: F, input: Input, token: &mut PassToken) -> Result<Output, String>
where F: Fn(Input, &mut Schala, Option<&mut PassDebugArtifact>) -> Result<Output, String>
{
let stage_names = stage_names();
let cur_stage_name = stage_names[n];
let ask = token.debug_requests.iter().find(|ask| ask.is_for_stage(cur_stage_name));
let parsing = match ask {
Some(DebugAsk::ByStage { token, .. }) if cur_stage_name == "parsing" => Some(
token.as_ref().map(|token| match &token[..] {
"compact" => ParsingDebugType::CompactAST,
"expanded" => ParsingDebugType::ExpandedAST,
"trace" => ParsingDebugType::Trace,
_ => ParsingDebugType::CompactAST,
}).unwrap_or(ParsingDebugType::CompactAST)
),
_ => None,
};
let mut debug_artifact = ask.map(|_| PassDebugArtifact {
parsing, ..Default::default()
});
let output = func(input, token.schala, debug_artifact.as_mut());
//TODO I think this is not counting the time since the *previous* stage
token.stage_durations.push((cur_stage_name.to_string(), token.sw.elapsed()));
if let Some(artifact) = debug_artifact {
for value in artifact.artifacts.into_iter() {
let resp = DebugResponse { ask: ask.unwrap().clone(), value };
token.debug_responses.push(resp);
}
}
output
}
let ComputationRequest { source, debug_requests } = request;
self.source_reference.load_new_source(source);
let sw = Stopwatch::start_new();
let mut stage_durations = Vec::new();
let mut debug_responses = Vec::new();
let mut tok = PassToken { schala: self, stage_durations: &mut stage_durations, sw: &sw, debug_requests: &debug_requests, debug_responses: &mut debug_responses };
let main_output: Result<String, String> = Ok(source)
.and_then(|source| output_wrapper(0, tokenizing, source, &mut tok))
.and_then(|tokens| output_wrapper(1, parsing, tokens, &mut tok))
.and_then(|ast| output_wrapper(2, symbol_table, ast, &mut tok))
.and_then(|ast| output_wrapper(3, scope_resolution, ast, &mut tok))
.and_then(|ast| output_wrapper(4, typechecking, ast, &mut tok))
.and_then(|ast| output_wrapper(5, ast_reducing, ast, &mut tok))
.and_then(|reduced_ast| output_wrapper(6, eval, reduced_ast, &mut tok));
let total_duration = sw.elapsed();
let global_output_stats = GlobalOutputStats {
total_duration, stage_durations
};
ComputationResponse {
main_output,
global_output_stats,
debug_responses,
}
}
fn request_meta(&mut self, request: LangMetaRequest) -> LangMetaResponse {
match request {
LangMetaRequest::StageNames => LangMetaResponse::StageNames(stage_names().iter().map(|s| s.to_string()).collect()),
LangMetaRequest::Docs { source } => self.handle_docs(source),
LangMetaRequest::ImmediateDebug(debug_request) =>
LangMetaResponse::ImmediateDebug(self.handle_debug_immediate(debug_request)),
LangMetaRequest::Custom { .. } => LangMetaResponse::Custom { kind: format!("not-implemented"), value: format!("") }
}
}
}

View File

@@ -0,0 +1,118 @@
use std::rc::Rc;
use crate::schala::SymbolTableHandle;
use crate::symbol_table::{ScopeSegment, FullyQualifiedSymbolName};
use crate::ast::*;
use crate::util::ScopeStack;
type FQSNPrefix = Vec<ScopeSegment>;
pub struct ScopeResolver<'a> {
symbol_table_handle: SymbolTableHandle,
name_scope_stack: ScopeStack<'a, Rc<String>, FQSNPrefix>,
}
impl<'a> ASTVisitor for ScopeResolver<'a> {
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 => {
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(),
}).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(),
}).collect();
for name in names.iter() {
self.name_scope_stack.insert(name.clone(), fqsn_prefix.clone());
}
}
};
}
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;
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);
symbol_table.map_id_to_fqsn(id, fqsn);
}
fn pattern(&mut self, pat: &Pattern) {
use Pattern::*;
match pat {
Ignored => (),
TuplePattern(_) => (),
Literal(_) => (),
TupleStruct(name, _) => self.qualified_name_in_pattern(name),
Record(name, _) => self.qualified_name_in_pattern(name),
VarOrName(name) => self.qualified_name_in_pattern(name),
};
}
}
impl<'a> ScopeResolver<'a> {
pub fn new(symbol_table_handle: SymbolTableHandle) -> ScopeResolver<'static> {
let name_scope_stack = ScopeStack::new(None);
ScopeResolver { symbol_table_handle, name_scope_stack }
}
pub fn resolve(&mut self, ast: &mut AST) -> Result<(), String> {
walk_ast(self, ast);
Ok(())
}
fn lookup_name_in_scope(&self, sym_name: &QualifiedName) -> FullyQualifiedSymbolName {
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 symbol_table.lookup_by_fqsn(&fqsn).is_some() {
symbol_table.map_id_to_fqsn(&id, fqsn);
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn basic_scope() {
}
}

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

@@ -1,38 +1,149 @@
use std::collections::HashMap;
use std::collections::hash_map::Entry;
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::{TypeBody, TypeSingletonName, Signature};
use crate::ast::{ItemId, TypeBody, TypeSingletonName, Signature, Statement, StatementKind, ModuleSpecifier};
use crate::typechecking::TypeName;
#[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>);
impl fmt::Display for FullyQualifiedSymbolName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let FullyQualifiedSymbolName(v) = self;
for segment in v {
write!(f, "::{}", segment)?;
}
Ok(())
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
pub struct ScopeSegment {
pub name: Rc<String>, //TODO maybe this could be a &str, for efficiency?
}
impl fmt::Display for ScopeSegment {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let kind = ""; //TODO implement some kind of kind-tracking here
write!(f, "{}{}", self.name, kind)
}
}
impl ScopeSegment {
pub fn new(name: Rc<String>) -> ScopeSegment {
ScopeSegment { name }
}
}
//cf. p. 150 or so of Language Implementation Patterns
pub struct SymbolTable {
pub values: HashMap<Rc<String>, Symbol> //TODO this will eventually have real type information
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? also make values
//non-public
impl SymbolTable {
pub fn new() -> SymbolTable {
SymbolTable { values: HashMap::new() }
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()
}
}
pub fn lookup_by_name(&self, name: &Rc<String>) -> Option<&Symbol> {
self.values.get(name)
pub fn map_id_to_fqsn(&mut self, id: &ItemId, fqsn: FullyQualifiedSymbolName) {
self.id_to_fqsn.insert(id.clone(), fqsn);
}
pub fn get_fqsn_from_id(&self, id: &ItemId) -> Option<FullyQualifiedSymbolName> {
self.id_to_fqsn.get(&id).cloned()
}
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() });
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)]
pub struct Symbol {
pub name: Rc<String>,
pub local_name: Rc<String>, //TODO does this need to be pub?
fully_qualified_name: FullyQualifiedSymbolName,
pub spec: SymbolSpec,
}
impl fmt::Display for Symbol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<Name: {}, Spec: {}>", self.name, self.spec)
write!(f, "<Local name: {}, Spec: {}>", self.local_name, self.spec)
}
}
@@ -41,12 +152,18 @@ pub enum SymbolSpec {
Func(Vec<TypeName>),
DataConstructor {
index: usize,
type_name: Rc<String>,
type_name: TypeName,
type_args: Vec<Rc<String>>,
},
RecordConstructor {
fields: HashMap<Rc<String>, Rc<String>>
}
index: usize,
members: HashMap<Rc<String>, TypeName>,
type_name: TypeName,
},
Binding,
Type {
name: TypeName
},
}
impl fmt::Display for SymbolSpec {
@@ -55,7 +172,9 @@ impl fmt::Display for SymbolSpec {
match self {
Func(type_names) => write!(f, "Func({:?})", type_names),
DataConstructor { index, type_name, type_args } => write!(f, "DataConstructor(idx: {})({:?} -> {})", index, type_args, type_name),
RecordConstructor { fields } => write!(f, "RecordConstructor( <fields> )"),
RecordConstructor { type_name, index, ..} => write!(f, "RecordConstructor(idx: {})(<members> -> {})", index, type_name),
Binding => write!(f, "Binding"),
Type { name } => write!(f, "Type <{}>", name),
}
}
}
@@ -63,81 +182,146 @@ impl fmt::Display for SymbolSpec {
impl SymbolTable {
/* note: this adds names for *forward reference* but doesn't actually create any types. solve that problem
* later */
pub fn add_top_level_symbols(&mut self, ast: &ast::AST) -> Result<(), String> {
use self::ast::Statement;
let mut scope_name_stack = Vec::new();
self.add_symbols_from_scope(&ast.statements, &mut scope_name_stack)
}
fn add_symbols_from_scope<'a>(&'a mut self, statements: &Vec<Statement>, scope_name_stack: &mut Vec<ScopeSegment>) -> Result<(), String> {
use self::ast::Declaration::*;
for statement in ast.0.iter() {
let statement = statement.node();
if let Statement::Declaration(decl) = statement {
match decl {
FuncSig(signature) | FuncDecl(signature, _) => self.add_function_signature(signature)?,
TypeDecl { name, body, mutable } => self.add_type_decl(name, body, mutable)?,
_ => ()
}
let mut seen_identifiers = DuplicateNameTrackTable::new();
let mut seen_modules = DuplicateNameTrackTable::new();
for statement in statements.iter() {
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);
}
_ => ()
}
},
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(())
}
pub fn debug_symbol_table(&self) -> String {
let mut output = format!("Symbol table\n");
for (name, sym) in &self.values {
let mut sorted_symbols: Vec<(&FullyQualifiedSymbolName, &Symbol)> = self.symbol_path_to_symbol.iter().collect();
sorted_symbols.sort_by(|(fqsn, _), (other_fqsn, _)| fqsn.cmp(other_fqsn));
for (name, sym) in sorted_symbols.iter() {
write!(output, "{} -> {}\n", name, sym).unwrap();
}
output
}
fn add_function_signature(&mut self, signature: &Signature) -> Result<(), String> {
fn add_function_signature(&mut self, signature: &Signature, scope_name_stack: &mut Vec<ScopeSegment>) -> Result<(), String> {
let mut local_type_context = LocalTypeContext::new();
let types = signature.params.iter().map(|param| match param {
(_, Some(type_identifier)) => Rc::new(format!("{:?}", type_identifier)),
(_, None) => local_type_context.new_universal_type()
let types = signature.params.iter().map(|param| match param.anno {
Some(ref type_identifier) => Rc::new(format!("{:?}", type_identifier)),
None => local_type_context.new_universal_type()
}).collect();
let spec = SymbolSpec::Func(types);
self.values.insert(
signature.name.clone(),
Symbol { name: signature.name.clone(), spec }
);
self.add_new_symbol(&signature.name, scope_name_stack, SymbolSpec::Func(types));
Ok(())
}
fn add_type_decl(&mut self, type_name: &TypeSingletonName, body: &TypeBody, _mutable: &bool) -> Result<(), String> {
//TODO handle type mutability
fn add_type_decl(&mut self, type_name: &TypeSingletonName, body: &TypeBody, _mutable: &bool, scope_name_stack: &mut Vec<ScopeSegment>) -> Result<(), String> {
use crate::ast::{TypeIdentifier, Variant};
let TypeBody(variants) = body;
let TypeSingletonName { name, .. } = type_name;
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(),
});
//TODO figure out why _params isn't being used here
for (index, var) in variants.iter().enumerate() {
match var {
Variant::UnitStruct(variant_name) => {
let spec = SymbolSpec::DataConstructor {
index,
type_name: name.clone(),
type_name: type_name.clone(),
type_args: vec![],
};
self.values.insert(variant_name.clone(), Symbol { name: variant_name.clone(), spec });
self.add_new_symbol(variant_name, scope_name_stack, spec);
},
Variant::TupleStruct(variant_name, tuple_members) => {
//TODO fix the notion of a tuple type
let type_args = tuple_members.iter().map(|type_name| match type_name {
TypeIdentifier::Singleton(TypeSingletonName { name, ..}) => name.clone(),
TypeIdentifier::Tuple(_) => unimplemented!(),
}).collect();
let spec = SymbolSpec::DataConstructor {
index,
type_name: name.clone(),
type_name: type_name.clone(),
type_args
};
let symbol = Symbol { name: variant_name.clone(), spec };
self.values.insert(variant_name.clone(), symbol);
self.add_new_symbol(variant_name, scope_name_stack, spec);
},
//TODO if there is only one variant, and it is a record, it doesn't need to have an
//explicit name
Variant::Record { name, members } => {
let fields = HashMap::new();
let spec = SymbolSpec::RecordConstructor { fields };
let symbol = Symbol { name: name.clone(), spec };
self.values.insert(name.clone(), symbol);
Variant::Record { name, members: defined_members } => {
let mut members = HashMap::new();
let mut duplicate_member_definitions = Vec::new();
for (member_name, member_type) in defined_members {
match members.entry(member_name.clone()) {
Entry::Occupied(_) => duplicate_member_definitions.push(member_name.clone()),
Entry::Vacant(v) => {
v.insert(match member_type {
TypeIdentifier::Singleton(TypeSingletonName { name, ..}) => name.clone(),
TypeIdentifier::Tuple(_) => unimplemented!(),
});
}
}
}
if duplicate_member_definitions.len() != 0 {
return Err(format!("Duplicate member(s) in definition of type {}: {:?}", type_name, duplicate_member_definitions));
}
let spec = SymbolSpec::RecordConstructor { index, type_name: type_name.clone(), members };
self.add_new_symbol(name, scope_name_stack, spec);
},
}
}
scope_name_stack.pop();
Ok(())
}
}
@@ -156,3 +340,4 @@ impl LocalTypeContext {
Rc::new(format!("{}", (('a' as u8) + n) as char))
}
}

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,
@@ -15,7 +17,7 @@ pub enum TokenKind {
Pipe, Backslash,
Comma, Period, Colon, Underscore,
Slash,
Slash, Equals,
Operator(Rc<String>),
DigitGroup(Rc<String>), HexLiteral(Rc<String>), BinNumberSigil,
@@ -55,7 +57,7 @@ pub enum Kw {
Alias, Type, SelfType, SelfIdent,
Interface, Impl,
True, False,
Module
Module, Import
}
lazy_static! {
@@ -82,14 +84,14 @@ lazy_static! {
"true" => Kw::True,
"false" => Kw::False,
"module" => Kw::Module,
"import" => Kw::Import,
};
}
#[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 {
@@ -100,7 +102,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 {
@@ -118,7 +120,7 @@ type CharData = (usize, usize, char);
pub fn tokenize(input: &str) -> Vec<Token> {
let mut tokens: Vec<Token> = Vec::new();
let mut input = input.lines().enumerate()
let mut input = input.lines().enumerate()
.intersperse((0, "\n"))
.flat_map(|(line_idx, ref line)| {
line.chars().enumerate().map(move |(ch_idx, ch)| (line_idx, ch_idx, ch))
@@ -168,7 +170,8 @@ pub fn tokenize(input: &str) -> Vec<Token> {
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
}
@@ -238,7 +241,7 @@ fn handle_alphabetic(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>
fn handle_operator(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>) -> TokenKind {
match c {
'<' | '>' | '|' | '.' => {
'<' | '>' | '|' | '.' | '=' => {
let ref next = input.peek().map(|&(_, _, c)| { c });
if !next.map(|n| { is_operator(&n) }).unwrap_or(false) {
return match c {
@@ -246,6 +249,7 @@ fn handle_operator(c: char, input: &mut Peekable<impl Iterator<Item=CharData>>)
'>' => RAngleBracket,
'|' => Pipe,
'.' => Period,
'=' => Equals,
_ => unreachable!(),
}
}
@@ -298,7 +302,7 @@ mod schala_tokenizer_tests {
let a = tokenize("let a: A<B> = c ++ d");
let token_kinds: Vec<TokenKind> = a.into_iter().map(move |t| t.kind).collect();
assert_eq!(token_kinds, vec![Keyword(Let), ident!("a"), Colon, ident!("A"),
LAngleBracket, ident!("B"), RAngleBracket, op!("="), ident!("c"), op!("++"), ident!("d")]);
LAngleBracket, ident!("B"), RAngleBracket, Equals, ident!("c"), op!("++"), ident!("d")]);
}
#[test]

View File

@@ -1,27 +1,148 @@
use std::rc::Rc;
use std::fmt::Write;
use ena::unify::{UnifyKey, InPlaceUnificationTable, UnificationTable, EqUnifyValue};
use crate::ast::*;
use crate::util::ScopeStack;
use crate::util::deref_optional_box;
#[derive(Debug, Clone, PartialEq)]
pub struct TypeData {
ty: Option<Type>
}
impl TypeData {
#[allow(dead_code)]
pub fn new() -> TypeData {
TypeData { ty: None }
}
}
pub type TypeName = Rc<String>;
pub struct TypeContext<'a> {
variable_map: ScopeStack<'a, Rc<String>, Type<TVar>>,
evar_count: u32
variable_map: ScopeStack<'a, Rc<String>, Type>,
unification_table: InPlaceUnificationTable<TypeVar>,
}
/// `InferResult` is the monad in which type inference takes place.
type InferResult<T> = Result<T, TypeError>;
#[derive(Debug, Clone)]
struct TypeError { msg: String }
pub struct TypeError { pub msg: String }
impl TypeError {
fn new<A>(msg: &str) -> InferResult<A> {
Err(TypeError { msg: msg.to_string() })
fn new<A, T>(msg: T) -> InferResult<A> where T: Into<String> {
Err(TypeError { msg: msg.into() })
}
}
#[allow(dead_code)] // avoids warning from Compound
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
Const(TypeConst),
Var(TypeVar),
Arrow {
params: Vec<Type>,
ret: Box<Type>
},
Compound {
ty_name: String,
args:Vec<Type>
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct TypeVar(usize);
impl UnifyKey for TypeVar {
type Value = Option<TypeConst>;
fn index(&self) -> u32 { self.0 as u32 }
fn from_index(u: u32) -> TypeVar { TypeVar(u as usize) }
fn tag() -> &'static str { "TypeVar" }
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypeConst {
Unit,
Nat,
Int,
Float,
StringT,
Bool,
Ordering,
//UserDefined
}
impl TypeConst {
pub fn to_string(&self) -> String {
use self::TypeConst::*;
match self {
Unit => format!("()"),
Nat => format!("Nat"),
Int => format!("Int"),
Float => format!("Float"),
StringT => format!("String"),
Bool => format!("Bool"),
Ordering => format!("Ordering"),
}
}
}
impl EqUnifyValue for TypeConst { }
macro_rules! ty {
($type_name:ident) => { Type::Const(TypeConst::$type_name) };
($t1:ident -> $t2:ident) => { Type::Arrow { params: vec![ty!($t1)], ret: box ty!($t2) } };
($t1:ident -> $t2:ident -> $t3:ident) => { Type::Arrow { params: vec![ty!($t1), ty!($t2)], ret: box ty!($t3) } };
($type_list:ident, $ret_type:ident) => {
Type::Arrow {
params: $type_list,
ret: box $ret_type,
}
}
}
//TODO find a better way to capture the to/from string logic
impl Type {
pub fn to_string(&self) -> String {
use self::Type::*;
match self {
Const(c) => c.to_string(),
Var(v) => format!("t_{}", v.0),
Arrow { params, box ref ret } => {
if params.len() == 0 {
format!("-> {}", ret.to_string())
} else {
let mut buf = String::new();
for p in params.iter() {
write!(buf, "{} -> ", p.to_string()).unwrap();
}
write!(buf, "{}", ret.to_string()).unwrap();
buf
}
},
Compound { .. } => format!("<some compound type>")
}
}
fn from_string(string: &str) -> Option<Type> {
Some(match string {
"()" | "Unit" => ty!(Unit),
"Nat" => ty!(Nat),
"Int" => ty!(Int),
"Float" => ty!(Float),
"String" => ty!(StringT),
"Bool" => ty!(Bool),
"Ordering" => ty!(Ordering),
_ => return None
})
}
}
/*
/// `Type` is parameterized by whether the type variables can be just universal, or universal or
/// existential.
#[derive(Debug, Clone)]
@@ -104,151 +225,264 @@ impl TConst {
TConst::User(Rc::new(name.to_string()))
}
}
*/
impl<'a> TypeContext<'a> {
pub fn new() -> TypeContext<'a> {
TypeContext {
variable_map: ScopeStack::new(None),
evar_count: 0
unification_table: UnificationTable::new(),
}
}
pub fn typecheck(&mut self, ast: &AST) -> Result<String, String> {
match self.infer_ast(ast) {
Ok(t) => Ok(format!("{:?}", t)),
Err(err) => Err(format!("Type error: {:?}", err))
}
}
}
/*
fn new_env(&'a self, new_var: Rc<String>, ty: Type) -> TypeContext<'a> {
let mut new_context = TypeContext {
variable_map: self.variable_map.new_scope(None),
unification_table: UnificationTable::new(), //???? not sure if i want this
};
impl<'a> TypeContext<'a> {
fn infer_ast(&mut self, ast: &AST) -> InferResult<Type<UVar>> {
self.infer_block(&ast.0)
new_context.variable_map.insert(new_var, ty);
new_context
}
*/
fn infer_statement(&mut self, stmt: &Statement) -> InferResult<Type<UVar>> {
match stmt {
Statement::ExpressionStatement(ref expr) => self.infer_expr(expr.node()),
Statement::Declaration(ref decl) => self.infer_decl(decl),
}
}
fn infer_expr(&mut self, expr: &Expression) -> InferResult<Type<UVar>> {
match expr {
Expression(expr_type, Some(type_anno)) => {
let tx = self.infer_expr_type(expr_type)?;
let ty = type_anno.to_monotype();
self.unify(&ty.to_tvar(), &tx.to_tvar()).map(|x| x.skolemize())
},
Expression(expr_type, None) => self.infer_expr_type(expr_type)
}
}
fn infer_decl(&mut self, _decl: &Declaration) -> InferResult<Type<UVar>> {
Ok(Type::Const(TConst::user("unimplemented")))
}
fn infer_expr_type(&mut self, expr_type: &ExpressionType) -> InferResult<Type<UVar>> {
use self::ExpressionType::*;
Ok(match expr_type {
NatLiteral(_) => Type::Const(TConst::Nat),
FloatLiteral(_) => Type::Const(TConst::Float),
StringLiteral(_) => Type::Const(TConst::StringT),
BoolLiteral(_) => Type::Const(TConst::Bool),
Value(name) => {
//TODO handle the distinction between 0-arg constructors and variables at some point
// need symbol table for that
match self.variable_map.lookup(name) {
Some(ty) => ty.clone().skolemize(),
None => return TypeError::new(&format!("Variable {} not found", name))
fn get_type_from_name(&self, name: &TypeIdentifier) -> InferResult<Type> {
use self::TypeIdentifier::*;
Ok(match name {
Singleton(TypeSingletonName { name,.. }) => {
match Type::from_string(&name) {
Some(ty) => ty,
None => return TypeError::new(format!("Unknown type name: {}", name))
}
},
IfExpression { discriminator, body } => self.infer_if_expr(discriminator, body)?,
Call { f, arguments } => {
let tf = self.infer_expr(f)?; //has to be an Arrow Type
let targ = self.infer_expr(&arguments[0].node())?; // TODO make this work with functions with more than one arg
match tf {
Type::Arrow(t1, t2) => {
self.unify(&t1.to_tvar(), &targ.to_tvar())?;
*t2.clone()
},
_ => return TypeError::new("not a function")
}
},
Lambda { params, .. } => {
let _arg_type = match &params[0] {
(_, Some(type_anno)) => type_anno.to_monotype().to_tvar(),
(_, None) => self.allocate_existential(),
};
//let _result_type = unimplemented!();
return TypeError::new("Unimplemented");
//Type::Arrow(Box::new(arg_type), Box::new(result_type))
}
_ => Type::Const(TConst::user("unimplemented"))
Tuple(_) => return TypeError::new("tuples aren't ready yet"),
})
}
fn infer_if_expr(&mut self, discriminator: &Discriminator, body: &IfExpressionBody) -> InferResult<Type<UVar>> {
let _test = match discriminator {
Discriminator::Simple(expr) => expr,
_ => return TypeError::new("Dame desu")
};
let (_then_clause, _maybe_else_clause) = match body {
IfExpressionBody::SimpleConditional(a, b) => (a, b),
_ => return TypeError::new("Dont work")
};
TypeError::new("Not implemented")
/// `typecheck` is the entry into the type-inference system, accepting an AST as an argument
/// Following the example of GHC, the compiler deliberately does typechecking before de-sugaring
/// the AST to ReducedAST
pub fn typecheck(&mut self, ast: &AST) -> Result<Type, TypeError> {
let mut returned_type = Type::Const(TypeConst::Unit);
for statement in ast.statements.iter() {
returned_type = self.statement(statement)?;
}
Ok(returned_type)
}
fn infer_block(&mut self, block: &Block) -> InferResult<Type<UVar>> {
let mut output = Type::Const(TConst::Unit);
fn statement(&mut self, statement: &Statement) -> InferResult<Type> {
match &statement.kind {
StatementKind::Expression(e) => self.expr(e),
StatementKind::Declaration(decl) => self.decl(&decl),
StatementKind::Import(_) => Ok(ty!(Unit)),
StatementKind::Module(_) => Ok(ty!(Unit)),
}
}
fn decl(&mut self, decl: &Declaration) -> InferResult<Type> {
use self::Declaration::*;
match decl {
Binding { name, expr, .. } => {
let ty = self.expr(expr)?;
self.variable_map.insert(name.clone(), ty);
},
_ => (),
}
Ok(ty!(Unit))
}
fn invoc(&mut self, invoc: &InvocationArgument) -> InferResult<Type> {
use InvocationArgument::*;
match invoc {
Positional(expr) => self.expr(expr),
_ => Ok(ty!(Nat)) //TODO this is wrong
}
}
fn expr(&mut self, expr: &Expression) -> InferResult<Type> {
match expr {
Expression { kind, type_anno: Some(anno), .. } => {
let t1 = self.expr_type(kind)?;
let t2 = self.get_type_from_name(anno)?;
self.unify(t2, t1)
},
Expression { kind, type_anno: None, .. } => self.expr_type(kind)
}
}
fn expr_type(&mut self, expr: &ExpressionKind) -> InferResult<Type> {
use self::ExpressionKind::*;
Ok(match expr {
NatLiteral(_) => ty!(Nat),
BoolLiteral(_) => ty!(Bool),
FloatLiteral(_) => ty!(Float),
StringLiteral(_) => ty!(StringT),
PrefixExp(op, expr) => self.prefix(op, expr)?,
BinExp(op, lhs, rhs) => self.binexp(op, lhs, rhs)?,
IfExpression { discriminator, body } => self.if_expr(deref_optional_box(discriminator), &**body)?,
Value(val) => self.handle_value(val)?,
Call { box ref f, arguments } => self.call(f, arguments)?,
Lambda { params, type_anno, body } => self.lambda(params, type_anno, body)?,
_ => ty!(Unit),
})
}
fn prefix(&mut self, op: &PrefixOp, expr: &Expression) -> InferResult<Type> {
let tf = match op.builtin.map(|b| b.get_type()) {
Some(ty) => ty,
None => return TypeError::new("no type found")
};
let tx = self.expr(expr)?;
self.handle_apply(tf, vec![tx])
}
fn binexp(&mut self, op: &BinOp, lhs: &Expression, rhs: &Expression) -> InferResult<Type> {
let tf = match op.builtin.map(|b| b.get_type()) {
Some(ty) => ty,
None => return TypeError::new("no type found"),
};
let t_lhs = self.expr(lhs)?;
let t_rhs = self.expr(rhs)?; //TODO is this order a problem? not sure
self.handle_apply(tf, vec![t_lhs, t_rhs])
}
fn if_expr(&mut self, discriminator: Option<&Expression>, body: &IfExpressionBody) -> InferResult<Type> {
use self::IfExpressionBody::*;
match (discriminator, body) {
(Some(expr), SimpleConditional{ then_case, else_case }) => self.handle_simple_if(expr, then_case, else_case),
_ => TypeError::new(format!("Complex conditionals not supported"))
}
}
fn handle_simple_if(&mut self, expr: &Expression, then_clause: &Block, else_clause: &Option<Block>) -> InferResult<Type> {
let t1 = self.expr(expr)?;
let t2 = self.block(then_clause)?;
let t3 = match else_clause {
Some(block) => self.block(block)?,
None => ty!(Unit)
};
let _ = self.unify(ty!(Bool), t1)?;
self.unify(t2, t3)
}
fn lambda(&mut self, params: &Vec<FormalParam>, type_anno: &Option<TypeIdentifier>, _body: &Block) -> InferResult<Type> {
let argument_types: InferResult<Vec<Type>> = params.iter().map(|param: &FormalParam| {
if let FormalParam { anno: Some(type_identifier), .. } = param {
self.get_type_from_name(type_identifier)
} else {
Ok(Type::Var(self.fresh_type_variable()))
}
}).collect();
let argument_types = argument_types?;
let ret_type = match type_anno.as_ref() {
Some(anno) => self.get_type_from_name(anno)?,
None => Type::Var(self.fresh_type_variable())
};
Ok(ty!(argument_types, ret_type))
}
fn call(&mut self, f: &Expression, args: &Vec<InvocationArgument>) -> InferResult<Type> {
let tf = self.expr(f)?;
let arg_types: InferResult<Vec<Type>> = args.iter().map(|ex| self.invoc(ex)).collect();
let arg_types = arg_types?;
self.handle_apply(tf, arg_types)
}
fn handle_apply(&mut self, tf: Type, args: Vec<Type>) -> InferResult<Type> {
Ok(match tf {
Type::Arrow { ref params, ret: box ref t_ret } if params.len() == args.len() => {
for (t_param, t_arg) in params.iter().zip(args.iter()) {
let _ = self.unify(t_param.clone(), t_arg.clone())?; //TODO I think this needs to reference a sub-scope
}
t_ret.clone()
},
Type::Arrow { .. } => return TypeError::new("Wrong length"),
_ => return TypeError::new(format!("Not a function"))
})
}
fn block(&mut self, block: &Block) -> InferResult<Type> {
let mut output = ty!(Unit);
for statement in block.iter() {
output = self.infer_statement(statement.node())?;
output = self.statement(statement)?;
}
Ok(output)
}
fn unify(&mut self, _t1: &Type<TVar>, _t2: &Type<TVar>) -> InferResult<Type<TVar>> {
TypeError::new("not implemented")
fn handle_value(&mut self, val: &QualifiedName) -> InferResult<Type> {
let QualifiedName { components: vec, .. } = val;
let var = &vec[0];
match self.variable_map.lookup(var) {
Some(ty) => Ok(ty.clone()),
None => TypeError::new(format!("Couldn't find variable: {}", &var)),
}
}
fn allocate_existential(&mut self) -> Type<TVar> {
let n = self.evar_count;
self.evar_count += 1;
Type::Var(TVar::Exist(ExistentialVar(n)))
fn unify(&mut self, t1: Type, t2: Type) -> InferResult<Type> {
use self::Type::*;
match (t1, t2) {
(Const(ref c1), Const(ref c2)) if c1 == c2 => Ok(Const(c1.clone())), //choice of c1 is arbitrary I *think*
(a @ Var(_), b @ Const(_)) => self.unify(b, a),
(Const(ref c1), Var(ref v2)) => {
self.unification_table.unify_var_value(v2.clone(), Some(c1.clone()))
.or_else(|_| TypeError::new(format!("Couldn't unify {:?} and {:?}", Const(c1.clone()), Var(*v2))))?;
Ok(Const(c1.clone()))
},
(Var(v1), Var(v2)) => {
//TODO add occurs check
self.unification_table.unify_var_var(v1.clone(), v2.clone())
.or_else(|e| {
println!("Unify error: {:?}", e);
TypeError::new(format!("Two type variables {:?} and {:?} couldn't unify", v1, v2))
})?;
Ok(Var(v1.clone())) //arbitrary decision I think
},
(a, b) => TypeError::new(format!("{:?} and {:?} do not unify", a, b)),
}
}
fn fresh_type_variable(&mut self) -> TypeVar {
let new_type_var = self.unification_table.new_key(None);
new_type_var
}
}
#[cfg(test)]
mod tests {
mod typechecking_tests {
use super::*;
fn parse(input: &str) -> AST {
let tokens: Vec<crate::tokenizing::Token> = crate::tokenizing::tokenize(input);
let mut parser = crate::parsing::Parser::new(tokens);
parser.parse().unwrap()
}
macro_rules! type_test {
($input:expr, $correct:expr) => {
{
macro_rules! assert_type_in_fresh_context {
($string:expr, $type:expr) => {
let mut tc = TypeContext::new();
let ast = parse($input);
tc.add_symbols(&ast);
assert_eq!($correct, tc.type_check(&ast).unwrap())
}
let (ref ast, _) = crate::util::quick_ast($string);
let ty = tc.typecheck(ast).unwrap();
assert_eq!(ty, $type)
}
}
#[test]
fn basic_test() {
assert_type_in_fresh_context!("1", ty!(Nat));
assert_type_in_fresh_context!(r#""drugs""#, ty!(StringT));
assert_type_in_fresh_context!("true", ty!(Bool));
}
#[test]
fn basic_inference() {
fn operators() {
//TODO fix these with new operator regime
/*
assert_type_in_fresh_context!("-1", ty!(Int));
assert_type_in_fresh_context!("1 + 2", ty!(Nat));
assert_type_in_fresh_context!("-2", ty!(Int));
assert_type_in_fresh_context!("!true", ty!(Bool));
*/
}
}

View File

@@ -1,6 +1,11 @@
use std::collections::HashMap;
use std::hash::Hash;
use std::cmp::Eq;
use std::ops::Deref;
pub fn deref_optional_box<T>(x: &Option<Box<T>>) -> Option<&T> {
x.as_ref().map(|b: &Box<T>| Deref::deref(b))
}
#[derive(Default, Debug)]
pub struct ScopeStack<'a, T: 'a, V: 'a> where T: Hash + Eq {
@@ -41,3 +46,23 @@ 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, 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(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)]
macro_rules! rc {
($string:tt) => { Rc::new(stringify!($string).to_string()) }
}

View File

@@ -1,13 +0,0 @@
[package]
name = "schala-repl-codegen"
version = "0.1.0"
authors = ["greg <greg.shuflin@protonmail.com>"]
[dependencies]
syn = { version = "0.15.6", features = ["full", "extra-traits"] }
quote = "0.6.8"
proc-macro2 = "0.4.19"
schala-repl = { path = "../schala-repl" }
[lib]
proc-macro = true

View File

@@ -1,199 +0,0 @@
#![feature(trace_macros)]
#![recursion_limit="128"]
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
extern crate quote;
extern crate syn;
use proc_macro::TokenStream;
use syn::{Ident, Attribute, DeriveInput};
fn find_attr_by_name<'a>(name: &str, attrs: &'a Vec<Attribute>) -> Option<&'a Attribute> {
attrs.iter().find(|attr| {
let first = attr.path.segments.first();
let seg: Option<&&syn::PathSegment> = first.as_ref().map(|x| x.value());
seg.map(|seg| seg.ident.to_string() == name).unwrap_or(false)
})
}
fn extract_attribute_arg_by_name(name: &str, attrs: &Vec<Attribute>) -> Option<String> {
use syn::{Meta, Lit, MetaNameValue};
find_attr_by_name(name, attrs)
.and_then(|attr| {
match attr.interpret_meta() {
Some(Meta::NameValue(MetaNameValue { lit: Lit::Str(litstr), .. })) => Some(litstr.value()),
_ => None,
}
})
}
fn extract_attribute_list(name: &str, attrs: &Vec<Attribute>) -> Option<Vec<(Ident, Option<Vec<Ident>>)>> {
use syn::{Meta, MetaList, NestedMeta};
find_attr_by_name(name, attrs)
.and_then(|attr| {
match attr.interpret_meta() {
Some(Meta::List(MetaList { nested, .. })) => {
Some(nested.iter().map(|nested_meta| match nested_meta {
&NestedMeta::Meta(Meta::Word(ref ident)) => (ident.clone(), None),
&NestedMeta::Meta(Meta::List(MetaList { ref ident, nested: ref nested2, .. })) => {
let own_args = nested2.iter().map(|nested_meta2| match nested_meta2 {
&NestedMeta::Meta(Meta::Word(ref ident)) => ident.clone(),
_ => panic!("Bad format for doubly-nested attribute list")
}).collect();
(ident.clone(), Some(own_args))
},
_ => panic!("Bad format for nested list")
}).collect())
},
_ => panic!("{} must be a comma-delimited list surrounded by parens", name)
}
})
}
fn get_attribute_identifier(attr_name: &str, attrs: &Vec<Attribute>) -> Option<proc_macro2::Ident> {
find_attr_by_name(attr_name, attrs).and_then(|attr| {
let tts = attr.tts.clone().into_iter().collect::<Vec<_>>();
if tts.len() == 2 {
let ref after_equals: proc_macro2::TokenTree = tts[1];
match after_equals {
proc_macro2::TokenTree::Ident(ident) => Some(ident.clone()),
_ => None
}
} else {
None
}
})
}
/* a pass_chain function signature with input A and output B looks like:
* fn(A, &mut ProgrammingLanguageInterface, Option<&mut DebugHandler>) -> Result<B, String>
*
* TODO use some kind of failure-handling library to make this better
*/
fn generate_pass_chain(idents: Vec<Ident>) -> proc_macro2::TokenStream {
let final_return = quote! {
{
let final_output: FinishedComputation = unfinished_computation.finish(Ok(input_to_next_stage));
final_output
}
};
let nested_passes = idents.iter()
.rev()
.fold(final_return, |later_fragment, pass_name| {
quote! {
{
let pass_name = stringify!(#pass_name);
let (output, duration) = {
let ref debug_map = eval_options.debug_passes;
let debug_handle = match debug_map.get(pass_name) {
Some(PassDebugOptionsDescriptor { opts }) => {
let ptr = &mut unfinished_computation;
ptr.cur_debug_options = opts.clone();
Some(ptr)
}
_ => None
};
let start = time::Instant::now();
let pass_output = #pass_name(input_to_next_stage, self, debug_handle);
let elapsed = start.elapsed();
(pass_output, elapsed)
};
if eval_options.debug_timing {
unfinished_computation.durations.push(duration);
}
match output {
Ok(input_to_next_stage) => #later_fragment,
//TODO this error type needs to be guaranteed to provide a useable string
Err(err) => return unfinished_computation.output(Err(format!("Pass {} failed:\n{}", pass_name, err))),
}
}
}
});
quote! {
{
use std::time;
use schala_repl::PassDebugOptionsDescriptor;
let eval_options = options;
let input_to_next_stage = input;
let mut unfinished_computation = UnfinishedComputation::default();
#nested_passes
}
}
}
#[proc_macro_derive(ProgrammingLanguageInterface,
attributes(LanguageName, SourceFileExtension, PipelineSteps, DocMethod, HandleCustomInterpreterDirectives))]
pub fn derive_programming_language_interface(input: TokenStream) -> TokenStream {
let ast: DeriveInput = syn::parse(input).unwrap();
let name = &ast.ident;
let attrs = &ast.attrs;
let language_name: String = extract_attribute_arg_by_name("LanguageName", attrs).expect("LanguageName is required");
let file_ext = extract_attribute_arg_by_name("SourceFileExtension", attrs).expect("SourceFileExtension is required");
let passes = extract_attribute_list("PipelineSteps", attrs).expect("PipelineSteps are required");
let pass_idents = passes.iter().map(|x| x.0.clone());
let get_doc_impl = match get_attribute_identifier("DocMethod", attrs) {
None => quote! { },
Some(method_name) => quote! {
fn get_doc(&self, commands: &Vec<&str>) -> Option<String> {
self.#method_name(commands)
}
}
};
let handle_custom_interpreter_directives_impl = match get_attribute_identifier("HandleCustomInterpreterDirectives", attrs) {
None => quote! { },
Some(method_name) => quote! {
fn handle_custom_interpreter_directives(&mut self, commands: &Vec<&str>) -> Option<String> {
//println!("If #method_name is &self not &mut self, this runs forever");
self.#method_name(commands)
}
}
};
let pass_descriptors = passes.iter().map(|pass| {
let name = pass.0.to_string();
let opts: Vec<String> = match &pass.1 {
None => vec![],
Some(opts) => opts.iter().map(|o| o.to_string()).collect(),
};
quote! {
PassDescriptor {
name: #name.to_string(),
debug_options: vec![#(format!(#opts)),*]
}
}
});
let pass_chain = generate_pass_chain(pass_idents.collect());
let tokens = quote! {
use schala_repl::PassDescriptor;
impl ProgrammingLanguageInterface for #name {
fn get_language_name(&self) -> String {
#language_name.to_string()
}
fn get_source_file_suffix(&self) -> String {
#file_ext.to_string()
}
fn execute_pipeline(&mut self, input: &str, options: &EvalOptions) -> FinishedComputation {
#pass_chain
}
fn get_passes(&self) -> Vec<PassDescriptor> {
vec![ #(#pass_descriptors),* ]
}
#get_doc_impl
#handle_custom_interpreter_directives_impl
}
};
let output: TokenStream = tokens.into();
output
}

View File

@@ -2,26 +2,23 @@
name = "schala-repl"
version = "0.1.0"
authors = ["greg <greg.shuflin@protonmail.com>"]
edition = "2018"
[dependencies]
llvm-sys = "70.0.2"
take_mut = "0.2.2"
itertools = "0.5.8"
getopts = "*"
getopts = "0.2.18"
lazy_static = "0.2.8"
maplit = "*"
colored = "1.5"
serde = "1.0.15"
serde_derive = "1.0.15"
serde_json = "1.0.3"
rocket = "0.4.0"
rocket_contrib = "0.4.0"
colored = "1.8"
serde = "1.0.91"
serde_derive = "1.0.91"
serde_json = "1.0.15"
phf = "0.7.12"
includedir = "0.2.0"
linefeed = "0.5.0"
linefeed = "0.6.0"
regex = "0.2"
cursive = "0.10"
ncurses = "5.98.0"
[build-dependencies]
includedir_codegen = "0.2.0"

View File

@@ -1,173 +1,80 @@
use std::collections::HashMap;
use colored::*;
use std::fmt::Write;
use std::time;
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct EvalOptions {
pub execution_method: ExecutionMethod,
pub debug_passes: HashMap<String, PassDebugOptionsDescriptor>,
pub debug_timing: bool,
}
#[derive(Debug, Hash, PartialEq)]
pub struct PassDescriptor {
pub name: String,
pub debug_options: Vec<String>
}
#[derive(Debug, Serialize, Deserialize)]
pub struct PassDebugOptionsDescriptor {
pub opts: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
pub enum ExecutionMethod {
Compile,
Interpret,
}
impl Default for ExecutionMethod {
fn default() -> ExecutionMethod {
ExecutionMethod::Interpret
}
}
#[derive(Debug, Default)]
pub struct UnfinishedComputation {
artifacts: Vec<(String, TraceArtifact)>,
pub durations: Vec<time::Duration>,
pub cur_debug_options: Vec<String>,
}
#[derive(Debug)]
pub struct FinishedComputation {
artifacts: Vec<(String, TraceArtifact)>,
durations: Vec<time::Duration>,
text_output: Result<String, String>,
}
impl UnfinishedComputation {
pub fn add_artifact(&mut self, artifact: TraceArtifact) {
self.artifacts.push((artifact.stage_name.clone(), artifact));
}
pub fn finish(self, text_output: Result<String, String>) -> FinishedComputation {
FinishedComputation {
artifacts: self.artifacts,
text_output,
durations: self.durations,
}
}
pub fn output(self, output: Result<String, String>) -> FinishedComputation {
FinishedComputation {
artifacts: self.artifacts,
text_output: output,
durations: self.durations,
}
}
}
impl FinishedComputation {
fn get_timing(&self) -> Option<String> {
if self.durations.len() != 0 {
let mut buf = String::new();
write!(&mut buf, "Timing: ").unwrap();
for duration in self.durations.iter() {
let timing = (duration.as_secs() as f64) + (duration.subsec_nanos() as f64 * 1e-9);
write!(&mut buf, "{}s, ", timing).unwrap()
}
write!(&mut buf, "\n").unwrap();
Some(buf)
} else {
None
}
}
pub fn to_repl(&self) -> String {
let mut buf = String::new();
for (stage, artifact) in self.artifacts.iter() {
let color = artifact.text_color;
let stage = stage.color(color).bold();
let output = artifact.debug_output.color(color);
write!(&mut buf, "{}: {}\n", stage, output).unwrap();
}
match self.get_timing() {
Some(timing) => write!(&mut buf, "{}", timing).unwrap(),
None => ()
}
match self.text_output {
Ok(ref output) => write!(&mut buf, "{}", output).unwrap(),
Err(ref err) => write!(&mut buf, "{} {}", "Error: ".red().bold(), err).unwrap(),
}
buf
}
pub fn to_noninteractive(&self) -> Option<String> {
match self.text_output {
Ok(_) => {
let mut buf = String::new();
for (stage, artifact) in self.artifacts.iter() {
let color = artifact.text_color;
let stage = stage.color(color).bold();
let output = artifact.debug_output.color(color);
write!(&mut buf, "{}: {}\n", stage, output).unwrap();
}
if buf == "" { None } else { Some(buf) }
},
Err(ref s) => Some(format!("{} {}", "Error: ".red().bold(), s))
}
}
}
#[derive(Debug)]
pub struct TraceArtifact {
stage_name: String,
debug_output: String,
text_color: &'static str,
}
impl TraceArtifact {
pub fn new(stage: &str, debug: String) -> TraceArtifact {
let color = match stage {
"parse_trace" | "ast" => "red",
"ast_reducing" => "red",
"tokens" => "green",
"type_check" => "magenta",
_ => "blue",
};
TraceArtifact { stage_name: stage.to_string(), debug_output: debug, text_color: color}
}
pub fn new_parse_trace(trace: Vec<String>) -> TraceArtifact {
let mut output = String::new();
for t in trace {
output.push_str(&t);
output.push_str("\n");
}
TraceArtifact { stage_name: "parse_trace".to_string(), debug_output: output, text_color: "red"}
}
}
use std::collections::HashSet;
pub trait ProgrammingLanguageInterface {
fn execute_pipeline(&mut self, _input: &str, _eval_options: &EvalOptions) -> FinishedComputation {
FinishedComputation { artifacts: vec![], text_output: Err(format!("Execution pipeline not done")), durations: vec![] }
}
fn get_language_name(&self) -> String;
fn get_source_file_suffix(&self) -> String;
fn get_passes(&self) -> Vec<PassDescriptor> {
vec![]
fn run_computation(&mut self, _request: ComputationRequest) -> ComputationResponse {
ComputationResponse {
main_output: Err(format!("Computation pipeline not implemented")),
global_output_stats: GlobalOutputStats::default(),
debug_responses: vec![],
}
}
fn handle_custom_interpreter_directives(&mut self, _commands: &Vec<&str>) -> Option<String> {
None
}
fn custom_interpreter_directives_help(&self) -> String {
format!(">> No custom interpreter directives specified <<")
}
fn get_doc(&self, _commands: &Vec<&str>) -> Option<String> {
None
fn request_meta(&mut self, _request: LangMetaRequest) -> LangMetaResponse {
LangMetaResponse::Custom { kind: format!("not-implemented"), value: format!("") }
}
}
pub struct ComputationRequest<'a> {
pub source: &'a str,
pub debug_requests: HashSet<DebugAsk>,
}
pub struct ComputationResponse {
pub main_output: Result<String, String>,
pub global_output_stats: GlobalOutputStats,
pub debug_responses: Vec<DebugResponse>,
}
#[derive(Default, Debug)]
pub struct GlobalOutputStats {
pub total_duration: time::Duration,
pub stage_durations: Vec<(String, time::Duration)>
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, Deserialize, Serialize)]
pub enum DebugAsk {
Timing,
ByStage { stage_name: String, token: Option<String> },
}
impl DebugAsk {
pub fn is_for_stage(&self, name: &str) -> bool {
match self {
DebugAsk::ByStage { stage_name, .. } if stage_name == name => true,
_ => false
}
}
}
pub struct DebugResponse {
pub ask: DebugAsk,
pub value: String
}
pub enum LangMetaRequest {
StageNames,
Docs {
source: String,
},
Custom {
kind: String,
value: String
},
ImmediateDebug(DebugAsk),
}
pub enum LangMetaResponse {
StageNames(Vec<String>),
Docs {
doc_string: String,
},
Custom {
kind: String,
value: String
},
ImmediateDebug(DebugResponse),
}

View File

@@ -5,98 +5,53 @@ extern crate getopts;
extern crate linefeed;
extern crate itertools;
extern crate colored;
extern crate ncurses;
extern crate cursive;
#[macro_use]
extern crate serde_derive;
extern crate serde_json;
#[macro_use]
extern crate rocket;
extern crate rocket_contrib;
extern crate includedir;
extern crate phf;
use std::collections::HashSet;
use std::path::Path;
use std::fs::File;
use std::io::Read;
use std::process::exit;
use std::default::Default;
mod new_repl;
mod repl;
mod language;
mod webapp;
const VERSION_STRING: &'static str = "0.1.0";
pub use language::{ProgrammingLanguageInterface,
ComputationRequest, ComputationResponse,
LangMetaRequest, LangMetaResponse,
DebugResponse, DebugAsk, GlobalOutputStats};
include!(concat!(env!("OUT_DIR"), "/static.rs"));
const VERSION_STRING: &'static str = "0.1.0";
pub use language::{ProgrammingLanguageInterface, EvalOptions,
ExecutionMethod, TraceArtifact, FinishedComputation, UnfinishedComputation, PassDebugOptionsDescriptor, PassDescriptor};
pub type PLIGenerator = Box<Fn() -> Box<ProgrammingLanguageInterface> + Send + Sync>;
pub fn repl_main(generators: Vec<PLIGenerator>) {
let languages: Vec<Box<ProgrammingLanguageInterface>> = generators.iter().map(|x| x()).collect();
let option_matches = program_options().parse(std::env::args()).unwrap_or_else(|e| {
pub fn start_repl(langs: Vec<Box<dyn ProgrammingLanguageInterface>>) {
let options = command_line_options().parse(std::env::args()).unwrap_or_else(|e| {
println!("{:?}", e);
exit(1);
});
if option_matches.opt_present("list-languages") {
for lang in languages {
println!("{}", lang.get_language_name());
}
exit(1);
}
if option_matches.opt_present("help") {
println!("{}", program_options().usage("Schala metainterpreter"));
if options.opt_present("help") {
println!("{}", command_line_options().usage("Schala metainterpreter"));
exit(0);
}
if option_matches.opt_present("webapp") {
webapp::web_main(generators);
exit(0);
}
let mut options = EvalOptions::default();
let debug_passes = if let Some(opts) = option_matches.opt_str("debug") {
let output: Vec<String> = opts.split_terminator(",").map(|s| s.to_string()).collect();
output
} else {
vec![]
};
let language_names: Vec<String> = languages.iter().map(|lang| {lang.get_language_name()}).collect();
let initial_index: usize =
option_matches.opt_str("lang")
.and_then(|lang| { language_names.iter().position(|x| { x.to_lowercase() == lang.to_lowercase() }) })
.unwrap_or(0);
options.execution_method = match option_matches.opt_str("eval-style") {
Some(ref s) if s == "compile" => ExecutionMethod::Compile,
_ => ExecutionMethod::Interpret,
};
match option_matches.free[..] {
match options.free[..] {
[] | [_] => {
/*
let mut repl = repl::Repl::new(languages, initial_index);
repl.run();
*/
new_repl::run();
let mut repl = repl::Repl::new(langs);
repl.run_repl();
}
[_, ref filename, _..] => {
run_noninteractive(filename, languages, options, debug_passes);
[_, ref filename, ..] => {
run_noninteractive(filename, langs);
}
};
}
fn run_noninteractive(filename: &str, languages: Vec<Box<ProgrammingLanguageInterface>>, mut options: EvalOptions, debug_passes: Vec<String>) {
fn run_noninteractive(filename: &str, languages: Vec<Box<dyn ProgrammingLanguageInterface>>) {
let path = Path::new(filename);
let ext = path.extension().and_then(|e| e.to_str()).unwrap_or_else(|| {
println!("Source file lacks extension");
@@ -110,53 +65,28 @@ fn run_noninteractive(filename: &str, languages: Vec<Box<ProgrammingLanguageInte
let mut source_file = File::open(path).unwrap();
let mut buffer = String::new();
source_file.read_to_string(&mut buffer).unwrap();
for pass in debug_passes.into_iter() {
if let Some(_) = language.get_passes().iter().find(|desc| desc.name == pass) {
options.debug_passes.insert(pass, PassDebugOptionsDescriptor { opts: vec![] });
}
}
let request = ComputationRequest {
source: &buffer,
debug_requests: HashSet::new(),
};
match options.execution_method {
ExecutionMethod::Compile => {
/*
let llvm_bytecode = language.compile(&buffer);
compilation_sequence(llvm_bytecode, filename);
*/
panic!("Not ready to go yet");
},
ExecutionMethod::Interpret => {
let output = language.execute_pipeline(&buffer, &options);
output.to_noninteractive().map(|text| println!("{}", text));
}
}
let response = language.run_computation(request);
match response.main_output {
Ok(s) => println!("{}", s),
Err(s) => println!("{}", s)
};
}
fn program_options() -> getopts::Options {
fn command_line_options() -> getopts::Options {
let mut options = getopts::Options::new();
options.optopt("s",
"eval-style",
"Specify whether to compile (if supported) or interpret the language. If not specified, the default is language-specific",
"[compile|interpret]"
);
options.optflag("",
"list-languages",
"Show a list of all supported languages");
options.optopt("l",
"lang",
"Start up REPL in a language",
"LANGUAGE");
options.optflag("h",
"help",
"Show help text");
options.optflag("w",
"webapp",
"Start up web interpreter");
options.optopt("d",
"debug",
"Debug a stage (l = tokenizer, a = AST, r = parse trace, s = symbol table)",
"[l|a|r|s]");
options
}

View File

@@ -1,11 +0,0 @@
use cursive::Cursive;
use cursive::views::{Dialog, TextView};
pub fn run() {
println!("YOLO");
let mut siv = Cursive::default();
siv.add_layer(Dialog::around(TextView::new("FUCKO"))
.title("YOLO SWAGGGGG")
.button("exit", |s| { s.quit() }));
siv.run();
}

View File

@@ -1,23 +1,34 @@
use super::{Repl, InterpreterDirectiveOutput};
use crate::repl::directive_actions::DirectiveAction;
use colored::*;
/// A CommandTree is either a `Terminal` or a `NonTerminal`. When command parsing reaches the first
/// Terminal, it will use the `DirectiveAction` found there to find an appropriate function to execute,
/// and then execute it with any remaining arguments
#[derive(Clone)]
pub enum CommandTree {
Terminal {
name: String,
children: Vec<CommandTree>,
help_msg: Option<String>,
function: Option<Box<(fn() -> Option<String>)>>,
action: DirectiveAction,
},
NonTerminal {
name: String,
children: Vec<CommandTree>,
help_msg: Option<String>,
function: Option<Box<(fn() -> Option<String>)>>,
action: DirectiveAction,
},
Top(Vec<CommandTree>),
}
impl CommandTree {
pub fn term(s: &str, help: Option<&str>) -> CommandTree {
CommandTree::Terminal {name: s.to_string(), help_msg: help.map(|x| x.to_string()), function: None }
pub fn nonterm_no_further_tab_completions(s: &str, help: Option<&str>) -> CommandTree {
CommandTree::NonTerminal {name: s.to_string(), help_msg: help.map(|x| x.to_string()), children: vec![], action: DirectiveAction::Null }
}
pub fn terminal(s: &str, help: Option<&str>, children: Vec<CommandTree>, action: DirectiveAction) -> CommandTree {
CommandTree::Terminal {name: s.to_string(), help_msg: help.map(|x| x.to_string()), children, action}
}
pub fn nonterm(s: &str, help: Option<&str>, children: Vec<CommandTree>) -> CommandTree {
@@ -25,7 +36,7 @@ impl CommandTree {
name: s.to_string(),
help_msg: help.map(|x| x.to_string()),
children,
function: None,
action: DirectiveAction::Null
}
}
@@ -38,16 +49,51 @@ 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(_) => ""
}
}
pub fn get_children(&self) -> Vec<&str> {
pub fn get_children(&self) -> &Vec<CommandTree> {
use CommandTree::*;
match self {
CommandTree::Terminal { .. } => vec![],
CommandTree::NonTerminal { children, .. } => children.iter().map(|x| x.get_cmd()).collect(),
CommandTree::Top(children) => children.iter().map(|x| x.get_cmd()).collect(),
Terminal { children, .. } |
NonTerminal { children, .. } |
Top(children) => children
}
}
pub fn get_subcommands(&self) -> Vec<&str> {
self.get_children().iter().map(|x| x.get_cmd()).collect()
}
pub fn perform(&self, repl: &mut Repl, arguments: &Vec<&str>) -> InterpreterDirectiveOutput {
let mut dir_pointer: &CommandTree = self;
let mut idx = 0;
let res: Result<(DirectiveAction, usize), String> = loop {
match dir_pointer {
CommandTree::Top(subcommands) | CommandTree::NonTerminal { children: subcommands, .. } => {
let next_command = match arguments.get(idx) {
Some(cmd) => cmd,
None => break Err(format!("Command requires arguments"))
};
idx += 1;
match subcommands.iter().find(|sc| sc.get_cmd() == *next_command) {
Some(command_tree) => {
dir_pointer = command_tree;
},
None => break Err(format!("Command {} not found", next_command))
};
},
CommandTree::Terminal { action, .. } => {
break Ok((action.clone(), idx));
},
}
};
match res {
Ok((action, idx)) => action.perform(repl, &arguments[idx..]),
Err(err) => Some(err.red().to_string())
}
}
}

View File

@@ -0,0 +1,133 @@
use super::{Repl, InterpreterDirectiveOutput};
use crate::repl::help::help;
use crate::language::{LangMetaRequest, LangMetaResponse, DebugAsk, DebugResponse};
use itertools::Itertools;
use std::fmt::Write as FmtWrite;
#[derive(Debug, Clone)]
pub enum DirectiveAction {
Null,
Help,
QuitProgram,
ListPasses,
ShowImmediate,
Show,
Hide,
TotalTimeOff,
TotalTimeOn,
StageTimeOff,
StageTimeOn,
Doc,
}
impl DirectiveAction {
pub fn perform(&self, repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput {
use DirectiveAction::*;
match self {
Null => None,
Help => help(repl, arguments),
QuitProgram => {
repl.save_before_exit();
::std::process::exit(0)
},
ListPasses => {
let language_state = repl.get_cur_language_state();
let pass_names = match language_state.request_meta(LangMetaRequest::StageNames) {
LangMetaResponse::StageNames(names) => names,
_ => vec![],
};
let mut buf = String::new();
for pass in pass_names.iter().map(|name| Some(name)).intersperse(None) {
match pass {
Some(pass) => write!(buf, "{}", pass).unwrap(),
None => write!(buf, " -> ").unwrap(),
}
}
Some(buf)
},
ShowImmediate => {
let cur_state = repl.get_cur_language_state();
let stage_name = match arguments.get(0) {
Some(s) => s.to_string(),
None => return Some(format!("Must specify a thing to debug")),
};
let meta = LangMetaRequest::ImmediateDebug(DebugAsk::ByStage { stage_name: stage_name.clone(), token: None });
let meta_response = cur_state.request_meta(meta);
let response = match meta_response {
LangMetaResponse::ImmediateDebug(DebugResponse { ask, value }) => match ask {
DebugAsk::ByStage { stage_name: ref this_stage_name, ..} if *this_stage_name == stage_name => value,
_ => return Some(format!("Wrong debug stage"))
},
_ => return Some(format!("Invalid language meta response")),
};
Some(response)
},
Show => {
let this_stage_name = match arguments.get(0) {
Some(s) => s.to_string(),
None => return Some(format!("Must specify a stage to show")),
};
let token = arguments.get(1).map(|s| s.to_string());
repl.options.debug_asks.retain(|ask| match ask {
DebugAsk::ByStage { stage_name, .. } if *stage_name == this_stage_name => false,
_ => true
});
let ask = DebugAsk::ByStage { stage_name: this_stage_name, token };
repl.options.debug_asks.insert(ask);
None
},
Hide => {
let stage_name_to_remove = match arguments.get(0) {
Some(s) => s.to_string(),
None => return Some(format!("Must specify a stage to hide")),
};
repl.options.debug_asks.retain(|ask| match ask {
DebugAsk::ByStage { stage_name, .. } if *stage_name == stage_name_to_remove => false,
_ => true
});
None
},
TotalTimeOff => total_time_off(repl, arguments),
TotalTimeOn => total_time_on(repl, arguments),
StageTimeOff => stage_time_off(repl, arguments),
StageTimeOn => stage_time_on(repl, arguments),
Doc => doc(repl, arguments),
}
}
}
fn total_time_on(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
repl.options.show_total_time = true;
None
}
fn total_time_off(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
repl.options.show_total_time = false;
None
}
fn stage_time_on(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
repl.options.show_stage_times = true;
None
}
fn stage_time_off(repl: &mut Repl, _: &[&str]) -> InterpreterDirectiveOutput {
repl.options.show_stage_times = false;
None
}
fn doc(repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput {
arguments.get(0).map(|cmd| {
let source = cmd.to_string();
let meta = LangMetaRequest::Docs { source };
let cur_state = repl.get_cur_language_state();
match cur_state.request_meta(meta) {
LangMetaResponse::Docs { doc_string } => Some(doc_string),
_ => Some(format!("Invalid doc response"))
}
}).unwrap_or(Some(format!(":docs needs an argument")))
}

View File

@@ -0,0 +1,55 @@
use crate::repl::command_tree::CommandTree;
use crate::repl::directive_actions::DirectiveAction;
pub fn directives_from_pass_names(pass_names: &Vec<String>) -> CommandTree {
let passes_directives: Vec<CommandTree> = pass_names.iter()
.map(|pass_name| {
if pass_name == "parsing" {
CommandTree::nonterm(pass_name, None, vec![
CommandTree::nonterm_no_further_tab_completions("compact", None),
CommandTree::nonterm_no_further_tab_completions("expanded", None),
CommandTree::nonterm_no_further_tab_completions("trace", None),
])
} else {
CommandTree::nonterm_no_further_tab_completions(pass_name, None)
}
})
.collect();
CommandTree::Top(get_list(&passes_directives, true))
}
fn get_list(passes_directives: &Vec<CommandTree>, include_help: bool) -> Vec<CommandTree> {
use DirectiveAction::*;
vec![
CommandTree::terminal("exit", Some("exit the REPL"), vec![], QuitProgram),
CommandTree::terminal("quit", Some("exit the REPL"), vec![], QuitProgram),
CommandTree::terminal("help", Some("Print this help message"), if include_help { get_list(passes_directives, false) } else { vec![] }, Help),
CommandTree::nonterm("debug",
Some("Configure debug information"),
vec![
CommandTree::terminal("list-passes", Some("List all registered compiler passes"), vec![], ListPasses),
CommandTree::terminal("show-immediate", None, passes_directives.clone(), ShowImmediate),
CommandTree::terminal("show", Some("Show debug output for a specific pass"), passes_directives.clone(), Show),
CommandTree::terminal("hide", Some("Hide debug output for a specific pass"), passes_directives.clone(), Hide),
CommandTree::nonterm("total-time", None, vec![
CommandTree::terminal("on", None, vec![], TotalTimeOn),
CommandTree::terminal("off", None, vec![], TotalTimeOff),
]),
CommandTree::nonterm("stage-times", Some("Computation time per-stage"), vec![
CommandTree::terminal("on", None, vec![], StageTimeOn),
CommandTree::terminal("off", None, vec![], StageTimeOff),
])
]
),
CommandTree::nonterm("lang",
Some("switch between languages, or go directly to a langauge by name"),
vec![
CommandTree::nonterm_no_further_tab_completions("next", None),
CommandTree::nonterm_no_further_tab_completions("prev", None),
CommandTree::nonterm("go", None, vec![]),
]
),
CommandTree::terminal("doc", Some("Get language-specific help for an item"), vec![], Doc),
]
}

View File

@@ -0,0 +1,59 @@
use std::fmt::Write as FmtWrite;
use colored::*;
use super::command_tree::CommandTree;
use super::{Repl, InterpreterDirectiveOutput};
pub fn help(repl: &mut Repl, arguments: &[&str]) -> InterpreterDirectiveOutput {
match arguments {
[] => return global_help(repl),
commands => {
let dirs = repl.get_directives();
Some(match get_directive_from_commands(commands, &dirs) {
None => format!("Directive `{}` not found", commands.last().unwrap()),
Some(dir) => {
let mut buf = String::new();
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
}
})
}
}
}
fn get_directive_from_commands<'a>(commands: &[&str], dirs: &'a CommandTree) -> Option<&'a CommandTree> {
let mut directive_list = dirs.get_children();
let mut matched_directive = None;
for cmd in commands {
let found = directive_list.iter().find(|directive| directive.get_cmd() == *cmd);
if let Some(dir) = found {
directive_list = dir.get_children();
}
matched_directive = found;
}
matched_directive
}
fn global_help(repl: &mut Repl) -> InterpreterDirectiveOutput {
let mut buf = String::new();
let sigil = repl.interpreter_directive_sigil;
writeln!(buf, "{} version {}", "Schala REPL".bright_red().bold(), crate::VERSION_STRING).unwrap();
writeln!(buf, "-----------------------").unwrap();
for directive in repl.get_directives().get_children() {
writeln!(buf, "{}{} - {}", sigil, directive.get_cmd(), directive.get_help()).unwrap();
}
let ref lang = repl.get_cur_language_state();
writeln!(buf, "").unwrap();
writeln!(buf, "Language-specific help for {}", lang.get_language_name()).unwrap();
writeln!(buf, "-----------------------").unwrap();
Some(buf)
}

View File

@@ -1,297 +1,193 @@
use std::fmt::Write as FmtWrite;
use std::io::{Read, Write};
use std::fs::File;
use std::sync::Arc;
use std::collections::HashSet;
use crate::language::{ProgrammingLanguageInterface,
ComputationRequest, LangMetaResponse, LangMetaRequest};
use colored::*;
use itertools::Itertools;
use language::{ProgrammingLanguageInterface, EvalOptions,
PassDebugOptionsDescriptor};
mod command_tree;
use self::command_tree::CommandTree;
mod repl_options;
use repl_options::ReplOptions;
mod directive_actions;
mod directives;
use directives::directives_from_pass_names;
mod help;
mod response;
use response::ReplResponse;
const HISTORY_SAVE_FILE: &'static str = ".schala_history";
const OPTIONS_SAVE_FILE: &'static str = ".schala_repl";
type InterpreterDirectiveOutput = Option<String>;
pub struct Repl {
options: EvalOptions,
languages: Vec<Box<ProgrammingLanguageInterface>>,
current_language_index: usize,
interpreter_directive_sigil: char,
pub interpreter_directive_sigil: char,
line_reader: ::linefeed::interface::Interface<::linefeed::terminal::DefaultTerminal>,
language_states: Vec<Box<dyn ProgrammingLanguageInterface>>,
options: ReplOptions,
}
#[derive(Clone)]
enum PromptStyle {
Normal,
Multiline
}
impl Repl {
pub fn new(languages: Vec<Box<ProgrammingLanguageInterface>>, initial_index: usize) -> Repl {
pub fn new(initial_states: Vec<Box<dyn ProgrammingLanguageInterface>>) -> Repl {
use linefeed::Interface;
let current_language_index = if initial_index < languages.len() { initial_index } else { 0 };
let line_reader = Interface::new("schala-repl").unwrap();
let interpreter_directive_sigil = ':';
Repl {
options: Repl::get_options(),
languages,
current_language_index,
interpreter_directive_sigil: ':',
line_reader
interpreter_directive_sigil,
line_reader,
language_states: initial_states,
options: ReplOptions::new(),
}
}
fn get_cur_language(&self) -> &ProgrammingLanguageInterface {
self.languages[self.current_language_index].as_ref()
}
fn get_options() -> EvalOptions {
File::open(OPTIONS_SAVE_FILE)
.and_then(|mut file| {
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
})
.and_then(|contents| {
let options: EvalOptions = ::serde_json::from_str(&contents)?;
Ok(options)
}).unwrap_or(EvalOptions::default())
}
fn save_options(&self) {
let ref options = self.options;
let read = File::create(OPTIONS_SAVE_FILE)
.and_then(|mut file| {
let buf = ::serde_json::to_string(options).unwrap();
file.write_all(buf.as_bytes())
});
if let Err(err) = read {
println!("Error saving {} file {}", OPTIONS_SAVE_FILE, err);
}
}
pub fn run(&mut self) {
use linefeed::ReadResult;
println!("Schala MetaInterpreter version {}", ::VERSION_STRING);
pub fn run_repl(&mut self) {
println!("Schala MetaInterpreter version {}", crate::VERSION_STRING);
println!("Type {}help for help with the REPL", self.interpreter_directive_sigil);
self.line_reader.load_history(HISTORY_SAVE_FILE).unwrap_or(());
loop {
let language_name = self.get_cur_language().get_language_name();
let directives = self.get_directives();
let tab_complete_handler = TabCompleteHandler::new(self.interpreter_directive_sigil, directives);
self.line_reader.set_completer(Arc::new(tab_complete_handler));
let prompt_str = format!("{} >> ", language_name);
self.line_reader.set_prompt(&prompt_str).unwrap();
match self.line_reader.read_line() {
Err(e) => {
println!("Terminal read error: {}", e);
},
Ok(ReadResult::Eof) => break,
Ok(ReadResult::Signal(_)) => break,
Ok(ReadResult::Input(ref input)) => {
self.line_reader.add_history_unique(input.to_string());
let output = match input.chars().nth(0) {
Some(ch) if ch == self.interpreter_directive_sigil => self.handle_interpreter_directive(input),
_ => Some(self.input_handler(input)),
};
if let Some(o) = output {
println!("=> {}", o);
}
}
}
}
self.line_reader.save_history(HISTORY_SAVE_FILE).unwrap_or(());
self.save_options();
self.load_options();
self.handle_repl_loop();
self.save_before_exit();
println!("Exiting...");
}
fn input_handler(&mut self, input: &str) -> String {
let ref mut language = self.languages[self.current_language_index];
let interpreter_output = language.execute_pipeline(input, &self.options);
interpreter_output.to_repl()
fn load_options(&mut self) {
self.line_reader.load_history(HISTORY_SAVE_FILE).unwrap_or(());
match ReplOptions::load_from_file(OPTIONS_SAVE_FILE) {
Ok(options) => {
self.options = options;
},
Err(()) => ()
};
}
fn get_directives(&self) -> CommandTree {
let ref passes = self.get_cur_language().get_passes();
fn handle_repl_loop(&mut self) {
use linefeed::ReadResult::*;
let sigil = self.interpreter_directive_sigil;
let passes_directives: Vec<CommandTree> = passes.iter()
.map(|pass_descriptor| {
let name = &pass_descriptor.name;
if pass_descriptor.debug_options.len() == 0 {
CommandTree::term(name, None)
} else {
let children: Vec<CommandTree> = pass_descriptor.debug_options.iter()
.map(|o| CommandTree::term(o, None)).collect();
CommandTree::NonTerminal {
name: name.clone(),
children,
help_msg: None,
function: None,
'main: loop {
macro_rules! match_or_break {
($line:expr) => {
match $line {
Err(e) => {
println!("readline IO Error: {}", e);
break 'main;
},
Ok(Eof) | Ok(Signal(_)) => break 'main,
Ok(Input(ref input)) => input,
}
}
}).collect();
}
self.update_line_reader();
let line = self.line_reader.read_line();
let input: &str = match_or_break!(line);
CommandTree::Top(vec![
CommandTree::term("exit", Some("exit the REPL")),
CommandTree::term("quit", Some("exit the REPL")),
CommandTree::term("help", Some("Print this help message")),
CommandTree::nonterm("debug",
Some("show or hide pass debug info for a given pass, or display the names of all passes, or turn timing on/off"),
vec![
CommandTree::term("passes", None),
CommandTree::nonterm("show", None, passes_directives.clone()),
CommandTree::nonterm("hide", None, passes_directives.clone()),
CommandTree::nonterm("timing", None, vec![
CommandTree::term("on", None),
CommandTree::term("off", None),
])
]
),
CommandTree::nonterm("lang",
Some("switch between languages, or go directly to a langauge by name"),
vec![
CommandTree::term("next", None),
CommandTree::term("prev", None),
CommandTree::nonterm("go", None, vec![]),
]
),
CommandTree::term("doc", Some("Get language-specific help for an item")),
])
self.line_reader.add_history_unique(input.to_string());
let mut chars = input.chars().peekable();
let repl_responses = match chars.nth(0) {
Some(ch) if ch == sigil => {
if chars.peek() == Some(&'{') {
let mut buf = String::new();
buf.push_str(input.get(2..).unwrap());
'multiline: loop {
self.set_prompt(PromptStyle::Multiline);
let new_line = self.line_reader.read_line();
let new_input = match_or_break!(new_line);
if new_input.starts_with(":}") {
break 'multiline;
} else {
buf.push_str(new_input);
buf.push_str("\n");
}
}
self.handle_input(&buf)
} else {
match self.handle_interpreter_directive(input) {
Some(directive_output) => println!("<> {}", directive_output),
None => (),
}
continue
}
},
_ => self.handle_input(input)
};
for repl_response in repl_responses.iter() {
println!("{}", repl_response);
}
}
}
fn handle_interpreter_directive(&mut self, input: &str) -> Option<String> {
fn update_line_reader(&mut self) {
let tab_complete_handler = TabCompleteHandler::new(self.interpreter_directive_sigil, self.get_directives());
self.line_reader.set_completer(Arc::new(tab_complete_handler)); //TODO fix this here
self.set_prompt(PromptStyle::Normal);
}
fn set_prompt(&mut self, prompt_style: PromptStyle) {
let prompt_str = match prompt_style {
PromptStyle::Normal => ">> ".to_string(),
PromptStyle::Multiline => ">| ".to_string(),
};
self.line_reader.set_prompt(&prompt_str).unwrap();
}
fn save_before_exit(&self) {
self.line_reader.save_history(HISTORY_SAVE_FILE).unwrap_or(());
self.options.save_to_file(OPTIONS_SAVE_FILE);
}
fn handle_interpreter_directive(&mut self, input: &str) -> InterpreterDirectiveOutput {
let mut iter = input.chars();
iter.next();
let commands: Vec<&str> = iter
let arguments: Vec<&str> = iter
.as_str()
.split_whitespace()
.collect();
let initial_cmd: &str = match commands.get(0).clone() {
None => return None,
Some(s) => s
if arguments.len() < 1 {
return None;
}
let directives = self.get_directives();
directives.perform(self, &arguments)
}
fn get_cur_language_state(&mut self) -> &mut Box<dyn ProgrammingLanguageInterface> {
//TODO this is obviously not complete
&mut self.language_states[0]
}
fn handle_input(&mut self, input: &str) -> Vec<ReplResponse> {
let mut debug_requests = HashSet::new();
for ask in self.options.debug_asks.iter() {
debug_requests.insert(ask.clone());
}
let request = ComputationRequest { source: input, debug_requests };
let ref mut language_state = self.get_cur_language_state();
let response = language_state.run_computation(request);
response::handle_computation_response(response, &self.options)
}
fn get_directives(&mut self) -> CommandTree {
let language_state = self.get_cur_language_state();
let pass_names = match language_state.request_meta(LangMetaRequest::StageNames) {
LangMetaResponse::StageNames(names) => names,
_ => vec![],
};
match initial_cmd {
"exit" | "quit" => {
self.save_options();
::std::process::exit(0)
},
"lang" | "language" => match commands.get(1) {
Some(&"show") => {
let mut buf = String::new();
for (i, lang) in self.languages.iter().enumerate() {
write!(buf, "{}{}\n", if i == self.current_language_index { "* "} else { "" }, lang.get_language_name()).unwrap();
}
Some(buf)
},
Some(&"go") => match commands.get(2) {
None => Some(format!("Must specify a language name")),
Some(&desired_name) => {
for (i, _) in self.languages.iter().enumerate() {
let lang_name = self.languages[i].get_language_name();
if lang_name.to_lowercase() == desired_name.to_lowercase() {
self.current_language_index = i;
return Some(format!("Switching to {}", self.languages[self.current_language_index].get_language_name()));
}
}
Some(format!("Language {} not found", desired_name))
}
},
Some(&"next") | Some(&"n") => {
self.current_language_index = (self.current_language_index + 1) % self.languages.len();
Some(format!("Switching to {}", self.languages[self.current_language_index].get_language_name()))
},
Some(&"previous") | Some(&"p") | Some(&"prev") => {
self.current_language_index = if self.current_language_index == 0 { self.languages.len() - 1 } else { self.current_language_index - 1 };
Some(format!("Switching to {}", self.languages[self.current_language_index].get_language_name()))
},
Some(e) => Some(format!("Bad `lang(uage)` argument: {}", e)),
None => Some(format!("Valid arguments for `lang(uage)` are `show`, `next`|`n`, `previous`|`prev`|`n`"))
},
"help" => {
let mut buf = String::new();
let ref lang = self.languages[self.current_language_index];
let directives = match self.get_directives() {
CommandTree::Top(children) => children,
_ => panic!("Top-level CommandTree not Top")
};
writeln!(buf, "MetaInterpreter options").unwrap();
writeln!(buf, "-----------------------").unwrap();
for directive in directives {
let trailer = " ";
writeln!(buf, "{}{}- {}", directive.get_cmd(), trailer, directive.get_help()).unwrap();
}
writeln!(buf, "").unwrap();
writeln!(buf, "Language-specific help for {}", lang.get_language_name()).unwrap();
writeln!(buf, "-----------------------").unwrap();
writeln!(buf, "{}", lang.custom_interpreter_directives_help()).unwrap();
Some(buf)
},
"debug" => self.handle_debug(commands),
"doc" => self.languages[self.current_language_index]
.get_doc(&commands)
.or(Some(format!("No docs implemented"))),
e => {
self.languages[self.current_language_index]
.handle_custom_interpreter_directives(&commands)
.or(Some(format!("Unknown command: {}", e)))
}
}
}
fn handle_debug(&mut self, commands: Vec<&str>) -> Option<String> {
let passes = self.get_cur_language().get_passes();
match commands.get(1) {
Some(&"timing") => match commands.get(2) {
Some(&"on") => { self.options.debug_timing = true; None }
Some(&"off") => { self.options.debug_timing = false; None }
_ => return Some(format!(r#"Argument to "timing" must be "on" or "off""#)),
},
Some(&"passes") => Some(
passes.into_iter()
.map(|desc| {
if self.options.debug_passes.contains_key(&desc.name) {
let color = "green";
format!("*{}", desc.name.color(color))
} else {
desc.name
}
})
.intersperse(format!(" -> "))
.collect()),
b @ Some(&"show") | b @ Some(&"hide") => {
let show = b == Some(&"show");
let debug_pass: String = match commands.get(2) {
Some(s) => s.to_string(),
None => return Some(format!("Must specify a stage to debug")),
};
let pass_opt = commands.get(3);
if let Some(desc) = passes.iter().find(|desc| desc.name == debug_pass) {
let mut opts = vec![];
if let Some(opt) = pass_opt {
opts.push(opt.to_string());
}
let msg = format!("{} debug for pass {}", if show { "Enabling" } else { "Disabling" }, debug_pass);
if show {
self.options.debug_passes.insert(desc.name.clone(), PassDebugOptionsDescriptor { opts });
} else {
self.options.debug_passes.remove(&desc.name);
}
Some(msg)
} else {
Some(format!("Couldn't find stage: {}", debug_pass))
}
},
_ => Some(format!("Unknown debug command"))
}
directives_from_pass_names(&pass_names)
}
}
struct TabCompleteHandler {
sigil: char,
top_level_commands: CommandTree,
@@ -313,43 +209,43 @@ impl<T: Terminal> Completer<T> for TabCompleteHandler {
fn complete(&self, word: &str, prompter: &::linefeed::prompter::Prompter<T>, start: usize, _end: usize) -> Option<Vec<Completion>> {
let line = prompter.buffer();
if line.starts_with(&format!("{}", self.sigil)) {
let mut words = line[1..(if start == 0 { 1 } else { start })].split_whitespace();
let mut completions = Vec::new();
let mut command_tree: Option<&CommandTree> = Some(&self.top_level_commands);
if !line.starts_with(self.sigil) {
return None;
}
loop {
match words.next() {
None => {
let top = match command_tree {
Some(CommandTree::Top(_)) => true,
_ => false
};
let word = if top { word.get(1..).unwrap() } else { word };
for cmd in command_tree.map(|x| x.get_children()).unwrap_or(vec![]).into_iter() {
if cmd.starts_with(word) {
completions.push(Completion {
completion: format!("{}{}", if top { ":" } else { "" }, cmd),
display: Some(cmd.to_string()),
suffix: ::linefeed::complete::Suffix::Some(' ')
})
}
let mut words = line[1..(if start == 0 { 1 } else { start })].split_whitespace();
let mut completions = Vec::new();
let mut command_tree: Option<&CommandTree> = Some(&self.top_level_commands);
loop {
match words.next() {
None => {
let top = match command_tree {
Some(CommandTree::Top(_)) => true,
_ => false
};
let word = if top { word.get(1..).unwrap() } else { word };
for cmd in command_tree.map(|x| x.get_subcommands()).unwrap_or(vec![]).into_iter() {
if cmd.starts_with(word) {
completions.push(Completion {
completion: format!("{}{}", if top { ":" } else { "" }, cmd),
display: Some(cmd.to_string()),
suffix: ::linefeed::complete::Suffix::Some(' ')
})
}
break;
},
Some(s) => {
let new_ptr: Option<&CommandTree> = command_tree.and_then(|cm| match cm {
CommandTree::Top(children) => children.iter().find(|c| c.get_cmd() == s),
CommandTree::NonTerminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
CommandTree::Terminal { .. } => None,
});
command_tree = new_ptr;
}
break;
},
Some(s) => {
let new_ptr: Option<&CommandTree> = command_tree.and_then(|cm| match cm {
CommandTree::Top(children) => children.iter().find(|c| c.get_cmd() == s),
CommandTree::NonTerminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
CommandTree::Terminal { children, .. } => children.iter().find(|c| c.get_cmd() == s),
});
command_tree = new_ptr;
}
}
Some(completions)
} else {
None
}
Some(completions)
}
}

View File

@@ -0,0 +1,47 @@
use crate::language::DebugAsk;
use std::io::{Read, Write};
use std::collections::HashSet;
use std::fs::File;
#[derive(Serialize, Deserialize)]
pub struct ReplOptions {
pub debug_asks: HashSet<DebugAsk>,
pub show_total_time: bool,
pub show_stage_times: bool,
}
impl ReplOptions {
pub fn new() -> ReplOptions {
ReplOptions {
debug_asks: HashSet::new(),
show_total_time: true,
show_stage_times: false,
}
}
pub fn save_to_file(&self, filename: &str) {
let res = File::create(filename)
.and_then(|mut file| {
let buf = crate::serde_json::to_string(self).unwrap();
file.write_all(buf.as_bytes())
});
if let Err(err) = res {
println!("Error saving {} file {}", filename, err);
}
}
pub fn load_from_file(filename: &str) -> Result<ReplOptions, ()> {
File::open(filename)
.and_then(|mut file| {
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
})
.and_then(|contents| {
let output: ReplOptions = crate::serde_json::from_str(&contents)?;
Ok(output)
})
.map_err(|_| ())
}
}

View File

@@ -0,0 +1,67 @@
use colored::*;
use std::fmt;
use std::fmt::Write;
use super::ReplOptions;
use crate::language::{ DebugAsk, ComputationResponse};
pub struct ReplResponse {
label: Option<String>,
text: String,
color: Option<Color>
}
impl fmt::Display for ReplResponse {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut buf = String::new();
if let Some(ref label) = self.label {
write!(buf, "({})", label).unwrap();
}
write!(buf, "=> {}", self.text).unwrap();
write!(f, "{}", match self.color {
Some(c) => buf.color(c),
None => buf.normal()
})
}
}
pub fn handle_computation_response(response: ComputationResponse, options: &ReplOptions) -> Vec<ReplResponse> {
let mut responses = vec![];
if options.show_total_time {
responses.push(ReplResponse {
label: Some("Total time".to_string()),
text: format!("{:?}", response.global_output_stats.total_duration),
color: None,
});
}
if options.show_stage_times {
responses.push(ReplResponse {
label: Some("Stage times".to_string()),
text: format!("{:?}", response.global_output_stats.stage_durations),
color: None,
});
}
for debug_resp in response.debug_responses {
let stage_name = match debug_resp.ask {
DebugAsk::ByStage { stage_name, .. } => stage_name,
_ => continue,
};
responses.push(ReplResponse {
label: Some(stage_name.to_string()),
text: debug_resp.value,
color: Some(Color::Red),
});
}
responses.push(match response.main_output {
Ok(s) => ReplResponse { label: None, text: s, color: None },
Err(e) => ReplResponse { label: Some("Error".to_string()), text: e, color: Some(Color::Red) },
});
responses
}

View File

@@ -1,44 +0,0 @@
use rocket;
use rocket::State;
use rocket::response::Content;
use rocket::http::ContentType;
use rocket_contrib::json::Json;
use language::{ProgrammingLanguageInterface, EvalOptions};
use WEBFILES;
use ::PLIGenerator;
#[get("/")]
fn index() -> Content<String> {
let path = "static/index.html";
let html_contents = String::from_utf8(WEBFILES.get(path).unwrap().into_owned()).unwrap();
Content(ContentType::HTML, html_contents)
}
#[get("/bundle.js")]
fn js_bundle() -> Content<String> {
let path = "static/bundle.js";
let js_contents = String::from_utf8(WEBFILES.get(path).unwrap().into_owned()).unwrap();
Content(ContentType::JavaScript, js_contents)
}
#[derive(Debug, Serialize, Deserialize)]
struct Input {
source: String,
}
#[derive(Serialize, Deserialize)]
struct Output {
text: String,
}
#[post("/input", format = "application/json", data = "<input>")]
fn interpreter_input(input: Json<Input>, generators: State<Vec<PLIGenerator>>) -> Json<Output> {
let schala_gen = generators.get(0).unwrap();
let mut schala: Box<ProgrammingLanguageInterface> = schala_gen();
let code_output = schala.execute_pipeline(&input.source, &EvalOptions::default());
Json(Output { text: code_output.to_repl() })
}
pub fn web_main(language_generators: Vec<PLIGenerator>) {
rocket::ignite().manage(language_generators).mount("/", routes![index, js_bundle, interpreter_input]).launch();
}

View File

@@ -1,20 +1,15 @@
extern crate schala_repl;
extern crate maaru_lang;
extern crate rukka_lang;
extern crate robo_lang;
//extern crate maaru_lang;
//extern crate rukka_lang;
//extern crate robo_lang;
extern crate schala_lang;
use schala_repl::{PLIGenerator, repl_main};
use schala_repl::{ProgrammingLanguageInterface, start_repl};
extern { }
fn main() {
let generators: Vec<PLIGenerator> = vec![
Box::new(|| { Box::new(schala_lang::Schala::new())}),
Box::new(|| { Box::new(maaru_lang::Maaru::new())}),
Box::new(|| { Box::new(robo_lang::Robo::new())}),
Box::new(|| { Box::new(rukka_lang::Rukka::new())}),
];
repl_main(generators);
let langs: Vec<Box<dyn ProgrammingLanguageInterface>> = vec![Box::new(schala_lang::Schala::new())];
start_repl(langs);
}