571 Commits
main ... v0.1.0

Author SHA1 Message Date
Sasha Koshka
89311688a4 Inline string literal since #46 has been remedied 2024-03-01 00:17:39 -05:00
Sasha Koshka
a4ddab7bb4 TestLiteralReference is now an error test 2024-03-01 00:13:32 -05:00
Sasha Koshka
4aa0a0f304 Fixed analyzer.isLocationExpression 2024-03-01 00:12:21 -05:00
Sasha Koshka
71a1f0a253 Add test case for #46 2024-03-01 00:04:15 -05:00
bd2172b732 Merge pull request 'generator-multi-unit-tests' (#51) from generator-multi-unit-tests into main
Reviewed-on: #51
2024-03-01 05:00:07 +00:00
Sasha Koshka
e2cb3820cf Add some multiunit tests from the analyzer to the generator 2024-02-29 23:59:15 -05:00
Sasha Koshka
eac7d492d6 Change name of generator multiunit test to match analyzer 2024-02-29 23:52:09 -05:00
Sasha Koshka
acf8046114 Putting complete covering of bit casts on hold for now 2024-02-28 20:08:54 -05:00
Sasha Koshka
69c4294522 Covered more cases with bitcast 2024-02-28 20:08:43 -05:00
Sasha Koshka
7acc9c5ab0 Add more test cases for bitcasting pointers 2024-02-28 19:49:35 -05:00
Sasha Koshka
f92008046b Merge branch 'main' into generator-multi-unit-tests 2024-02-28 19:42:11 -05:00
f7b590d823 Merge pull request 'analyzer-fix-bitcast' (#50) from analyzer-fix-bitcast into main
Reviewed-on: #50
2024-02-29 00:34:41 +00:00
Sasha Koshka
c2cdb86b12 The buge (#47) is vanquished!!!! 2024-02-28 19:30:33 -05:00
Sasha Koshka
7225ef7dc3 Expressions now have HasExplicitType() 2024-02-28 18:31:46 -05:00
Sasha Koshka
e24d22a24f Add test case to analyzer for #47 2024-02-28 18:03:15 -05:00
Sasha Koshka
f3e9f33dd3 Completed out TestUnitWriterInterface 2024-02-28 17:43:17 -05:00
Sasha Koshka
aa82eb190a Renamed TestPtrIntCast to TestPtrCast, will put more things in it 2024-02-28 17:36:11 -05:00
Sasha Koshka
35479e68a8 Add and fix test case for #47 (on the generator side at least) 2024-02-28 13:36:21 -05:00
Sasha Koshka
40c5d10d4b Generator uses proper type owner UUID during interface conversion 2024-02-28 12:30:38 -05:00
Sasha Koshka
68be34ddfe Generator multiunit tests print what is being analyzed 2024-02-28 11:50:41 -05:00
ba58259eaf Fix example unit UUID 2024-02-27 19:50:55 +00:00
e2a61e9506 Minor grammar fixes 2024-02-27 19:41:19 +00:00
0395df9944 Fix ordering of dirs 2024-02-27 19:32:23 +00:00
774f36724d Correct dir paths 2024-02-27 19:30:54 +00:00
Sasha Koshka
1c2186e9fc Add problematic multiunit test case from #49 2024-02-27 03:06:54 -05:00
f21828794e Merge pull request 'test-compiled-code' (#48) from test-compiled-code into main
Reviewed-on: #48
2024-02-27 07:50:17 +00:00
Sasha Koshka
5e740ebf5f Compiler has bug() to wrap unexpected errors 2024-02-27 02:45:32 -05:00
Sasha Koshka
d5bb1be894 Add some interface tests to compiler 2024-02-27 02:36:35 -05:00
Sasha Koshka
0c39d0a1cb Compiler debug output makes more sense 2024-02-27 02:33:35 -05:00
Sasha Koshka
dda8cea996 Generator's "errNotFound" is more useful when it bubbles to the surface 2024-02-27 02:29:52 -05:00
Sasha Koshka
2c3bc9acce Fix analyzer.Tree.methodExists and clear raw maps
This was too urgent not to continue without
2024-02-27 02:24:03 -05:00
Sasha Koshka
60c49a1d9a Add test for printing argv
Disabled for now due to #47
2024-02-27 00:22:02 -05:00
Sasha Koshka
d92aa7c3a9 Add test case for counting cli args 2024-02-27 00:09:01 -05:00
Sasha Koshka
73f70304ed Temporarily circumvent crash in analyzer #46 2024-02-27 00:00:33 -05:00
Sasha Koshka
2b9c99fff2 Added a test case for depending on compiled units 2024-02-26 23:59:47 -05:00
Sasha Koshka
4c26410274 Added a test case for depending on compiled units 2024-02-26 23:50:14 -05:00
Sasha Koshka
cc2b1315a0 Tests can now compile dependencies first 2024-02-26 23:32:40 -05:00
Sasha Koshka
785a77b21b Fixed test case file for systeminclude 2024-02-26 14:40:03 -05:00
Sasha Koshka
cc3315280a Resolver no longer requires system units to be files (????????) 2024-02-26 14:39:44 -05:00
Sasha Koshka
ad828ee01e Added test case for grabbing a module from /usr/include/fspl 2024-02-26 14:33:31 -05:00
Sasha Koshka
179c0e00c2 Fix hello world test 2024-02-26 14:24:47 -05:00
Sasha Koshka
c1d574b60f Compiler test now uses testcommon.CompareHex 2024-02-26 14:22:51 -05:00
Sasha Koshka
6bef8aea76 Add testcommon.CompareHex 2024-02-26 14:22:33 -05:00
Sasha Koshka
818b8058e4 testcommon.LogColumns has a configurable width 2024-02-26 14:18:09 -05:00
Sasha Koshka
e5144fdf6a Split compiler_test.go into common file and test file 2024-02-26 14:04:37 -05:00
Sasha Koshka
5c8afd52af Added test case for exit code 2024-02-26 14:03:10 -05:00
Sasha Koshka
e87717ac21 Compiler test now holds output from compiler until end 2024-02-26 13:56:37 -05:00
Sasha Koshka
9459f6192b Compiler uses a default optimization level when unconfigured 2024-02-26 13:46:54 -05:00
Sasha Koshka
c65a307f4a Add the hello world test case 2024-02-26 13:46:30 -05:00
Sasha Koshka
da434f00b6 Add a test-data directory for compiler 2024-02-26 13:46:10 -05:00
Sasha Koshka
f42b76d5c1 Compiler now passes relatvive paths to fs.FS as it should 2024-02-26 12:50:49 -05:00
Sasha Koshka
33995288cc Much of the compiler now uses fs.FS instead of os 2024-02-26 11:46:59 -05:00
Sasha Koshka
a158f26120 Moved everything that should touch "os" to compiler.go 2024-02-26 11:36:16 -05:00
Sasha Koshka
515eb172a5 Moved Compiler.CompileUnit into another file
Need to be sure it is the only thing that can access OS files
2024-02-26 11:35:03 -05:00
Sasha Koshka
e55e332e17 Fixed doc for fsplc and fsplmod 2024-02-23 03:20:51 -05:00
Sasha Koshka
2fc272fccf Add a tool to manage modules 2024-02-23 01:41:36 -05:00
Sasha Koshka
7ad4bec332 Lexer skips over zero runes now 2024-02-23 01:08:58 -05:00
Sasha Koshka
0bbeeda319 Metadata.String quotes UUID 2024-02-23 00:24:09 -05:00
Sasha Koshka
3db6aa211e entity.Metadata now has String() 2024-02-23 00:18:46 -05:00
Sasha Koshka
a0f29f36f5 Cli now handles printing stuff 2024-02-22 21:24:07 -05:00
Sasha Koshka
779c50e961 Cli now displays usage information for subcommands correctly 2024-02-22 20:25:37 -05:00
Sasha Koshka
7e98950574 Update fsplc to use new cli features 2024-02-22 20:17:38 -05:00
Sasha Koshka
ea20d1d022 Update fsplc doc 2024-02-22 20:17:23 -05:00
Sasha Koshka
2a622a1db3 Add more functionality to cli package 2024-02-22 20:12:41 -05:00
Sasha Koshka
623bcabb84 Changed repository import paths 2024-02-22 19:22:53 -05:00
Sasha Koshka
7b9b45e5ac Changed go.mod version to 1.19 2024-02-21 13:49:46 -05:00
Sasha Koshka
4c6ff81c18 Reflected those changes in unit design doc 2024-02-21 13:46:26 -05:00
Sasha Koshka
7f31fc1547 Module search paths are more useful now 2024-02-21 13:45:51 -05:00
Sasha Koshka
a580d5d796 Merge branch 'implement-modules' 2024-02-21 13:27:35 -05:00
7f4e23fb71 Merge branch 'main' into implement-modules 2024-02-21 18:26:44 +00:00
Sasha Koshka
7d530772bf Updated compiler to use Resolver 2024-02-21 13:21:58 -05:00
Sasha Koshka
24a7995819 Add a "Resolver" that resolves unit addresses 2024-02-21 13:21:41 -05:00
Sasha Koshka
1ba6afef23 Compiler understands compiling to . 2024-02-20 01:48:18 -05:00
Sasha Koshka
bfee5ecc92 Compiler now only parses module files that end in .fspl 2024-02-20 01:39:07 -05:00
Sasha Koshka
c49e963310 cli package now prints out usage correctly 2024-02-20 01:27:26 -05:00
Sasha Koshka
6f429571ff Compiler is a bit more adaptable now 2024-02-20 01:23:00 -05:00
Sasha Koshka
3ef8ecca66 Oops haha, print statement. 2024-02-20 00:52:30 -05:00
Sasha Koshka
2cedeb9c0d The compiler now compiles 2024-02-20 00:52:23 -05:00
Sasha Koshka
12bf91d010 Updated generator tests 2024-02-19 23:05:30 -05:00
Sasha Koshka
49b6264ba4 Fixed generator map sorting 2024-02-19 21:57:21 -05:00
Sasha Koshka
c4873915bb Types are given proper link names in the generator 2024-02-19 21:09:21 -05:00
Sasha Koshka
66ee370f23 Generator now has module support 2024-02-19 21:00:20 -05:00
Sasha Koshka
34c6fcbd57 Documented how methods are named 2024-02-19 12:09:21 -05:00
Sasha Koshka
b6edaa0814 entity.Key now has an optional method field 2024-02-19 12:05:13 -05:00
Sasha Koshka
33b6414647 Move Key to Entity 2024-02-19 11:41:36 -05:00
Sasha Koshka
abb326bffc Fixed wording issue in design/units.md 2024-02-19 11:18:17 -05:00
Sasha Koshka
74c6d93151 Analyzer does not allow operations on restricted types 2024-02-17 00:19:13 -05:00
Sasha Koshka
dd09b86cb8 Analyzer does not allow literals to be assigned to restricted types 2024-02-17 00:12:32 -05:00
Sasha Koshka
f5bae2c231 Analyzer does not allow value casting of restricted types 2024-02-16 23:52:52 -05:00
Sasha Koshka
f0d42f996c Analyzer does not allow subscripting, slicing of restricted types 2024-02-16 23:10:38 -05:00
Sasha Koshka
a9522b5c20 Add more multi-unit test cases 2024-02-16 22:50:29 -05:00
Sasha Koshka
3039bdbaf7 Analyzer does not allow marking functions/methods as restricted 2024-02-16 22:31:38 -05:00
Sasha Koshka
6ea10c3783 Analyzer does not allow member access on restricted types 2024-02-16 22:26:22 -05:00
Sasha Koshka
ac56be4468 Analyzer does not allow calling behaviors of restricted iface 2024-02-16 22:19:47 -05:00
Sasha Koshka
e0cb31a85b Analyzer no longer thinks String is private 2024-02-16 22:13:30 -05:00
Sasha Koshka
ef2a2ab059 Analyzer now fills out this information 2024-02-16 13:51:11 -05:00
Sasha Koshka
1c6e179f0a Entity now stores unit and access information for types 2024-02-16 13:30:15 -05:00
Sasha Koshka
3ef9f404b1 Should also make sure ~ only applies to types 2024-02-16 13:09:48 -05:00
Sasha Koshka
e38c185042 Add more test cases for restricted typedefs in units 2024-02-16 13:06:40 -05:00
Sasha Koshka
745e79157c When analyzing a typedef, update the unit information first 2024-02-16 12:54:34 -05:00
Sasha Koshka
f6eeb011bf This is mega silly 2024-02-16 12:49:47 -05:00
Sasha Koshka
95db63b1b8 Pass TestUnitPrivateMethod Err 2024-02-16 12:48:25 -05:00
Sasha Koshka
d2cb1f78c4 Fix behavior of Tree.analyzeMethodOrBehavior() 2024-02-16 12:43:42 -05:00
Sasha Koshka
6dc958742b Real quick add unicode test to parser 2024-02-15 12:43:09 -05:00
Sasha Koshka
0684410631 Ok so maybe that one commit wasn't entirely true 2024-02-15 01:12:51 -05:00
Sasha Koshka
e2367b26c2 Analyzer checks access permissions when using named types 2024-02-15 01:08:21 -05:00
Sasha Koshka
8019a11826 Parser properly unions position of named types 2024-02-15 01:07:25 -05:00
Sasha Koshka
9178e55a20 Parser does *not* actually discard private toplevels while skimming
And it shouldn't! Because it doesn't, the analyzer can say "this is
private" instead of "this doesn't exist".
2024-02-15 01:06:21 -05:00
Sasha Koshka
78fa0f703d Add some initial analyzer tests for units 2024-02-14 23:20:01 -05:00
Sasha Koshka
afe3683c2a I ohasdhjklashf dslkfj ksdl 2024-02-14 23:10:43 -05:00
Sasha Koshka
699ef84bef Forgot about the nickname map haha 2024-02-14 22:48:55 -05:00
Sasha Koshka
75d89e9c2a Should be able to write tests for units now.
Haha unit tests. Surely this won't get confusing.
2024-02-14 22:39:08 -05:00
Sasha Koshka
49493fe7d0 Improved analyzer testing infrastructure 2024-02-14 22:06:10 -05:00
Sasha Koshka
7a4b9850df Parser now actually skims files 2024-02-14 18:49:59 -05:00
Sasha Koshka
5940d47ad7 Improved the skim test case 2024-02-14 17:40:37 -05:00
Sasha Koshka
736f117fac Add parser test case for skimming 2024-02-14 17:35:44 -05:00
Sasha Koshka
ee4dfc3e4a All analyzer tests pass 2024-02-14 17:24:00 -05:00
Sasha Koshka
7420fac2bd I mispelled stream 2024-02-14 13:49:06 -05:00
Sasha Koshka
bc5f435ea6 Made analyzer error testing 20% cooler 2024-02-14 13:48:23 -05:00
Sasha Koshka
06b1cc0cb6 Use stram0.fspl so I don't have to rewrite every test 2024-02-14 13:41:24 -05:00
Sasha Koshka
953e9336db Analyzer does not crash 2024-02-14 13:40:26 -05:00
Sasha Koshka
04a94a2dc9 Analyzer compiles 2024-02-14 13:35:50 -05:00
Sasha Koshka
ab1f25785b Add section about uniqueness and UUIDs to units design doc 2024-02-14 13:00:11 -05:00
Sasha Koshka
940346eecf Begin implemeting my better idea in the analyzer 2024-02-14 10:38:03 -05:00
Sasha Koshka
832e9232aa Made wording in entity even better 2024-02-14 10:22:33 -05:00
Sasha Koshka
2985d13131 Add a file with my better idea in it 2024-02-14 10:22:08 -05:00
Sasha Koshka
f30e6d839d So I have a better idea 2024-02-14 10:06:20 -05:00
Sasha Koshka
b27d5fe006 treeParser.lookupUnit returns the current unit UUID on empty string 2024-02-14 02:04:28 -05:00
Sasha Koshka
98789cf779 Parser and entity now refer to units as units instead of modules 2024-02-14 02:01:01 -05:00
Sasha Koshka
8ad421b1e1 Parser now translates names to unit UUIDs 2024-02-14 01:55:51 -05:00
Sasha Koshka
794d44787d Entity now deals in UUIDs 2024-02-14 01:48:47 -05:00
Sasha Koshka
c69091898a Parser now has something of a concept of modules 2024-02-14 01:44:25 -05:00
Sasha Koshka
89500ec5ef Add module names to top level entities 2024-02-13 21:36:08 -05:00
Sasha Koshka
1636fa0172 Mini-analysis of metadata in Compiler.ParseModule 2024-02-13 21:24:13 -05:00
Sasha Koshka
a46939b3f8 Break compiler out into its own package 2024-02-13 21:01:13 -05:00
Sasha Koshka
87fa994f27 Break out ParseModule and ParseSourceFile from CompileUnit 2024-02-13 20:55:59 -05:00
Sasha Koshka
a4b60a5eff Metadata is now an entity 2024-02-13 20:53:15 -05:00
Sasha Koshka
a9d470d46a Metadata is now an entity 2024-02-13 20:52:25 -05:00
Sasha Koshka
50cbf25c1e Compiler CLI can drive internals 2024-02-13 19:15:40 -05:00
Sasha Koshka
0bcd41c873 Module compilation stub in compiler command 2024-02-13 19:10:46 -05:00
Sasha Koshka
a240e26776 Add method of entity.Address to generate a nickname 2024-02-13 19:10:24 -05:00
Sasha Koshka
81bef17d6f Add some utility methods to entity.Address 2024-02-13 17:29:42 -05:00
Sasha Koshka
fb374c5420 Auto-nicknaming should happen at the analysis stage 2024-02-13 17:08:48 -05:00
Sasha Koshka
87d5ca4cee Add test cases for metadata parser 2024-02-13 16:33:15 -05:00
Sasha Koshka
a837577217 Add metaParser test-common.go 2024-02-13 16:32:59 -05:00
Sasha Koshka
211a6d358a Implemented metadata file parsing 2024-02-13 16:22:10 -05:00
Sasha Koshka
9598890aa4 Changed wording in fsplParser.treeParser.bug() 2024-02-13 16:21:50 -05:00
Sasha Koshka
b4adef7a1c Nickname is no longer a type 2024-02-13 16:20:45 -05:00
Sasha Koshka
24961269f2 Flesh out meta.Tree and meta.treeParser with more methods 2024-02-13 16:03:22 -05:00
Sasha Koshka
c68b3ab4c1 Add Directive interface 2024-02-13 16:03:07 -05:00
bda19ccdcf Minor readme fixes 2024-02-13 19:58:28 +00:00
f06ca0250a Add information (or lack thereof) of learning the language 2024-02-13 19:57:22 +00:00
b184056a9d Update roadmap 2024-02-13 19:40:51 +00:00
f3df9ce15a License under GPLv3 2024-02-13 19:16:11 +00:00
Sasha Koshka
364141ad0d Add metadata entities 2024-02-13 13:29:30 -05:00
Sasha Koshka
27947f7ca4 Updated compiler command 2024-02-13 13:16:03 -05:00
Sasha Koshka
6bc5cc8766 I blame debian for this 2024-02-13 13:14:45 -05:00
Sasha Koshka
b6bd81a244 Updated generator 2024-02-13 13:12:53 -05:00
Sasha Koshka
ce6ca856ee Updated analyzer 2024-02-13 13:12:45 -05:00
Sasha Koshka
6bb5846427 Improve doc comments for Parser 2024-02-13 13:07:37 -05:00
Sasha Koshka
548c8c4de1 Separated parser into two packages 2024-02-13 13:03:22 -05:00
Sasha Koshka
7093e65aed Extracted re-usable parsing utilities from parser.parser 2024-02-13 12:31:52 -05:00
Sasha Koshka
76d71cd1e8 Asajkdhaskldj 2024-02-13 00:50:30 -05:00
Sasha Koshka
64e5adcf41 Rename module design doc to unit design doc 2024-02-13 00:27:51 -05:00
Sasha Koshka
4385b4cf75 Add future work section 2024-02-13 00:10:14 -05:00
Sasha Koshka
c76711cd02 Revised module design, cleared up wording 2024-02-13 00:01:39 -05:00
Sasha Koshka
66d618a6fc Add design document for modules 2024-02-12 23:37:04 -05:00
2c127c5170 Merge pull request 'fix-nil-void-type' (#36) from fix-nil-void-type into main
Reviewed-on: sashakoshka/fspl#36
2024-02-12 19:49:57 +00:00
Sasha Koshka
83b58c1ef6 Analyzer makes use of entity.FormatType 2024-02-12 14:47:49 -05:00
Sasha Koshka
f77f341c3d Add test cases for formatting nil type as Void 2024-02-12 14:47:09 -05:00
Sasha Koshka
3662bda3a8 Add entity.FormatType() 2024-02-12 14:34:49 -05:00
f2f522645f Merge pull request 'polish-compiler' (#35) from polish-compiler into main
Reviewed-on: sashakoshka/fspl#35
2024-02-11 18:06:08 +00:00
Sasha Koshka
eb7a34115b Compiler now takes in a format override parameter 2024-02-11 12:55:43 -05:00
Sasha Koshka
58a15d3da0 Add cli.NewValueSet() to validate against several values 2024-02-11 12:38:46 -05:00
Sasha Koshka
928bf64d9d Fix some issues with cli related to argument values 2024-02-11 12:37:08 -05:00
Sasha Koshka
385657a5c1 Better documentation for cli package 2024-02-11 12:32:53 -05:00
Sasha Koshka
f6b25c8543 Broke cli into a separate package 2024-02-11 12:13:49 -05:00
Sasha Koshka
f90364697c Compiler now parses CLI argument values correctly 2024-02-11 03:40:47 -05:00
Sasha Koshka
8bf97af763 Compiler prints a special error if output path has no extension 2024-02-11 03:40:00 -05:00
Sasha Koshka
20ed736823 errors.Format does not crash when formatting normal error 2024-02-11 03:35:27 -05:00
Sasha Koshka
a68633f061 Created a new argument parsing system 2024-02-11 03:33:16 -05:00
Sasha Koshka
ac16322860 Break fsplc Compiler type into a different file 2024-02-11 01:55:07 -05:00
e8b8139b20 Merge pull request 'fix-chained-methods' (#32) from fix-chained-methods into main
Reviewed-on: sashakoshka/fspl#32
2024-02-11 02:35:37 +00:00
Sasha Koshka
fce7595126 New test for generator confirmed to work, filled out 2024-02-10 21:31:10 -05:00
Sasha Koshka
6bb61b294b testcommon last column no longer has trailing spaces 2024-02-10 21:24:09 -05:00
Sasha Koshka
5026576878 Add same test as stub to generator 2024-02-10 21:20:24 -05:00
Sasha Koshka
6aa75b80c5 Fixed crash in analyzer that caused #31 2024-02-10 21:16:22 -05:00
Sasha Koshka
4425823404 Test case code was wrong, methods need to return Number 2024-02-10 21:15:56 -05:00
Sasha Koshka
6e4494e7ba Analyzer test-common now formats errors properly 2024-02-10 21:14:30 -05:00
Sasha Koshka
cbbccea64a Add #31 as test case 2024-02-10 21:10:49 -05:00
Sasha Koshka
547b625a3f Add .editorconfig 2024-02-10 19:03:38 -05:00
Sasha Koshka
813a95bc8c OOPS 2024-02-10 18:57:53 -05:00
Sasha Koshka
2c6623e763 Unexported parser 2024-02-10 18:56:25 -05:00
8c6b7a96af Merge pull request 'document-packages' (#30) from document-packages into main
Reviewed-on: sashakoshka/fspl#30
2024-02-10 23:52:17 +00:00
Sasha Koshka
9ca1483f82 Updated spec a little 2024-02-10 18:49:10 -05:00
Sasha Koshka
c78deebcc6 Fix small issues with generator readme 2024-02-10 18:32:35 -05:00
Sasha Koshka
9d48bcb868 Add generator README.md 2024-02-10 18:26:51 -05:00
Sasha Koshka
803923b3d2 Bring over better doc on assignment modes to the source file 2024-02-10 14:50:43 -05:00
Sasha Koshka
bb77db4c80 Add analyzer README.md 2024-02-10 14:47:47 -05:00
Sasha Koshka
5f804c71da HHHHHHHHH 2024-02-09 22:36:21 -05:00
Sasha Koshka
341bbb9a6e God 2024-02-09 20:23:22 -05:00
Sasha Koshka
877b6d669a Fix mistake in parser readme 2024-02-09 20:22:05 -05:00
Sasha Koshka
a230c5d77b Add parser README.md 2024-02-09 20:18:21 -05:00
Sasha Koshka
2dacebefae DESIGN.md -> README.md
Want this to show up in git web interfaces
2024-02-09 18:02:03 -05:00
Sasha Koshka
1804cbbd98 I need to sleep 2024-02-09 04:44:09 -05:00
Sasha Koshka
2e9f5ee11e Enhanced lexer/DESIGN.md 2024-02-09 04:42:03 -05:00
Sasha Koshka
6e85c6ae19 Add DESIGN.md to lexer 2024-02-09 04:37:39 -05:00
d10eb4e41e Merge pull request 'dont-create-lexer' (#27) from dont-create-lexer into main
Reviewed-on: sashakoshka/fspl#27
2024-02-09 09:14:29 +00:00
Sasha Koshka
194617dd34 Update fsplc command 2024-02-09 04:05:44 -05:00
Sasha Koshka
5d09926d30 Updated generator's test-common 2024-02-09 04:02:55 -05:00
Sasha Koshka
a8a74ec31f Updated analyzer's test-common 2024-02-09 04:01:41 -05:00
Sasha Koshka
7d2831109c Udpated parser's test-common 2024-02-09 03:59:00 -05:00
Sasha Koshka
727ccf3660 Remedy #25 2024-02-09 03:48:44 -05:00
Sasha Koshka
ce66fc879a Change parser constructor, tree's parsing methods
Now accept lexers instead of io.Readers
2024-02-09 03:44:29 -05:00
5e3ef3f57c Merge pull request 'document-packages' (#26) from document-packages into main
Reviewed-on: sashakoshka/fspl#26
2024-02-09 08:39:46 +00:00
Sasha Koshka
361c4f7289 Remove llvm/README.md
The information in that file is now a doc comment
2024-02-09 03:37:10 -05:00
Sasha Koshka
d59703aa5d Add package level doc comment to llvm 2024-02-09 03:35:42 -05:00
Sasha Koshka
229c88b58a Add module level doc comment to analyzer 2024-02-09 03:30:24 -05:00
Sasha Koshka
503b692104 Add documentation for fsplc command 2024-02-09 03:22:36 -05:00
Sasha Koshka
9bb10ef00d Completed doc comments in entity 2024-02-09 01:11:24 -05:00
Sasha Koshka
097d7dfbe3 Add module level doc comment to testcommon 2024-02-09 01:03:02 -05:00
Sasha Koshka
206a9804dd Add doc comments to integer 2024-02-09 01:00:54 -05:00
Sasha Koshka
cd419cb12d Added module-level doc comments to generator and generator/native 2024-02-09 00:57:06 -05:00
Sasha Koshka
3cd512cefb Complete parser doc comments 2024-02-09 00:44:26 -05:00
Sasha Koshka
862e6b1e25 Note for later 2024-02-09 00:44:18 -05:00
Sasha Koshka
5c2df7f837 Added extensive doc comments to errors package 2024-02-08 23:42:04 -05:00
Sasha Koshka
b6c7a0482a Complete doc comments in lexer 2024-02-08 23:20:07 -05:00
Sasha Koshka
678f1f15ab Removed lexer.Symbols 2024-02-08 23:06:58 -05:00
Sasha Koshka
2f4522cc6c Added module overview doc comment to lexer 2024-02-08 23:05:36 -05:00
Sasha Koshka
567c59559f Compiler outputs formatted errors 2024-02-08 17:01:38 -05:00
8ff47a3076 Merge pull request 'error-interface' (#23) from error-interface into main
Reviewed-on: sashakoshka/fspl#23
2024-02-08 21:55:47 +00:00
Sasha Koshka
fbcc1f14d2 Brought analyzer up to date 2024-02-08 16:53:15 -05:00
Sasha Koshka
93061188e8 Brought parser up to date 2024-02-08 16:51:48 -05:00
Sasha Koshka
df9b96eefc Brought lexer up to date 2024-02-08 16:50:42 -05:00
Sasha Koshka
85788fca25 Line and column numbers are entirely handled by errors.Format 2024-02-08 16:48:52 -05:00
Sasha Koshka
d2a4d785f6 errors.Error and errors.Position are in two different files 2024-02-08 16:24:46 -05:00
Sasha Koshka
29012110f8 errors.Error is now an interface, implementation is hidden 2024-02-08 16:23:05 -05:00
Sasha Koshka
e60a6e246f Unexport generator.resultMode 2024-02-08 16:06:00 -05:00
4089765633 Merge pull request 'remove-participle' (#10) from remove-participle into main
Reviewed-on: sashakoshka/fspl#10
2024-02-08 18:34:18 +00:00
993416d3d8 Merge pull request 'remove-participle-refit-generator' (#9) from remove-participle-refit-generator into remove-participle
Reviewed-on: sashakoshka/fspl#9
2024-02-08 18:33:37 +00:00
Sasha Koshka
24f1044b8f Fixed commaList 2024-02-08 13:32:15 -05:00
Sasha Koshka
ee90077f90 Fixed all generator tests 2024-02-08 13:32:04 -05:00
Sasha Koshka
0a87781445 Generator compiles and runs 2024-02-08 13:14:21 -05:00
Sasha Koshka
da4d21a87b Go mod tidy. No more participle, officially! 2024-02-08 13:12:42 -05:00
16317a9eb8 Merge pull request 'remove-particple-refit-analyzer' (#8) from remove-particple-refit-analyzer into remove-participle
Reviewed-on: sashakoshka/fspl#8
2024-02-08 18:08:31 +00:00
Sasha Koshka
807732b460 Parser now accepts multiplication operations 2024-02-08 13:05:47 -05:00
Sasha Koshka
5fa787dc64 Fix TestTypeInterfaceBehaviorUniqueErr 2024-02-08 12:57:20 -05:00
Sasha Koshka
775671442b Signature positions are stored properly 2024-02-08 12:54:31 -05:00
Sasha Koshka
498df4fad8 Parser now includes positional information in toplevels 2024-02-08 12:52:47 -05:00
Sasha Koshka
19ba849e4b Bring analyzer tests up to date as much as possible 2024-02-08 12:49:17 -05:00
Sasha Koshka
ba52a2df2e Change interface prefix from ? to ~
? will eventually be used in place of * to denote an optional
pointer, and ~ already means "approximation", which works well for
a polymorphic data structure.
2024-02-08 04:10:11 -05:00
Sasha Koshka
cb7180fa1c Remove rouge println in parser 2024-02-08 04:00:25 -05:00
Sasha Koshka
c9fa465430 Off by one error oh my god 2024-02-08 03:58:29 -05:00
Sasha Koshka
9e815d0bea Updated analyzer's test-common.go 2024-02-08 03:56:09 -05:00
Sasha Koshka
5bb9508a76 Rework analyzer code to fit new parser 2024-02-08 03:51:21 -05:00
090d52b8f8 Merge pull request 'parser-remove-participle' (#7) from parser-remove-participle into remove-participle
Reviewed-on: sashakoshka/fspl#7
2024-02-08 08:29:23 +00:00
Sasha Koshka
95e12d6d19 Parse namespaced function calls 2024-02-08 03:28:09 -05:00
Sasha Koshka
202fd1f0bc Bring most tests up to date 2024-02-08 03:13:40 -05:00
Sasha Koshka
ef7be1f7e8 Parse member access, assignment, method calls 2024-02-08 03:10:02 -05:00
Sasha Koshka
68b144a2df Loll oops 2024-02-08 02:22:57 -05:00
Sasha Koshka
be314a1b79 Unify entity.Statement and entity.Expression 2024-02-08 02:22:19 -05:00
Sasha Koshka
95118a183b Relaxed unneeded rules on AST 2024-02-08 02:19:00 -05:00
Sasha Koshka
227a329538 Parse bit and value casts 2024-02-08 01:54:29 -05:00
Sasha Koshka
e1ae32278f Parse reference operations 2024-02-08 01:37:36 -05:00
Sasha Koshka
335060ce84 Parse length operations 2024-02-08 01:33:55 -05:00
Sasha Koshka
99d4799883 Parse slice operations 2024-02-08 01:29:16 -05:00
Sasha Koshka
de1f873929 Struct literals print correctly 2024-02-08 00:56:43 -05:00
Sasha Koshka
b715dbcce3 Parse operations 2024-02-08 00:50:54 -05:00
Sasha Koshka
e9348ea58f Added string constructor for entity.Operator 2024-02-08 00:29:24 -05:00
Sasha Koshka
5fad8a79dd Re-organize the position of parsing functions in expression.go 2024-02-08 00:16:39 -05:00
Sasha Koshka
a0a155c7e6 Parse dereferences and subscripts 2024-02-08 00:14:52 -05:00
Sasha Koshka
1cdc14a313 Lowercased entity descriptions 2024-02-07 22:58:58 -05:00
Sasha Koshka
e4c8268e18 Moved parser expression decision tree diagram to expression.go
Because having ASCII flowcharts in comments makes any code base at
least 20% cooler.
2024-02-07 22:53:10 -05:00
Sasha Koshka
1bf47a9ebe Parse function calls 2024-02-07 22:49:11 -05:00
Sasha Koshka
78786f4ea2 Parse break and return expressions 2024-02-07 22:33:33 -05:00
Sasha Koshka
116a687099 Decision tree infrastructure for LBracket branch 2024-02-07 20:03:18 -05:00
Sasha Koshka
e0c62e40bf Parse struct literals 2024-02-07 19:48:01 -05:00
Sasha Koshka
1a4bade4dd Parse blocks 2024-02-07 19:24:57 -05:00
Sasha Koshka
b17ffdc6d9 Update expression-decision-tree 2024-02-07 19:17:25 -05:00
Sasha Koshka
30d479565b Fix parsing integer 0 2024-02-07 19:16:05 -05:00
Sasha Koshka
f997254270 Parse array literals 2024-02-07 19:14:39 -05:00
Sasha Koshka
2882a28621 Update expression-decision-tree 2024-02-07 18:27:10 -05:00
Sasha Koshka
ac83753c56 Parse if/else expressions 2024-02-07 18:25:42 -05:00
Sasha Koshka
be35cda999 Parse loops 2024-02-07 18:11:12 -05:00
Sasha Koshka
161ee28e7b Move literal parsing routines to a separate file 2024-02-07 18:01:14 -05:00
Sasha Koshka
c1df3f5ae5 Add stars in expression-decision-tree to mark what isn't done 2024-02-07 17:57:39 -05:00
Sasha Koshka
8cd0871939 Fix expression parsing control flow 2024-02-07 17:51:44 -05:00
Sasha Koshka
ea8d5e6b3a Add parsing boolean and nil literals 2024-02-07 17:25:00 -05:00
Sasha Koshka
87c6490183 Fix commaList 2024-02-07 17:09:35 -05:00
Sasha Koshka
4a3e06362d What crack was I smoking when I made LiteralNil.Value 2024-02-07 15:46:42 -05:00
Sasha Koshka
ed239aabb9 Remove entity.Boolean because I have overcome my haters 2024-02-07 15:41:48 -05:00
Sasha Koshka
854abfed1c Broke out Parser.parseVariable() for consistency 2024-02-07 15:36:33 -05:00
Sasha Koshka
d445743200 Add parsing int, float, and string literals 2024-02-07 14:15:06 -05:00
Sasha Koshka
a47023fc95 Declaration and variable expression parsing 2024-02-07 13:26:25 -05:00
Sasha Koshka
16f27cd2ee Parser panic information is now more detailed 2024-02-07 13:01:25 -05:00
Sasha Koshka
cc8426bf85 Add tokens to startTokensExpression 2024-02-07 12:50:19 -05:00
Sasha Koshka
531d3ca22b Created an expression decision tree plan 2024-02-07 12:44:54 -05:00
Sasha Koshka
51ef3682f1 Function headings are now parsed correctly 2024-02-07 11:38:15 -05:00
Sasha Koshka
0762d72486 Allow arbitrary integer width 2024-02-07 11:22:37 -05:00
Sasha Koshka
02ec77b855 Updated TestType test case for parser 2024-02-07 03:39:37 -05:00
Sasha Koshka
97605d8c2a Parse integer, word, and float types 2024-02-07 03:37:02 -05:00
Sasha Koshka
424b621bae Fix function parsing 2024-02-07 03:22:00 -05:00
Sasha Koshka
48fe20b472 Parse interface types 2024-02-07 03:18:02 -05:00
Sasha Koshka
1529e591e8 Parse signatures 2024-02-07 03:17:50 -05:00
Sasha Koshka
19910b77a9 Parse struct types 2024-02-07 03:02:43 -05:00
Sasha Koshka
f8399612f6 De-ambiguify interface and struct type syntax 2024-02-07 03:02:16 -05:00
Sasha Koshka
cb7734afe6 Array type parsing 2024-02-07 00:32:18 -05:00
Sasha Koshka
23362b058d Normalize parsing of access control symbols 2024-02-07 00:23:10 -05:00
Sasha Koshka
59e3185500 Parse pointers/slices 2024-02-07 00:22:58 -05:00
Sasha Koshka
6ad56ed4ee Add some convenience methods to parser, fix appendCopy 2024-02-07 00:21:44 -05:00
Sasha Koshka
8adaa0e053 Added parsing named types 2024-02-06 23:48:25 -05:00
Sasha Koshka
9dab29f5cb Token.Is and Token.ValueIs work properly 2024-02-06 23:16:48 -05:00
Sasha Koshka
39d8ed088d Lexer now records the width of tokens 2024-02-06 23:08:25 -05:00
Sasha Koshka
84d88a7644 I want to look at my juicy error formatting even when it passes 2024-02-06 22:33:18 -05:00
Sasha Koshka
100986327e FINALLY errors and lexer agree on row/col positions properly 2024-02-06 22:11:46 -05:00
Sasha Koshka
6924d39dcb Fixed error test cases 2024-02-06 21:59:41 -05:00
Sasha Koshka
6fc16c7045 Lexer saves error lines 2024-02-06 21:54:20 -05:00
Sasha Koshka
e6043effe0 Lexer tests check for line content, print formatted errors 2024-02-06 19:08:21 -05:00
Sasha Koshka
f3f8b324dc Parser errors make more sense 2024-02-06 18:58:09 -05:00
Sasha Koshka
c2d9b4d40f Added Token.String() 2024-02-06 18:56:04 -05:00
Sasha Koshka
ef14fc2219 Parser is incomplete but compiles 2024-02-06 18:29:37 -05:00
Sasha Koshka
825e71a720 Created a plan for expression parsing 2024-02-06 17:04:59 -05:00
Sasha Koshka
77783c1004 Add a guide to writing parser methods so I don't forget 2024-02-06 16:56:10 -05:00
Sasha Koshka
c1c2ab5124 Further normalize expectation responsibilities of parsing methods 2024-02-06 16:39:15 -05:00
Sasha Koshka
a09a0ce34c Typedef parsing complete, without detail 2024-02-06 16:32:14 -05:00
Sasha Koshka
76c129f91c Add type parsing stub 2024-02-06 16:31:53 -05:00
Sasha Koshka
f4e5e1ea3d Appended -Core to parsing methods that only partially parse things 2024-02-06 16:30:53 -05:00
Sasha Koshka
bca9de4106 Parsing functions *always* begin on current token, leave trailing one 2024-02-06 16:22:49 -05:00
Sasha Koshka
1b50c8f879 Method and function parsing complete, without detail 2024-02-06 16:13:05 -05:00
Sasha Koshka
3c67cf576c Expression parsing stub 2024-02-06 16:12:51 -05:00
Sasha Koshka
79f51ce4c6 Parser.expectValue() and Parser.expectValueDesc() take in a kind parameter now 2024-02-06 15:57:27 -05:00
Sasha Koshka
3e89953d07 Add parser.Tree.AddDeclaration() 2024-02-06 15:54:21 -05:00
Sasha Koshka
08f1027834 Added commaList function 2024-02-06 12:20:13 -05:00
Sasha Koshka
118e7672bd Why did I name a prepend function appendr?? 2024-02-06 11:45:08 -05:00
Sasha Koshka
4a2671c601 askl;d;sldkf 2024-02-06 02:55:39 -05:00
Sasha Koshka
ac433f07ec Rename typeStartTokens to startTokensType 2024-02-06 02:41:45 -05:00
Sasha Koshka
3d1bef3a32 Move top-level expect from Parser.parseTopLevel into Parser.parse() 2024-02-06 02:40:19 -05:00
Sasha Koshka
eafc834ef3 Parser.parseSignature stub 2024-02-06 02:36:51 -05:00
Sasha Koshka
8cfb4f3e6d Parser top-level entity base 2024-02-06 02:32:29 -05:00
Sasha Koshka
cce8c756fc Parser now remembers last token 2024-02-06 02:28:23 -05:00
Sasha Koshka
66f0e2d8d9 Un-participle-ify entity.Access 2024-02-06 02:27:09 -05:00
Sasha Koshka
5fe6e32f92 Add Dot and DoubleDot as separate tokens 2024-02-06 02:23:15 -05:00
Sasha Koshka
7db231a945 Add ValueIs method to lexer.Token 2024-02-06 02:19:09 -05:00
Sasha Koshka
ec3907d841 Add TokenKind.String 2024-02-06 01:52:29 -05:00
Sasha Koshka
b6a920af58 Added expect functions to parser 2024-02-06 01:45:30 -05:00
Sasha Koshka
ff9c275980 Add Token.Is() to check if token is any of a set of kinds 2024-02-06 01:31:52 -05:00
Sasha Koshka
04e5ead3db Go mod tidy 2024-02-06 01:14:38 -05:00
Sasha Koshka
0d79482568 Define basic parser structure 2024-02-06 01:12:07 -05:00
Sasha Koshka
dc6a36b548 Cleared particple code from parser 2024-02-06 00:52:31 -05:00
Sasha Koshka
57d1ab9b2a Parser test-common uses testcommon package 2024-02-06 00:49:28 -05:00
Sasha Koshka
50ec220d08 Remove entity participle struct tags 2024-02-05 15:17:59 -05:00
Sasha Koshka
187ed48c4f Remove entity dependency on participle 2024-02-05 15:16:11 -05:00
031f7008fa Merge pull request 'lexer-remove-participle' (#3) from lexer-remove-participle into remove-participle
Reviewed-on: sashakoshka/fspl#3
2024-02-05 20:06:38 +00:00
Sasha Koshka
d2ecd7a266 Lexer has no dependency on participle now 2024-02-05 15:04:54 -05:00
Sasha Koshka
6bfc073608 Added new Errorf function, added row/column offset 2024-02-05 15:03:13 -05:00
Sasha Koshka
db2b7e5f90 Error package now formats tabs correctly 2024-02-05 11:44:34 -05:00
Sasha Koshka
3e9f3553fe TODO message in errors package 2024-02-01 03:21:23 -05:00
Sasha Koshka
7c29d552f2 Wrote tests for and fixed errors package 2024-02-01 03:20:17 -05:00
Sasha Koshka
e3f5ffa5c5 Added erros package 2024-02-01 02:51:04 -05:00
Sasha Koshka
554fc1f7a7 Put functionality from generator/test-common.go in its own package 2024-02-01 01:59:17 -05:00
Sasha Koshka
b95df4c23c Fix crash when using int literals as floats 2024-01-29 13:29:53 -05:00
Sasha Koshka
8340dbe7c2 Update tests 2024-01-29 03:30:54 -05:00
Sasha Koshka
0d20f1cbc0 Slice length fields are properly stored as Index values 2024-01-29 03:11:43 -05:00
Sasha Koshka
af79419809 Update tests 2024-01-29 02:20:16 -05:00
Sasha Koshka
0c1382896e Updated test cases for literals 2024-01-29 00:30:48 -05:00
Sasha Koshka
1e827a9ea1 Array literals and string literals now exhibit identical behavior
They both fill unspecified data with zeros, and add a null
terminator when directly assigned to a pointer.
2024-01-29 00:25:34 -05:00
Sasha Koshka
06a183c7af String generation now uses correct type names, data length, and nulls 2024-01-28 17:10:03 -05:00
Sasha Koshka
9b80c29a15 Fix tests 2024-01-28 16:44:34 -05:00
Sasha Koshka
044e73efda Add source code printout for tests 2024-01-28 14:46:47 -05:00
Sasha Koshka
8107f82531 Broke up the monolithic TestLiteral 2024-01-28 14:36:04 -05:00
Sasha Koshka
e42c292433 Increased the coverage of TestLiteralArray 2024-01-28 14:15:54 -05:00
Sasha Koshka
c71cc43a47 *Greatly* reduced the amount of excess IR related to string literals 2024-01-28 14:11:41 -05:00
Sasha Koshka
0d4a86ef02 Struct literals now support irDestLoc 2024-01-28 04:50:18 -05:00
Sasha Koshka
82c05039b7 Added the ability for array literals to be undersized 2024-01-28 04:43:01 -05:00
Sasha Koshka
86df537ece Added test for nested array literals
Who knew it would work first try?
2024-01-28 04:24:15 -05:00
Sasha Koshka
f057f3b4a4 Array literals now support irDestLoc 2024-01-28 04:18:33 -05:00
Sasha Koshka
85703f6059 Add more tests for loops 2024-01-28 03:29:49 -05:00
Sasha Koshka
fe85582088 Fixed extra lines after IR blocks 2024-01-28 03:18:39 -05:00
Sasha Koshka
5940e540b3 Fix some issues with control flow, add tests 2024-01-28 03:16:05 -05:00
Sasha Koshka
3c7565861d Add test case for comparisons on defined types 2024-01-28 02:18:18 -05:00
Sasha Koshka
cd6ed93b98 FCmp and ICmp now accept defined types 2024-01-28 00:18:37 -05:00
Sasha Koshka
56ae34c297 Updated all tests except for literal_test.go
You Will See Why
2024-01-27 23:24:19 -05:00
Sasha Koshka
57ef5933ee Well it would help if it generated well formed IR 2024-01-27 23:00:03 -05:00
Sasha Koshka
f35f3e0df0 Assignment coercions are much more IR efficient 2024-01-27 22:45:28 -05:00
Sasha Koshka
773e718592 Complete TestInterfaceInStruct (no longer segfaults somehow) 2024-01-27 19:49:22 -05:00
Sasha Koshka
41c9f57063 More using qualified names in IR 2024-01-27 18:16:30 -05:00
Sasha Koshka
d9c6bd644b Oh my god I swapped the columns 2024-01-27 18:09:51 -05:00
Sasha Koshka
954f6c877e Added cool output to failed tests 2024-01-27 18:07:49 -05:00
Sasha Koshka
05e75cb746 Literal generation now uses qualified type names when possible 2024-01-27 17:16:23 -05:00
Sasha Koshka
3a4a5683b5 Remove redundant val variable/declaration generation 2024-01-27 14:17:52 -05:00
Sasha Koshka
f994c4dc08 Rename some routines in data 2024-01-27 14:03:38 -05:00
15541b8675 Merge pull request 'generator-reduce-redundant-ir' (#2) from generator-reduce-redundant-ir into main
Reviewed-on: sashakoshka/fspl#2
2024-01-27 17:40:37 +00:00
Sasha Koshka
805a9a110f Updated more tests (cutting out extra load insts) 2024-01-27 17:35:33 +00:00
Sasha Koshka
91d4b92e6b Fix more names and orgaization of statement generation 2024-01-27 17:33:15 +00:00
Sasha Koshka
021a289623 Added -Any suffix to appropriate generation routines 2024-01-27 17:24:47 +00:00
Sasha Koshka
5085ea8502 Fixed segfault relating to break and return statements 2024-01-27 17:11:44 +00:00
Sasha Koshka
b13130db8e Introducing ResultMode! time to got to bed. 2024-01-27 10:43:27 +00:00
Sasha Koshka
21fd6a4130 Update some tests 2024-01-27 10:06:17 +00:00
Sasha Koshka
706e2e0286 Updated control flow generation 2024-01-27 10:03:15 +00:00
Sasha Koshka
6d28d52cdf Void functions/methods no longer request a value 2024-01-27 09:22:19 +00:00
Sasha Koshka
fc5dda739e Made generic block generation routine 2024-01-27 09:18:41 +00:00
Sasha Koshka
8bf3938842 Made panic messages in expression-multiplex.go more informative 2024-01-27 09:15:39 +00:00
Sasha Koshka
43130fd0da Add Val onto value expressions 2024-01-27 09:09:27 +00:00
Sasha Koshka
cc5ed3c2ee Update block generation routines 2024-01-27 09:06:17 +00:00
Sasha Koshka
b1799c25e1 Add generateStatement routine 2024-01-27 08:53:29 +00:00
Sasha Koshka
1f653be8cc Renamed generateStatement to generateStatementVal 2024-01-27 08:49:25 +00:00
Sasha Koshka
43aae56b37 Add generateExpression function 2024-01-27 08:48:00 +00:00
Sasha Koshka
f9d53d7ab7 Rename expression source files 2024-01-27 08:40:02 +00:00
Sasha Koshka
fbaec1efdb Rename generateExpression to generateExpressionVal 2024-01-27 08:39:05 +00:00
Sasha Koshka
10fb780e50 Nitpick 2024-01-27 08:26:46 +00:00
Sasha Koshka
6de2a9d37d Change restricted access modifier to tilde (~) 2024-01-27 07:42:13 +00:00
Sasha Koshka
8606c761e8 Same thing for bit casts 2024-01-27 07:05:34 +00:00
Sasha Koshka
b56dceb3e1 Generator now no longer generates ineffectual value casts 2024-01-27 07:03:13 +00:00
Sasha Koshka
7318bfbc88 Added a way to test IR type equality 2024-01-27 06:49:30 +00:00
ca6be75cf0 Merge pull request 'llvm-implement-globals' (#1) from llvm-implement-globals into main
Reviewed-on: sashakoshka/fspl#1
2024-01-27 04:33:39 +00:00
Sasha Koshka
9ac9af8bcb Add IsConstant to Global 2024-01-27 04:16:41 +00:00
Sasha Koshka
ed350f94b8 Add basic globals implementation 2024-01-27 01:49:18 +00:00
Sasha Koshka
709ec56873 Move addrspace to attribute file, will put more things in there 2024-01-27 01:48:29 +00:00
Sasha Koshka
6b9c165d14 Add function to encode global identifier 2024-01-27 01:47:52 +00:00
Sasha Koshka
b0c62642f5 Add more tests to generator 2024-01-27 01:00:07 +00:00
Sasha Koshka
4c66ad4b44 Replace bugs section with caveats in README 2024-01-26 22:11:35 +00:00
Sasha Koshka
35dbbebb8b Updated stuff to use the new routines in data.go 2024-01-26 05:35:47 +00:00
Sasha Koshka
098248da19 More routines in data.go 2024-01-26 05:35:22 +00:00
Sasha Koshka
a80cd600b5 Added a test case for a Writer interface
I really need to cut out a lot of the unnecessary IR that the
generator emits
2024-01-26 04:32:55 +00:00
Sasha Koshka
ea32fcc7d5 Added a test case for casting slices to pointers 2024-01-26 04:21:30 +00:00
Sasha Koshka
d28bfbac16 Fix conversion of slices to pointers 2024-01-26 04:07:58 +00:00
Sasha Koshka
34b9812db4 Same thing as last commit 2024-01-26 04:05:58 +00:00
Sasha Koshka
85a582e349 Add more comments to explain casting rules in analyzer 2024-01-26 00:40:22 +00:00
Sasha Koshka
8e458111b5 Change the location at which defined IR types are wrapped
Defined IR types are wrapped in a TypeDefined by generateTypeNamed.
This causes them to be sent through GEP, which is necessary for
testing.
2024-01-25 22:57:52 +00:00
Sasha Koshka
f6604dcecd Wrote test for getelementptr'ing into chained typedefs 2024-01-25 18:19:34 +00:00
Sasha Koshka
67a0157cbd Use TypeDefined in generator 2024-01-25 18:19:22 +00:00
Sasha Koshka
2a927b209f Added a way to wrap a defined type via TypeDefined
This allows "copies" of a type that refer to a source to be created
and have their names changed/redefined without modifying the source
type.
2024-01-25 18:08:26 +00:00
Sasha Koshka
f1d48d74a2 Add test for chained typedefs with different names 2024-01-25 17:56:20 +00:00
Sasha Koshka
5b5c66d409 I forgot comments existed haha 2024-01-25 08:27:18 +00:00
Sasha Koshka
699cd88000 Nicer testing framework for lexer 2024-01-25 08:27:17 +00:00
Sasha Koshka
54ac39ee01 Lexer now accepts files with no blank line at the end 2024-01-25 08:27:17 +00:00
a0d31fad38 Update README.md 2024-01-25 07:43:21 +00:00
Sasha Koshka
b20dbcebbb Add logo to readme 2024-01-25 07:42:32 +00:00
Sasha Koshka
bb59f8de4a Fix duplicated pointer types 2024-01-25 07:36:51 +00:00
Sasha Koshka
4b28d40b89 AbstractType's TypeName member is now private 2024-01-25 07:05:07 +00:00
Sasha Koshka
9390eb6b2e Added test to debug absurd behavior relating to type defs 2023-12-24 22:49:10 -05:00
70c45f6fe1 Interface method calls no longer cause a segfault 2023-12-24 22:18:47 -05:00
7757133d43 Something was off with TestCompare 2023-12-24 21:45:47 -05:00
7d926be83a Fix code I probably wrote while high off my ass 2023-12-24 21:39:56 -05:00
Sasha Koshka
d4f81b0667 Empty struct literal values are initialized to zero 2023-12-23 17:25:27 -05:00
Sasha Koshka
3cca2dd130 Added nil (zero initializer) literals 2023-12-21 23:33:38 -05:00
Sasha Koshka
eeae7ac1ed Generator can produce link names 2023-12-20 03:05:27 -05:00
Sasha Koshka
af73d8e251 Added module referencing to parser 2023-12-19 15:21:10 -05:00
Sasha Koshka
308efbbd5e Add access specifiers and link names to parser 2023-12-17 11:23:42 -05:00
Sasha Koshka
ae60aa173e Made the type checker a tad more robust 2023-12-15 13:23:37 -05:00
Sasha Koshka
6bc59ca0d1 I forgor 2023-12-15 12:46:33 -05:00
Sasha Koshka
7572e8bf08 Fix comparison predicates 2023-12-15 12:40:43 -05:00
da7807d653 Attempt to further fix interface assignment 2023-12-14 21:21:07 -05:00
b7faa727af Method calling now properly passes pointer instead of value 2023-12-14 01:23:34 -05:00
4a28ee61c0 generateAssignmentSource assigns interface methods to iface dest 2023-12-14 00:47:14 -05:00
62a83f5544 Add some extra notes to readme 2023-12-13 03:44:57 -05:00
c9eccdd86f Oops pt 2 2023-12-13 03:40:40 -05:00
e796f055ad Oops 2023-12-13 03:38:44 -05:00
895e874e2f Compiler can fall back to clang when llc is not present 2023-12-13 03:28:24 -05:00
5cd1be88ce Add logo 2023-12-13 03:20:15 -05:00
Sasha Koshka
20db37b704 Changed my mind about brackets on break and return statements 2023-12-12 19:51:17 -05:00
5dc9331668 Updated language spec 2023-12-08 17:31:16 -05:00
35ecdd7a8b Add README.md 2023-12-08 00:28:58 -05:00
Sasha Koshka
ea934445c6 Add test for aforementioned issue 2023-12-07 17:14:37 -05:00
Sasha Koshka
139a66f239 Fix IsUnsigned 2023-12-07 17:05:26 -05:00
Sasha Koshka
8c64c07fff If statements get a type 2023-12-07 04:16:58 -05:00
Sasha Koshka
019a8732cb Augh 2023-12-07 04:03:28 -05:00
Sasha Koshka
8d1d87dbb8 If/else branching is no longer... like how it was 2023-12-07 03:58:57 -05:00
Sasha Koshka
1176da7cb9 If/else generation will not attempt to overwrite branches 2023-12-07 03:54:01 -05:00
988649098c Operands to IR binary instructions now have identifiers instead of names 2023-12-07 03:04:29 -05:00
e470fb1f5c Generator alloca's variables when they are defined 2023-12-07 02:48:27 -05:00
Sasha Koshka
2353ecd0e1 Fix case where generator would assign ID to void function calls 2023-12-05 22:36:41 -05:00
Sasha Koshka
e32d349e2e Add some more test cases to the generator 2023-12-05 22:29:29 -05:00
Sasha Koshka
8bc00720ac Fix bug with popping block managers off of the stack 2023-12-05 22:27:55 -05:00
Sasha Koshka
058a985d92 Add "internal error" prefix for generator errors 2023-12-05 22:08:06 -05:00
Sasha Koshka
f7a6e905f6 Fix comments 2023-12-05 22:04:12 -05:00
Sasha Koshka
6074b414b5 Add comments 2023-12-05 16:52:33 -05:00
588800e02c Slice operations now load the data pointer 2023-12-05 15:35:17 -05:00
5205f01877 Revert slices back to having just two fields 2023-12-05 03:07:54 -05:00
b0c3839ed9 Fixed several cases where the generator would output invalid IR 2023-12-03 22:23:04 -05:00
a7fe9a592b Generator passes method owner as pointer 2023-12-03 15:38:07 -05:00
ab8d616e2b Fix stale code 2023-12-02 23:42:54 -05:00
b1cd82d4ed Generator generates methods properly 2023-12-02 23:41:35 -05:00
c0997b3e47 Oops 2023-12-02 23:36:12 -05:00
fa69bd2294 Generator now pierces pointers when accessing members as well 2023-12-02 23:35:22 -05:00
5f30623e8f Member access can now pierce through pointers in the analyzer 2023-12-02 23:23:44 -05:00
65ec3c4ad0 Analyzer adds a "this" pointer to methods 2023-12-02 23:06:58 -05:00
3b6aa7fffb String literals can be assigned to strings now 2023-12-02 22:45:22 -05:00
55a86ad64c That should be literally every feature 2023-12-02 21:45:15 -05:00
5539f8722f Created function for making slices 2023-12-02 04:21:10 -05:00
67eb74c66e Ditch brackets around break and return statements 2023-12-01 01:53:33 -05:00
Sasha Koshka
9ccb1490bf Completed operations 2023-12-01 01:18:10 -05:00
Sasha Koshka
ccd3ddd82a Made native target actually work 2023-11-30 17:16:55 -05:00
af4827590e Operation stub part II 2023-11-30 15:44:30 -05:00
1c5a58aa23 Add operation stub 2023-11-30 02:13:47 -05:00
f87a4a4b78 Added casting 2023-11-30 02:06:12 -05:00
c24113ba51 Add length to analyzer and parser 2023-11-30 02:05:32 -05:00
78d4276a57 Added bit casts 2023-11-29 20:37:56 -05:00
a58b058025 Generator properly uses subscript offset 2023-11-29 20:31:40 -05:00
593683156a Generator grabs slice type from proper place 2023-11-29 20:31:07 -05:00
b96d9c6c4c Multi-layer location expression type inference 2023-11-29 20:07:16 -05:00
0b4aef1530 Analyzer does light upward type inference for location expressions 2023-11-29 18:52:27 -05:00
c5a61e15f6 Generator now adds null terminator to c-strings 2023-11-29 00:45:38 -05:00
37819645cd Reduce element type when generating string literal data 2023-11-29 00:21:58 -05:00
0afccbe6b1 Facehoof 2023-11-29 00:13:54 -05:00
Sasha Koshka
ced43a161d Added string literals to generator 2023-11-29 00:01:35 -05:00
Sasha Koshka
c08f5d7706 Add string literals to generator WIP 2023-11-28 01:15:12 -05:00
Sasha Koshka
9808fc0e7c Added string literal to analyzer 2023-11-26 17:27:31 -05:00
905522af56 Added string literal to parser 2023-11-26 05:16:40 -05:00
fddccf2967 Add escape sequences to strings in lexer 2023-11-26 04:56:14 -05:00
aa107072d8 This should have been way more commits 2023-11-26 04:02:22 -05:00
3a751666ba Fixed scope OverVariables stopping immediately 2023-11-26 04:01:55 -05:00
f08a09cee2 fsplc command now infers output type from output file name ext 2023-11-26 04:01:03 -05:00
ae9bace68a Analyzer no longer deletes dereferences 2023-11-26 04:00:44 -05:00
36e70c0c97 LLVM improvements 2023-11-26 04:00:20 -05:00
0a54cbdd63 Typedefs that are never referenced are never generated 2023-11-23 01:44:48 -05:00
8258c4f400 Basic compiler command 2023-11-23 01:26:28 -05:00
94bf21536d Generator returns an LLVM module 2023-11-23 01:26:14 -05:00
15fece8154 Various fixes 2023-11-23 00:02:00 -05:00
a2d4252428 Migrated generator to use new llvm code generation package 2023-11-22 20:37:37 -05:00
0efc4d209e Add custom LLVM code generation package 2023-11-22 20:37:16 -05:00
0331b486bf Generator WIP 2023-11-21 15:04:22 -05:00
ff9fdfeab0 Slight modifications to analyzer 2023-11-21 15:04:01 -05:00
Sasha Koshka
05e298583e Functions/behaviors stub 2023-11-16 22:41:20 -05:00
Sasha Koshka
652a6c313b Add CLI stub 2023-11-16 22:40:57 -05:00
Sasha Koshka
729af04963 Add type generator 2023-11-16 22:04:56 -05:00
13b18c0ce0 Generator stub II 2023-11-16 15:52:28 -05:00
3f96704723 Generator stub 2023-11-14 02:49:55 -05:00
5e4d2a32e4 Pass all analyzer tests 2023-11-04 17:59:04 -04:00
9706e844d6 Pass all but one test 2023-11-04 16:34:40 -04:00
162487490e Fix goofy bugs 2023-10-31 21:52:47 -04:00
b6a7b237ba uhhhh 2023-10-31 15:43:19 -04:00
58513e7d15 Can now assign array directly to slice 2023-10-31 01:46:18 -04:00
1a09e9ba2a Pass all literal assignment tests 2023-10-29 15:18:44 -04:00
b839517e9a Array literal can be used in array and slice 2023-10-29 15:13:25 -04:00
45574d3cd5 Pass a bunch of tests 2023-10-29 15:09:20 -04:00
8ce12b8a71 Removed Void as a concept 2023-10-29 14:47:28 -04:00
7b0d2d50bd Expression analysis actually runs now 2023-10-29 14:40:29 -04:00
65a32c3cdb Fix entity string conversion 2023-10-29 14:34:21 -04:00
e47096c219 Analyze return statement 2023-10-29 14:28:51 -04:00
b884978b87 Analyze break statements 2023-10-29 02:59:45 -04:00
9e85f869d5 Analyze more expressions 2023-10-29 02:18:41 -04:00
7df9517788 Add loop stack management to scopeManager and scopeContextManager 2023-10-29 02:18:03 -04:00
8cb7e2d8b0 Analyze all the literals 2023-10-28 03:24:17 -04:00
8a5d08d7fc Revise spec a bit 2023-10-28 03:24:08 -04:00
24ae32c95a Start analyzing literals 2023-10-27 16:10:31 -04:00
7d8a1cd714 Add package for determining integer sizes 2023-10-27 16:10:05 -04:00
61a04d51a4 More expressions (wip) 2023-10-26 10:43:50 -04:00
fb5d17742e Analysis of value/bit casts 2023-10-25 01:57:02 -04:00
2a5ffb12a1 Location expressions 2023-10-24 21:54:59 -04:00
bec91323b9 Added analysis for numerous types of expressions 2023-10-24 18:29:33 -04:00
5a48415bd5 I forgor 2023-10-24 01:34:58 -04:00
4448d3ccb4 Function call analysis 2023-10-20 13:48:05 -04:00
5d4977828e Added basic equality checks to types 2023-10-19 21:53:19 -04:00
84f76f8ba4 WIP expression analysis type checking 2023-10-19 19:13:55 -04:00
13d281388a New syntax for method calls
This will free up the '::' token for namespace/module access. Exciting!
2023-10-19 17:19:23 -04:00
bed87812d5 Expression analysis stub 2023-10-18 02:29:24 -04:00
7c0e96777b That, but for operation arg count testing 2023-10-18 01:39:43 -04:00
87dcc18a8d Updated analyzer tests to account for lexer changes 2023-10-18 01:33:22 -04:00
c375c18922 Star is now a separate token 2023-10-18 01:14:17 -04:00
f65f9e042c Implement custom lexer FINALLY 2023-10-18 00:48:35 -04:00
6ee91bee08 Finished analyzing method signatures 2023-10-17 03:21:25 -04:00
416c6b1d59 hngnggg i forgor to commit
- implemented scope management
- finished function signature analyzing
- added method analyzing
  - had to restructure type analysis slightly to do this
2023-10-17 03:11:11 -04:00
90796775d3 Booleans are now literals 2023-10-09 00:24:48 -04:00
8cc9b88126 Function analysis stub 2023-10-09 00:13:13 -04:00
4b2158b1f0 Added recursive type definitions 2023-10-08 23:54:45 -04:00
fcf4eba79e All the builtin/primitive types are in their own place 2023-10-07 17:32:47 -04:00
feb0da1172 Testing ALL the types 2023-10-07 02:27:49 -04:00
47fb051caa Add readme 2023-10-07 01:55:33 -04:00
0113536b0d the ansalyzer 2023-10-07 01:18:05 -04:00
Sasha Koshka
160cbd16ad Reorganized tests 2023-10-06 18:58:17 -04:00
160 changed files with 18421 additions and 1338 deletions

12
.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 8
charset = utf-8
[*.md]
indent_style = space
indent_size = 2

674
LICENSE Normal file
View File

@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

119
README.md Normal file
View File

@@ -0,0 +1,119 @@
# FSPL
<img src="assets/fspl.svg" width="128" alt="FSPL logo.">
Freestanding programming language: a high-ish-level language that has absolutely
no need for any runtime support, designed to work well in scenarios where
dragging along a language runtime is either not possible or simply unwanted.
This language is designed for:
- Operating system development
- Embedded software
- Integrating cleanly into existing software
## Design Principles
- Abstractions must happen at compile time unless absolutely necessary
- Compiler must not generate any functions that the user does not write
- Compiler must avoid generating logic that the user does not write
## Installation
You can install the compiler by running:
`go install ./cmd/fsplc`
The `fsplc` program depends on the LLVM IR compiler (`llc`). If it is not found,
it will attempt to use `clang` instead but with some features disabled. Please
ensure either `llc` or `clang` are installed and accessible from your PATH
before using this software.
## Usage
The `fsplc` program may be used as follows:
`fsplc [ARGUMENT(S)...] FILE(S)...`
The program compiles all input files into one output. The output file type is
determined by the filename extension of the output file:
| Extension | Type |
| --------- | --------------- |
| .s | Native assembly |
| .o | Object file |
| .ll | LLVM IR |
If no output file is specified, it will default to an object file with the name
of the first input file.
Object files can be linked into an executable binary using the linker of your
choice, or by using a C compiler such as `clang`:
`clang -o OUTPUT INPUT.o`
Using a C compiler will link the C standard library to your program, which may
be useful for building normal user applications.
## Learning the language
At this time, there is no guided method of learning how to write FSPL code.
However, a good place to start is the `design` directory, which contains a
language specification among other things. The language specification goes into
detail about the syntax and semantics of the language, and assuming some
background in C programming, it should be enough to attain a reasonable grasp
of the language.
## Caveats, Bugs
Note that the compiler is still relatively early in development, and has
numerous bugs. In addition, language features and syntax are not yet set in
stone and may change in the future. Please report any bugs you find to the
[issue tracker](https://git.tebibyte.media/sashakoshka/fspl/issues).
## Roadmap
Late 2023 (These have been implemented):
- Top-level entities
- Type definitions
- Methods
- Defined and external functions
- Type system
- Strict, static, bottom-up type inference
- Pointers
- Arrays
- Slices
- Structs
- Interfaces
- Expressions and control structures
- Literals adapt to types via bottom-up type inference
- Assignment
- Variable declaration
- Variable access
- Function calls
- Method calls
- Interface behavior calls
- Operations
- Casting
- Blocks
- If/else
- Loops
Q1 2024:
- Union types (carry type information)
- Match statements
- Modules
- Mutable/immutable variables
- For/range loops
Q2 2024:
- Basic, non-final standard library routines
- Conditional compilation
- Shared library compilation
- Constants
- Vararg
- FSPL vararg using Slices
- Optional per-function C-style vararg for compatibility
Q3 2024:
- Generics
- Ownership system
- Lightweight, modularized (and of course, totally optional) standard library to replace those written in Q2
At the beginning of Q4 2024, a 1.0 version of the language will be released.

89
analyzer/README.md Normal file
View File

@@ -0,0 +1,89 @@
# analyzer
## Responsibilities
- Define syntax tree type that contains entities
- Turn streams of tokens into abstract syntax tree entities
## Organization
The entry point for all logic defined in this package is the Tree type. On this
type, the Analyze() method is defined. This method checks the semantic
correctness of an AST, fills in semantic fields within its data structures, and
arranges them into the Tree.
Tree contains a scopeContextManager. The job of scopeContextManager is to manage
a stack of scopeContexts, which are each tied to a function or method that is
currently being analyzed. In turn, each scopeContext manages stacks of
entity.Scopes and entity.Loops. This allows for greedy/recursive analysis of
functions and methods.
## Operation
When the analyze method is called, several hidden fields in the Tree are filled
out. Tree.ensure() instantiates data that can persist between analyses, which
consists of map initialization and merging the data in the builtinTypes map into
Tree.Types.
After Tree.ensure completes, Tree.assembleRawMaps() takes top-level entities
from the AST and organizes them into rawTypes, rawFunctions, and rawMethods. It
does this so that top-level entites can be indexed by name. While doing this, it
ensures that function and type names are unique, and method names are unique
within the type they are defined on.
Next, Tree.analyzeDeclarations() is called. This is the entry point for the
actual analysis logic. For each item in the raw top-level entity maps, it calls
a specific analysis routine, which is one of:
- Tree.analyzeTypedef()
- Tree.analyzeFunction()
- Tree.analyzeMethod()
These routines all have two crucial properties that make them very useful:
- They refer to top-level entities by name instead of by memory location
- If the entity has already been analyzed, they return that entity instead of
analyzing it again
Because of this, they are also used as accessors for top level entities within
more specific analysis routines. For example, the routine Tree.analyzeCall()
will call Tree.analyzeFunction() in order to get information about the function
that is being called. If the function has not yet been analyzed, it is analyzed
(making use of scopeContextManager to push a new scopeContext), and other
routines (including Tree.analyzeDeclarations()) will not have to analyze it all
over agian. After a top-level entity has been analyzed, these routines will
always return the same pointer to the one instance of the analyzed entity.
## Expression Analysis and Assignment
Since expressions make up the bulk of FSPL, expression analysis makes up the
bulk of the semantic analyzer. Whenever an expression needs to be analyzed,
Tree.analyzeExpression() is called. This activates a switch to call one of many
specialized analysis routines based on the expression entity's concrete type.
Much of expression analysis consists of the analyze checking to see if the
result of one expression can be assigned to the input of another. To this end,
assignment rules are used. There are five different assignment modes:
- Strict: Structural equivalence, but named types are treated as opaque and are
not tested. This applies to the root of the type, and to types enclosed as
members, elements, etc. This is the assignment mode most often used.
- Weak: Like strict, but the root types specifically are compared as if they
were not named. analyzer.ReduceToBase() is used to accomplish this.
- Structural: Full structural equivalence, and named types are always reduced.
- Coerce: Data of the source type must be convert-able to the destination type.
This is used in value casts.
- Force: All assignment rules are ignored. This is only used in bit casts.
All expression analysis routines take in as a parameter the type that the result
expression is being assigned to, and the assignment mode. To figure out whether
or not they can be assigned, they in turn (usually) call Tree.canAssign().
Tree.canAssign() is used to determine whether data of a source type can be
assigned to a destination type, given an assignment mode. However, it is not
called automatically by Tree.analyzeExpression() because:
- Determining the source type is sometimes non-trivial (see
Tree.analyzeOperation())
- Literals have their own very weak assignment rules, and are designed to be
assignable to a wide range of data types

View File

@@ -1,320 +1,500 @@
package analyzer
import "testing"
import "fmt"
import "math"
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/integer"
func TestAssignLiteralErrUnsignedNegative (test *testing.T) {
testStringErr (test,
"expected unsigned integer", 3, 11,
`
[main] = {
x:UInt = -5
}
`)
type strictness int; const (
// Structural equivalence, but named types are treated as opaque and are
// not tested. This applies to the root of the type, and to types
// enclosed as members, elements, etc. This is the assignment mode most
// often used.
strict strictness = iota
// Like strict, but the root types specifically are compared as if they
// were not named. analyzer.ReduceToBase() is used to accomplish this.
// Assignment to private/restricted types is not allowed.
weak
// Full structural equivalence, and named types are always reduced.
// Assignment to private/restricted types is not allowed.
structural
// Data of the source type must be convert-able to the destination type.
// This is used in value casts. Assignment to private/restricted types
// is not allowed.
coerce
// All assignment rules are ignored. This is only used in bit casts.
force
)
// canAssign takes in an analyzed destination type and an analyzed source type,
// and determines whether source can be assigned to destination.
func (this *Tree) canAssign (
pos errors.Position,
// assuming both are analyzed already
destination entity.Type,
mode strictness,
source entity.Type,
) error {
fail := func () error {
switch mode {
case strict:
return errors.Errorf (
pos, "expected %v",
entity.FormatType(destination))
case weak:
return errors.Errorf (
pos, "cannot use %v as %v",
entity.FormatType(source),
entity.FormatType(destination))
case structural:
return errors.Errorf (
pos, "cannot convert from %v to %v",
entity.FormatType(source),
entity.FormatType(destination))
default:
panic(fmt.Sprint (
"BUG: analyzer doesnt know about mode", mode))
}
}
// check access permissions
if destination != nil && source != nil &&
mode != strict && mode != force {
err := this.typeRestricted(pos, destination)
if err != nil { return err }
err = this.typeRestricted(pos, source)
if err != nil { return err }
}
// short circuit if force is selected
if mode == force { return nil }
// check for coerce strictness
if mode == coerce {
return this.canAssignCoerce(pos, destination, source)
}
// do structural equivalence if structural is selected
if mode == structural {
if this.areStructurallyEquivalent(destination, source) {
return nil
} else {
return fail()
}
}
// first, check if this is interface assignment
if destination, ok := this.isInterface(destination); ok {
return this.canAssignInterface(pos, destination, source)
}
// then, check for array to slice assignment
if destination, ok := ReduceToBase(destination).(*entity.TypeSlice); ok {
if source, ok := ReduceToBase(source). (*entity.TypeArray); ok {
return this.canAssignSliceArray(pos, destination, source)
}}
// if weak, only compare the first base types
if mode == weak {
destination = ReduceToBase(destination)
source = ReduceToBase(source)
}
switch destination := destination.(type) {
// no type
case nil:
return nil
// named type
case *entity.TypeNamed:
if source, ok := source.(*entity.TypeNamed); ok {
if destination.Name == source.Name {
return nil
}
}
// composite base types
case *entity.TypePointer:
if source, ok := source.(*entity.TypePointer); ok {
mode := mode
if mode > structural { mode = structural }
return this.canAssign (
pos, destination.Referenced,
mode, source.Referenced)
}
case *entity.TypeSlice:
if source, ok := source.(*entity.TypeSlice); ok {
mode := mode
if mode > structural { mode = structural }
return this.canAssign (
pos, destination.Element,
mode, source.Element)
}
case *entity.TypeArray:
if source, ok := source.(*entity.TypeArray); ok {
mode := mode
if mode > structural { mode = structural }
return this.canAssign (
pos, destination.Element,
mode, source.Element)
}
// other base type
case *entity.TypeStruct,
*entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
if destination.Equals(source) {
return nil
}
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type", destination))
}
return fail()
}
func TestAssignLiteralErrIntegerFloat (test *testing.T) {
testStringErr (test,
"expected integer", 3, 10,
`
[main] = {
x:Int = 5.5
}
`)
// canAssignCoerce determines if data of an analyzed source type can be
// converted into data of an analyzed destination type.
func (this *Tree) canAssignCoerce (
pos errors.Position,
destination entity.Type,
source entity.Type,
) error {
fail := func () error {
return errors.Errorf (
pos, "cannot convert from %v to %v",
entity.FormatType(source),
entity.FormatType(destination))
}
destination = ReduceToBase(destination)
source = ReduceToBase(source)
switch destination := destination.(type) {
// no type
case nil:
return nil
// base type
case *entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
switch source.(type) {
case *entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
// integers, floats, and words may all be assigned to
// eachother
return nil
default:
return fail()
}
case *entity.TypePointer:
switch source := source.(type) {
case *entity.TypeSlice:
// a slice may be assigned to a pointer if its element
// type can be assigned (structural) to the pointer's
// referenced type
err := this.canAssign (
pos, destination.Referenced,
structural, source.Element)
if err != nil {
return fail()
}
default:
// a pointer may be assigned to another pointer if they
// are structurally equivalent
if !this.areStructurallyEquivalent(destination, source) {
return fail()
}
}
case *entity.TypeSlice,
*entity.TypeArray,
*entity.TypeStruct:
// composite types may be assigned to eachother if they are
// structurally equivalent
if !this.areStructurallyEquivalent(destination, source) {
return fail()
}
default: fail()
}
return nil
}
func TestAssignLiteralErrIntegerStruct (test *testing.T) {
testStringErr (test,
"expected integer", 3, 10,
`
[main] = {
x:Int = (x: 5)
}
`)
// canAssignSliceArray takes in an analyzed slice type and an analyzed array
// type, and determines whether the array can be assigned to the slice.
func (this *Tree) canAssignSliceArray (
pos errors.Position,
destination *entity.TypeSlice,
source *entity.TypeArray,
) error {
err := this.canAssign(pos, destination.Element, strict, source.Element)
if err != nil {
return errors.Errorf (
pos, "expected %v",
entity.FormatType(destination))
} else {
return nil
}
}
func TestAssignLiteralErrIntegerArray (test *testing.T) {
testStringErr (test,
"expected integer", 3, 10,
`
[main] = {
x:Int = (* 5)
}
`)
// canAssignInterface takes in an analyzed interface destination type and an
// analyzed source type, and determines whether source can be assigned to
// destination.
func (this *Tree) canAssignInterface (
pos errors.Position,
destination *entity.TypeInterface,
source entity.Type,
) error {
for name, behavior := range destination.BehaviorMap {
// get method
method, err := this.analyzeMethodOrBehavior (
pos, source, name)
if err != nil { return err }
// extract signature
var signature *entity.Signature
switch method.(type) {
case *entity.Signature:
signature = method.(*entity.Signature)
case *entity.Method:
signature = method.(*entity.Method).Signature
default:
panic(fmt.Sprint (
"Tree.analyzeMethodOrBehavior returned",
method))
}
// check equivalence
if !signature.Equals(behavior) {
return errors.Errorf (
pos, "%v has wrong signature for method %v",
source, name)
}
}
return nil
}
func TestAssignLiteralErrIntegerOverflow (test *testing.T) {
testStringErr (test,
"expected smaller number", 3, 11,
`
[main] = {
x:Byte = 1000
}
`)
// areStructurallyEquivalent tests whether two types are structurally equivalent
// to eachother.
func (this *Tree) areStructurallyEquivalent (left, right entity.Type) bool {
left = ReduceToBase(left)
right = ReduceToBase(right)
switch left.(type) {
case nil: return false
// composite
case *entity.TypePointer:
left := left.(*entity.TypePointer)
right, ok := right.(*entity.TypePointer)
if !ok { return false }
return this.areStructurallyEquivalent(left.Referenced, right.Referenced)
case *entity.TypeSlice:
left := left.(*entity.TypeSlice)
right, ok := right.(*entity.TypeSlice)
if !ok { return false }
return this.areStructurallyEquivalent(left.Element, right.Element)
case *entity.TypeArray:
left := left.(*entity.TypeArray)
right, ok := right.(*entity.TypeArray)
if !ok { return false }
return left.Length == right.Length &&
this.areStructurallyEquivalent(left.Element, right.Element)
case *entity.TypeStruct:
left := left.(*entity.TypeStruct)
right, ok := right.(*entity.TypeStruct)
if !ok { return false }
if len(left.MemberMap) != len(right.MemberMap) { return false }
for name, member := range left.MemberMap {
other, ok := right.MemberMap[name]
if !ok { return false }
if !this.areStructurallyEquivalent(member.Type(), other.Type()) {
return false
}
}
// terminals
case *entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
return left.Equals(right)
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type", left))
}
return false
}
func TestAssignLiteralErrArrayInt (test *testing.T) {
testStringErr (test,
"expected array", 3, 11,
`
[main] = {
x:3:Int = 5
}
`)
// isLocationExpression returns whether or not an expression is a valid location
// expression.
func (this *Tree) isLocationExpression (expression entity.Expression) error {
cannot := func (pos errors.Position, kind string) error {
return errors.Errorf(pos, "cannot assign to %s", kind)
}
switch expression := expression.(type) {
case *entity.Variable:
return nil
case *entity.Declaration:
return nil
case *entity.Call:
return cannot(expression.Position, "function call")
case *entity.MethodCall:
return cannot(expression.Position, "method call")
case *entity.Subscript:
return this.isLocationExpression(expression.Slice)
case *entity.Slice:
return cannot(expression.Position, "slice operation")
case *entity.Dereference:
return this.isLocationExpression(expression.Pointer)
case *entity.Reference:
return cannot(expression.Position, "reference operation")
case *entity.ValueCast:
return cannot(expression.Position, "value cast")
case *entity.BitCast:
return cannot(expression.Position, "bit cast")
case *entity.Operation:
return cannot(expression.Position, fmt.Sprintf (
"cannot assign to %v operation",
expression.Operator))
case *entity.Block:
return cannot(expression.Position, "block")
case *entity.MemberAccess:
return this.isLocationExpression (
expression.Source)
case *entity.IfElse:
return cannot(expression.Position, "if/else")
case *entity.Loop:
return cannot(expression.Position, "loop")
case *entity.Break:
return cannot(expression.Position, "break statement")
case *entity.Return:
return cannot(expression.Position, "return statement")
case *entity.LiteralInt:
return cannot(expression.Position, "integer literal")
case *entity.LiteralFloat:
return cannot(expression.Position, "float literal")
case *entity.LiteralString:
return cannot(expression.Position, "string literal")
case *entity.LiteralArray:
return cannot(expression.Position, "array literal")
case *entity.LiteralStruct:
return cannot(expression.Position, "struct literal")
case *entity.LiteralBoolean:
return cannot(expression.Position, "boolean literal")
case *entity.LiteralNil:
return cannot(expression.Position, "nil literal")
default:
panic(fmt.Sprint (
"BUG: analyzer doesnt know about expression",
expression))
}
}
func TestAssignLiteralErrArrayWrongLength (test *testing.T) {
testStringErr (test,
"expected 3 elements", 3, 11,
`
[main] = {
x:3:Int = (* 1 2 3 4)
}
`)
// ReduceToBase takes in an analyzed type and reduces it to its first non-name
// type.
func ReduceToBase (ty entity.Type) entity.Type {
if namedty, ok := ty.(*entity.TypeNamed); ok {
return ReduceToBase(namedty.Type)
} else {
return ty
}
}
func TestAssignLiteralErrArrayWrongType (test *testing.T) {
testStringErr (test,
"expected integer", 3, 19,
`
[main] = {
x:3:Int = (* 1 2 3.3 4)
}
`)
// isInterface takes in an analyzed type and returns true and an interface if
// that type refers to an interface. If not, it returns nil, false.
func (this *Tree) isInterface (ty entity.Type) (*entity.TypeInterface, bool) {
ty = ReduceToBase(ty)
switch ty.(type) {
case *entity.TypeInterface: return ty.(*entity.TypeInterface), true
default: return nil, false
}
}
func TestAssignLiteralErrSliceWrongType (test *testing.T) {
testStringErr (test,
"expected integer", 3, 19,
`
[main] = {
x:*:Int = (* 1 2 3.3 4)
}
`)
// isNumeric returns whether or not the specified type is a number.
func isNumeric (ty entity.Type) bool {
ty = ReduceToBase(ty)
switch ty.(type) {
case *entity.TypeInt, *entity.TypeFloat, *entity.TypeWord: return true
default: return false
}
}
func TestAssignLiteralErrStructWrongType (test *testing.T) {
testStringErr (test,
"expected integer", 3, 26,
`
[main] = {
x:3:(x:Int y:Int) = (x: 5.5 y: 3)
}
`)
}
func TestAssignLiteralErrStructInteger (test *testing.T) {
testStringErr (test,
"expected struct", 3, 26,
`
[main] = {
x:3:(x:Int y:Int) = 5
}
`)
// isInteger returns whether or not the specified type is an integer.
func isInteger (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypeInt, *entity.TypeWord: return true
default: return false
}
}
func TestAssignLiteralErrInterfaceInt (test *testing.T) {
testStringErr (test,
"cannot assign literal to interface", 6, 2,
`
Bird: ([fly distance:F64] [land])
[main] = {
b:Bird = 5
}
`)
// isOrdered returns whether or not the values of the specified type can be
// ordered.
func isOrdered (ty entity.Type) bool {
return isNumeric(ty)
}
func TestAssignLiteralErrInterfaceFloat (test *testing.T) {
testStringErr (test,
"cannot assign literal to interface", 6, 2,
`
Bird: ([fly distance:F64] [land])
[main] = {
b:Bird = 5.5
}
`)
// isBoolean returns whether or not the specified type is a boolean.
func isBoolean (ty entity.Type) bool {
for {
named, ok := ty.(*entity.TypeNamed)
if !ok { return false }
if named.Name == "Bool" { return true }
}
}
func TestAssignLiteralErrInterfaceArray (test *testing.T) {
testStringErr (test,
"cannot assign literal to interface", 6, 2,
`
Bird: ([fly distance:F64] [land])
[main] = {
b:Bird = (* 1 2 3 4)
}
`)
// isFloat returns whether or not the specified type is a float.
func isFloat (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypeFloat: return true
default: return false
}
}
func TestAssignLiteralErrInterfaceStruct (test *testing.T) {
testStringErr (test,
"cannot assign literal to interface", 6, 2,
`
Bird: ([fly distance:F64] [land])
BlueJay: Int
BlueJay::[land] = { }
[main] = {
b:Bird = (x: 5 y: 6)
}
`)
// IsUnsigned returns whether or not the specified type is an unsigned integer.
func IsUnsigned (ty entity.Type) bool {
switch ty := ReduceToBase(ty).(type) {
case *entity.TypeInt: return !ty.Signed
case *entity.TypeWord: return !ty.Signed
default: return false
}
}
func TestAssignLiteral (test *testing.T) {
testString (test,
`
[main] = {
a:F64 = 5.3
b:F32 = 5.3
c:F64 = -5
d:F32 = -5
e:Byte = 64
z:UInt = 5
x:Int = -5
arr:4:2:Int = (*
(* 1 2)
(* 3 4)
(* 5 6)
(* 7 8))
slice:*:Int = (* 3 1 2 3)
struct:(x:Int y:Int) = (
x: 9
y: 10)
}
`)
}
func TestAssignInterfaceErrBadSignature (test *testing.T) {
testStringErr (test,
"BlueJay has wrong signature for method fly", 7, 2,
`
Bird: ([fly distance:F64] [land])
BlueJay: Int
BlueJay::[fly] = { }
BlueJay::[land] = { }
[main] = {
b:Bird = [@a:BlueJay]
}
`)
}
func TestAssignInterface (test *testing.T) {
testString (test,
`
Bird: ([fly distance:F64] [land])
BlueJay: Int
BlueJay::[fly distance:F64] = { }
BlueJay::[fly land] = { }
[main] = {
a:BlueJay
b:Bird = [@a]
}
`)
}
func TestCastErrIntPointer (test *testing.T) {
testStringErr (test,
"cannot convert from *Int to Int", 2, 14,
`
[main]:Int = [~ [@ a:Int] Int]
`)
}
func TestCastErrIntStruct (test *testing.T) {
testStringErr (test,
"cannot convert from (x:Int y:Int) to Int", 2, 14,
`
[main]:Int = [~ a:(x:Int y:Int) Int]
`)
}
func TestCastErrIntArray (test *testing.T) {
testStringErr (test,
"cannot convert from 5:Int to Int", 2, 14,
`
[main]:Int = [~ a:5:Int Int]
`)
}
func TestCastErrIntSlice (test *testing.T) {
testStringErr (test,
"cannot convert from *:Int to Int", 2, 14,
`
[main]:Int = [~ a:*:Int Int]
`)
}
func TestCastErrPointerInt (test *testing.T) {
testStringErr (test,
"cannot convert from Int to *Int", 2, 15,
`
[main]:*Int = [~ [@ a:Int] *Int]
`)
}
func TestCastErrStructInt (test *testing.T) {
testStringErr (test,
"cannot convert from Int to (x:Int y:Int)", 2, 24,
`
[main]:(x:Int y:Int) = [~ a:Int (x:Int y:Int)]
`)
}
func TestCastErrArrayInt (test *testing.T) {
testStringErr (test,
"cannot convert from Int to 5:Int", 2, 13,
`
[main]:Int = [~ a:5:Int Int]
`)
}
func TestCastErrSliceInt (test *testing.T) {
testStringErr (test,
"cannot convert from Int to *:Int", 2, 16,
`
[main]:*:Int = [~ a:Int *:Int]
`)
}
func TestCast (test *testing.T) {
testString (test,
`
Bird: ([fly distance:F64] [land])
BlueJay: Int
BlueJay::[fly distance:F64] = { }
BlueJay::[fly land] = { }
IntDerived: Int
[main] = {
a:IntDerived = 5
b:Int [~ [~ [~ a Byte] F64] Int]
c:Int [~~ [~~ [~~ a Byte] F64] Int]
d:(x:Int y:Int) = (x: 1 y: 2)
e:(z:Int a:Int) = [~~ d (z:Int a:Int)]
f:Bird = [@ [~~ 0 BlueJay]]
g:*:Int = (~ h:5:Int *:int)
}
`)
}
// TODO: complete and test error cases
func TestPropagateAssignRules (test *testing.T) {
testString (test,
`
[f]:Byte = 5
A:Int
A::[g]:Int = 2
B:([g]:Int)
[main] = {
a:Int
b:Int = a
c:Int = d:Int
d:Int = { a:F64 b }
e:Byte = [f]
g:(x:Int y:(w:F64 z:F64)) = (x: 1 y: (w: 1.2 z: 78.5))
h:F64 = g.x.z
i:A
j:Int = [i::g]
k:B = i
l:Int = [k::g]
m:5:Int = (* 0 1 2 3 4)
n:Int = [. m 3]
}
`)
// inRange returns whether the specified value can fit within the given integer
// type.
func inRange (ty entity.Type, value int64) bool {
base := ReduceToBase(ty)
switch base.(type) {
case *entity.TypeInt:
base := base.(*entity.TypeInt)
if base.Signed {
return value > integer.SignedMin(base.Width) &&
value < integer.SignedMax(base.Width)
} else {
return value >= 0 &&
uint64(value) < integer.UnsignedMax(base.Width)
}
case *entity.TypeWord:
base := base.(*entity.TypeWord)
if base.Signed {
return value > math.MinInt && value < math.MaxInt
} else {
return value >= 0 && uint64(value) < math.MaxUint
}
default: return false
}
}

289
analyzer/assignment_test.go Normal file
View File

@@ -0,0 +1,289 @@
package analyzer
import "testing"
func TestAssignmentLiteralErrUnsignedNegative (test *testing.T) {
testStringErr (test,
"integer literal out of range for type UInt", 3, 11,
`
[main] = {
x:UInt = -5
}
`)
}
func TestAssignmentLiteralErrIntegerFloat (test *testing.T) {
testStringErr (test,
"cannot use float literal as Int", 3, 10,
`
[main] = {
x:Int = 5.5
}
`)
}
func TestAssignmentLiteralErrIntegerStruct (test *testing.T) {
testStringErr (test,
"cannot use struct literal as Int", 3, 10,
`
[main] = {
x:Int = (. x: 5)
}
`)
}
func TestAssignmentLiteralErrIntegerArray (test *testing.T) {
testStringErr (test,
"cannot use array literal as Int", 3, 10,
`
[main] = {
x:Int = (* 5)
}
`)
}
func TestAssignmentLiteralErrIntegerOverflow (test *testing.T) {
testStringErr (test,
"integer literal out of range for type Byte", 3, 11,
`
[main] = {
x:Byte = 1000
}
`)
}
func TestAssignmentLiteralErrArrayInt (test *testing.T) {
testStringErr (test,
"cannot use integer literal as 3:Int", 3, 12,
`
[main] = {
x:3:Int = 5
}
`)
}
func TestAssignmentLiteralErrArrayWrongLength (test *testing.T) {
testStringErr (test,
"expected 3 elements or less", 3, 12,
`
[main] = {
x:3:Int = (* 1 2 3 4)
}
`)
}
func TestAssignmentLiteralErrArrayWrongType (test *testing.T) {
testStringErr (test,
"cannot use float literal as Int", 3, 17,
`
[main] = {
x:3:Int = (* 1 3.3 4)
}
`)
}
func TestAssignmentLiteralErrSliceWrongType (test *testing.T) {
testStringErr (test,
"cannot use float literal as Int", 3, 17,
`
[main] = {
x:*:Int = (* 1 3.3 4)
}
`)
}
func TestAssignmentLiteralErrStructWrongType (test *testing.T) {
testStringErr (test,
"cannot use float literal as Int", 3, 28,
`
[main] = {
x:(. x:Int y:Int) = (. x: 5.5 y: 3)
}
`)
}
func TestAssignmentLiteralErrStructInteger (test *testing.T) {
testStringErr (test,
"cannot use integer literal as (. x:Int y:Int)", 3, 22,
`
[main] = {
x:(. x:Int y:Int) = 5
}
`)
}
func TestAssignmentLiteralErrInterfaceInt (test *testing.T) {
testStringErr (test,
"cannot use integer literal as Bird", 4, 11,
`
Bird: (~ [fly distance:F64] [land])
[main] = {
b:Bird = 5
}
`)
}
func TestAssignmentLiteralErrInterfaceFloat (test *testing.T) {
testStringErr (test,
"cannot use float literal as Bird", 4, 11,
`
Bird: (~ [fly distance:F64] [land])
[main] = {
b:Bird = 5.5
}
`)
}
func TestAssignmentLiteralErrInterfaceArray (test *testing.T) {
testStringErr (test,
"cannot use array literal as Bird", 4, 11,
`
Bird: (~ [fly distance:F64] [land])
[main] = {
b:Bird = (* 1 2 3 4)
}
`)
}
func TestAssignmentLiteralErrInterfaceStruct (test *testing.T) {
testStringErr (test,
"cannot use struct literal as Bird", 4, 11,
`
Bird: (~ [fly distance:F64] [land])
[main] = {
b:Bird = (. x: 5 y: 6)
}
`)
}
func TestAssignmentLiteralErrDerefByteFloat (test *testing.T) {
testStringErr (test,
"cannot use float literal as Byte", 4, 17,
`
[main] = {
buffer:8:Byte
[. buffer 7] = 0.0
}
`)
}
func TestAssignmentLiteral (test *testing.T) {
testString (test,
`
[main] = {
a:F64 = 5.3
b:F32 = 5.3
c:F64 = -5
d:F32 = -5
e:Byte = 64
z:UInt = 5
x:Int = -5
arr:4:2:Int = (*
(* 1 2)
(* 3 4)
(* 5 6)
(* 7 8))
slice:*:Int = (* 3 1 2 3)
struct:(. x:Int y:Int) = (.
x: 9
y: 10)
[.[.arr 1] 0] = 6
[.slice 4] = 5
}
`)
}
func TestAssignmentInterfaceErrBadSignature (test *testing.T) {
testStringErr (test,
"BlueJay has wrong signature for method fly", 7, 11,
`
Bird: (~ [fly distance:F64] [land])
BlueJay: Int
BlueJay.[fly] = { }
BlueJay.[land] = { }
[main] = {
b:Bird = a:BlueJay
}
`)
}
func TestAssignmentInterfaceErrMissingMethod (test *testing.T) {
testStringErr (test,
"no method named fly defined on this type", 6, 11,
`
Bird: (~ [fly distance:F64] [land])
BlueJay: Int
BlueJay.[land] = { }
[main] = {
b:Bird = a:BlueJay
}
`)
}
func TestAssignmentInterfaceErrBadLayer (test *testing.T) {
testStringErr (test,
"no method named fly defined on this type", 7, 11,
`
Bird: (~ [fly distance:F64])
BlueJay: Int
BlueJay.[fly distance:F64] = { }
BlueJayRef: *BlueJay
[main] = {
b:Bird = a:BlueJayRef
}
`)
}
func TestAssignmentInterface (test *testing.T) {
testString (test,
`
Bird: (~ [fly distance:F64] [land])
BlueJay: Int
BlueJay.[fly distance:F64] = { }
BlueJay.[land] = { }
[main] = {
b:Bird = a:BlueJay
ref:*Bird = [@ a]
c:Bird = ref
}
`)
}
func TestAssignmentErrByteSliceString (test *testing.T) {
testStringErr (test,
"expected *:Byte", 4, 13,
`
[main] = {
a:String = 'hello'
b:*:Byte = a
}
`)
}
// TODO: complete and test error cases
func TestAssignmentPropagateRules (test *testing.T) {
testString (test,
`
[f]:Byte = 5
A:Int
A.[g]:Int = 2
B:(~ [g]:Int)
[main] = {
a:Int
b:Int = a
c:Int = d:Int
d = { a:F64 b }
e:Byte = [f]
g:(. x:Int y:(. w:F64 z:F64)) = (. x: 1 y: (. w: 1.2 z: 78.5))
gx:(. w:F64 z:F64) = g.y
h:F64 = gx.z
i:A
j:Int = i.[g]
k:B = i
l:Int = k.[g]
m:5:Int = (* 0 1 2 3 4)
n:Int = [. m 3]
o:*:Int = m
}
`)
}

51
analyzer/builtin.go Normal file
View File

@@ -0,0 +1,51 @@
package analyzer
import "git.tebibyte.media/fspl/fspl/entity"
var primitiveTypes = map[string] entity.Type {
"Int": &entity.TypeWord { Acc: entity.AccessPublic, Signed: true },
"UInt": &entity.TypeWord { Acc: entity.AccessPublic, },
"I8": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 8 },
"I16": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 16 },
"I32": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 32 },
"I64": &entity.TypeInt { Acc: entity.AccessPublic, Signed: true, Width: 64 },
"U8": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 },
"U16": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 16 },
"U32": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 32 },
"U64": &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 64 },
"F32": &entity.TypeFloat { Acc: entity.AccessPublic, Width: 32 },
"F64": &entity.TypeFloat { Acc: entity.AccessPublic, Width: 64 },
}
var builtinTypes = map[string] entity.Type { }
func builtinType (name string) *entity.TypeNamed {
ty, ok := builtinTypes[name]
if !ok { panic("BUG: compiler tried to reference missing builtin " + name) }
return &entity.TypeNamed {
Name: name,
Type: ty,
Acc: entity.AccessPublic,
}
}
func primitiveType (name string) *entity.TypeNamed {
ty, ok := primitiveTypes[name]
if !ok { panic("BUG: compiler tried to reference missing primitive " + name) }
return &entity.TypeNamed {
Name: name,
Type: ty,
Acc: entity.AccessPublic,
}
}
func init () {
builtinTypes["Index"] = &entity.TypeWord { Acc: entity.AccessPublic }
builtinTypes["Byte"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 }
builtinTypes["Bool"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 }
builtinTypes["Rune"] = &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 32 }
builtinTypes["String"] = &entity.TypeSlice {
Acc: entity.AccessPublic,
Element: &entity.TypeInt { Acc: entity.AccessPublic, Signed: false, Width: 8 },
}
}

122
analyzer/cast_test.go Normal file
View File

@@ -0,0 +1,122 @@
package analyzer
import "testing"
func TestCastErrIntPointer (test *testing.T) {
testStringErr (test,
"cannot convert from *Int to Int", 4, 9,
`
[main]:Int = {
a:*Int
[~ Int a]
}
`)
}
func TestCastErrIntStruct (test *testing.T) {
testStringErr (test,
"cannot convert from (. x:Int y:Int) to Int", 2, 21,
`
[main]:Int = [~ Int a:(. x:Int y:Int)]
`)
}
func TestCastErrIntArray (test *testing.T) {
testStringErr (test,
"cannot convert from 5:Int to Int", 2, 21,
`
[main]:Int = [~ Int a:5:Int]
`)
}
func TestCastErrIntSlice (test *testing.T) {
testStringErr (test,
"cannot convert from *:Int to Int", 2, 21,
`
[main]:Int = [~ Int a:*:Int]
`)
}
func TestCastErrPointerInt (test *testing.T) {
testStringErr (test,
"cannot convert from Int to *Int", 2, 23,
`
[main]:*Int = [~ *Int a:Int]
`)
}
func TestCastErrStructInt (test *testing.T) {
testStringErr (test,
"cannot convert from Int to (. x:Int y:Int)", 2, 45,
`
[main]:(. x:Int y:Int) = [~ (. x:Int y:Int) a:Int]
`)
}
func TestCastErrArrayInt (test *testing.T) {
testStringErr (test,
"cannot convert from 5:Int to Int", 2, 21,
`
[main]:Int = [~ Int a:5:Int]
`)
}
func TestCastErrSliceInt (test *testing.T) {
testStringErr (test,
"cannot convert from Int to *:Int", 2, 25,
`
[main]:*:Int = [~ *:Int a:Int]
`)
}
func TestCast (test *testing.T) {
testString (test,
`
Bird: (~ [fly distance:F64] [land])
BlueJay: Int
BlueJay.[fly distance:F64] = { }
BlueJay.[land] = { }
IntDerived: Int
[main] = {
a:IntDerived = 5
b:Int [~ Int [~ F64 [~ Byte a]]]
c:Int [~~ Int [~~ F64 [~~ Byte a]]]
d:(. x:Int y:Int) = (. x: 1 y: 2)
e:(. z:Int a:Int) = [~~ (. z:Int a:Int) d]
f:Bird = [~~ BlueJay 0]
g:String = 'hello'
h:*:Byte = [~ *:Byte g]
}
`)
}
func TestBitCastPointer (test *testing.T) {
testString (test,
`
Struct: (. x:Int y:Int)
Array: 4:Int
[main] = {
ptr:*Byte
stc:Struct
arr:Array
str:String
idx:Index
; struct
ptr = [~~*Byte [~~Struct ptr]]
stc = [~~Struct [~~*Byte stc]]
; array
ptr = [~~*Byte [~~Array ptr]]
arr = [~~Array [~~*Byte arr]]
; slice
ptr = [~~*Byte [~~String ptr]]
str = [~~String [~~*Byte str]]
; int
ptr = [~~*Byte [~~Index ptr]]
idx = [~~Index [~~*Byte idx]]
; arithmetic
ptr = [~~*Byte [+ 1 [~~Index ptr]]]
}
`)
}

5
analyzer/doc.go Normal file
View File

@@ -0,0 +1,5 @@
// Package analyzer implements the semantic analysis stage of the FSPL compiler.
// The analyzer takes in a well-formed abstract syntax tree, ensures its
// semantic correctness, and fills in the semantic information stored within
// the tree.
package analyzer

View File

@@ -0,0 +1,73 @@
package analyzer
import "fmt"
import "git.tebibyte.media/fspl/fspl/entity"
func (this *Tree) analyzeExpression (
into entity.Type,
mode strictness,
expression entity.Expression,
) (
entity.Expression,
error,
) {
switch expression := expression.(type) {
case *entity.Assignment:
return this.analyzeAssignment(expression)
case *entity.Variable:
return this.analyzeVariable(into, mode, expression)
case *entity.Declaration:
return this.analyzeDeclaration(into, mode, expression)
case *entity.Call:
return this.analyzeCall(into, mode, expression)
case *entity.MethodCall:
return this.analyzeMethodCall(into, mode, expression)
case *entity.Subscript:
return this.analyzeSubscript(into, mode, expression)
case *entity.Slice:
return this.analyzeSlice(into, mode, expression)
case *entity.Length:
return this.analyzeLength(into, mode, expression)
case *entity.Dereference:
return this.analyzeDereference(into, mode, expression)
case *entity.Reference:
return this.analyzeReference(into, mode, expression)
case *entity.ValueCast:
return this.analyzeValueCast(into, mode, expression)
case *entity.BitCast:
return this.analyzeBitCast(into, mode, expression)
case *entity.Operation:
return this.analyzeOperation(into, mode, expression)
case *entity.Block:
return this.analyzeBlock(into, mode, expression)
case *entity.MemberAccess:
return this.analyzeMemberAccess(into, mode, expression)
case *entity.IfElse:
return this.analyzeIfElse(into, mode, expression)
case *entity.Loop:
return this.analyzeLoop(into, mode, expression)
case *entity.Break:
return this.analyzeBreak(into, mode, expression)
case *entity.Return:
return this.analyzeReturn(into, mode, expression)
case *entity.LiteralInt:
return this.analyzeLiteralInt(into, mode, expression)
case *entity.LiteralFloat:
return this.analyzeLiteralFloat(into, mode, expression)
case *entity.LiteralArray:
return this.analyzeLiteralArray(into, mode, expression)
case *entity.LiteralString:
return this.analyzeLiteralString(into, mode, expression)
case *entity.LiteralStruct:
return this.analyzeLiteralStruct(into, mode, expression)
case *entity.LiteralBoolean:
return this.analyzeLiteralBoolean(into, mode, expression)
case *entity.LiteralNil:
return this.analyzeLiteralNil(into, mode, expression)
default:
panic(fmt.Sprintf (
"BUG: analyzer doesnt know about expression %v, ty: %T",
expression, expression))
}
}

805
analyzer/expression.go Normal file
View File

@@ -0,0 +1,805 @@
package analyzer
import "fmt"
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/entity"
// All expression analysis routines must take in the type they are being
// assigned to and return an error if they can't be assigned to it. if the type
// is nil, the expression must ignore it unless it can do upwards type
// inference.
func (this *Tree) analyzeAssignment (
assignment *entity.Assignment,
) (
entity.Expression,
error,
) {
// analyze location
location, err := this.analyzeExpression(nil, strict, assignment.Location)
if err != nil { return nil, err }
assignment.Location = location
// ensure location is location expression
err = this.isLocationExpression(location)
if err != nil { return nil, err }
// analyze value
value, err := this.analyzeExpression(location.Type(), strict, assignment.Value)
if err != nil { return nil, err }
assignment.Value = value
return assignment, nil
}
func (this *Tree) analyzeVariable (
into entity.Type,
mode strictness,
variable *entity.Variable,
) (
entity.Expression,
error,
) {
declaration := this.variable(variable.Name)
if declaration == nil {
return nil, errors.Errorf (
variable.Position, "no variable named %s",
variable.Name)
}
err := this.canAssign(variable.Position, into, mode, declaration.Type())
if err != nil { return nil, err }
variable.Declaration = declaration
return variable, nil
}
func (this *Tree) analyzeDeclaration (
into entity.Type,
mode strictness,
declaration *entity.Declaration,
) (
entity.Expression,
error,
) {
scope, _ := this.topScope()
existing := scope.Variable(declaration.Name)
if existing != nil {
return nil, errors.Errorf (
declaration.Position,
"%s already declared in block at %v",
declaration.Name, existing.Position)
}
ty, err := this.analyzeType(declaration.Ty, false)
declaration.Ty = ty
if err != nil { return nil, err }
err = this.canAssign(declaration.Position, into, mode, declaration.Type())
if err != nil { return nil, err }
this.addVariable(declaration)
return declaration, nil
}
func (this *Tree) analyzeCall (
into entity.Type,
mode strictness,
call *entity.Call,
) (
entity.Expression,
error,
) {
// get function
unit, err := this.resolveNickname(call.Position, call.UnitNickname)
if err != nil { return nil, err }
function, err := this.analyzeFunction(call.Position, entity.Key {
Unit: unit,
Name: call.Name,
})
if err != nil { return nil, err }
call.Function = function
call.Unit = function.Unit
// check access permissions
if function.Acc == entity.AccessPrivate && function.Unit != this.unit {
return nil, errors.Errorf (
call.Position, "function %v::[%v] is private",
call.UnitNickname, call.Name)
}
// check return result
err = this.canAssign(call.Position, into, mode, function.Signature.Return)
if err != nil { return nil, err }
// check arg count
if len(call.Arguments) > len(function.Signature.Arguments) {
return nil, errors.Errorf (
call.Position, "too many arguments in call to %s",
call.Name)
} else if len(call.Arguments) < len(function.Signature.Arguments) {
return nil, errors.Errorf (
call.Position, "too few arguments in call to %s",
call.Name)
}
// check arg types
for index, argument := range call.Arguments {
signature := function.Signature
correct := signature.ArgumentMap[signature.ArgumentOrder[index]]
argument, err := this.analyzeExpression(correct.Type(), strict, argument)
if err != nil { return nil, err }
call.Arguments[index] = argument
}
return call, nil
}
func (this *Tree) analyzeMethodCall (
into entity.Type,
mode strictness,
call *entity.MethodCall,
) (
entity.Expression,
error,
) {
// get method
source, err := this.analyzeExpression(nil, strict, call.Source)
if err != nil { return nil, err }
method, err := this.analyzeMethodOrBehavior (
call.Position, source.Type(), call.Name)
if err != nil { return nil, err }
// extract signature
var signature *entity.Signature
switch method := method.(type) {
case *entity.Signature:
signature = method
call.Behavior = signature
// since this is a behavior, check access permissions of the
// interface
err = this.typeRestricted(call.Position, source.Type())
if err != nil { return nil, err }
case *entity.Method:
signature = method.Signature
call.Method = method
// since this is a method, check access permissions of the
// method
if method.Acc == entity.AccessPrivate && method.Unit != this.unit {
return nil, errors.Errorf (
call.Position, "method %v.[%v] is private",
method.TypeName, method.Signature.Name)
}
default:
panic(fmt.Sprint (
"Tree.analyzeMethodOrBehavior returned ",
method))
}
// check return result
err = this.canAssign(call.Position, into, mode, signature.Return)
if err != nil { return nil, err }
// check arg count
if len(call.Arguments) > len(signature.Arguments) {
return nil, errors.Errorf (
call.Position, "too many arguments in call to %s",
call.Name)
} else if len(call.Arguments) < len(signature.Arguments) {
return nil, errors.Errorf (
call.Position, "too few arguments in call to %s",
call.Name)
}
// check arg types
for index, argument := range call.Arguments {
correct := signature.ArgumentMap[signature.ArgumentOrder[index]]
argument, err := this.analyzeExpression(correct.Type(), strict, argument)
if err != nil { return nil, err }
call.Arguments[index] = argument
}
return call, nil
}
func (this *Tree) analyzeSubscript (
into entity.Type,
mode strictness,
subscript *entity.Subscript,
) (
entity.Expression,
error,
) {
slice, err := this.analyzeExpression (
&entity.TypeSlice {
Position: subscript.Position,
Element: into,
}, weak,
subscript.Slice)
if err != nil { return nil, err }
subscript.Slice = slice
subscript.Ty = into
// check permissions
err = this.typeRestricted(subscript.Position, slice.Type())
if err != nil { return nil, err }
offset, err := this.analyzeExpression (
builtinType("Index"), weak,
subscript.Offset)
if err != nil { return nil, err }
subscript.Offset = offset
var frailType bool
switch into := into.(type) {
case nil: frailType = true
case *entity.TypeSlice: frailType = into.Element == nil
}
if frailType {
ty := ReduceToBase(subscript.Slice.Type())
switch ty := ty.(type) {
case *entity.TypeSlice: subscript.Ty = ty.Element
case *entity.TypeArray: subscript.Ty = ty.Element
}
}
return subscript, nil
}
func (this *Tree) analyzeSlice (
into entity.Type,
mode strictness,
slice *entity.Slice,
) (
entity.Expression,
error,
) {
value, err := this.analyzeExpression(into, weak, slice.Slice)
if err != nil { return nil, err }
slice.Slice = value
// check permissions
err = this.typeRestricted(slice.Position, value.Type())
if err != nil { return nil, err }
if slice.Start != nil {
start, err := this.analyzeExpression (
builtinType("Index"), weak,
slice.Start)
if err != nil { return nil, err }
slice.Start = start
}
if slice.End != nil {
end, err := this.analyzeExpression (
builtinType("Index"), weak,
slice.End)
if err != nil { return nil, err }
slice.End = end
}
return slice, nil
}
func (this *Tree) analyzeLength (
into entity.Type,
mode strictness,
length *entity.Length,
) (
entity.Expression,
error,
) {
value, err := this.analyzeExpression(nil, strict, length.Slice)
if err != nil { return nil, err }
length.Slice = value
length.Ty = builtinType("Index")
return length, nil
}
func (this *Tree) analyzeDereference (
into entity.Type,
mode strictness,
dereference *entity.Dereference,
) (
entity.Expression,
error,
) {
pointer, err := this.analyzeExpression (
&entity.TypePointer {
Position: dereference.Position,
Referenced: into,
}, weak,
dereference.Pointer)
if err != nil { return nil, err }
dereference.Pointer = pointer
dereference.Ty = into
var frailType bool
switch into := into.(type) {
case nil: frailType = true
case *entity.TypePointer: frailType = into.Referenced == nil
}
if frailType {
ty := ReduceToBase(dereference.Pointer.Type())
switch ty := ty.(type) {
case *entity.TypePointer: dereference.Ty = ty.Referenced
}
}
return dereference, nil
}
func (this *Tree) analyzeReference (
into entity.Type,
mode strictness,
reference *entity.Reference,
) (
entity.Expression,
error,
) {
referenced, ok := into.(*entity.TypePointer)
if !ok {
return nil, errors.Errorf(reference.Position, "expected %v", into)
}
value, err := this.analyzeExpression (
referenced.Referenced, weak,
reference.Value)
if err != nil { return nil, err }
err = this.isLocationExpression(reference.Value)
if err != nil { return nil, err }
reference.Value = value
reference.Ty = referenced.Referenced
return reference, nil
}
func (this *Tree) analyzeValueCast (
into entity.Type,
mode strictness,
cast *entity.ValueCast,
) (
entity.Expression,
error,
) {
ty, err := this.analyzeType(cast.Ty, false)
if err != nil { return nil, err }
cast.Ty = ty
err = this.canAssign(cast.Position, into, mode, cast.Type())
if err != nil { return nil, err }
value, err := this.analyzeExpression(cast.Ty, coerce, cast.Value)
if err != nil { return nil, err }
cast.Value = value
return cast, nil
}
func (this *Tree) analyzeBitCast (
into entity.Type,
mode strictness,
cast *entity.BitCast,
) (
entity.Expression,
error,
) {
ty, err := this.analyzeType(cast.Ty, false)
if err != nil { return nil, err }
cast.Ty = ty
err = this.canAssign(cast.Position, into, mode, cast.Type())
if err != nil { return nil, err }
value, err := this.analyzeExpression(cast.Ty, force, cast.Value)
if err != nil { return nil, err }
cast.Value = value
return cast, nil
}
func (this *Tree) analyzeOperation (
into entity.Type,
mode strictness,
operation *entity.Operation,
) (
entity.Expression,
error,
) {
// check permissions
err := this.typeRestricted(operation.Position, into)
if err != nil { return nil, err }
intoUntrustworthy := into == nil || mode == force || mode == coerce
undefined := func (ty entity.Type) (entity.Expression, error) {
return nil, errors.Errorf (
operation.Position, "operator %v undefined for %v",
operation.Operator, entity.FormatType(ty))
}
wrongInto := func () (entity.Expression, error) {
return nil, errors.Errorf (
operation.Position, "expected %v",
entity.FormatType(into))
}
wrongArgCount := func () (entity.Expression, error) {
return nil, errors.Errorf (
operation.Position, "wrong argument count for %v",
operation.Operator)
}
determineArgumentType := func () (entity.Type, int, error) {
// find the first argument that has explicit type information.
boss := -1
var argumentType entity.Type
for index, argument := range operation.Arguments {
if argument.HasExplicitType() {
argument, err := this.analyzeExpression(nil, strict, argument)
if err != nil { return nil, -1, err }
boss = index
operation.Arguments[index] = argument
argumentType = argument.Type()
break
}
}
if argumentType == nil {
return nil, -1, errors.Errorf (
operation.Position,
"operation arguments have ambiguous type")
}
return argumentType, boss, err
}
nSameType := func (n int, constraint func (entity.Type) bool) (entity.Expression, error) {
if n > 0 {
if len(operation.Arguments) != n { return wrongArgCount() }
} else {
if len(operation.Arguments) < 1 { return wrongArgCount() }
}
n = len(operation.Arguments)
boss := -1
var argumentType entity.Type
if intoUntrustworthy {
// determine the type of all arguments (and the return
// type) using determineArgumentType
var err error
argumentType, boss, err = determineArgumentType()
if err != nil { return nil, err }
if !constraint(argumentType) { return undefined(argumentType) }
} else {
// into is trustworthy, so we don't need to determine
// anything
argumentType = into
}
if !constraint(argumentType) { return undefined(argumentType) }
operation.Ty = argumentType
for index, argument := range operation.Arguments {
if index == boss { continue }
argument, err := this.analyzeExpression (
operation.Ty, strict,
argument)
if err != nil { return nil, err }
operation.Arguments[index] = argument
}
return operation, nil
}
comparison := func (argConstraint func (entity.Type) bool) (entity.Expression, error) {
if len(operation.Arguments) < 2 { return wrongArgCount() }
if !isBoolean(into) && !intoUntrustworthy { return wrongInto() }
operation.Ty = builtinType("Bool")
// determine argument type
argumentType, boss, err := determineArgumentType()
if err != nil { return nil, err }
if !argConstraint(argumentType) { return undefined(argumentType) }
// analyze all remaining arguments
for index, argument := range operation.Arguments {
if index == boss { continue }
argument, err := this.analyzeExpression (
argumentType, strict, argument)
if err != nil { return nil, err }
operation.Arguments[index] = argument
}
return operation, nil
}
switch operation.Operator {
// math
case entity.OperatorAdd,
entity.OperatorSubtract,
entity.OperatorMultiply,
entity.OperatorDivide,
entity.OperatorIncrement,
entity.OperatorDecrement:
return nSameType(-1, isNumeric)
case entity.OperatorModulo:
return nSameType(2, isNumeric)
// logic
case entity.OperatorLogicalNot:
return nSameType(1, isBoolean)
case entity.OperatorLogicalOr,
entity.OperatorLogicalAnd,
entity.OperatorLogicalXor:
return nSameType(-1, isBoolean)
// bit manipulation
case entity.OperatorNot:
return nSameType(1, isInteger)
case entity.OperatorOr, entity.OperatorAnd, entity.OperatorXor:
return nSameType(-1, isInteger)
case entity.OperatorLeftShift, entity.OperatorRightShift:
if len(operation.Arguments) != 2 { return wrongArgCount() }
if !isInteger(into) { return wrongInto() }
arg, err := this.analyzeExpression(into, mode, operation.Arguments[0])
if err != nil { return nil, err }
operation.Arguments[0] = arg
operation.Ty = arg.Type()
offset, err := this.analyzeExpression (
builtinType("Index"), weak,
operation.Arguments[1])
if err != nil { return nil, err }
operation.Arguments[1] = offset
return operation, nil
// comparison
case entity.OperatorLess,
entity.OperatorGreater,
entity.OperatorLessEqual,
entity.OperatorGreaterEqual:
return comparison(isOrdered)
case entity.OperatorEqual:
return comparison(isOrdered)
default:
panic(fmt.Sprint (
"BUG: analyzer doesnt know about operator ",
operation.Operator))
}
}
func (this *Tree) analyzeBlock (
into entity.Type,
mode strictness,
block *entity.Block,
) (
entity.Expression,
error,
) {
this.pushScope(block)
defer this.popScope()
if len(block.Steps) == 0 && into != nil {
return nil, errors.Errorf (
block.Position, "block must have at least one statement")
}
final := len(block.Steps) - 1
for index, step := range block.Steps {
if index == final && into != nil {
expression, ok := step.(entity.Expression)
if !ok {
return nil, errors.Errorf (
block.Position, "expected expression")
}
step, err := this.analyzeExpression(into, strict, expression)
if err != nil { return nil, err }
block.Steps[index] = step
block.Ty = step.Type()
} else {
step, err := this.analyzeExpression(nil, strict, step)
if err != nil { return nil, err }
block.Steps[index] = step
}
}
return block, nil
}
func (this *Tree) analyzeMemberAccess (
into entity.Type,
mode strictness,
access *entity.MemberAccess,
) (
entity.Expression,
error,
) {
source, err := this.analyzeExpression(nil, strict, access.Source)
if err != nil { return nil, err }
// determine type with the members
var sourceType *entity.TypeStruct
var qualifiedSourceType entity.Type
switch sourceTypeAny := ReduceToBase(source.Type()).(type) {
case *entity.TypeStruct:
sourceType = sourceTypeAny
qualifiedSourceType = source.Type()
case *entity.TypePointer:
referenced, ok := ReduceToBase(sourceTypeAny.Referenced).(*entity.TypeStruct)
if !ok {
return nil, errors.Errorf (
access.Position, "cannot access members of %v",
source)
}
sourceType = referenced
qualifiedSourceType = sourceTypeAny.Referenced
default:
return nil, errors.Errorf (
access.Position, "cannot access members of %v",
source)
}
// check permissions
err = this.typeRestricted(access.Position, qualifiedSourceType)
if err != nil { return nil, err }
// get member
member, ok := sourceType.MemberMap[access.Member]
if !ok {
return nil, errors.Errorf (
access.Position, "no member %v",
access)
}
err = this.canAssign(access.Position, into, mode, member.Type())
if err != nil { return nil, err }
access.Ty = member.Type()
return access, nil
}
func (this *Tree) analyzeIfElse (
into entity.Type,
mode strictness,
ifelse *entity.IfElse,
) (
entity.Expression,
error,
) {
condition, err := this.analyzeExpression (
&entity.TypeNamed {
Name: "Bool",
Type: builtinType("Bool"),
},
weak, ifelse.Condition)
if err != nil { return nil, err }
ifelse.Condition = condition
trueBranch, err := this.analyzeExpression(into, strict, ifelse.True)
if err != nil { return nil, err }
ifelse.True = trueBranch
if ifelse.False == nil {
if into != nil {
return nil, errors.Errorf (
ifelse.Position,
"else case required when using value of if ")
}
} else {
falseBranch, err := this.analyzeExpression(into, strict, ifelse.False)
if err != nil { return nil, err }
ifelse.False = falseBranch
}
ifelse.Ty = into
return ifelse, nil
}
func (this *Tree) analyzeLoop (
into entity.Type,
mode strictness,
loop *entity.Loop,
) (
entity.Expression,
error,
) {
loop.Ty = into
this.pushLoop(loop)
defer this.popLoop()
body, err := this.analyzeExpression(nil, strict, loop.Body)
if err != nil { return nil, err }
loop.Body = body
return loop, nil
}
func (this *Tree) analyzeBreak (
into entity.Type,
mode strictness,
brk *entity.Break,
) (
entity.Expression,
error,
) {
if into != nil {
return nil, errors.Errorf (
brk.Position, "expected %v",
entity.FormatType(into))
}
loop, ok := this.topLoop()
if !ok {
return nil, errors.Errorf (
brk.Position,
"break statement must be within loop")
}
brk.Loop = loop
if loop.Type() != nil && brk.Value == nil {
return nil, errors.Errorf (
brk.Position,
"break statement must have value")
}
if brk.Value != nil {
value, err := this.analyzeExpression(loop.Type(), strict, brk.Value)
if err != nil { return nil, err }
brk.Value = value
}
return brk, nil
}
func (this *Tree) analyzeReturn (
into entity.Type,
mode strictness,
ret *entity.Return,
) (
entity.Expression,
error,
) {
if into != nil {
return nil, errors.Errorf (
ret.Position, "expected %v",
entity.FormatType(into))
}
ret.Declaration, _ = this.topDeclaration()
var ty entity.Type
switch ret.Declaration.(type) {
case *entity.Function:
ty = ret.Declaration.(*entity.Function).Signature.Return
case *entity.Method:
ty = ret.Declaration.(*entity.Method).Signature.Return
}
if ty != nil && ret.Value == nil {
return nil, errors.Errorf (
ret.Position,
"break statement must have value")
}
if ret.Value != nil {
value, err := this.analyzeExpression(ty, strict, ret.Value)
if err != nil { return nil, err }
ret.Value = value
}
return ret, nil
}

66
analyzer/function.go Normal file
View File

@@ -0,0 +1,66 @@
package analyzer
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/entity"
func (this *Tree) analyzeFunction (
pos errors.Position,
key entity.Key,
) (
*entity.Function,
error,
) {
var err error
// return if exists already
if function, exists := this.Functions[key]; exists {
return function, nil
}
// error if function is missing
function, exists := this.rawFunctions[key]
if !exists {
return nil, errors.Errorf(pos, "no function named %s", key.Name)
}
// set unit
function.Unit = key.Unit
// functions cannot be marked as restricted
if function.Acc == entity.AccessRestricted {
return nil, errors.Errorf(pos, "cannot mark function as restricted")
}
// create a new scope context for this function
this.pushScopeContext(function)
this.pushScope(function)
defer this.popScopeContext()
defer this.popScope()
// analyze signature and add arguments to root scope of function
function.Signature, err = this.assembleSignatureMap(function.Signature)
if err != nil { return function, err }
for name, argument := range function.Signature.ArgumentMap {
argument.Ty, err = this.analyzeType(argument.Ty, false)
this.addVariable(argument)
function.Signature.ArgumentMap[name] = argument
if err != nil { return function, err }
}
function.Signature.Return, err =
this.analyzeType(function.Signature.Return, false)
if err != nil { return function, err }
// add incomplete function to complete functions because there is enough
// information for it to be complete from the point of view of other
// parts of the code
this.Functions[key] = function
// analyze function body
if function.Body != nil {
body, err := this.analyzeExpression (
function.Signature.Return, strict, function.Body)
if err != nil { return nil, err }
function.Body = body
}
return function, nil
}

35
analyzer/function_test.go Normal file
View File

@@ -0,0 +1,35 @@
package analyzer
import "testing"
func TestFunctionUniqueErr (test *testing.T) {
testStringErr (test,
"hello already declared at stream0.fspl:2:1", 3, 1,
`
[hello] = { }
[hello] = { }
`)
}
func TestFunctionUnique (test *testing.T) {
testString (test,
`
[hello] = { }
[world] = { }
`)
}
func TestFunctionArgumentUniqueErr (test *testing.T) {
testStringErr (test,
"x already listed as argument at stream0.fspl:2:7", 2, 13,
`
[main x:Int x:U8 y:Int] = { }
`)
}
func TestFunctionArgumentUnique (test *testing.T) {
testString (test,
`
[main x:Int y:U8 z:Int] = { }
`)
}

297
analyzer/literal.go Normal file
View File

@@ -0,0 +1,297 @@
package analyzer
import "unicode/utf16"
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/entity"
func (this *Tree) analyzeLiteralInt (
into entity.Type,
mode strictness,
literal *entity.LiteralInt,
) (
entity.Expression,
error,
) {
err := this.typeRestricted(literal.Position, into)
if err != nil { return nil, err }
if !isNumeric(into) {
return nil, errors.Errorf (
literal.Position, "cannot use integer literal as %v",
entity.FormatType(into))
}
if isInteger(into) && !inRange(into, int64(literal.Value)) {
return nil, errors.Errorf (
literal.Position, "integer literal out of range for type %v",
entity.FormatType(into))
}
literal.Ty = into
return literal, nil
}
func (this *Tree) analyzeLiteralFloat (
into entity.Type,
mode strictness,
literal *entity.LiteralFloat,
) (
entity.Expression,
error,
) {
err := this.typeRestricted(literal.Position, into)
if err != nil { return nil, err }
if !isFloat(into) {
return nil, errors.Errorf (
literal.Position, "cannot use float literal as %v",
entity.FormatType(into))
}
literal.Ty = into
return literal, nil
}
func (this *Tree) analyzeLiteralString (
into entity.Type,
mode strictness,
literal *entity.LiteralString,
) (
entity.Expression,
error,
) {
err := this.typeRestricted(literal.Position, into)
if err != nil { return nil, err }
base := ReduceToBase(into)
errCantUse := func () error {
return errors.Errorf (
literal.Position, "cannot use string literal as %v",
entity.FormatType(into))
}
fillUTF32 := func () {
literal.ValueUTF32 = []rune(literal.ValueUTF8)
}
fillUTF16 := func () {
literal.ValueUTF16 = utf16.Encode([]rune(literal.ValueUTF8))
}
switch base := base.(type) {
case *entity.TypeInt:
var length int; switch {
case base.Width >= 32:
fillUTF32()
length = len(literal.ValueUTF32)
case base.Width >= 16:
fillUTF16()
length = len(literal.ValueUTF16)
default:
length = len(literal.ValueUTF8)
}
if length > 1 {
return nil, errors.Errorf (
literal.Position,
"cannot fit string literal of length " +
"%v into %v",
length, entity.FormatType(into))
} else if length < 1 {
return nil, errors.Errorf (
literal.Position,
"string literal must have data when " +
"assigning to %v",
entity.FormatType(into))
}
case *entity.TypeArray:
element := ReduceToBase(base.Element)
if element, ok := element.(*entity.TypeInt); ok {
var length int; switch {
case element.Width >= 32:
fillUTF32()
length = len(literal.ValueUTF32)
case element.Width >= 16:
fillUTF16()
length = len(literal.ValueUTF16)
default:
length = len(literal.ValueUTF8)
}
if length > base.Length {
return nil, errors.Errorf (
literal.Position,
"cannot fit string literal of length " +
"%v into array of length %v",
length, base.Length)
}
} else {
return nil, errCantUse()
}
case *entity.TypeSlice:
element := ReduceToBase(base.Element)
if element, ok := element.(*entity.TypeInt); ok {
if element.Width >= 32 {
fillUTF32()
} else if element.Width >= 16 {
fillUTF16()
}
} else {
return nil, errCantUse()
}
case *entity.TypePointer:
referenced := ReduceToBase(base.Referenced)
if referenced, ok := referenced.(*entity.TypeInt); ok {
if referenced.Width != 8 {
return nil, errCantUse()
}
} else {
return nil, errCantUse()
}
default:
return nil, errCantUse()
}
literal.Ty = into
return literal, nil
}
func (this *Tree) analyzeLiteralArray (
into entity.Type,
mode strictness,
literal *entity.LiteralArray,
) (
entity.Expression,
error,
) {
err := this.typeRestricted(literal.Position, into)
if err != nil { return nil, err }
base := ReduceToBase(into)
var elementType entity.Type
switch base.(type) {
case *entity.TypeArray:
base := base.(*entity.TypeArray)
if base.Length < len(literal.Elements) {
return nil, errors.Errorf (
literal.Position, "expected %v elements or less",
base.Length)
}
elementType = base.Element
case *entity.TypeSlice:
base := base.(*entity.TypeSlice)
elementType = base.Element
case *entity.TypePointer:
base := base.(*entity.TypePointer)
elementType = base.Referenced
default:
return nil, errors.Errorf (
literal.Position, "cannot use array literal as %v",
entity.FormatType(into))
}
for index, element := range literal.Elements {
element, err := this.analyzeExpression(elementType, strict, element)
if err != nil { return nil, err }
literal.Elements[index] = element
}
literal.Ty = into
return literal, nil
}
func (this *Tree) analyzeLiteralStruct (
into entity.Type,
mode strictness,
literal *entity.LiteralStruct,
) (
entity.Expression,
error,
) {
err := this.typeRestricted(literal.Position, into)
if err != nil { return nil, err }
base, ok := ReduceToBase(into).(*entity.TypeStruct)
if !ok {
return nil, errors.Errorf (
literal.Position, "cannot use struct literal as %v",
entity.FormatType(into))
}
literal, err = this.assembleStructLiteralMap(literal)
if err != nil { return nil, err }
for name, member := range literal.MemberMap {
correct, ok := base.MemberMap[name]
if !ok {
return nil, errors.Errorf (
literal.Position, "no member %v.%s",
into, name)
}
value, err := this.analyzeExpression(correct.Type(), strict, member.Value)
if err != nil { return nil, err }
member.Value = value
}
literal.Ty = into
return literal, nil
}
func (this *Tree) analyzeLiteralBoolean (
into entity.Type,
mode strictness,
literal *entity.LiteralBoolean,
) (
entity.Expression,
error,
) {
err := this.typeRestricted(literal.Position, into)
if err != nil { return nil, err }
if !isBoolean(into) {
return nil, errors.Errorf (
literal.Position, "cannot use boolean literal as %v",
entity.FormatType(into))
}
literal.Ty = into
return literal, nil
}
func (this *Tree) analyzeLiteralNil (
into entity.Type,
mode strictness,
literal *entity.LiteralNil,
) (
entity.Expression,
error,
) {
err := this.typeRestricted(literal.Position, into)
if err != nil { return nil, err }
literal.Ty = into
return literal, nil
}
func (this *Tree) assembleStructLiteralMap (
literal *entity.LiteralStruct,
) (
*entity.LiteralStruct,
error,
) {
literal.MemberMap = make(map[string] *entity.Member)
literal.MemberOrder = make([]string, len(literal.Members))
for index, member := range literal.Members {
if previous, exists := literal.MemberMap[member.Name]; exists {
return literal, errors.Errorf (
member.Position, "%s already listed in struct literal at %v",
member.Name, previous.Position)
}
literal.MemberMap [member.Name] = member
literal.MemberOrder[index] = member.Name
literal.Members [index] = member
}
return literal, nil
}

53
analyzer/literal_test.go Normal file
View File

@@ -0,0 +1,53 @@
package analyzer
import "testing"
func TestLiteralStructMemberUniqueErr (test *testing.T) {
testStringErr (test,
"x already listed in struct literal at stream0.fspl:5:3", 7, 3,
`
Point: (. x:Int y:Int z:Int)
[main] = {
point:Point = (.
x: 5
y: 0
x: 32)
}
`)
}
func TestLiteralStructMemberUnique (test *testing.T) {
testString (test,
`
Point: (. x:Int y:Int z:Int)
[main] = {
point:Point = (.
x: 5
y: 0
z: 32)
}
`)
}
func TestLiteralStructNested (test *testing.T) {
testString (test,
`
[main] = {
g:(. x:Int y:(. w:F64 z:F64)) = (.
x: 1
y: (.
w: 1.2
z: 78.5))
}
`)
}
func TestLiteralReferenceErr (test *testing.T) {
testStringErr (test,
"cannot assign to string literal", 3, 20,
`
[main] = {
byteptr:*Byte = [@'\n']
}
`)
}

178
analyzer/method.go Normal file
View File

@@ -0,0 +1,178 @@
package analyzer
import "fmt"
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/entity"
// analyzeMethod analyzes a method that is directly owned by the specified type.
func (this *Tree) analyzeMethod (
pos errors.Position,
key entity.Key,
) (
*entity.Method,
error,
) {
// get parent typedef
typeKey := key.StripMethod()
owner, err := this.analyzeTypedef(pos, typeKey, false)
if err != nil { return nil, err }
// return if exists already
if method, exists := owner.Methods[key.Method]; exists {
return method, nil
}
// error if method is missing
method, exists := this.rawMethods[key]
if !exists {
return nil, errors.Errorf(pos, "no method named %v", key)
}
// set method's unit, very important information yes
method.Unit = owner.Unit
// methods cannot be marked as restricted
if method.Acc == entity.AccessRestricted {
return nil, errors.Errorf(pos, "cannot mark method as restricted")
}
// create a new scope context for this method
this.pushScopeContext(method)
this.pushScope(method)
defer this.popScopeContext()
defer this.popScope()
// analyze signature and add arguments to root scope of method
method.Signature, err = this.assembleSignatureMap(method.Signature)
if err != nil { return method, err }
for name, argument := range method.Signature.ArgumentMap {
argument.Ty, err = this.analyzeType(argument.Ty, false)
this.addVariable(argument)
method.Signature.ArgumentMap[name] = argument
if err != nil { return method, err }
}
method.Signature.Return, err =
this.analyzeType(method.Signature.Return, false)
if err != nil { return method, err }
// add owner to method
method.This = &entity.Declaration {
Position: method.Position,
Name: "this",
Ty: &entity.TypePointer {
Position: method.Position,
Referenced: &entity.TypeNamed {
Position: method.Position,
Unt: key.Unit,
Name: key.Name,
Type: owner.Type,
},
},
}
this.addVariable(method.This)
// add incomplete method to complete methods because there is enough
// information for it to be complete from the point of view of other
// parts of the code
owner.Methods[key.Method] = method
// analyze method body
if method.Body != nil {
body, err := this.analyzeExpression (
method.Signature.Return, strict, method.Body)
if err != nil { return nil, err }
method.Body = body
}
return method, nil
}
func (this *Tree) methodExists (pos errors.Position, key entity.Key) (bool, error) {
// check raw map
_, existsInRaw := this.rawMethods[key]
if existsInRaw { return true, nil }
// check parent typedef
typeKey := key.StripMethod()
owner, err := this.analyzeTypedef(pos, typeKey, false)
if err != nil { return false, err }
_, existsInType := owner.Methods[key.Method]
return existsInType, nil
}
// analyzeMethodOrBehavior returns *entity.Signature if it found an interface
// behavior, and *entity.Method if it found a method.
func (this *Tree) analyzeMethodOrBehavior (
pos errors.Position,
ty entity.Type,
name string,
) (
any,
error,
) {
return this.analyzeMethodOrBehaviorInternal(pos, ty, name, true)
}
func (this *Tree) analyzeMethodOrBehaviorInternal (
pos errors.Position,
ty entity.Type,
name string,
pierceReference bool,
) (
any,
error,
) {
switch ty.(type) {
case *entity.TypeNamed:
ty := ty.(*entity.TypeNamed)
key := entity.Key {
Unit: ty.Type.Unit(),
Name: ty.Name,
Method: name,
}
exists, err := this.methodExists(pos, key)
if err != nil { return nil, err }
if exists {
method, err := this.analyzeMethod(pos, key)
if err != nil { return nil, err }
return method, nil
} else {
return this.analyzeMethodOrBehaviorInternal (
pos, ty.Type, name, false)
}
case *entity.TypeInterface:
ty := ty.(*entity.TypeInterface)
if behavior, ok := ty.BehaviorMap[name]; ok {
return behavior, nil
} else {
return nil, errors.Errorf (
pos, "no behavior or method named %s",
name)
}
case *entity.TypePointer:
if pierceReference {
ty := ty.(*entity.TypePointer)
return this.analyzeMethodOrBehaviorInternal (
pos, ty.Referenced, name, false)
} else {
return nil, errors.Errorf (
pos, "no method named %s defined on this type",
name)
}
case *entity.TypeSlice,
*entity.TypeArray,
*entity.TypeStruct,
*entity.TypeInt,
*entity.TypeFloat,
*entity.TypeWord:
return nil, errors.Errorf (
pos, "no method named %s defined on this type",
name)
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", ty))
}
}

69
analyzer/method_test.go Normal file
View File

@@ -0,0 +1,69 @@
package analyzer
import "testing"
func TestMethodUniqueErr (test *testing.T) {
testStringErr (test,
"Bird.fly already declared at stream0.fspl:3:1", 4, 1,
`
Bird: Int
Bird.[fly] = { }
Bird.[fly distance:Int] = { }
`)
}
func TestMethodUnique (test *testing.T) {
testString (test,
`
Bird: Int
Bird.[fly] = { }
Bird.[land] = { }
Bird.[walk distance:Int] = { }
Bat: Int
Bat.[fly] = { }
[fly] = { }
`)
}
func TestMethodArgumentUniqueErr (test *testing.T) {
testStringErr (test,
"x already listed as argument at stream0.fspl:3:12", 3, 18,
`
Bird: Int
Bird.[main x:Int x:U8 y:Int] = { }
`)
}
func TestMethodArgumentUnique (test *testing.T) {
testString (test,
`
Bird: Int
Bird.[main x:Int y:U8 z:Int] = { }
`)
}
func TestMethodThis (test *testing.T) {
testString (test,
`
Number: Int
Number.[add x:Number]:Number = [++[.this] x]
StringHolder: (. string:String)
StringHolder.[setString string:String] = {
this.string = string
}
`)
}
func TestMethodChained (test *testing.T) {
testString (test,
`
Number: Int
Number.[add x:Number]:Number = [+ [.this] x]
Number.[sub x:Number]:Number = [- [.this] x]
Number.[mul x:Number]:Number = [* [.this] x]
Number.[div x:Number]:Number = [/ [.this] x]
[main]: Number = [~Number 5].[add 8].[mul 3]
`)
}

20
analyzer/misc.go Normal file
View File

@@ -0,0 +1,20 @@
package analyzer
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/entity"
func (this *Tree) assembleSignatureMap (signature *entity.Signature) (*entity.Signature, error) {
signature.ArgumentMap = make(map[string] *entity.Declaration)
signature.ArgumentOrder = make([]string, len(signature.Arguments))
for index, member := range signature.Arguments {
if previous, exists := signature.ArgumentMap[member.Name]; exists {
return signature, errors.Errorf (
member.Position, "%s already listed as argument at %v",
member.Name, previous.Position)
}
signature.ArgumentMap [member.Name] = member
signature.ArgumentOrder[index] = member.Name
signature.Arguments [index] = member
}
return signature, nil
}

33
analyzer/modules-plan.md Normal file
View File

@@ -0,0 +1,33 @@
note to self clean this up afterward and move it into design/modules because
this realllllly sucks.
semantic tree: index top-level declarations by a key:
Key struct {
UUID
Name
}
Tree.Analyze should take in:
UUID
Syntax tree
only analyze one unit at a time.
parser should have no idea about what a unit is, and UUID fields in top level declarations are to be considered semantic.
important: in order for the compiler to make sure everything is analyzed, it does this
module:
take in semantic tree, address, skimming book as parameters
parse metadata file
if currently parsing a unit with this uuid, error
push this units uuid onto a stack
for each dependency unit, recurse, passing in semantic tree and true to skimming
parse unit files into one syntax tree. if skimming, do a skim parse
analyze syntax tree into semantic tree
pop this units uuid off of the stack
have a simpler version for source files
this routine analyzes leaf units (no dependencies) first, which makes them available in the semantic tree for their dependents to depend on. this travels upward the root is reached.

219
analyzer/multiunit_test.go Normal file
View File

@@ -0,0 +1,219 @@
package analyzer
import "testing"
func TestTwoUnit (test *testing.T) {
testUnits (test,
`[main]:something::X = 5`,
"something.fspl",
`+ X:Int`,
)}
func TestUnitPrivateTypeErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::X is private", 1, 8,
`[main]:other::X = 5`,
"other.fspl",
`- X:Int`,
)}
func TestUnitPrivateFunctionErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "function other::[x] is private", 1, 11,
`[y]:Int = other::[x]`,
"other.fspl",
`- [x]:Int = 5`,
)}
func TestUnitPrivateMethodErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "method T.[x] is private", 1, 21,
`[y]:Int = z:other::T.[x]`,
"other.fspl",
`
+ T:Int
- T.[x]:Int = 5`,
)}
func TestUnitAssignRestricted (test *testing.T) {
testUnits (test,
`[main] = {
x:other::RestrictedInt
y:other::RestrictedInt
x = y
}`,
"other.fspl",
`~ RestrictedInt:Int`,
)}
func TestUnitAssignLiteralRestrictedErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::RestrictedInt is restricted", 1, 31,
`[main]:other::RestrictedInt = 5`,
"other.fspl",
`~ RestrictedInt:Int`,
)}
func TestAssignLiteralRestricted (test *testing.T) {
testString (test,
`~ RestrictedInt:Int
[main]:RestrictedInt = 5`,
)}
func TestUnitMemberAccessRestrictedErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::RestrictedStruct is restricted", 3, 3,
`[main] = {
x:other::RestrictedStruct
x.x = 5
}`,
"other.fspl",
`~ RestrictedStruct:(. x:Int y:Int)`,
)}
func TestMemberAccessRestricted (test *testing.T) {
testString (test,
`~ RestrictedStruct:(. x:Int y:Int)
[main] = {
x:RestrictedStruct
x.x = 5
}`,
)}
func TestUnitSubscriptRestrictedErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::RestrictedArr is restricted", 3, 4,
`[main] = {
x:other::RestrictedArr
[.x 0] = 5
}`,
"other.fspl",
`~ RestrictedArr:5:Int`,
)}
func TestSubscriptRestricted (test *testing.T) {
testString (test,
`~ RestrictedArr:5:Int
[main] = {
x:RestrictedArr
[.x 0] = 5
}`,
)}
func TestUnitMathRestrictedErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::RestrictedInt is restricted", 2, 27,
`[main] = {
x:other::RestrictedInt = [+
y:other::RestrictedInt
z:other::RestrictedInt]
}`,
"other.fspl",
`~ RestrictedInt:Int`,
)}
func TestMathRestricted (test *testing.T) {
testString (test,
`~ RestrictedInt:Int
[main] = {
x:RestrictedInt = [+
y:RestrictedInt
z:RestrictedInt]
}`,
)}
func TestNestedUnitTypedef (test *testing.T) {
testUnits (test,
`[main]:layer1::X = 5`,
"layer0.fspl",
`+ X:Int`,
"layer1.fspl",
`+ X:layer0::X`,
)}
func TestUnitBehaviorCallRestrictedErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::RestrictedInterface is restricted", 3, 3,
`[main] = {
x:other::RestrictedInterface
x.[y]
}`,
"other.fspl",
`~ RestrictedInterface:(~ [y])`,
)}
func TestBehaviorCallRestricted (test *testing.T) {
testString (test,
`[main] = {
x:RestrictedInterface
x.[y]
}
~ RestrictedInterface:(~ [y])`,
)}
func TestUnitCastRestrictedErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::RestrictedInt is restricted", 2, 16,
`[main] = {
x:Int = [~Int y:other::RestrictedInt]
}`,
"other.fspl",
`~ RestrictedInt:Int`,
)}
func TestCastRestricted (test *testing.T) {
testString (test,
`[main] = {
x:Int = [~Int y:RestrictedInt]
}
~ RestrictedInt:Int`,
)}
func TestFunctionRestrictedErr (test *testing.T) {
testStringErr (test,
"cannot mark function as restricted", 1, 1,
`~ [f]`,
)}
func TestMethodRestrictedErr (test *testing.T) {
testStringErr (test,
"cannot mark method as restricted", 2, 1,
`T:Int
~ T.[f]`,
)}
func TestUnitInterfaceSatisfaction (test *testing.T) {
testUnits (test,
`[sayHello writer:io::Writer] = writer.[write 'well hello their\n']
[main]: I32 'main' = {
stdout:io::File = 1
[sayHello stdout]
0
}`,
"cstdio.fspl",
`+ FileDescriptor: Int
+ [write file:FileDescriptor buffer:*Byte count:Index]: Index 'write'`,
"io.fspl",
`+ Writer: (~ [write buffer:*:Byte]: Index)
+ File: cstdio::FileDescriptor
+ File.[write buffer:*:Byte]:Index =
cstdio::[write
[~cstdio::FileDescriptor [.this]]
[~*Byte buffer] [#buffer]]`,
)}

View File

@@ -2,141 +2,29 @@ package analyzer
import "testing"
func TestFunctionUniqueErr (test *testing.T) {
testStringErr (test,
"hello already declared at stream0.fspl:2:1", 3, 1,
`
[hello] = { }
[hello] = { }
`)
}
func TestFunctionUniqueErrShadowBuiltin (test *testing.T) {
testStringErr (test,
"cannot shadow builtin true ", 2, 2,
`
[true]:Bool = false
`)
}
func TestFunctionUnique (test *testing.T) {
testString (test,
`
[hello] = { }
[world] = { }
`)
}
func TestTypeUniqueErr (test *testing.T) {
testStringErr (test,
"hello already declared at stream0.fspl:2:1", 3, 1,
`
hello: *Int
hello: (x:Int y:Int)
`)
}
func TestTypeUniqueErrShadowBuiltin (test *testing.T) {
testStringErr (test,
"cannot shadow builtin true ", 2, 2,
`
true:Int
`)
}
func TestTypeUnique (test *testing.T) {
testString (test,
`
hello: *Int
world: (x:Int y:Int)
`)
}
func TestTypeNameErrMissing (test *testing.T) {
testStringErr (test,
"no type named example", 2, 20,
`
[main] = { example:Example }
`)
}
func TestTypeName (test *testing.T) {
testString (test,
`
Example: Int
AllBuiltin: (
int: Int
uint: UInt
byte: Byte
rune: Rune
string: String
i8: I8
i8: I16
i8: I32
i8: I64
u8: U8
u8: U16
u8: U32
u8: U64
f32: F32
f64: F64
bool: Bool)
[main] = {
example:Example
allBuiltin:AllBuiltin
}
`)
}
func TestFunctionUniqueErr (test *testing.T) {
testStringErr (test,
"hello already declared at stream0.fspl:2:1", 3, 1,
`
hello: *Int
[hello] = { }
`)
}
func TestFunctionUnique (test *testing.T) {
testString (test,
`
hello: *Int
[world] = { }
`)
}
func TestMethodUniqueErr (test *testing.T) {
testStringErr (test,
"Bird.fly already declared at stream0.fspl:3:1", 4, 1,
`
Bird: Int
Bird::[fly] = { }
Bird::[fly distance:Int] = { }
`)
}
func TestMethodUnique (test *testing.T) {
testString (test,
`
Bird: Int
Bird::[fly] = { }
Bird::[land] = { }
Bird::[walk distance:Int] = { }
Bat: Int
Bat::[fly] = { }
[fly] = { }
`)
}
func TestMethodNameErrMissing (test *testing.T) {
testStringErr (test,
"no method Type::world", 5, 3,
"no method named world defined on this type", 6, 10,
`
Type: Int
Type::[something] = { }
Type.[something] = { }
[main] = {
instance:Type
[instance::world]
instance.[world]
}
`)
}
func TestMethodNameErrBadLayer (test *testing.T) {
testStringErr (test,
"no method named something defined on this type", 7, 10,
`
Type: Int
Type.[something] = { }
RefType: *Type
[main] = {
instance:RefType
instance.[something]
}
`)
}
@@ -145,90 +33,16 @@ func TestMethodName (test *testing.T) {
testString (test,
`
Type: Int
Type::[world] = { }
Type.[world] = { }
[main] = {
instance:Type
[instance::world]
instance.[world]
ref:*Type = [@ instance]
ref.[world]
}
`)
}
func TestBehaviorUniqueErr (test *testing.T) {
testStringErr (test,
"fly already listed in interface at stream0.fspl:2:8", 2, 14,
`
Bird: ([fly] [fly])
`)
}
func TestBehaviorUnique (test *testing.T) {
testString (test,
`
Bird: ([fly] [land])
`)
}
func TestBehaviorNameErrMissing (test *testing.T) {
testStringErr (test,
"no behavior Bird::swim", 8, 3,
`
Bird: ([fly] [land])
BirdImpl: Int
BirdImpl::[fly] = { }
BirdImpl::[land] = { }
[main] = {
bird:Bird = [@impl:BirdImpl]
[bird::swim]
}
`)
}
func TestBehaviorName (test *testing.T) {
testString (test,
`
Bird: ([fly] [land])
BirdImpl: Int
BirdImpl::[fly] = { }
BirdImpl::[land] = { }
[main] = {
bird:Bird = [@impl:BirdImpl]
[bird::fly]
}
`)
}
func TestFunctionArgumentUniqueErr (test *testing.T) {
testStringErr (test,
"x already declared in block at stream0.fspl:2:8", 2, 14,
`
[main x:Int x:U8 y:Int] = { }
`)
}
func TestFunctionArgumentUnique (test *testing.T) {
testString (test,
`
[main x:Int y:U8 z:Int] = { }
`)
}
func TestMethodArgumentUniqueErr (test *testing.T) {
testStringErr (test,
"x already declared in block at stream0.fspl:3:13", 3, 19,
`
Bird: Int
Bird::[main x:Int x:U8 y:Int] = { }
`)
}
func TestMethodArgumentUnique (test *testing.T) {
testString (test,
`
Bird: Int
Bird::[main x:Int y:U8 z:Int] = { }
`)
}
func TestVariableUniqueErr (test *testing.T) {
testStringErr (test,
"x already declared in block at stream0.fspl:3:2", 5, 2,
@@ -241,20 +55,10 @@ testStringErr (test,
`)
}
func TestVariableUniqueErrShadowBuiltin (test *testing.T) {
testStringErr (test,
"cannot shadow builtin false ", 3, 2,
`
[main] = {
false:Bool = true
}
`)
}
func TestVariableUnique (test *testing.T) {
testString (test,
`
x: Int
[x] = { }
[y] = { }
[main x:Int] = {
x:Int
@@ -268,20 +72,9 @@ x: Int
`)
}
func TestVariableNameErrType (test *testing.T) {
testStringErr (test,
"named type example cannot be used as location expression", 4, 2,
`
example: Int
[main] = {
example = 5
}
`)
}
func TestVariableNameErrFunction (test *testing.T) {
testStringErr (test,
"function example cannot be used as location expression", 4, 2,
"no variable named example", 4, 2,
`
[example] = { }
[main] = {
@@ -292,7 +85,7 @@ testStringErr (test,
func TestVariableNameErrMissing (test *testing.T) {
testStringErr (test,
"no variable named example", 4, 2,
"no variable named example", 3, 2,
`
[main] = {
example = 5
@@ -328,96 +121,13 @@ testString (test,
example = 3
}
}
}
`)
}
func TestStructMemberUniqueErr (test *testing.T) {
testStringErr (test,
"x already listed in struct at stream0.fspl:3:2", 6, 2,
`
Bird: (
x:Int
y:Int
z:Int
x:U8)
`)
}
func TestStructMemberUnique (test *testing.T) {
testString (test,
`
Bird: (
x:Int
y:Int
z:Int
a:U8)
`)
}
func TestStructMemberNameErrMissing (test *testing.T) {
testStringErr (test,
"no member Type.world", 5, 2,
`
Type: (something:Int)
[main] = {
instance:Type
instance.world = 5
}
`)
}
func TestStructMemberName (test *testing.T) {
testString (test,
`
Type: (world:Int)
[main] = {
instance:Type
instance.world = 5
}
`)
}
func TestStructLiteralMemberUniqueErr (test *testing.T) {
testStringErr (test,
"x already listed in struct literal at stream0.fspl:5:2", 7, 2,
`
Point: (x:Int y:Int z:Int)
[main] = {
point:Point = (
x: 5
y: 0
x: 32)
}
`)
}
func TestStructLiteralMemberUnique (test *testing.T) {
testString (test,
`
Point: (x:Int y:Int z:Int)
[main] = {
point:Point = (
x: 5
y: 0
z: 32)
}
`)
}
func TestFunctionNameErrType (test *testing.T) {
testStringErr (test,
"cannot call named type example", 3, 3,
`
example: Int
[main] = [example]
`)
}
func TestFunctionNameErrVar (test *testing.T) {
testStringErr (test,
"cannot call example:Int", 4, 3,
"no function named example", 4, 2,
`
[main] = {
example:Int
@@ -428,7 +138,7 @@ testStringErr (test,
func TestFunctionNameErrMissing (test *testing.T) {
testStringErr (test,
"no function named example", 2, 3,
"no function named example", 2, 10,
`
[main] = [example]
`)
@@ -441,3 +151,55 @@ testString (test,
[main] = [example]
`)
}
func TestInterfaceBehaviorNameErrMissing (test *testing.T) {
testStringErr (test,
"no behavior or method named swim", 8, 6,
`
Bird: (~ [fly] [land])
BirdImpl: Int
BirdImpl.[fly] = { }
BirdImpl.[land] = { }
[main] = {
bird:Bird = impl:BirdImpl
bird.[swim]
}
`)
}
func TestInterfaceBehaviorName (test *testing.T) {
testString (test,
`
Bird: (~ [fly] [land])
BirdImpl: Int
BirdImpl.[fly] = { }
BirdImpl.[land] = { }
[main] = {
bird:Bird = impl:BirdImpl
bird.[fly]
}
`)
}
func TestStructMemberNameErrMissing (test *testing.T) {
testStringErr (test,
"no member instance.world", 5, 10,
`
Type: (. something:Int)
[main] = {
instance:Type
instance.world = 5
}
`)
}
func TestStructMemberName (test *testing.T) {
testString (test,
`
Type: (. world:Int)
[main] = {
instance:Type
instance.world = 5
}
`)
}

View File

@@ -2,112 +2,82 @@ package analyzer
import "testing"
func TestOperatorModUnderArgCountErr (test *testing.T) {
func TestOperationModUnderArgCountErr (test *testing.T) {
testStringErr (test,
"wrong argument count for %", 10, 2,
"wrong argument count for %", 2, 10,
`
[main] = [% 2]
`)
}
func TestOperatorModOverArgCountErr (test *testing.T) {
func TestOperationModOverArgCountErr (test *testing.T) {
testStringErr (test,
"wrong argument count for %", 10, 2,
"wrong argument count for %", 2, 10,
`
[main] = [% 2 3 1]
`)
}
func TestOperatorLogicalNegationUnderArgCountErr (test *testing.T) {
func TestOperationLogicalNegationOverArgCountErr (test *testing.T) {
testStringErr (test,
"wrong argument count for !", 10, 2,
`
[main] = [!]
`)
}
func TestOperatorLogicalNegationOverArgCountErr (test *testing.T) {
testStringErr (test,
"wrong argument count for !", 10, 2,
"wrong argument count for !", 2, 10,
`
[main] = [! 348 92]
`)
}
func TestOperatorBitwiseNegationUnderArgCountErr (test *testing.T) {
func TestOperationBitwiseNegationOverArgCountErr (test *testing.T) {
testStringErr (test,
"wrong argument count for !!", 10, 2,
`
[main] = [!!]
`)
}
func TestOperatorBitwiseNegationOverArgCountErr (test *testing.T) {
testStringErr (test,
"wrong argument count for !!", 10, 2,
"wrong argument count for !!", 2, 10,
`
[main] = [!! 348 92]
`)
}
func TestOperatorBitShiftLeftUnderArgCountErr (test *testing.T) {
func TestOperationBitShiftLeftOverArgCountErr (test *testing.T) {
testStringErr (test,
"wrong argument count for <<", 10, 2,
`
[main] = [<<]
`)
}
func TestOperatorBitShiftLeftOverArgCountErr (test *testing.T) {
testStringErr (test,
"wrong argument count for <<", 10, 2,
"wrong argument count for <<", 2, 10,
`
[main] = [<< 348 92 324]
`)
}
func TestOperatorBitShiftRightUnderArgCountErr (test *testing.T) {
func TestOperationBitShiftRightOverArgCountErr (test *testing.T) {
testStringErr (test,
"wrong argument count for >>", 10, 2,
`
[main] = [>>]
`)
}
func TestOperatorBitShiftRightOverArgCountErr (test *testing.T) {
testStringErr (test,
"wrong argument count for >>", 10, 2,
"wrong argument count for >>", 2, 10,
`
[main] = [>> 348 92 324]
`)
}
func TestOperatorArgCount (test *testing.T) {
func TestOperationArgCount (test *testing.T) {
testString (test,
`
[main] = {
[+ 1 2 3 4]
[++ 1]
[- 1 2 3 4]
[-- 1]
[* 5 6]
[/ 32 16]
[% 5 6]
[!! true]
[|| false]
[&& true true false true]
[^^ true true false true]
[! 1]
[| 1 2]
[& 3 1]
[^ 1 2 3 4]
[<< 1 8]
[>> 8 1]
[< 1 2 3 4]
[> 1 2 3 4]
[<= 10 5 3]
[>= 1 2 3 3 4]
[= 2 2 2 3 9]
x:Int
b:Bool
x = [+ 1 2 3 4]
x = [++ 1]
x = [- 1 2 3 4]
x = [-- 1]
x = [* 5 6]
x = [/ 32 16]
x = [% 5 6]
x = [!! 1]
x = [|| 1 2]
x = [&& 3 1 5]
x = [^^ 1 2 3 4]
b = [! true]
b = [| true false]
b = [& true true]
b = [^ true true false true]
x = [<< 1 8]
x = [>> 8 1]
b = [< x 2 3 4]
b = [> x 2 3 4]
b = [<= x 5 3]
b = [>= x 2 3 3 4]
b = [= x 2 2 3 9]
}
`)
}

132
analyzer/scope.go Normal file
View File

@@ -0,0 +1,132 @@
package analyzer
import "git.tebibyte.media/fspl/fspl/entity"
// scopeContextManager is a stack of scopeContexts allowing multiple stacks of
// scopes to be managed at the same time in case the analysis of one scoped
// entity causes the analysis of another.
type scopeContextManager []scopeContext
func (this *scopeContextManager) pushScopeContext (declaration entity.TopLevel) {
*this = append(*this, scopeContext { declaration: declaration })
}
func (this *scopeContextManager) popScopeContext () {
this.assertPopulated()
*this = (*this)[:len(*this) - 1]
}
func (this *scopeContextManager) pushLoop (loop *entity.Loop) {
this.assertPopulated()
(*this)[len(*this) - 1].pushLoop(loop)
}
func (this *scopeContextManager) popLoop () {
this.assertPopulated()
(*this)[len(*this) - 1].popLoop()
}
func (this *scopeContextManager) topLoop () (*entity.Loop, bool) {
this.assertPopulated()
return (*this)[len(*this) - 1].topLoop()
}
func (this *scopeContextManager) topDeclaration () (entity.TopLevel, bool) {
if len(*this) < 1 { return nil, false }
return (*this)[len(*this) - 1].declaration, true
}
func (this *scopeContextManager) pushScope (scope entity.Scoped) {
this.assertPopulated()
(*this)[len(*this) - 1].pushScope(scope)
}
func (this *scopeContextManager) popScope () {
this.assertPopulated()
(*this)[len(*this) - 1].popScope()
}
func (this *scopeContextManager) topScope () (entity.Scoped, bool) {
this.assertPopulated()
return (*this)[len(*this) - 1].topScope()
}
func (this *scopeContextManager) variable (name string) *entity.Declaration {
this.assertPopulated()
return (*this)[len(*this) - 1].variable(name)
}
func (this *scopeContextManager) addVariable (declaration *entity.Declaration) {
this.assertPopulated()
(*this)[len(*this) - 1].addVariable(declaration)
}
func (this *scopeContextManager) assertPopulated () {
if len(*this) < 1 {
panic("scopeContextManager must have at least 1 scope context")
}
}
// scopeContext is a stack of scopes and loops used when analyzing scoped
// entities.
type scopeContext struct {
scopes []entity.Scoped
loops []*entity.Loop
declaration entity.TopLevel
}
func (this *scopeContext) pushLoop (loop *entity.Loop) {
this.loops = append(this.loops, loop)
}
func (this *scopeContext) popLoop () {
this.assertLoopPopulated()
this.loops = this.loops[:len(this.loops) - 1]
}
func (this *scopeContext) topLoop () (*entity.Loop, bool) {
if len(this.loops) < 1 { return nil, false }
return this.loops[len(this.loops) - 1], true
}
func (this *scopeContext) pushScope (scope entity.Scoped) {
this.scopes = append(this.scopes, scope)
}
func (this *scopeContext) popScope () {
this.assertScopePopulated()
this.scopes = this.scopes[:len(this.scopes) - 1]
}
func (this *scopeContext) topScope () (entity.Scoped, bool) {
if len(this.scopes) < 1 { return nil, false }
return this.scopes[len(this.scopes) - 1], true
}
func (this *scopeContext) variable (name string) *entity.Declaration {
this.assertScopePopulated()
for index := len(this.scopes) - 1; index >= 0; index -- {
variable := this.scopes[index].Variable(name)
if variable != nil {
return variable
}
}
return nil
}
func (this *scopeContext) addVariable (declaration *entity.Declaration) {
this.assertScopePopulated()
this.scopes[len(this.scopes) - 1].AddVariable(declaration)
}
func (this *scopeContext) assertScopePopulated () {
if len(this.scopes) < 1 {
panic("scopeContext must have at least 1 scope")
}
}
func (this *scopeContext) assertLoopPopulated () {
if len(this.scopes) < 1 {
panic("scopeContext must have at least 1 loop")
}
}

View File

@@ -1,82 +1,199 @@
package analyzer
import "io"
import "fmt"
import "testing"
import "strings"
import "github.com/alecthomas/participle/v2"
import "git.tebibyte.media/sashakoshka/fspl/parser"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/lexer"
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/parser/fspl"
func testStringErr (
test *testing.T,
errMessage string,
errLine int,
errColumn int,
errRow int,
errStart int,
input string,
) {
testReaderErr (
test,
errMessage, errLine, errColumn,
strings.NewReader(input))
}
func testReaderErr (
test *testing.T,
errMessage string,
errLine int,
errColumn int,
inputs ...io.Reader,
) {
ast := parser.Tree { }
for index, stream := range inputs {
err := ast.Parse(fmt.Sprintf("stream%d.fspl", index), stream)
if err != nil {
test.Error("parser returned error: ", err)
return
}
}
address := entity.Address("stream0.fspl")
ast, ok := treeOf(test, address, input, false)
if !ok { return }
tree := Tree { }
err := tree.Analyze(ast)
err := tree.Analyze(address.UUID(), nil, ast)
if err == nil {
test.Error("analyzer did not return error")
return
}
got := err.(participle.Error)
gotMessage := got.Message()
gotLine := got.Position().Line
gotColumn := got.Position().Column
correct :=
gotMessage == errMessage &&
gotLine == errLine &&
gotColumn == errColumn
if !correct {
test.Log("errors do not match")
test.Logf("got:\n%v:%v: %v", gotLine, gotColumn, gotMessage)
test.Logf("correct:\n%v:%v: %v", errLine, errColumn, errMessage)
test.Fail()
return
}
compareErr(test, address, string(address), errMessage, errRow, errStart, err)
}
func testString (test *testing.T, input string) {
testReader(test, strings.NewReader(input))
}
func testReader (test *testing.T, inputs ...io.Reader) {
ast := parser.Tree { }
for index, stream := range inputs {
err := ast.Parse(fmt.Sprintf("stream%d.fspl", index), stream)
if err != nil {
test.Error("parser returned error: ", err)
return
}
}
address := entity.Address("stream0.fspl")
ast, ok := treeOf(test, address, input, false)
if !ok { return }
tree := Tree { }
err := tree.Analyze(ast)
err := tree.Analyze(address.UUID(), nil, ast)
if err != nil {
test.Error("analyzer returned error: ", err)
test.Error("analyzer returned error:\n" + errors.Format(err))
return
}
}
func testUnits (
test *testing.T,
main string,
dependencies ...string,
) {
if len(dependencies) % 2 != 0 {
panic("dependencies len must be even. forget an address?")
}
tree := Tree { }
nicknames := make(map[string] uuid.UUID)
// dependencies
for index := 0; index < len(dependencies) - 1; index += 2 {
address := entity.Address(dependencies[index])
source := dependencies[index + 1]
ast, ok := treeOf(test, address, source, true)
if !ok { return }
err := tree.Analyze(address.UUID(), nicknames, ast)
if err != nil {
test.Error("analyzer returned error:\n" + errors.Format(err))
return
}
unit := address.UUID()
nickname, ok := address.Nickname()
if !ok {
test.Errorf("could not generate nickname for %v", address)
return
}
nicknames[nickname] = unit
}
// main
address := entity.Address("main.fspl")
ast, ok := treeOf(test, address, main, false)
if !ok { return }
err := tree.Analyze(address.UUID(), nicknames, ast)
if err != nil {
test.Error("analyzer returned error:\n" + errors.Format(err))
return
}
}
func testUnitsErr (
test *testing.T,
errFile string,
errMessage string,
errRow int,
errStart int,
main string,
dependencies ...string,
) {
if len(dependencies) % 2 != 0 {
panic("dependencies len must be even. forget an address?")
}
tree := Tree { }
nicknames := make(map[string] uuid.UUID)
// dependencies
for index := 0; index < len(dependencies) - 1; index += 2 {
address := entity.Address(dependencies[index])
source := dependencies[index + 1]
ast, ok := treeOf(test, address, source, true)
if !ok { return }
err := tree.Analyze(address.UUID(), nicknames, ast)
if err != nil {
compareErr(test, address, errFile, errMessage, errRow, errStart, err)
return
}
unit := address.UUID()
nickname, ok := address.Nickname()
if !ok {
test.Errorf("could not generate nickname for %v", address)
return
}
nicknames[nickname] = unit
}
// main
address := entity.Address("main.fspl")
ast, ok := treeOf(test, address, main, false)
if !ok { return }
err := tree.Analyze(address.UUID(), nicknames, ast)
if err != nil {
compareErr(test, address, errFile, errMessage, errRow, errStart, err)
return
}
test.Error("analyzer did not return error")
}
func treeOf (test *testing.T, address entity.Address, input string, skim bool) (fsplParser.Tree, bool) {
ast := fsplParser.Tree { }
lx, err := lexer.LexReader (
string(address),
strings.NewReader(input))
if err != nil {
test.Error("lexer returned error:\n" + errors.Format(err))
return ast, false
}
if skim {
err = ast.Skim(lx)
} else {
err = ast.Parse(lx)
}
if err != nil {
test.Error("parser returned error:\n" + errors.Format(err))
return ast, false
}
return ast, true
}
func compareErr (
test *testing.T,
address entity.Address,
errFile string,
errMessage string,
errRow int,
errStart int,
err error,
) bool {
got := err.(errors.Error)
gotMessage := got.Error()
gotFile := got.Position().File
gotRow := got.Position().Row + 1
gotStart := got.Position().Start + 1
correct :=
gotFile == errFile &&
gotMessage == errMessage &&
gotRow == errRow &&
gotStart == errStart
if correct { return true }
test.Log("errors do not match:")
if gotMessage != errMessage {
test.Log("- messages do not match:")
test.Logf(" [%s]", gotMessage)
test.Logf(" [%s]", errMessage)
}
if gotRow != errRow {
test.Logf("- rows do not match: (%d, %d)", gotRow, errRow)
}
if gotStart != errStart {
test.Logf("- columns do not match: (%d, %d)", gotStart, errStart)
}
test.Log("got:\n" + errors.Format(got))
test.Logf("correct:\n%v:%v: %v", errRow, errStart, errMessage)
test.Fail()
return false
}

View File

@@ -1,27 +1,39 @@
package analyzer
import "github.com/alecthomas/participle/v2"
import "github.com/alecthomas/participle/v2/lexer"
import "git.tebibyte.media/sashakoshka/fspl/entity"
import "git.tebibyte.media/sashakoshka/fspl/parser"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/parser/fspl"
// Tree is a semantic tree. It contains the same constructs as the syntax tree,
// but with their semantic information filled in.
type Tree struct {
rawTypes map[string] *entity.Typedef
rawFunctions map[string] *entity.Function
rawMethods map[string] *entity.Method
ast parser.Tree
Types map[entity.Key] *entity.Typedef
Functions map[entity.Key] *entity.Function
Types map[string] entity.Type
Functions map[string] *entity.Function
unit uuid.UUID
ast fsplParser.Tree
nicknames map[string] uuid.UUID
rawTypes map[entity.Key] *entity.Typedef
rawFunctions map[entity.Key] *entity.Function
rawMethods map[entity.Key] *entity.Method
scopeContextManager
incompleteTypes map[entity.Key] *entity.Typedef
}
// Analyze takes in an AST and analyzes its contents within the context of this
// semantic tree. Analyzed entities will be added to the tree.
func (this *Tree) Analyze (ast parser.Tree) error {
func (this *Tree) Analyze (
unit uuid.UUID,
nicknames map[string] uuid.UUID,
ast fsplParser.Tree,
) error {
this.ensure()
this.ast = ast
this.resetRawMaps()
this.ast = ast
this.unit = unit
this.nicknames = nicknames
err := this.assembleRawMaps()
if err != nil { return err }
return this.analyzeDeclarations()
@@ -29,72 +41,142 @@ func (this *Tree) Analyze (ast parser.Tree) error {
func (this *Tree) assembleRawMaps () error {
for _, declaration := range this.ast.Declarations {
switch declaration.(type) {
switch declaration := declaration.(type) {
case *entity.Typedef:
ty := declaration.(*entity.Typedef)
err := this.rawNameUnique(ty.Pos, ty.Name)
key := entity.Key {
Unit: this.unit,
Name: declaration.Name,
}
err := this.topLevelNameAvailable(declaration.Position, key)
if err != nil { return err }
this.rawTypes[ty.Name] = ty
declaration.Methods = make(map[string] *entity.Method)
this.rawTypes[key] = declaration
case *entity.Function:
function := declaration.(*entity.Function)
err := this.rawNameUnique (
function.Pos,
function.Signature.Name)
key := entity.Key {
Unit: this.unit,
Name: declaration.Signature.Name,
}
err := this.topLevelNameAvailable(declaration.Position, key)
if err != nil { return err }
this.rawFunctions[function.Signature.Name] = function
this.rawFunctions[key] = declaration
case *entity.Method:
method := declaration.(*entity.Method)
name := method.TypeName + "." + method.Signature.Name
err := this.rawNameUnique(method.Pos, name)
key := entity.Key {
Unit: this.unit,
Name: declaration.TypeName,
Method: declaration.Signature.Name,
}
err := this.topLevelNameAvailable(declaration.Position, key)
if err != nil { return err }
this.rawMethods[name] = method
this.rawMethods[key] = declaration
}
}
return nil
}
func (this *Tree) rawNameUnique (currentPos lexer.Position, name string) error {
var pos lexer.Position
var unique = true
if ty, isType := this.rawTypes[name]; isType {
pos = ty.Pos
unique = false
func (this *Tree) topLevelNameAvailable (currentPos errors.Position, key entity.Key) error {
if _, isPrimitive := primitiveTypes[key.Name]; isPrimitive {
return errors.Errorf (
currentPos, "cannot shadow primitive %s",
key.Name)
}
if function, isFunction := this.rawFunctions[name]; isFunction {
pos = function.Pos
unique = false
if _, isBuiltin := builtinTypes[key.Name]; isBuiltin {
return errors.Errorf (
currentPos, "cannot shadow builtin %s",
key.Name)
}
if method, isMethod := this.rawMethods[name]; isMethod {
pos = method.Pos
unique = false
}
if unique {
return nil
} else {
return participle.Errorf (
if ty, isType := this.rawTypes[key]; isType {
return errors.Errorf (
currentPos, "%s already declared at %v",
name, pos)
key.Name, ty.Position)
}
if function, isFunction := this.rawFunctions[key]; isFunction {
return errors.Errorf (
currentPos, "%s already declared at %v",
key.Name, function.Position)
}
if method, isMethod := this.rawMethods[key]; isMethod {
return errors.Errorf (
currentPos, "%s.%s already declared at %v",
key.Name, key.Method, method.Position)
}
return nil
}
func (this *Tree) analyzeDeclarations () error {
for name := range this.rawTypes {
ty, err := this.analyzeTypedef(name)
for key, rawType := range this.rawTypes {
ty, err := this.analyzeTypedef(rawType.Position, key, false)
if err != nil { return err }
this.Types[key] = ty
}
for key, rawFunction := range this.rawFunctions {
_, err := this.analyzeFunction(rawFunction.Position, key)
if err != nil { return err }
}
for _, rawMethod := range this.rawMethods {
_, err := this.analyzeMethod (
rawMethod.Position,
entity.Key {
Unit: this.unit,
Name: rawMethod.TypeName,
Method: rawMethod.Signature.Name,
})
if err != nil { return err }
this.Types[name] = ty
}
// TODO functions
// TODO methods
return nil
}
func (this *Tree) resolveNickname (pos errors.Position, nickname string) (uuid.UUID, error) {
if nickname == "" { return this.unit, nil }
fail := func () (uuid.UUID, error) {
return uuid.UUID { }, errors.Errorf (
pos, "no unit named %s",
nickname)
}
if this.nicknames == nil { return fail() }
unit, ok := this.nicknames[nickname]
if !ok { return fail() }
return unit, nil
}
func (this *Tree) ensure () {
if this.rawTypes != nil { return }
this.rawTypes = make(map[string] *entity.Typedef)
this.rawFunctions = make(map[string] *entity.Function)
this.rawMethods = make(map[string] *entity.Method)
this.Types = make(map[string] entity.Type)
this.Functions = make(map[string] *entity.Function)
this.resetRawMaps()
this.incompleteTypes = make(map[entity.Key] *entity.Typedef)
this.Types = make(map[entity.Key] *entity.Typedef)
this.Functions = make(map[entity.Key] *entity.Function)
for name, ty := range builtinTypes {
// builtin types have a zero UUID in their key
this.Types[entity.Key { Name: name }] = &entity.Typedef {
Acc: entity.AccessPublic,
Name: name,
Type: ty,
}
}
}
func (this *Tree) resetRawMaps () {
if this.rawTypes != nil &&
this.rawFunctions != nil &&
this.rawMethods != nil {
if len(this.rawTypes) > 0 {
this.rawTypes = make(map[entity.Key] *entity.Typedef)
}
if len(this.rawFunctions) > 0 {
this.rawFunctions = make(map[entity.Key] *entity.Function)
}
if len(this.rawMethods) > 0 {
this.rawMethods = make(map[entity.Key] *entity.Method)
}
} else {
this.rawTypes = make(map[entity.Key] *entity.Typedef)
this.rawFunctions = make(map[entity.Key] *entity.Function)
this.rawMethods = make(map[entity.Key] *entity.Method)
}
}

View File

@@ -1,100 +1,278 @@
package analyzer
import "fmt"
import "github.com/alecthomas/participle/v2"
import "git.tebibyte.media/sashakoshka/fspl/entity"
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/entity"
func (this *Tree) analyzeTypedef (pos lexer.Position, name string) (entity.Type, error) {
if ty, exists := this.Types[name]; exists {
return ty
func (this *Tree) analyzeTypedef (
pos errors.Position,
key entity.Key,
acceptIncomplete bool,
) (
*entity.Typedef,
error,
) {
// return a builtin if it exists (search types with zero uuid)
if definition, exists := this.Types[entity.Key { Name: key.Name }]; exists {
return definition, nil
}
// return if exists already
if definition, exists := this.Types[key]; exists {
return definition, nil
}
definition, exists := this.RawTypes[name]
if !exists {
return participle.Errorf(pos, "no type named %s", name)
// check if incomplete
if definition, incomplete := this. incompleteTypes[key]; incomplete {
if acceptIncomplete {
return definition, nil
} else {
return nil, errors.Errorf (
pos, "type %s cannot be used in this context",
key.Name)
}
}
return this.analyzeType(definition.Type)
// analyze if it still needs to be analyzed
if definition, exists := this.rawTypes[key]; exists {
// update unit
definition.Unit = key.Unit
// analyze
var err error
definition.Type, err = this.analyzeTypeInternal (
definition.Type, definition, false)
return definition, err
}
// if we couldn't get the type, error
return nil, errors.Errorf(pos, "no type named %s", key.Name)
}
func (this *Tree) analyzeType (ty entity.Type) (entity.Type, error) {
if ty == entity.Void() { return ty }
var err error
func (this *Tree) analyzeType (
ty entity.Type,