Compare commits

...

727 Commits
v0.2.0 ... main

Author SHA1 Message Date
Sasha Koshka 3bc504aae6 Clarify install instructions in README.md 2024-04-19 08:31:57 +00:00
Sasha Koshka 1085cfdc3c Typo 2024-04-12 05:10:28 +00:00
Sasha Koshka ecd9f79aae Add FAQ to README.md 2024-04-12 05:08:55 +00:00
Sasha Koshka c934a6c76c Updated readme (conditional comp. -> GOOS/GOARCH) 2024-04-09 19:00:59 +00:00
Sasha Koshka 018dc31d04 Update FSPL parser readme 2024-04-03 11:04:14 -04:00
Sasha Koshka 4b90ba77d3 Capitalize W*ndows 2024-04-02 18:36:52 +00:00
Sasha Koshka 2a6f086350 Merge pull request 'implement-reserved-identifiers' (#76) from implement-reserved-identifiers into main
Reviewed-on: #76
2024-04-02 07:00:59 +00:00
Sasha Koshka 767e7bd33c Fix analyzer tests that used "switch" as function name 2024-04-02 02:59:49 -04:00
Sasha Koshka 6c41e71ade Analyzer tests for reserved idents 2024-04-02 02:59:42 -04:00
Sasha Koshka 423f3ba22f Add function in parser for checking if a word is reserved 2024-04-02 02:59:11 -04:00
Sasha Koshka 08eedaf74d Merge pull request 'os-native-filepaths' (#75) from os-native-filepaths into main
Reviewed-on: #75
2024-03-31 05:34:22 +00:00
Sasha Koshka 88e10158ae It is now possible for tests to pass on windows 2024-03-31 01:33:28 -04:00
Sasha Koshka 93d4aea1b1 Compiler tests properly add extension to obj files 2024-03-31 01:33:28 -04:00
Sasha Koshka 810175b6c9 Object files have platform specific extensions 2024-03-31 01:33:28 -04:00
Sasha Koshka b6b0a1e592 parent 00110039e2
author Sasha Koshka <sashakoshka@tebibyte.media> 1711851967 -0400
committer Sasha Koshka <sashakoshka@tebibyte.media> 1711862974 -0400

Compiler tests now run on Windows

Compiler tests show linker log

Compiler tests show linker log
2024-03-31 01:33:08 -04:00
Sasha Koshka 00110039e2 Compiler defaults to .o when output name isn't specified 2024-03-28 18:16:58 -04:00
Sasha Koshka 15e418d8c1 Add custom fs implementation 2024-03-28 18:12:37 -04:00
Sasha Koshka 1c61235b63 openAbsolute should strip out the volume name on Windows 2024-03-28 17:16:50 -04:00
Sasha Koshka 4e6103418c Compiler normalizes paths before passing them to an fs 2024-03-28 16:52:31 -04:00
Sasha Koshka e805060370 Generator native uses GOARCH and GOOS instead of cond. compilation 2024-03-28 16:02:56 -04:00
Sasha Koshka eccb2e9a5b Compiler uses runtime.GOOS instead of conditional compilation 2024-03-28 13:11:27 -04:00
Sasha Koshka e574b248e5 Total windows path separator death 2024-03-27 13:39:38 -04:00
Sasha Koshka 43ed297a12 Add Windows paths to design/units.md 2024-03-27 13:38:13 -04:00
Sasha Koshka 76615df4d4 Add option to specify module search paths manually 2024-03-27 13:13:22 -04:00
Sasha Koshka 2caed5b4ae Add an executable filetype 2024-03-27 12:52:20 -04:00
Sasha Koshka cd396952f2 Certain filename extentions depend on target os 2024-03-27 12:40:45 -04:00
Sasha Koshka 0404202691 Compiler takes in a target 2024-03-27 12:10:19 -04:00
Sasha Koshka 92ad52b2aa Add OS field to Target 2024-03-27 12:05:30 -04:00
Sasha Koshka 10246f7268 Add untested windows native paths 2024-03-27 11:24:26 -04:00
Sasha Koshka 2edda8a960 Add stub for Windows native parameters 2024-03-26 13:15:56 -04:00
Sasha Koshka 11e7a83eb4 Add native paths for unix 2024-03-26 13:15:03 -04:00
Sasha Koshka 9b45bc56d4 Resolver is now used behind a pointer 2024-03-26 13:12:38 -04:00
Sasha Koshka b9e0b3265e fsplc and fsplmod use rooted paths to identify things
Remedies #71
2024-03-26 02:07:27 -04:00
Sasha Koshka 48fa712cb7 Update fsplc/doc.go usage 2024-03-26 02:07:04 -04:00
Sasha Koshka cad6a0759a Compiler now uses LLVM-like output filetype names
Remedies #40
2024-03-26 01:53:10 -04:00
Sasha Koshka 6c7fcf6045 Add compiler test for puts libc function 2024-03-26 01:18:39 -04:00
Sasha Koshka eb2c1c8aed Fixed error message for types in match exprs not being unions 2024-03-26 01:12:47 -04:00
Sasha Koshka 78ea6e45b2 Update roadmap 2024-03-26 01:01:02 -04:00
Sasha Koshka ebafbc2a38 Merge pull request 'implement-switch-expressions' (#70) from implement-switch-expressions into main
Reviewed-on: #70
2024-03-26 04:55:45 +00:00
Sasha Koshka 650289f03e Generate switch statements 2024-03-26 00:42:27 -04:00
Sasha Koshka 5dda7b2186 Generate match statement default cases 2024-03-25 22:34:24 -04:00
Sasha Koshka 4931f97496 Switch statement analysis passes tests 2024-03-25 19:50:54 -04:00
Sasha Koshka f3bdfef5c5 Add analyzer tests to switch statements 2024-03-25 19:49:48 -04:00
Sasha Koshka a50a5febb9 Untested analysis of switch statements 2024-03-25 12:08:28 -04:00
Sasha Koshka 6916b3b7b1 Analyze match expression default cases 2024-03-25 11:29:24 -04:00
Sasha Koshka d8f82d3646 Parse switch expressions 2024-03-25 02:18:18 -04:00
Sasha Koshka e27fb9e79b Parse default match cases 2024-03-25 01:53:40 -04:00
Sasha Koshka 1f04c3a593 Added switch statements, default cases to entity 2024-03-25 01:16:41 -04:00
Sasha Koshka 7e05785c09 Added switch statements to spec 2024-03-25 00:46:30 -04:00
Sasha Koshka dcad5a2d18 Update roadmap 2024-03-25 00:36:04 +00:00
Sasha Koshka 3cd3384006 Remove the star from array literals 2024-03-24 20:31:25 -04:00
Sasha Koshka 7f58f7da8b Change the compiler test names to make them more legible 2024-03-24 20:09:10 -04:00
Sasha Koshka 4342c15e38 Merge pull request 'implement-range-loops' (#68) from implement-range-loops into main
Reviewed-on: #68
2024-03-25 00:03:53 +00:00
Sasha Koshka 18e7850b58 Update generator tests 2024-03-24 20:02:57 -04:00
Sasha Koshka 70c58f74de Fix assignment from array to slice 2024-03-24 19:57:24 -04:00
Sasha Koshka 185ef7f6f9 For loops loop back around to the loop header 2024-03-24 03:35:04 -04:00
Sasha Koshka f3c0901493 For loop elements are properly copied 2024-03-24 03:09:33 -04:00
Sasha Koshka f84ddf8cad What? 2024-03-20 13:29:26 -04:00
Sasha Koshka cd08801a29 Added a timeout for compiler tests 2024-03-20 12:38:46 -04:00
Sasha Koshka 84e21b3832 Update generator test cases 2024-03-20 11:54:49 -04:00
Sasha Koshka 648b1df547 Generator generates valid gep index for for loop indexing 2024-03-20 11:51:44 -04:00
Sasha Koshka a4443444c1 Add compiler test cases for loops 2024-03-20 11:35:14 -04:00
Sasha Koshka a647f27e8c Fix test cases 2024-03-20 11:19:24 -04:00
Sasha Koshka ecd6eba434 Loops now handle multiple break statements correctly 2024-03-20 04:19:47 -04:00
Sasha Koshka 50f088842a For loop generation should be more correct now 2024-03-20 02:55:31 -04:00
Sasha Koshka 491a9b2369 Untested for loop generation 2024-03-20 02:52:01 -04:00
Sasha Koshka a9adc77658 For loop generation stub 2024-03-20 02:09:26 -04:00
Sasha Koshka 16686bfa3d Update README.md 2024-03-19 19:15:18 +00:00
Sasha Koshka 7cf770df7f Update usage instructions 2024-03-19 19:13:07 +00:00
Sasha Koshka 909d463637 Update roadmap part one bazillion 2024-03-19 18:42:33 +00:00
Sasha Koshka 9b1f6d695c Oh come on 2024-03-19 18:39:52 +00:00
Sasha Koshka 838c34b0a1 Update roadmap again 2024-03-19 18:39:29 +00:00
Sasha Koshka 7d74e59e64 Update roadmap 2024-03-19 18:30:05 +00:00
Sasha Koshka c0476f14ae Analyze for loops 2024-03-16 13:25:53 -04:00
Sasha Koshka bb1886ab9c Add tests for for loop analysis 2024-03-16 13:23:35 -04:00
Sasha Koshka 197373ee27 Add breakable interface 2024-03-16 01:43:56 -04:00
Sasha Koshka c576c4022e Add for parsing 2024-03-15 03:03:59 -04:00
Sasha Koshka 67a8b67dec Add for parsing to parser test 2024-03-15 03:03:39 -04:00
Sasha Koshka 8d69e4cdd3 Fix For.String() 2024-03-15 03:02:50 -04:00
Sasha Koshka ec873ea895 Index and Element must be declarations 2024-03-15 02:50:28 -04:00
Sasha Koshka 45ab5d95a2 Add range loops to entity 2024-03-14 22:28:50 -04:00
Sasha Koshka bb2caeb88a Add range loops to spec 2024-03-14 22:28:35 -04:00
Sasha Koshka b07dbf8eba Merge pull request 'entity-consistency' (#57) from entity-consistency into main
Reviewed-on: #57
2024-03-14 07:21:34 +00:00
Sasha Koshka babcce643f Update compiler 2024-03-14 03:20:55 -04:00
Sasha Koshka 6be48aea28 Update generator 2024-03-14 03:20:47 -04:00
Sasha Koshka f58c01be52 Updated analyzer 2024-03-14 03:18:46 -04:00
Sasha Koshka 4df7a8905e Updated parser 2024-03-14 03:14:08 -04:00
Sasha Koshka 2be41be609 Add the cool epic to meta 2024-03-14 02:43:59 -04:00
Sasha Koshka 0a56a61f4f Only top level entities are in toplevel.go 2024-03-14 02:41:51 -04:00
Sasha Koshka fc2fb42e53 Misc entities now have Pos/Position thing going on 2024-03-14 02:40:13 -04:00
Sasha Koshka 9335841fc0 Clean up wording in type interface documentation 2024-03-14 02:37:58 -04:00
Sasha Koshka e35a576022 Add more behaviors to expression interface 2024-03-14 02:37:12 -04:00
Sasha Koshka 1c9700378c Moved key/hash stuff into its own file 2024-03-14 02:36:39 -04:00
Sasha Koshka 56b91d788f Add more methods to toplevel entities 2024-03-14 02:23:26 -04:00
Sasha Koshka fc6efafc34 Add access control modes to spec 2024-03-14 02:11:26 -04:00
Sasha Koshka a147143fc3 Missed a spot 2024-03-14 01:55:18 -04:00
Sasha Koshka a8fd79991c Renamed restricted access to opaque access 2024-03-14 01:54:22 -04:00
Sasha Koshka a9c85bf017 Changed restricted access specified from ~ to # 2024-03-14 01:42:19 -04:00
Sasha Koshka db98e590fc Change interface symbol from ~ to & 2024-03-13 23:05:58 -04:00
Sasha Koshka e0d4be8db8 Removed "in" from match statement 2024-03-13 22:44:11 -04:00
Sasha Koshka 0ea05e5836 Fix calling methods on pointers to types in other units 2024-03-13 11:51:51 -04:00
Sasha Koshka d2c8fdd05c Add generator tests for referring to methods in other units 2024-03-13 11:15:15 -04:00
Sasha Koshka 1565376418 Add TestMethodPtr to generator 2024-03-11 11:50:12 -04:00
Sasha Koshka 566bf41843 Re-format roadmap 2024-03-07 23:20:06 +00:00
Sasha Koshka 45f46b04b0 Merge pull request 'assign-return-break-to-anything' (#56) from assign-return-break-to-anything into main
Reviewed-on: #56
2024-03-06 22:42:18 +00:00
Sasha Koshka be08308cb2 Add a compiler test for this 2024-03-06 17:41:52 -05:00
Sasha Koshka 4f31b712df Document new Bool width in spec 2024-03-06 16:51:11 -05:00
Sasha Koshka 8607f08093 Bool is now an i1 2024-03-06 16:46:27 -05:00
Sasha Koshka ff99c37219 Add tests about this to the generator 2024-03-06 16:44:22 -05:00
Sasha Koshka e889a9c49b Generator understands assigning return/break to things 2024-03-06 16:43:58 -05:00
Sasha Koshka ebc4aacf5b Updated spec 2024-03-06 15:37:30 -05:00
Sasha Koshka 6643c92948 Break and return can be assigned to anything 2024-03-06 15:34:00 -05:00
Sasha Koshka 4df8a45a16 Add test cases for #55 2024-03-06 15:31:47 -05:00
Sasha Koshka 3d6a258873 Rename analyzer's match test file to control flow test 2024-03-06 15:21:33 -05:00
Sasha Koshka 6a9be94601 Merge pull request 'implement-union-types' (#54) from implement-union-types into main
Reviewed-on: #54
2024-03-06 20:16:54 +00:00
Sasha Koshka bf62730e79 One last test 2024-03-06 15:16:15 -05:00
Sasha Koshka 040be67476 Add compiler tests for unions/matches 2024-03-06 15:06:21 -05:00
Sasha Koshka 6cd51f3c5f Add match/union generator tests 2024-03-06 14:50:58 -05:00
Sasha Koshka ab24c1cfc1 Generator supports assigning unions to unions 2024-03-06 14:49:49 -05:00
Sasha Koshka e94d438332 Use TypesEqual() instead of comparing hashes 2024-03-06 14:16:36 -05:00
Sasha Koshka ef947b8dc6 Analyzer lets unions of the same type be assigned to eachother 2024-03-06 14:00:37 -05:00
Sasha Koshka 41116e0b13 Add union assignment tests to analyzer 2024-03-06 13:50:55 -05:00
Sasha Koshka 98d8edc319 Generate phi node for default case in match switch 2024-03-06 13:09:20 -05:00
Sasha Koshka ff749a03dd Generate assignment to unions 2024-03-06 13:09:03 -05:00
Sasha Koshka daea6cb22a Generate match statements 2024-03-06 00:42:40 -05:00
Sasha Koshka 54f69ca786 Add match stub 2024-03-05 03:32:31 -05:00
Sasha Koshka 779aaf3cea Generate union types 2024-03-05 03:15:44 -05:00
Sasha Koshka 8ec7929c1e New test TestMatchErrNotUnion 2024-03-04 14:22:39 -05:00
Sasha Koshka fd8a15db66 Analyze match expressions 2024-03-04 14:19:33 -05:00
Sasha Koshka b1cef5d95f Add more semantic fields to Match and Case 2024-03-04 14:09:48 -05:00
Sasha Koshka 2a6778837f Improved match tests 2024-03-04 14:09:19 -05:00
Sasha Koshka 8fa9a579fd entity.Match.String() prints "in" instead of "on" 2024-03-04 13:36:07 -05:00
Sasha Koshka ce79e16de7 Add some match statement tests 2024-03-04 13:34:22 -05:00
Sasha Koshka cd91f9dc20 Add more tests for the union type 2024-03-02 12:36:22 -05:00
Sasha Koshka 24268e498e Add some tests for the union type 2024-03-02 12:26:16 -05:00
Sasha Koshka a6dabc27f9 The assemble map pattern doesnt work for unions
That entire file needs to be completely rewritten is stg
2024-03-02 12:21:28 -05:00
Sasha Koshka 459020126d Add assignment rules for unions 2024-03-02 12:12:43 -05:00
Sasha Koshka 3a1d9632dc Analyzer checks uniqueness of types in union by comparing hashes 2024-03-02 12:09:49 -05:00
Sasha Koshka 8beb785009 And so does analyzer 2024-03-02 01:34:16 -05:00
Sasha Koshka 66633be90c Parser understands these new developments 2024-03-02 01:30:45 -05:00
Sasha Koshka bc88782938 Add Position() method to type 2024-03-02 01:26:29 -05:00
Sasha Koshka e30f2a74da Augh 2024-03-01 21:45:14 -05:00
Sasha Koshka 0e2259bb45 Add match test to parser, fixed parsing and stringing of match 2024-03-01 21:44:20 -05:00
Sasha Koshka f94c562896 Added match statements to the parser
Added match statements to the parser
2024-03-01 21:36:40 -05:00
Sasha Koshka fb7558576e Add match expressions to entity 2024-03-01 21:22:32 -05:00
Sasha Koshka 8d611b6fa6 Add match statement to expresison parsing decision tree 2024-03-01 21:10:03 -05:00
Sasha Koshka c17a381ba5 Add match statements to the spec 2024-03-01 21:03:49 -05:00
Sasha Koshka c45d0215ba Add union types to parser 2024-03-01 12:35:11 -05:00
Sasha Koshka 0135972e14 Add parser test for union type 2024-03-01 12:34:29 -05:00
Sasha Koshka 24fa83b004 Update expression parsing decision tree in parser/fspl/README.md 2024-03-01 12:11:09 -05:00
Sasha Koshka b40953a0ec Add TypeUnion to entity 2024-03-01 03:00:17 -05:00
Sasha Koshka cde0eec9aa Add union type syntax to spec 2024-03-01 02:54:26 -05:00
Sasha Koshka 096da5bdfd Types can now produce hashes of themselves 2024-03-01 02:53:29 -05:00
Sasha Koshka fe14072392 Add a hashing system to Entity 2024-03-01 02:53:10 -05:00
Sasha Koshka 0947a8de44 Add design for union type 2024-03-01 02:10:21 -05:00
Sasha Koshka 278a243804 Add pkg.go.dev badge 2024-03-01 00:24:04 -05:00
Sasha Koshka 005b2fcd56 Inline string literal since #46 has been remedied 2024-03-01 00:17:39 -05:00
Sasha Koshka 5e93a7afd1 TestLiteralReference is now an error test 2024-03-01 00:13:32 -05:00
Sasha Koshka c22ac87cd0 Fixed analyzer.isLocationExpression 2024-03-01 00:12:21 -05:00
Sasha Koshka 8ae6ea3626 Add test case for #46 2024-03-01 00:04:15 -05:00
Sasha Koshka 8e3b21f807 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 2ae4111f64 Add some multiunit tests from the analyzer to the generator 2024-02-29 23:59:15 -05:00
Sasha Koshka b1fc69dacd Change name of generator multiunit test to match analyzer 2024-02-29 23:52:09 -05:00
Sasha Koshka ee02e71b1d Putting complete covering of bit casts on hold for now 2024-02-28 20:08:54 -05:00
Sasha Koshka 80e310010e Covered more cases with bitcast 2024-02-28 20:08:43 -05:00
Sasha Koshka fad3597d7a Add more test cases for bitcasting pointers 2024-02-28 19:49:35 -05:00
Sasha Koshka 4cfa431919 Merge branch 'main' into generator-multi-unit-tests 2024-02-28 19:42:11 -05:00
Sasha Koshka 6b4ea0e255 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 e2d944d534 The buge (#47) is vanquished!!!! 2024-02-28 19:30:33 -05:00
Sasha Koshka 81c4f1e46b Expressions now have HasExplicitType() 2024-02-28 18:31:46 -05:00
Sasha Koshka 60ef92bd4c Add test case to analyzer for #47 2024-02-28 18:03:15 -05:00
Sasha Koshka 36456ad1bc Completed out TestUnitWriterInterface 2024-02-28 17:43:17 -05:00
Sasha Koshka ad8cf0f48f Renamed TestPtrIntCast to TestPtrCast, will put more things in it 2024-02-28 17:36:11 -05:00
Sasha Koshka 75b48b7000 Add and fix test case for #47 (on the generator side at least) 2024-02-28 13:36:21 -05:00
Sasha Koshka ddf1b57799 Generator uses proper type owner UUID during interface conversion 2024-02-28 12:30:38 -05:00
Sasha Koshka 802a492be1 Generator multiunit tests print what is being analyzed 2024-02-28 11:50:41 -05:00
Sasha Koshka 552c73f606 Fix example unit UUID 2024-02-27 19:50:55 +00:00
Sasha Koshka e26728d27f Minor grammar fixes 2024-02-27 19:41:19 +00:00
Sasha Koshka 981a5332e7 Fix ordering of dirs 2024-02-27 19:32:23 +00:00
Sasha Koshka 1f5e0fe8c1 Correct dir paths 2024-02-27 19:30:54 +00:00
Sasha Koshka b3ae80e809 Add problematic multiunit test case from #49 2024-02-27 03:06:54 -05:00
Sasha Koshka e5344c034b 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 d6f0c470ee Compiler has bug() to wrap unexpected errors 2024-02-27 02:45:32 -05:00
Sasha Koshka ef07772ce4 Add some interface tests to compiler 2024-02-27 02:36:35 -05:00
Sasha Koshka 7720a1f629 Compiler debug output makes more sense 2024-02-27 02:33:35 -05:00
Sasha Koshka 90d735163b Generator's "errNotFound" is more useful when it bubbles to the surface 2024-02-27 02:29:52 -05:00
Sasha Koshka 1d90b3c7d7 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 5d5ffb9c4c Add test for printing argv
Disabled for now due to #47
2024-02-27 00:22:02 -05:00
Sasha Koshka b9eec89773 Add test case for counting cli args 2024-02-27 00:09:01 -05:00
Sasha Koshka 64d85c7b70 Temporarily circumvent crash in analyzer #46 2024-02-27 00:00:33 -05:00
Sasha Koshka 1d4f958efa Added a test case for depending on compiled units 2024-02-26 23:59:47 -05:00
Sasha Koshka b441cc1e7b Added a test case for depending on compiled units 2024-02-26 23:50:14 -05:00
Sasha Koshka 48876a8229 Tests can now compile dependencies first 2024-02-26 23:32:40 -05:00
Sasha Koshka c9f453f35e Fixed test case file for systeminclude 2024-02-26 14:40:03 -05:00
Sasha Koshka c20a5deede Resolver no longer requires system units to be files (????????) 2024-02-26 14:39:44 -05:00
Sasha Koshka 496d4aeb01 Added test case for grabbing a module from /usr/include/fspl 2024-02-26 14:33:31 -05:00
Sasha Koshka aa3cabe82b Fix hello world test 2024-02-26 14:24:47 -05:00
Sasha Koshka 2a74389830 Compiler test now uses testcommon.CompareHex 2024-02-26 14:22:51 -05:00
Sasha Koshka e898379707 Add testcommon.CompareHex 2024-02-26 14:22:33 -05:00
Sasha Koshka dc47a4c8c0 testcommon.LogColumns has a configurable width 2024-02-26 14:18:09 -05:00
Sasha Koshka 2edbd6c8b3 Split compiler_test.go into common file and test file 2024-02-26 14:04:37 -05:00
Sasha Koshka 76eed1153e Added test case for exit code 2024-02-26 14:03:10 -05:00
Sasha Koshka c28ea57777 Compiler test now holds output from compiler until end 2024-02-26 13:56:37 -05:00
Sasha Koshka 04420cb7d3 Compiler uses a default optimization level when unconfigured 2024-02-26 13:46:54 -05:00
Sasha Koshka a418baa113 Add the hello world test case 2024-02-26 13:46:30 -05:00
Sasha Koshka 27b11c0cdc Add a test-data directory for compiler 2024-02-26 13:46:10 -05:00
Sasha Koshka 6f9b6f07d9 Compiler now passes relatvive paths to fs.FS as it should 2024-02-26 12:50:49 -05:00
Sasha Koshka d2f4dcd8f3 Much of the compiler now uses fs.FS instead of os 2024-02-26 11:46:59 -05:00
Sasha Koshka 59d38d8c9d Moved everything that should touch "os" to compiler.go 2024-02-26 11:36:16 -05:00
Sasha Koshka aa07bee99d 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 c93f3916a1 Fixed doc for fsplc and fsplmod 2024-02-23 03:20:51 -05:00
Sasha Koshka 14b36bba1a Add a tool to manage modules 2024-02-23 01:41:36 -05:00
Sasha Koshka 3297f6671e Lexer skips over zero runes now 2024-02-23 01:08:58 -05:00
Sasha Koshka 1fd34731ff Metadata.String quotes UUID 2024-02-23 00:24:09 -05:00
Sasha Koshka bd4ed45a45 entity.Metadata now has String() 2024-02-23 00:18:46 -05:00
Sasha Koshka 4987ed0b4d Cli now handles printing stuff 2024-02-22 21:24:07 -05:00
Sasha Koshka 39f62a3d3f Cli now displays usage information for subcommands correctly 2024-02-22 20:25:37 -05:00
Sasha Koshka 367f4c5d34 Update fsplc to use new cli features 2024-02-22 20:17:38 -05:00
Sasha Koshka 793314fdd7 Update fsplc doc 2024-02-22 20:17:23 -05:00
Sasha Koshka 5ca80ded92 Add more functionality to cli package 2024-02-22 20:12:41 -05:00
Sasha Koshka 23052aa6f0 Changed repository import paths 2024-02-22 19:22:53 -05:00
Sasha Koshka 9d43ef75ee Changed go.mod version to 1.19 2024-02-21 13:49:46 -05:00
Sasha Koshka 418e013dda Reflected those changes in unit design doc 2024-02-21 13:46:26 -05:00
Sasha Koshka ddafa0935a Module search paths are more useful now 2024-02-21 13:45:51 -05:00
Sasha Koshka 24b15cfea6 Merge branch 'implement-modules' 2024-02-21 13:27:35 -05:00
Sasha Koshka bc9f1302b2 Merge branch 'main' into implement-modules 2024-02-21 18:26:44 +00:00
Sasha Koshka e4eb8c4e62 Updated compiler to use Resolver 2024-02-21 13:21:58 -05:00
Sasha Koshka 222dae2dac Add a "Resolver" that resolves unit addresses 2024-02-21 13:21:41 -05:00
Sasha Koshka 39d584bd1b Compiler understands compiling to . 2024-02-20 01:48:18 -05:00
Sasha Koshka d4a7420471 Compiler now only parses module files that end in .fspl 2024-02-20 01:39:07 -05:00
Sasha Koshka 108c89c18e cli package now prints out usage correctly 2024-02-20 01:27:26 -05:00
Sasha Koshka f0fbe6440a Compiler is a bit more adaptable now 2024-02-20 01:23:00 -05:00
Sasha Koshka 5890e6188b Oops haha, print statement. 2024-02-20 00:52:30 -05:00
Sasha Koshka 07ea408d4e The compiler now compiles 2024-02-20 00:52:23 -05:00
Sasha Koshka f06a0e29b9 Updated generator tests 2024-02-19 23:05:30 -05:00
Sasha Koshka 7600bc28d9 Fixed generator map sorting 2024-02-19 21:57:21 -05:00
Sasha Koshka a22b6137d5 Types are given proper link names in the generator 2024-02-19 21:09:21 -05:00
Sasha Koshka 306b029951 Generator now has module support 2024-02-19 21:00:20 -05:00
Sasha Koshka 2f2e762d02 Documented how methods are named 2024-02-19 12:09:21 -05:00
Sasha Koshka ff18aae2b6 entity.Key now has an optional method field 2024-02-19 12:05:13 -05:00
Sasha Koshka 4166fb8817 Move Key to Entity 2024-02-19 11:41:36 -05:00
Sasha Koshka d3df73e0ec Fixed wording issue in design/units.md 2024-02-19 11:18:17 -05:00
Sasha Koshka 569dd14f59 Analyzer does not allow operations on restricted types 2024-02-17 00:19:13 -05:00
Sasha Koshka 4b87551702 Analyzer does not allow literals to be assigned to restricted types 2024-02-17 00:12:32 -05:00
Sasha Koshka 9d009a1f64 Analyzer does not allow value casting of restricted types 2024-02-16 23:52:52 -05:00
Sasha Koshka 1b92c2cf81 Analyzer does not allow subscripting, slicing of restricted types 2024-02-16 23:10:38 -05:00
Sasha Koshka 3bc7467c85 Add more multi-unit test cases 2024-02-16 22:50:29 -05:00
Sasha Koshka ed282efdb7 Analyzer does not allow marking functions/methods as restricted 2024-02-16 22:31:38 -05:00
Sasha Koshka 654ba361e2 Analyzer does not allow member access on restricted types 2024-02-16 22:26:22 -05:00
Sasha Koshka 2d1b2bab43 Analyzer does not allow calling behaviors of restricted iface 2024-02-16 22:19:47 -05:00
Sasha Koshka f027a50756 Analyzer no longer thinks String is private 2024-02-16 22:13:30 -05:00
Sasha Koshka 444637bc15 Analyzer now fills out this information 2024-02-16 13:51:11 -05:00
Sasha Koshka f78a71950b Entity now stores unit and access information for types 2024-02-16 13:30:15 -05:00
Sasha Koshka c4cb1ce273 Should also make sure ~ only applies to types 2024-02-16 13:09:48 -05:00
Sasha Koshka eb9444dad3 Add more test cases for restricted typedefs in units 2024-02-16 13:06:40 -05:00
Sasha Koshka f93c9b26c8 When analyzing a typedef, update the unit information first 2024-02-16 12:54:34 -05:00
Sasha Koshka 37aa620f33 This is mega silly 2024-02-16 12:49:47 -05:00
Sasha Koshka 158a4220be Pass TestUnitPrivateMethod Err 2024-02-16 12:48:25 -05:00
Sasha Koshka 1fea25ba91 Fix behavior of Tree.analyzeMethodOrBehavior() 2024-02-16 12:43:42 -05:00
Sasha Koshka 6c7c7c9d99 Real quick add unicode test to parser 2024-02-15 12:43:09 -05:00
Sasha Koshka fcf44fd1ec Ok so maybe that one commit wasn't entirely true 2024-02-15 01:12:51 -05:00
Sasha Koshka ed498a3bc0 Analyzer checks access permissions when using named types 2024-02-15 01:08:21 -05:00
Sasha Koshka 159317965d Parser properly unions position of named types 2024-02-15 01:07:25 -05:00
Sasha Koshka 6b9cdcc239 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 87c2acf650 Add some initial analyzer tests for units 2024-02-14 23:20:01 -05:00
Sasha Koshka 43a488fb58 I ohasdhjklashf dslkfj ksdl 2024-02-14 23:10:43 -05:00
Sasha Koshka 14566196a9 Forgot about the nickname map haha 2024-02-14 22:48:55 -05:00
Sasha Koshka 63a9b5b540 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 d8714cbc1d Improved analyzer testing infrastructure 2024-02-14 22:06:10 -05:00
Sasha Koshka 199da663e9 Parser now actually skims files 2024-02-14 18:49:59 -05:00
Sasha Koshka fb6cbe2c9d Improved the skim test case 2024-02-14 17:40:37 -05:00
Sasha Koshka 9bfc3bf4c6 Add parser test case for skimming 2024-02-14 17:35:44 -05:00
Sasha Koshka 86145c0975 All analyzer tests pass 2024-02-14 17:24:00 -05:00
Sasha Koshka cbfabba8a7 I mispelled stream 2024-02-14 13:49:06 -05:00
Sasha Koshka 724178ce6e Made analyzer error testing 20% cooler 2024-02-14 13:48:23 -05:00
Sasha Koshka 05bd29fc71 Use stram0.fspl so I don't have to rewrite every test 2024-02-14 13:41:24 -05:00
Sasha Koshka cd4e9608d0 Analyzer does not crash 2024-02-14 13:40:26 -05:00
Sasha Koshka b27684de46 Analyzer compiles 2024-02-14 13:35:50 -05:00
Sasha Koshka 7d93a0abf6 Add section about uniqueness and UUIDs to units design doc 2024-02-14 13:00:11 -05:00
Sasha Koshka cb33628996 Begin implemeting my better idea in the analyzer 2024-02-14 10:38:03 -05:00
Sasha Koshka 22dc2ef175 Made wording in entity even better 2024-02-14 10:22:33 -05:00
Sasha Koshka 025302a959 Add a file with my better idea in it 2024-02-14 10:22:08 -05:00
Sasha Koshka 6cd60cc9a6 So I have a better idea 2024-02-14 10:06:20 -05:00
Sasha Koshka ccb7fe46d0 treeParser.lookupUnit returns the current unit UUID on empty string 2024-02-14 02:04:28 -05:00
Sasha Koshka d3d739a24d Parser and entity now refer to units as units instead of modules 2024-02-14 02:01:01 -05:00
Sasha Koshka da4f9b8d67 Parser now translates names to unit UUIDs 2024-02-14 01:55:51 -05:00
Sasha Koshka 89b7273c2f Entity now deals in UUIDs 2024-02-14 01:48:47 -05:00
Sasha Koshka 3d75cdbd02 Parser now has something of a concept of modules 2024-02-14 01:44:25 -05:00
Sasha Koshka 7d32211e70 Add module names to top level entities 2024-02-13 21:36:08 -05:00
Sasha Koshka c3bbbc1536 Mini-analysis of metadata in Compiler.ParseModule 2024-02-13 21:24:13 -05:00
Sasha Koshka 62cf6deeb6 Break compiler out into its own package 2024-02-13 21:01:13 -05:00
Sasha Koshka 2c968404ea Break out ParseModule and ParseSourceFile from CompileUnit 2024-02-13 20:55:59 -05:00
Sasha Koshka 9d70abc084 Metadata is now an entity 2024-02-13 20:53:15 -05:00
Sasha Koshka b8afa6d9c0 Metadata is now an entity 2024-02-13 20:52:25 -05:00
Sasha Koshka 42cf7eed67 Compiler CLI can drive internals 2024-02-13 19:15:40 -05:00
Sasha Koshka 5448d07815 Module compilation stub in compiler command 2024-02-13 19:10:46 -05:00
Sasha Koshka d117e15157 Add method of entity.Address to generate a nickname 2024-02-13 19:10:24 -05:00
Sasha Koshka c89cbc24fe Add some utility methods to entity.Address 2024-02-13 17:29:42 -05:00
Sasha Koshka 8b5c6c2d8a Auto-nicknaming should happen at the analysis stage 2024-02-13 17:08:48 -05:00
Sasha Koshka ab9b0a3fa3 Add test cases for metadata parser 2024-02-13 16:33:15 -05:00
Sasha Koshka 97f149cf42 Add metaParser test-common.go 2024-02-13 16:32:59 -05:00
Sasha Koshka 7ed50c8d6f Implemented metadata file parsing 2024-02-13 16:22:10 -05:00
Sasha Koshka f35ba89cd0 Changed wording in fsplParser.treeParser.bug() 2024-02-13 16:21:50 -05:00
Sasha Koshka 8d2d71be89 Nickname is no longer a type 2024-02-13 16:20:45 -05:00
Sasha Koshka 59c2ed8502 Flesh out meta.Tree and meta.treeParser with more methods 2024-02-13 16:03:22 -05:00
Sasha Koshka 5d86161b63 Add Directive interface 2024-02-13 16:03:07 -05:00
Sasha Koshka c7043c2217 Minor readme fixes 2024-02-13 19:58:28 +00:00
Sasha Koshka b78d5acff2 Add information (or lack thereof) of learning the language 2024-02-13 19:57:22 +00:00
Sasha Koshka f39ba91450 Update roadmap 2024-02-13 19:40:51 +00:00
Sasha Koshka 254f3f84c0 License under GPLv3 2024-02-13 19:16:11 +00:00
Sasha Koshka 73b4643162 Add metadata entities 2024-02-13 13:29:30 -05:00
Sasha Koshka ab7d72dfe4 Updated compiler command 2024-02-13 13:16:03 -05:00
Sasha Koshka 76df41f813 I blame debian for this 2024-02-13 13:14:45 -05:00
Sasha Koshka b743e56c2a Updated generator 2024-02-13 13:12:53 -05:00
Sasha Koshka cf40ab0707 Updated analyzer 2024-02-13 13:12:45 -05:00
Sasha Koshka 6b56600c8f Improve doc comments for Parser 2024-02-13 13:07:37 -05:00
Sasha Koshka 5f035df827 Separated parser into two packages 2024-02-13 13:03:22 -05:00
Sasha Koshka 5b24bdc32b Extracted re-usable parsing utilities from parser.parser 2024-02-13 12:31:52 -05:00
Sasha Koshka 4a5928e268 Asajkdhaskldj 2024-02-13 00:50:30 -05:00
Sasha Koshka 9bd91b73ab Rename module design doc to unit design doc 2024-02-13 00:27:51 -05:00
Sasha Koshka ca690b9270 Add future work section 2024-02-13 00:10:14 -05:00
Sasha Koshka 051f96b639 Revised module design, cleared up wording 2024-02-13 00:01:39 -05:00
Sasha Koshka 4d4572e36d Add design document for modules 2024-02-12 23:37:04 -05:00
Sasha Koshka b44e4f3f9d 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 4e385edac8 Analyzer makes use of entity.FormatType 2024-02-12 14:47:49 -05:00
Sasha Koshka 069371d859 Add test cases for formatting nil type as Void 2024-02-12 14:47:09 -05:00
Sasha Koshka cb3e5b6bfb Add entity.FormatType() 2024-02-12 14:34:49 -05:00
Sasha Koshka 7d2f435d18 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 203fb4fca4 Compiler now takes in a format override parameter 2024-02-11 12:55:43 -05:00
Sasha Koshka ea0f2ca169 Add cli.NewValueSet() to validate against several values 2024-02-11 12:38:46 -05:00
Sasha Koshka 0ca9f5c069 Fix some issues with cli related to argument values 2024-02-11 12:37:08 -05:00
Sasha Koshka c5e75a7182 Better documentation for cli package 2024-02-11 12:32:53 -05:00
Sasha Koshka 0d2d7fae02 Broke cli into a separate package 2024-02-11 12:13:49 -05:00
Sasha Koshka 0c48538888 Compiler now parses CLI argument values correctly 2024-02-11 03:40:47 -05:00
Sasha Koshka 2932e0c7d6 Compiler prints a special error if output path has no extension 2024-02-11 03:40:00 -05:00
Sasha Koshka 21ab4f5fca errors.Format does not crash when formatting normal error 2024-02-11 03:35:27 -05:00
Sasha Koshka b4a886b126 Created a new argument parsing system 2024-02-11 03:33:16 -05:00
Sasha Koshka 2bc5eb1f49 Break fsplc Compiler type into a different file 2024-02-11 01:55:07 -05:00
Sasha Koshka 4895f09f99 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 bf73821dbd New test for generator confirmed to work, filled out 2024-02-10 21:31:10 -05:00
Sasha Koshka 2d5a747be6 testcommon last column no longer has trailing spaces 2024-02-10 21:24:09 -05:00
Sasha Koshka 2571fcde99 Add same test as stub to generator 2024-02-10 21:20:24 -05:00
Sasha Koshka e6bc09e622 Fixed crash in analyzer that caused #31 2024-02-10 21:16:22 -05:00
Sasha Koshka 29608f87da Test case code was wrong, methods need to return Number 2024-02-10 21:15:56 -05:00
Sasha Koshka 07b7571f94 Analyzer test-common now formats errors properly 2024-02-10 21:14:30 -05:00
Sasha Koshka d6d7423087 Add #31 as test case 2024-02-10 21:10:49 -05:00
Sasha Koshka 05f31c00a8 Add .editorconfig 2024-02-10 19:03:38 -05:00
Sasha Koshka af1b032a50 OOPS 2024-02-10 18:57:53 -05:00
Sasha Koshka 88e415e9d5 Unexported parser 2024-02-10 18:56:25 -05:00
Sasha Koshka 1593ecef7b 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 b0c5130858 Updated spec a little 2024-02-10 18:49:10 -05:00
Sasha Koshka deae98001d Fix small issues with generator readme 2024-02-10 18:32:35 -05:00
Sasha Koshka a91922e749 Add generator README.md 2024-02-10 18:26:51 -05:00
Sasha Koshka 51e85f6cf8 Bring over better doc on assignment modes to the source file 2024-02-10 14:50:43 -05:00
Sasha Koshka eacde3a4f9 Add analyzer README.md 2024-02-10 14:47:47 -05:00
Sasha Koshka 21e6fb94a1 HHHHHHHHH 2024-02-09 22:36:21 -05:00
Sasha Koshka e2eb5bbee1 God 2024-02-09 20:23:22 -05:00
Sasha Koshka dd87e1338c Fix mistake in parser readme 2024-02-09 20:22:05 -05:00
Sasha Koshka 4d7d9ac33d Add parser README.md 2024-02-09 20:18:21 -05:00
Sasha Koshka ed2acd2f86 DESIGN.md -> README.md
Want this to show up in git web interfaces
2024-02-09 18:02:03 -05:00
Sasha Koshka c7c56dcb39 I need to sleep 2024-02-09 04:44:09 -05:00
Sasha Koshka f8ca067f54 Enhanced lexer/DESIGN.md 2024-02-09 04:42:03 -05:00
Sasha Koshka f807ced41d Add DESIGN.md to lexer 2024-02-09 04:37:39 -05:00
Sasha Koshka 52da21b60d 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 92d4916997 Update fsplc command 2024-02-09 04:05:44 -05:00
Sasha Koshka 35299ea526 Updated generator's test-common 2024-02-09 04:02:55 -05:00
Sasha Koshka 6325e46c1c Updated analyzer's test-common 2024-02-09 04:01:41 -05:00
Sasha Koshka 86a45d5e8a Udpated parser's test-common 2024-02-09 03:59:00 -05:00
Sasha Koshka d6aa28602c Remedy #25 2024-02-09 03:48:44 -05:00
Sasha Koshka 0de38082ec Change parser constructor, tree's parsing methods
Now accept lexers instead of io.Readers
2024-02-09 03:44:29 -05:00
Sasha Koshka b2f238314a 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 4d19ad085f Remove llvm/README.md
The information in that file is now a doc comment
2024-02-09 03:37:10 -05:00
Sasha Koshka 74a5527067 Add package level doc comment to llvm 2024-02-09 03:35:42 -05:00
Sasha Koshka 92070d142e Add module level doc comment to analyzer 2024-02-09 03:30:24 -05:00
Sasha Koshka e49fc397d3 Add documentation for fsplc command 2024-02-09 03:22:36 -05:00
Sasha Koshka 0832a948bf Completed doc comments in entity 2024-02-09 01:11:24 -05:00
Sasha Koshka b5b36c429e Add module level doc comment to testcommon 2024-02-09 01:03:02 -05:00
Sasha Koshka af9ae75d5c Add doc comments to integer 2024-02-09 01:00:54 -05:00
Sasha Koshka 100861dc47 Added module-level doc comments to generator and generator/native 2024-02-09 00:57:06 -05:00
Sasha Koshka fad0a89dc9 Complete parser doc comments 2024-02-09 00:44:26 -05:00
Sasha Koshka 4da3e81d3c Note for later 2024-02-09 00:44:18 -05:00
Sasha Koshka 5e5f112bfe Added extensive doc comments to errors package 2024-02-08 23:42:04 -05:00
Sasha Koshka df7c45d479 Complete doc comments in lexer 2024-02-08 23:20:07 -05:00
Sasha Koshka 6cf92b66f5 Removed lexer.Symbols 2024-02-08 23:06:58 -05:00
Sasha Koshka a236bb0804 Added module overview doc comment to lexer 2024-02-08 23:05:36 -05:00
Sasha Koshka e21a865c61 Compiler outputs formatted errors 2024-02-08 17:01:38 -05:00
Sasha Koshka b89bed2c94 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 a70aa29701 Brought analyzer up to date 2024-02-08 16:53:15 -05:00
Sasha Koshka 50cbebd399 Brought parser up to date 2024-02-08 16:51:48 -05:00
Sasha Koshka aa1dd6df0e Brought lexer up to date 2024-02-08 16:50:42 -05:00
Sasha Koshka 33c29acfb6 Line and column numbers are entirely handled by errors.Format 2024-02-08 16:48:52 -05:00
Sasha Koshka 0d149d6fd7 errors.Error and errors.Position are in two different files 2024-02-08 16:24:46 -05:00
Sasha Koshka 5f6dbf2baf errors.Error is now an interface, implementation is hidden 2024-02-08 16:23:05 -05:00
Sasha Koshka 122cafbc2b Unexport generator.resultMode 2024-02-08 16:06:00 -05:00
Sasha Koshka fb95b935d6 Merge pull request 'remove-participle' (#10) from remove-participle into main
Reviewed-on: sashakoshka/fspl#10
2024-02-08 18:34:18 +00:00
Sasha Koshka 483c87b3a3 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 e9b066d180 Fixed commaList 2024-02-08 13:32:15 -05:00
Sasha Koshka 74e84c7da4 Fixed all generator tests 2024-02-08 13:32:04 -05:00
Sasha Koshka 94a25f0aac Generator compiles and runs 2024-02-08 13:14:21 -05:00
Sasha Koshka c6424ea315 Go mod tidy. No more participle, officially! 2024-02-08 13:12:42 -05:00
Sasha Koshka 9a3532bd3e 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 2f9f3614d6 Parser now accepts multiplication operations 2024-02-08 13:05:47 -05:00
Sasha Koshka b614dde503 Fix TestTypeInterfaceBehaviorUniqueErr 2024-02-08 12:57:20 -05:00
Sasha Koshka ba4a455d2e Signature positions are stored properly 2024-02-08 12:54:31 -05:00
Sasha Koshka ed3d8e766c Parser now includes positional information in toplevels 2024-02-08 12:52:47 -05:00
Sasha Koshka 04b4d71c64 Bring analyzer tests up to date as much as possible 2024-02-08 12:49:17 -05:00
Sasha Koshka b6cc02fcb8 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 b3fc38e7c0 Remove rouge println in parser 2024-02-08 04:00:25 -05:00
Sasha Koshka bb2d1966b9 Off by one error oh my god 2024-02-08 03:58:29 -05:00
Sasha Koshka cf75245820 Updated analyzer's test-common.go 2024-02-08 03:56:09 -05:00
Sasha Koshka d6a645f93b Rework analyzer code to fit new parser 2024-02-08 03:51:21 -05:00
Sasha Koshka 17e3d1dfbe 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 8ec3649637 Parse namespaced function calls 2024-02-08 03:28:09 -05:00
Sasha Koshka 823cb44406 Bring most tests up to date 2024-02-08 03:13:40 -05:00
Sasha Koshka 1f3f94e24f Parse member access, assignment, method calls 2024-02-08 03:10:02 -05:00
Sasha Koshka fa252e59ab Loll oops 2024-02-08 02:22:57 -05:00
Sasha Koshka 93c9f42aab Unify entity.Statement and entity.Expression 2024-02-08 02:22:19 -05:00
Sasha Koshka f04bf32ce2 Relaxed unneeded rules on AST 2024-02-08 02:19:00 -05:00
Sasha Koshka 50fc1d16de Parse bit and value casts 2024-02-08 01:54:29 -05:00
Sasha Koshka d724b16cdc Parse reference operations 2024-02-08 01:37:36 -05:00
Sasha Koshka abbc8e52bf Parse length operations 2024-02-08 01:33:55 -05:00
Sasha Koshka 90a505540e Parse slice operations 2024-02-08 01:29:16 -05:00
Sasha Koshka 92b15eef6c Struct literals print correctly 2024-02-08 00:56:43 -05:00
Sasha Koshka 3d89ed08cc Parse operations 2024-02-08 00:50:54 -05:00
Sasha Koshka d7d2d90b20 Added string constructor for entity.Operator 2024-02-08 00:29:24 -05:00
Sasha Koshka 0bed97da02 Re-organize the position of parsing functions in expression.go 2024-02-08 00:16:39 -05:00
Sasha Koshka c347add41c Parse dereferences and subscripts 2024-02-08 00:14:52 -05:00
Sasha Koshka 7697bab51a Lowercased entity descriptions 2024-02-07 22:58:58 -05:00
Sasha Koshka 333172d00b 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 5dede3434f Parse function calls 2024-02-07 22:49:11 -05:00
Sasha Koshka 4087e12fb9 Parse break and return expressions 2024-02-07 22:33:33 -05:00
Sasha Koshka 8ad89623a9 Decision tree infrastructure for LBracket branch 2024-02-07 20:03:18 -05:00
Sasha Koshka 3cf6154e62 Parse struct literals 2024-02-07 19:48:01 -05:00
Sasha Koshka dc64daa0e0 Parse blocks 2024-02-07 19:24:57 -05:00
Sasha Koshka 8a553beaa8 Update expression-decision-tree 2024-02-07 19:17:25 -05:00
Sasha Koshka c90bb00241 Fix parsing integer 0 2024-02-07 19:16:05 -05:00
Sasha Koshka 8515bc7350 Parse array literals 2024-02-07 19:14:39 -05:00
Sasha Koshka 0fd38baf77 Update expression-decision-tree 2024-02-07 18:27:10 -05:00
Sasha Koshka 47ca7b98c6 Parse if/else expressions 2024-02-07 18:25:42 -05:00
Sasha Koshka 790f1ca6be Parse loops 2024-02-07 18:11:12 -05:00
Sasha Koshka 78050694d6 Move literal parsing routines to a separate file 2024-02-07 18:01:14 -05:00
Sasha Koshka cd8c65e232 Add stars in expression-decision-tree to mark what isn't done 2024-02-07 17:57:39 -05:00
Sasha Koshka 9cc015859e Fix expression parsing control flow 2024-02-07 17:51:44 -05:00
Sasha Koshka e32a3d997f Add parsing boolean and nil literals 2024-02-07 17:25:00 -05:00
Sasha Koshka 4280576f5f Fix commaList 2024-02-07 17:09:35 -05:00
Sasha Koshka 7b6c1b8398 What crack was I smoking when I made LiteralNil.Value 2024-02-07 15:46:42 -05:00
Sasha Koshka 6cab43332e Remove entity.Boolean because I have overcome my haters 2024-02-07 15:41:48 -05:00
Sasha Koshka 7028ec89b6 Broke out Parser.parseVariable() for consistency 2024-02-07 15:36:33 -05:00
Sasha Koshka ebfaba0d7d Add parsing int, float, and string literals 2024-02-07 14:15:06 -05:00
Sasha Koshka a053eeb207 Declaration and variable expression parsing 2024-02-07 13:26:25 -05:00
Sasha Koshka 239dcfe5a0 Parser panic information is now more detailed 2024-02-07 13:01:25 -05:00
Sasha Koshka a80b6ad74e Add tokens to startTokensExpression 2024-02-07 12:50:19 -05:00
Sasha Koshka a14df2bfbb Created an expression decision tree plan 2024-02-07 12:44:54 -05:00
Sasha Koshka e14b33657f Function headings are now parsed correctly 2024-02-07 11:38:15 -05:00
Sasha Koshka 7e8e46a15d Allow arbitrary integer width 2024-02-07 11:22:37 -05:00
Sasha Koshka 56602e0f17 Updated TestType test case for parser 2024-02-07 03:39:37 -05:00
Sasha Koshka a54feb4522 Parse integer, word, and float types 2024-02-07 03:37:02 -05:00
Sasha Koshka aaff208be2 Fix function parsing 2024-02-07 03:22:00 -05:00
Sasha Koshka 21f55472fb Parse interface types 2024-02-07 03:18:02 -05:00
Sasha Koshka 9cd7877b07 Parse signatures 2024-02-07 03:17:50 -05:00
Sasha Koshka 7c1d4790ab Parse struct types 2024-02-07 03:02:43 -05:00
Sasha Koshka 673d43a0c8 De-ambiguify interface and struct type syntax 2024-02-07 03:02:16 -05:00
Sasha Koshka 8140e80c15 Array type parsing 2024-02-07 00:32:18 -05:00
Sasha Koshka 9fcfc63935 Normalize parsing of access control symbols 2024-02-07 00:23:10 -05:00
Sasha Koshka 4f852012ca Parse pointers/slices 2024-02-07 00:22:58 -05:00
Sasha Koshka 096cc173db Add some convenience methods to parser, fix appendCopy 2024-02-07 00:21:44 -05:00
Sasha Koshka 0fef43e13d Added parsing named types 2024-02-06 23:48:25 -05:00
Sasha Koshka d693132513 Token.Is and Token.ValueIs work properly 2024-02-06 23:16:48 -05:00
Sasha Koshka 28fa7321df Lexer now records the width of tokens 2024-02-06 23:08:25 -05:00
Sasha Koshka 0133e87831 I want to look at my juicy error formatting even when it passes 2024-02-06 22:33:18 -05:00
Sasha Koshka 62c96ed761 FINALLY errors and lexer agree on row/col positions properly 2024-02-06 22:11:46 -05:00
Sasha Koshka 4d2d52b425 Fixed error test cases 2024-02-06 21:59:41 -05:00
Sasha Koshka 8b7ac97e0d Lexer saves error lines 2024-02-06 21:54:20 -05:00
Sasha Koshka c38d15fa87 Lexer tests check for line content, print formatted errors 2024-02-06 19:08:21 -05:00
Sasha Koshka 48d8823a4d Parser errors make more sense 2024-02-06 18:58:09 -05:00
Sasha Koshka 1aa4a5d290 Added Token.String() 2024-02-06 18:56:04 -05:00
Sasha Koshka f179997837 Parser is incomplete but compiles 2024-02-06 18:29:37 -05:00
Sasha Koshka e95fe53338 Created a plan for expression parsing 2024-02-06 17:04:59 -05:00
Sasha Koshka e44537cebe Add a guide to writing parser methods so I don't forget 2024-02-06 16:56:10 -05:00
Sasha Koshka c0ca9108e7 Further normalize expectation responsibilities of parsing methods 2024-02-06 16:39:15 -05:00
Sasha Koshka befb178291 Typedef parsing complete, without detail 2024-02-06 16:32:14 -05:00
Sasha Koshka 0f2459d004 Add type parsing stub 2024-02-06 16:31:53 -05:00
Sasha Koshka 28a50325cb Appended -Core to parsing methods that only partially parse things 2024-02-06 16:30:53 -05:00
Sasha Koshka 683a15a215 Parsing functions *always* begin on current token, leave trailing one 2024-02-06 16:22:49 -05:00
Sasha Koshka fa5e32f374 Method and function parsing complete, without detail 2024-02-06 16:13:05 -05:00
Sasha Koshka 68be179675 Expression parsing stub 2024-02-06 16:12:51 -05:00
Sasha Koshka afebc02706 Parser.expectValue() and Parser.expectValueDesc() take in a kind parameter now 2024-02-06 15:57:27 -05:00
Sasha Koshka b1777bc77d Add parser.Tree.AddDeclaration() 2024-02-06 15:54:21 -05:00
Sasha Koshka 02370adcd5 Added commaList function 2024-02-06 12:20:13 -05:00
Sasha Koshka 7a4b67b538 Why did I name a prepend function appendr?? 2024-02-06 11:45:08 -05:00
Sasha Koshka d1aec952e7 askl;d;sldkf 2024-02-06 02:55:39 -05:00
Sasha Koshka 88b7cd456e Rename typeStartTokens to startTokensType 2024-02-06 02:41:45 -05:00
Sasha Koshka 69dfbecce1 Move top-level expect from Parser.parseTopLevel into Parser.parse() 2024-02-06 02:40:19 -05:00
Sasha Koshka 879951ed7f Parser.parseSignature stub 2024-02-06 02:36:51 -05:00
Sasha Koshka 89ef8ee800 Parser top-level entity base 2024-02-06 02:32:29 -05:00
Sasha Koshka 7e1695e056 Parser now remembers last token 2024-02-06 02:28:23 -05:00
Sasha Koshka fc9b3bbfa5 Un-participle-ify entity.Access 2024-02-06 02:27:09 -05:00
Sasha Koshka db66ff62fb Add Dot and DoubleDot as separate tokens 2024-02-06 02:23:15 -05:00
Sasha Koshka 0c44b87797 Add ValueIs method to lexer.Token 2024-02-06 02:19:09 -05:00
Sasha Koshka 9e20ba5210 Add TokenKind.String 2024-02-06 01:52:29 -05:00
Sasha Koshka 799e7323bc Added expect functions to parser 2024-02-06 01:45:30 -05:00
Sasha Koshka 0ae38485ee Add Token.Is() to check if token is any of a set of kinds 2024-02-06 01:31:52 -05:00
Sasha Koshka 360785c224 Go mod tidy 2024-02-06 01:14:38 -05:00
Sasha Koshka 463811ac83 Define basic parser structure 2024-02-06 01:12:07 -05:00
Sasha Koshka 126bec1fc9 Cleared particple code from parser 2024-02-06 00:52:31 -05:00
Sasha Koshka c323715505 Parser test-common uses testcommon package 2024-02-06 00:49:28 -05:00
Sasha Koshka f7636ab410 Remove entity participle struct tags 2024-02-05 15:17:59 -05:00
Sasha Koshka decc5939a1 Remove entity dependency on participle 2024-02-05 15:16:11 -05:00
Sasha Koshka eab8163cf1 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 fc88e27abf Lexer has no dependency on participle now 2024-02-05 15:04:54 -05:00
Sasha Koshka 0eea2b61a3 Added new Errorf function, added row/column offset 2024-02-05 15:03:13 -05:00
Sasha Koshka 197fa01a1e Error package now formats tabs correctly 2024-02-05 11:44:34 -05:00
Sasha Koshka 40bef8d02c TODO message in errors package 2024-02-01 03:21:23 -05:00
Sasha Koshka 4713e15f28 Wrote tests for and fixed errors package 2024-02-01 03:20:17 -05:00
Sasha Koshka c24957f2e5 Added erros package 2024-02-01 02:51:04 -05:00
Sasha Koshka 66771b3145 Put functionality from generator/test-common.go in its own package 2024-02-01 01:59:17 -05:00
Sasha Koshka f659866ae1 Fix crash when using int literals as floats 2024-01-29 13:29:53 -05:00
Sasha Koshka 4aab296de3 Update tests 2024-01-29 03:30:54 -05:00
Sasha Koshka b4cb7454b0 Slice length fields are properly stored as Index values 2024-01-29 03:11:43 -05:00
Sasha Koshka c0f771db09 Update tests 2024-01-29 02:20:16 -05:00
Sasha Koshka 5c8749d8cd Updated test cases for literals 2024-01-29 00:30:48 -05:00
Sasha Koshka 06759a6e39 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 192044e074 String generation now uses correct type names, data length, and nulls 2024-01-28 17:10:03 -05:00
Sasha Koshka ff3efe646e Fix tests 2024-01-28 16:44:34 -05:00
Sasha Koshka 01526aa914 Add source code printout for tests 2024-01-28 14:46:47 -05:00
Sasha Koshka 31cf892a63 Broke up the monolithic TestLiteral 2024-01-28 14:36:04 -05:00
Sasha Koshka fa90e50c3b Increased the coverage of TestLiteralArray 2024-01-28 14:15:54 -05:00
Sasha Koshka 83aefbae07 *Greatly* reduced the amount of excess IR related to string literals 2024-01-28 14:11:41 -05:00
Sasha Koshka 1f53fc5214 Struct literals now support irDestLoc 2024-01-28 04:50:18 -05:00
Sasha Koshka 3d6142f496 Added the ability for array literals to be undersized 2024-01-28 04:43:01 -05:00
Sasha Koshka 235252b591 Added test for nested array literals
Who knew it would work first try?
2024-01-28 04:24:15 -05:00
Sasha Koshka 185a6114a6 Array literals now support irDestLoc 2024-01-28 04:18:33 -05:00
Sasha Koshka a4c7212709 Add more tests for loops 2024-01-28 03:29:49 -05:00
Sasha Koshka e9a9fc75c6 Fixed extra lines after IR blocks 2024-01-28 03:18:39 -05:00
Sasha Koshka 1b4a0621de Fix some issues with control flow, add tests 2024-01-28 03:16:05 -05:00
Sasha Koshka 7e4abb7bba Add test case for comparisons on defined types 2024-01-28 02:18:18 -05:00
Sasha Koshka f595a2e742 FCmp and ICmp now accept defined types 2024-01-28 00:18:37 -05:00
Sasha Koshka 9a4241cb11 Updated all tests except for literal_test.go
You Will See Why
2024-01-27 23:24:19 -05:00
Sasha Koshka 3ca9b5ff50 Well it would help if it generated well formed IR 2024-01-27 23:00:03 -05:00
Sasha Koshka 079a05ccca Assignment coercions are much more IR efficient 2024-01-27 22:45:28 -05:00
Sasha Koshka cfe12634ef Complete TestInterfaceInStruct (no longer segfaults somehow) 2024-01-27 19:49:22 -05:00
Sasha Koshka 928fef02cd More using qualified names in IR 2024-01-27 18:16:30 -05:00
Sasha Koshka 2f8b3fcad6 Oh my god I swapped the columns 2024-01-27 18:09:51 -05:00
Sasha Koshka 7bc24eabe0 Added cool output to failed tests 2024-01-27 18:07:49 -05:00
Sasha Koshka 839178d1d2 Literal generation now uses qualified type names when possible 2024-01-27 17:16:23 -05:00
Sasha Koshka f039be92b6 Remove redundant val variable/declaration generation 2024-01-27 14:17:52 -05:00
Sasha Koshka c0f004d3b8 Rename some routines in data 2024-01-27 14:03:38 -05:00
Sasha Koshka 658ff9e842 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 55f0138a64 Updated more tests (cutting out extra load insts) 2024-01-27 17:35:33 +00:00
Sasha Koshka a4fc8746bd Fix more names and orgaization of statement generation 2024-01-27 17:33:15 +00:00
Sasha Koshka 64d246a997 Added -Any suffix to appropriate generation routines 2024-01-27 17:24:47 +00:00
Sasha Koshka e188b2f349 Fixed segfault relating to break and return statements 2024-01-27 17:11:44 +00:00
Sasha Koshka 8e9b5e0613 Introducing ResultMode! time to got to bed. 2024-01-27 10:43:27 +00:00
Sasha Koshka a423d572ad Update some tests 2024-01-27 10:06:17 +00:00
Sasha Koshka 12ab7dd3e3 Updated control flow generation 2024-01-27 10:03:15 +00:00
Sasha Koshka b6da53afd6 Void functions/methods no longer request a value 2024-01-27 09:22:19 +00:00
Sasha Koshka c63e077f9a Made generic block generation routine 2024-01-27 09:18:41 +00:00
Sasha Koshka 75ec6a2ddc Made panic messages in expression-multiplex.go more informative 2024-01-27 09:15:39 +00:00
Sasha Koshka 8b45c91239 Add Val onto value expressions 2024-01-27 09:09:27 +00:00
Sasha Koshka 934af40e4b Update block generation routines 2024-01-27 09:06:17 +00:00
Sasha Koshka f827bfeb7a Add generateStatement routine 2024-01-27 08:53:29 +00:00
Sasha Koshka 32ce14f032 Renamed generateStatement to generateStatementVal 2024-01-27 08:49:25 +00:00
Sasha Koshka ac6a969ac9 Add generateExpression function 2024-01-27 08:48:00 +00:00
Sasha Koshka 07a4895b60 Rename expression source files 2024-01-27 08:40:02 +00:00
Sasha Koshka cfc894ff29 Rename generateExpression to generateExpressionVal 2024-01-27 08:39:05 +00:00
Sasha Koshka 8dedae5cb9 Nitpick 2024-01-27 08:26:46 +00:00
Sasha Koshka 7035648396 Change restricted access modifier to tilde (~) 2024-01-27 07:42:13 +00:00
Sasha Koshka c2df4203bd Same thing for bit casts 2024-01-27 07:05:34 +00:00
Sasha Koshka 01a3ac2e1b Generator now no longer generates ineffectual value casts 2024-01-27 07:03:13 +00:00
Sasha Koshka 73e2cffda2 Added a way to test IR type equality 2024-01-27 06:49:30 +00:00
Sasha Koshka f12405b3f4 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 d6a5ded758 Add IsConstant to Global 2024-01-27 04:16:41 +00:00
Sasha Koshka 14a02f5e22 Add basic globals implementation 2024-01-27 01:49:18 +00:00
Sasha Koshka 417c7772e4 Move addrspace to attribute file, will put more things in there 2024-01-27 01:48:29 +00:00
Sasha Koshka f39edcca0c Add function to encode global identifier 2024-01-27 01:47:52 +00:00
Sasha Koshka 6fd0d600e2 Add more tests to generator 2024-01-27 01:00:07 +00:00
Sasha Koshka deb2c04650 Replace bugs section with caveats in README 2024-01-26 22:11:35 +00:00
Sasha Koshka 4114b5c3aa Updated stuff to use the new routines in data.go 2024-01-26 05:35:47 +00:00
Sasha Koshka effebe5f64 More routines in data.go 2024-01-26 05:35:22 +00:00
Sasha Koshka cf201a08c6 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 794c41766c Added a test case for casting slices to pointers 2024-01-26 04:21:30 +00:00
Sasha Koshka 8a6d4599d1 Fix conversion of slices to pointers 2024-01-26 04:07:58 +00:00
Sasha Koshka 2ce910c54a Same thing as last commit 2024-01-26 04:05:58 +00:00
Sasha Koshka db31c9b80e Add more comments to explain casting rules in analyzer 2024-01-26 00:40:22 +00:00
Sasha Koshka ea697c19dd 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 4878c62531 Wrote test for getelementptr'ing into chained typedefs 2024-01-25 18:19:34 +00:00
Sasha Koshka 143aeb5236 Use TypeDefined in generator 2024-01-25 18:19:22 +00:00
Sasha Koshka 3840220582 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 496ef09154 Add test for chained typedefs with different names 2024-01-25 17:56:20 +00:00
Sasha Koshka 60f42f8718 I forgot comments existed haha 2024-01-25 08:27:18 +00:00
Sasha Koshka 5cc14ac04e Nicer testing framework for lexer 2024-01-25 08:27:17 +00:00
Sasha Koshka 598e3412be Lexer now accepts files with no blank line at the end 2024-01-25 08:27:17 +00:00
Sasha Koshka 13a36e43ba Update README.md 2024-01-25 07:43:21 +00:00
Sasha Koshka f083d016b4 Add logo to readme 2024-01-25 07:42:32 +00:00
Sasha Koshka dae023b2af Fix duplicated pointer types 2024-01-25 07:36:51 +00:00
Sasha Koshka e32655e285 AbstractType's TypeName member is now private 2024-01-25 07:05:07 +00:00
Sasha Koshka 3d94027c09 Added test to debug absurd behavior relating to type defs 2023-12-24 22:49:10 -05:00
Sasha Koshka 182b361237 Interface method calls no longer cause a segfault 2023-12-24 22:18:47 -05:00
Sasha Koshka b750fa3c77 Something was off with TestCompare 2023-12-24 21:45:47 -05:00
Sasha Koshka 47b3594bbf Fix code I probably wrote while high off my ass 2023-12-24 21:39:56 -05:00
Sasha Koshka 06df727f7f Empty struct literal values are initialized to zero 2023-12-23 17:25:27 -05:00
Sasha Koshka cd688c91af Added nil (zero initializer) literals 2023-12-21 23:33:38 -05:00
Sasha Koshka a9ff511496 Generator can produce link names 2023-12-20 03:05:27 -05:00
Sasha Koshka 88bc83f277 Added module referencing to parser 2023-12-19 15:21:10 -05:00
Sasha Koshka 0645e2b43a Add access specifiers and link names to parser 2023-12-17 11:23:42 -05:00
Sasha Koshka 6bcbd6aff9 Made the type checker a tad more robust 2023-12-15 13:23:37 -05:00
Sasha Koshka b220235a43 I forgor 2023-12-15 12:46:33 -05:00
Sasha Koshka e55d87d378 Fix comparison predicates 2023-12-15 12:40:43 -05:00
Sasha Koshka c4df65d189 Attempt to further fix interface assignment 2023-12-14 21:21:07 -05:00
Sasha Koshka f72bf533e0 Method calling now properly passes pointer instead of value 2023-12-14 01:23:34 -05:00
Sasha Koshka 6fa8adf871 generateAssignmentSource assigns interface methods to iface dest 2023-12-14 00:47:14 -05:00
Sasha Koshka 91eb3e8e9e Add some extra notes to readme 2023-12-13 03:44:57 -05:00
Sasha Koshka 9ea5deb23a Oops pt 2 2023-12-13 03:40:40 -05:00
Sasha Koshka b0b71d7723 Oops 2023-12-13 03:38:44 -05:00
Sasha Koshka 0ff4fd9ba4 Compiler can fall back to clang when llc is not present 2023-12-13 03:28:24 -05:00
Sasha Koshka 0a4f63680c Add logo 2023-12-13 03:20:15 -05:00
Sasha Koshka 414f4595dc Changed my mind about brackets on break and return statements 2023-12-12 19:51:17 -05:00
Sasha Koshka 93f6894ef0 Updated language spec 2023-12-08 17:31:16 -05:00
Sasha Koshka 3d95f41613 Add README.md 2023-12-08 00:28:58 -05:00
Sasha Koshka 900e90d468 Add test for aforementioned issue 2023-12-07 17:14:37 -05:00
Sasha Koshka d16079cbe8 Fix IsUnsigned 2023-12-07 17:05:26 -05:00
Sasha Koshka b2f8b85969 If statements get a type 2023-12-07 04:16:58 -05:00
Sasha Koshka 4a981f3253 Augh 2023-12-07 04:03:28 -05:00
Sasha Koshka 6045ed6ee3 If/else branching is no longer... like how it was 2023-12-07 03:58:57 -05:00
Sasha Koshka c780668cd0 If/else generation will not attempt to overwrite branches 2023-12-07 03:54:01 -05:00
Sasha Koshka fb360c3398 Operands to IR binary instructions now have identifiers instead of names 2023-12-07 03:04:29 -05:00
Sasha Koshka 30c346c6b7 Generator alloca's variables when they are defined 2023-12-07 02:48:27 -05:00
Sasha Koshka 8b7dcdf70c Fix case where generator would assign ID to void function calls 2023-12-05 22:36:41 -05:00
Sasha Koshka 7d436290c4 Add some more test cases to the generator 2023-12-05 22:29:29 -05:00
Sasha Koshka e06fd099ce Fix bug with popping block managers off of the stack 2023-12-05 22:27:55 -05:00
Sasha Koshka 2fa3cc1762 Add "internal error" prefix for generator errors 2023-12-05 22:08:06 -05:00
Sasha Koshka 6e861c4e52 Fix comments 2023-12-05 22:04:12 -05:00
Sasha Koshka 0e3101b4d0 Add comments 2023-12-05 16:52:33 -05:00
Sasha Koshka ead5ed3222 Slice operations now load the data pointer 2023-12-05 15:35:17 -05:00
Sasha Koshka 8085da442b Revert slices back to having just two fields 2023-12-05 03:07:54 -05:00
Sasha Koshka 7f866d102d Fixed several cases where the generator would output invalid IR 2023-12-03 22:23:04 -05:00
Sasha Koshka e24b9b8f17 Generator passes method owner as pointer 2023-12-03 15:38:07 -05:00
Sasha Koshka 52ac569c06 Fix stale code 2023-12-02 23:42:54 -05:00
Sasha Koshka 7dbde0a1d7 Generator generates methods properly 2023-12-02 23:41:35 -05:00
Sasha Koshka a18d9ea0de Oops 2023-12-02 23:36:12 -05:00
Sasha Koshka 0045b35790 Generator now pierces pointers when accessing members as well 2023-12-02 23:35:22 -05:00
Sasha Koshka eece7479a0 Member access can now pierce through pointers in the analyzer 2023-12-02 23:23:44 -05:00
Sasha Koshka 342e9a3a89 Analyzer adds a "this" pointer to methods 2023-12-02 23:06:58 -05:00
Sasha Koshka ffe873a3e4 String literals can be assigned to strings now 2023-12-02 22:45:22 -05:00
Sasha Koshka 3f88513dc1 That should be literally every feature 2023-12-02 21:45:15 -05:00
Sasha Koshka ea502dc2bb Created function for making slices 2023-12-02 04:21:10 -05:00
Sasha Koshka 7b139199c0 Ditch brackets around break and return statements 2023-12-01 01:53:33 -05:00
Sasha Koshka 31d5317dc8 Completed operations 2023-12-01 01:18:10 -05:00
Sasha Koshka 635add38e6 Made native target actually work 2023-11-30 17:16:55 -05:00
Sasha Koshka 6b2ed12c90 Operation stub part II 2023-11-30 15:44:30 -05:00
Sasha Koshka 4fa7848452 Add operation stub 2023-11-30 02:13:47 -05:00
Sasha Koshka 3fd00d56eb Added casting 2023-11-30 02:06:12 -05:00
Sasha Koshka 4c07b9f206 Add length to analyzer and parser 2023-11-30 02:05:32 -05:00
Sasha Koshka df1bc20388 Added bit casts 2023-11-29 20:37:56 -05:00
Sasha Koshka ca401442b4 Generator properly uses subscript offset 2023-11-29 20:31:40 -05:00
Sasha Koshka 7cf4067167 Generator grabs slice type from proper place 2023-11-29 20:31:07 -05:00
Sasha Koshka 6ddb32fd5c Multi-layer location expression type inference 2023-11-29 20:07:16 -05:00
Sasha Koshka 19a6b73f0e Analyzer does light upward type inference for location expressions 2023-11-29 18:52:27 -05:00
Sasha Koshka 1cd972f50f Generator now adds null terminator to c-strings 2023-11-29 00:45:38 -05:00
Sasha Koshka 1d4bcb44a2 Reduce element type when generating string literal data 2023-11-29 00:21:58 -05:00
Sasha Koshka c709500d58 Facehoof 2023-11-29 00:13:54 -05:00
Sasha Koshka e92377bc95 Added string literals to generator 2023-11-29 00:01:35 -05:00
Sasha Koshka 19b8825f5b Add string literals to generator WIP 2023-11-28 01:15:12 -05:00
Sasha Koshka e5be4dba02 Added string literal to analyzer 2023-11-26 17:27:31 -05:00
Sasha Koshka d0245e4375 Added string literal to parser 2023-11-26 05:16:40 -05:00
Sasha Koshka 496a8c5e78 Add escape sequences to strings in lexer 2023-11-26 04:56:14 -05:00
Sasha Koshka 85e77c36c6 This should have been way more commits 2023-11-26 04:02:22 -05:00
Sasha Koshka e71b9951e9 Fixed scope OverVariables stopping immediately 2023-11-26 04:01:55 -05:00
Sasha Koshka 2559b88fbb fsplc command now infers output type from output file name ext 2023-11-26 04:01:03 -05:00
Sasha Koshka 2db97a42b1 Analyzer no longer deletes dereferences 2023-11-26 04:00:44 -05:00
Sasha Koshka 38a4cd7aaa LLVM improvements 2023-11-26 04:00:20 -05:00
Sasha Koshka dc16a17271 Typedefs that are never referenced are never generated 2023-11-23 01:44:48 -05:00
Sasha Koshka 31829df4da Basic compiler command 2023-11-23 01:26:28 -05:00
Sasha Koshka 05b389dd3c Generator returns an LLVM module 2023-11-23 01:26:14 -05:00
Sasha Koshka 3b9ed0c35f Various fixes 2023-11-23 00:02:00 -05:00
Sasha Koshka 7690f683a4 Migrated generator to use new llvm code generation package 2023-11-22 20:37:37 -05:00
Sasha Koshka 8ce12613e2 Add custom LLVM code generation package 2023-11-22 20:37:16 -05:00
Sasha Koshka 54b693620e Generator WIP 2023-11-21 15:04:22 -05:00
Sasha Koshka 72941e1ab4 Slight modifications to analyzer 2023-11-21 15:04:01 -05:00
Sasha Koshka ec78adf42d Functions/behaviors stub 2023-11-16 22:41:20 -05:00
Sasha Koshka 4cbe29e777 Add CLI stub 2023-11-16 22:40:57 -05:00
Sasha Koshka 3a1d9e20c2 Add type generator 2023-11-16 22:04:56 -05:00
Sasha Koshka e501577555 Generator stub II 2023-11-16 15:52:28 -05:00
Sasha Koshka 1c6e485bf7 Generator stub 2023-11-14 02:49:55 -05:00
Sasha Koshka 605fd24228 Pass all analyzer tests 2023-11-04 17:59:04 -04:00
Sasha Koshka 47beb9e41f Pass all but one test 2023-11-04 16:34:40 -04:00
Sasha Koshka a2e22d1154 Fix goofy bugs 2023-10-31 21:52:47 -04:00
Sasha Koshka af52bbb070 uhhhh 2023-10-31 15:43:19 -04:00
Sasha Koshka dd9f866165 Can now assign array directly to slice 2023-10-31 01:46:18 -04:00
Sasha Koshka fa60f8c814 Pass all literal assignment tests 2023-10-29 15:18:44 -04:00
Sasha Koshka 8cdeab6efa Array literal can be used in array and slice 2023-10-29 15:13:25 -04:00
Sasha Koshka 6f14b8a1b3 Pass a bunch of tests 2023-10-29 15:09:20 -04:00
Sasha Koshka d435a1b1be Removed Void as a concept 2023-10-29 14:47:28 -04:00
Sasha Koshka 24af8e0b49 Expression analysis actually runs now 2023-10-29 14:40:29 -04:00
Sasha Koshka ab262207db Fix entity string conversion 2023-10-29 14:34:21 -04:00
Sasha Koshka 359441d5fa Analyze return statement 2023-10-29 14:28:51 -04:00
Sasha Koshka 8751cc7b89 Analyze break statements 2023-10-29 02:59:45 -04:00
Sasha Koshka b8d573f2cd Analyze more expressions 2023-10-29 02:18:41 -04:00
Sasha Koshka 2018ccead6 Add loop stack management to scopeManager and scopeContextManager 2023-10-29 02:18:03 -04:00
Sasha Koshka 68951a3af4 Analyze all the literals 2023-10-28 03:24:17 -04:00
Sasha Koshka 037c59172c Revise spec a bit 2023-10-28 03:24:08 -04:00
Sasha Koshka a6fec780c7 Start analyzing literals 2023-10-27 16:10:31 -04:00
Sasha Koshka c88ba16bdc Add package for determining integer sizes 2023-10-27 16:10:05 -04:00
Sasha Koshka 4da8ecd25f More expressions (wip) 2023-10-26 10:43:50 -04:00
Sasha Koshka 964daf01ed Analysis of value/bit casts 2023-10-25 01:57:02 -04:00
Sasha Koshka 0bcc5e7d46 Location expressions 2023-10-24 21:54:59 -04:00
Sasha Koshka 9a7087ab14 Added analysis for numerous types of expressions 2023-10-24 18:29:33 -04:00
Sasha Koshka ee8eabcfc4 I forgor 2023-10-24 01:34:58 -04:00
Sasha Koshka 5ac046d5b1 Function call analysis 2023-10-20 13:48:05 -04:00
Sasha Koshka 99c09d9b07 Added basic equality checks to types 2023-10-19 21:53:19 -04:00
Sasha Koshka fc97c3ed49 WIP expression analysis type checking 2023-10-19 19:13:55 -04:00
Sasha Koshka 75c18763a9 New syntax for method calls
This will free up the '::' token for namespace/module access. Exciting!
2023-10-19 17:19:23 -04:00
Sasha Koshka 30636e18aa Expression analysis stub 2023-10-18 02:29:24 -04:00
Sasha Koshka 92e8205bb9 That, but for operation arg count testing 2023-10-18 01:39:43 -04:00
Sasha Koshka 1302731793 Updated analyzer tests to account for lexer changes 2023-10-18 01:33:22 -04:00
Sasha Koshka cdee4adf1c Star is now a separate token 2023-10-18 01:14:17 -04:00
Sasha Koshka a80492420e Implement custom lexer FINALLY 2023-10-18 00:48:35 -04:00
Sasha Koshka 1dab6e3805 Finished analyzing method signatures 2023-10-17 03:21:25 -04:00
Sasha Koshka decefce142 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
Sasha Koshka 8b4244e3cb Booleans are now literals 2023-10-09 00:24:48 -04:00
Sasha Koshka e31ef61e6b Function analysis stub 2023-10-09 00:13:13 -04:00
Sasha Koshka fcadc0971b Added recursive type definitions 2023-10-08 23:54:45 -04:00
Sasha Koshka 091a59c1bd All the builtin/primitive types are in their own place 2023-10-07 17:32:47 -04:00
Sasha Koshka 6f40b6216b Testing ALL the types 2023-10-07 02:27:49 -04:00
Sasha Koshka f1e415b038 Add readme 2023-10-07 01:55:33 -04:00
Sasha Koshka 07bf334689 the ansalyzer 2023-10-07 01:18:05 -04:00
Sasha Koshka 2373f715ff Reorganized tests 2023-10-06 18:58:17 -04:00
173 changed files with 21289 additions and 1337 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>.

141
README.md Normal file
View File

@ -0,0 +1,141 @@
# FSPL
<img src="assets/fspl.svg" width="128" alt="FSPL logo.">
[![Go Reference](https://pkg.go.dev/badge/git.tebibyte.media/fspl/fspl.svg)](https://pkg.go.dev/git.tebibyte.media/fspl/fspl)
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
Before installing the compiler with `go install`, you will need to install the
[Go programming language](https://go.dev/) on your system. Afterwards, you can
install the compiler and associated tooling by running:
`go install ./cmd/*`
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)...] ADDRESS`
The program compiles the unit with the specified address into one output file.
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
nickname of the input address. For more information on how to use the `fsplc`
program, run `fsplc --help`.
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
Q4 2023:
- [x] Type definitions
- [x] Methods
- [x] Defined and external functions
- [x] Strict, static, bottom-up type inference
- [x] Pointers
- [x] Arrays
- [x] Slices
- [x] Structs
- [x] Interfaces
- [x] Integer, floating point, string, struct, and array Literals
- [x] Assignment
- [x] Variables
- [x] Function, method, and interface behavior calls
- [x] Operations
- [x] Casting
- [x] Blocks
- [x] If/else
- [x] Loops
Q1 2024:
- [x] Union types (carry type information)
- [x] Match statements
- [x] Modules
- [x] For/range loops
- [x] Switch statements
Q2 2024:
- [ ] Mutable/immutable variables
- [ ] Basic, non-final standard library routines
- [ ] Conditional compilation
- [ ] Shared library compilation
- [ ] Constants
- [ ] ABI documentation
Q3 2024
- [ ] Vararg
- [ ] FSPL vararg using Slices
- [ ] Optional per-function C-style vararg for compatibility
- [ ] 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.
## FAQ
I have either been asked these questions, or expect to be at some point.
### What happened to ARF?
A couple of years ago, I attempted to create a programming language under the
name ARF, with the goal of trying out some new and interesting syntax I'd come
up with. I made several attempts to build a working compiler for it, but never
quite succeeded. When I entered my senior year at university, I decided to have
another crack at it as my senior project. By then, the ideas I had for the
language had changed so much, it was basically a Ship of Theseus situation and
it really just needed a new name altogether. Plus, ARF never really stood for
anything in the first place.
### Why Go?
Out of all the languages I know, I probably know Go the best. It was an obvious
choice for making something this complex. However, I'd like to self-host the
compiler eventually.
### I know you as X name from Y place, why is your name here Sasha Koshka?
I consider it a bad idea to share your real name over the internet.

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,566 @@
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/opaque types is not allowed.
weak
// Full structural equivalence, and named types are always reduced.
// Assignment to private/opaque 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/opaque 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.typeOpaque(pos, destination)
if err != nil { return err }
err = this.typeOpaque(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 union assignment
if _, ok := this.isUnion(destination); ok {
return this.canAssignUnion(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
}
`)
// canAssignUnion takes in an analyzed union destination type and an
// analyzed source type, and determines whether source can be assigned to
// destination.
func (this *Tree) canAssignUnion (
pos errors.Position,
destination entity.Type,
source entity.Type,
) error {
sourceHash := source.Hash()
union, _ := this.isUnion(destination)
// check if types are identical
if entity.TypesEqual(source, destination) { return nil }
// check if type is included within the union
if _, ok := union.AllowedMap[sourceHash]; ok { return nil }
return errors.Errorf (
pos, "%v is not included within union %v",
source, destination)
}
func TestAssignLiteralErrArrayInt (test *testing.T) {
testStringErr (test,
"expected array", 3, 11,
`
[main] = {
x:3:Int = 5
}
`)
// 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 TestAssignLiteralErrArrayWrongLength (test *testing.T) {
testStringErr (test,
"expected 3 elements", 3, 11,
`
[main] = {
x:3:Int = (* 1 2 3 4)
}
`)
// isLocationExpression returns whether or not an expression is a valid location
// expression.
func (this *Tree) isLocationExpression (expression entity.Expression) error {
// TODO: have some Name() method of Expression so we can use it in a
// default case here. this will prevent crashes when new features are
// added.
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.Match:
return cannot(expression.Position(), "match")
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 TestAssignLiteralErrArrayWrongType (test *testing.T) {
testStringErr (test,
"expected integer", 3, 19,
`
[main] = {
x:3:Int = (* 1 2 3.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 TestAssignLiteralErrSliceWrongType (test *testing.T) {
testStringErr (test,
"expected integer", 3, 19,
`
[main] = {
x:*: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 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
}
`)
// isUnion takes in an analyzed type and returns true and a union if that type
// refers to a union. If not, it returns nil, false.
func (this *Tree) isUnion (ty entity.Type) (*entity.TypeUnion, bool) {
ty = ReduceToBase(ty)
switch ty.(type) {
case *entity.TypeUnion: return ty.(*entity.TypeUnion), true
default: return nil, false
}
}
func TestAssignLiteralErrInterfaceInt (test *testing.T) {
testStringErr (test,
"cannot assign literal to interface", 6, 2,
`
Bird: ([fly distance:F64] [land])
[main] = {
b:Bird = 5
}
`)
// 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 TestAssignLiteralErrInterfaceFloat (test *testing.T) {
testStringErr (test,
"cannot assign literal to interface", 6, 2,
`
Bird: ([fly distance:F64] [land])
[main] = {
b:Bird = 5.5
}
`)
// isInteger returns whether or not the specified type is an integer or word.
func isInteger (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypeInt, *entity.TypeWord: return true
default: return false
}
}
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)
}
`)
// isWord returns whether or not the specified type is a word.
func isWord (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypeWord: 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)
}
`)
// isPointer returns whether or not the specified type is a pointer.
func isPointer (ty entity.Type) bool {
switch ReduceToBase(ty).(type) {
case *entity.TypePointer: return true
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)
}
`)
// isOrdered returns whether or not the values of the specified type can be
// ordered.
func isOrdered (ty entity.Type) bool {
return isNumeric(ty)
}
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]
}
`)
// 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 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]
}
`)
// 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 TestCastErrIntPointer (test *testing.T) {
testStringErr (test,
"cannot convert from *Int to Int", 2, 14,
`
[main]:Int = [~ [@ a:Int] Int]
`)
// 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 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]
`)
// integerWidth returns the width of the type, if it is an integer.
func integerWidth (ty entity.Type) (int, bool) {
switch ty := ReduceToBase(ty).(type) {
case *entity.TypeInt: return ty.Width, true
default: return 0, false
}
}
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
}
}

323
analyzer/assignment_test.go Normal file
View File

@ -0,0 +1,323 @@
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, 15,
`
[main] = {
x:3:Int = (1 3.3 4)
}
`)
}
func TestAssignmentLiteralErrSliceWrongType (test *testing.T) {
testStringErr (test,
"cannot use float literal as Int", 3, 15,
`
[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 TestAssignmentUnion (test *testing.T) {
testString (test,
`
U: (| Int F64)
[main] = {
x:F64 = 7.7
y:U = x
}
`)
}
func TestAssignmentUnionToUnion (test *testing.T) {
testString (test,
`
U: (| Int F64)
[main] = {
x:U y:U
x = y
}
`)
}
func TestAssignmentUnionErrNotListed (test *testing.T) {
testStringErr (test,
"I64 is not included within union U", 5, 8,
`
U: (| Int F64)
[main] = {
x:I64 = 7
y:U = x
}
`)
}
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: 1 }
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]]]
}
`)
}

View File

@ -0,0 +1,237 @@
package analyzer
import "testing"
func TestMatch (test *testing.T) {
testString (test,
`
U: (| Int F64)
[matchToInt u:U]:Int = match u
| u:Int u
| u:F64 [~Int u]
`)
}
func TestMatchReturn (test *testing.T) {
testString (test,
`
U: (| Int F64)
[isInt u:U]:Bool = {
match u | u:Int [return true]
false
}
`)
}
func TestMatchReturnValueUsed (test *testing.T) {
testString (test,
`
U: (| Int F64)
[isInt u:U]:Bool = match u
| u:Int [return true]
| u:F64 false
`)
}
func TestMatchDefault (test *testing.T) {
testString (test,
`
U: (| Int F64)
[isInt u:U]:Bool = match u
| u:Int [return true]
* false
`)
}
func TestMatchUnionUnderComplete (test *testing.T) {
testString (test,
`
U: (| Int F64 UInt)
[print str:String]
[matchToInt u:U] = match u
| u:Int [print 'Int']
| u:F64 [print 'F64']
`)
}
func TestMatchErrUnionOverComplete (test *testing.T) {
testStringErr (test,
"UInt is not included within U", 5, 6,
`
U: (| Int F64)
[matchToInt u:U]:Int = match u
| u:Int u
| u:UInt [~Int u]
| u:F64 [~Int u]
`)
}
func TestMatchErrNotUnion (test *testing.T) {
testStringErr (test,
"type U is not a union", 3, 30,
`
U: Int
[matchToInt u:U]:Int = match u
| u:Int u
| u:F64 [~Int u]
`)
}
func TestMatchErrUnionUnderComplete (test *testing.T) {
testStringErr (test,
"match does not cover all types within U", 3, 24,
`
U: (| Int F64 UInt)
[matchToInt u:U]:Int = match u
| u:Int u
| u:F64 [~Int u]
`)
}
func TestMatchErrDuplicate (test *testing.T) {
testStringErr (test,
"Int already listed in match at stream0.fspl:4:6", 6, 6,
`
U: (| Int F64 UInt)
[matchToInt u:U]:Int = match u
| u:Int u
| u:F64 [~Int u]
| u:Int u
`)
}
func TestSwitch (test *testing.T) {
testString (test,
`
[f x:Int]:Int = switch x
| 0 5
| 1 4
| 2 3
* 0
`)
}
func TestSwitchReturn (test *testing.T) {
testString (test,
`
[is5 x:Int]:Bool = {
switch x | 5 [return true]
false
}
`)
}
func TestSwitchReturnValueUsed (test *testing.T) {
testString (test,
`
[is5 x:Int]:Bool = switch x
| 5 [return true]
* false
`)
}
func TestSwitchErrBadLiteral (test *testing.T) {
testStringErr (test,
"cannot use array literal as Int", 4, 4,
`
[f x:Int]:Int = switch x
| 0 5
| (1) 4
| 2 3
* 0
`)
}
func TestSwitchErrNotConstant (test *testing.T) {
testStringErr (test,
"y cannot represent a constant integer", 7, 4,
`
[f x:Int]:Int = {
y:Int = 1
switch x
| 0 5
| y 4
| 2 3
* 0
}
`)
}
func TestSwitchErrDuplicate (test *testing.T) {
testStringErr (test,
"65 already listed in match at stream0.fspl:6:2", 7, 4,
`
[f x:I8]:Int = switch x
| 0 5
| 1 4
| 2 3
| 65 2
| 'A' 1
* 0
`)
}
func TestIfElseReturnValueUsed (test *testing.T) {
testString (test,
`
[is5 x:Int]:Bool = if [= x 5]
then true
else [return false]
`)
}
func TestIfElseBreakValueUsed (test *testing.T) {
testString (test,
`
[f x:Int]: Int = loop {
y:Int = if [= x 5]
then [break 0]
else x
}
`)
}
func TestFor (test *testing.T) {
testString (test,
`
[f str:String] = for c:Byte in str { }
[g str:String] = for i:Index c:Byte in str { }
`)}
func TestForBreak (test *testing.T) {
testString (test,
`
[f str:String]:Byte = for c:Byte in str [break c]
`)}
func TestForBreakBadType (test *testing.T) {
testStringErr (test,
"expected Byte", 2, 56,
`
[f str:String]:Byte = for i:Index c:Byte in str [break i]
`)}
func TestForErrBadIndex (test *testing.T) {
testStringErr (test,
"expected Index", 2, 22,
`
[f str:String] = for i:Int c:Byte in str { }
`)}
func TestForErrBadOver (test *testing.T) {
testStringErr (test,
"cannot use U8 as Int", 2, 31,
`
[f str:String] = for c:Int in str { }
`)}
func TestForErrDeclarationScoped (test *testing.T) {
testStringErr (test,
"no variable named c", 4, 2,
`
[f str:String] = {
for c:Byte in str { }
c = 'a'
}
`)}

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,79 @@
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.Match:
return this.analyzeMatch(into, mode, expression)
case *entity.Switch:
return this.analyzeSwitch(into, mode, expression)
case *entity.Loop:
return this.analyzeLoop(into, mode, expression)
case *entity.For:
return this.analyzeFor(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))
}
}

1040
analyzer/expression.go Normal file

File diff suppressed because it is too large Load Diff

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.Unt = key.Unit
// functions cannot be marked as opaque
if function.Acc == entity.AccessOpaque {
return nil, errors.Errorf(pos, "cannot mark function as opaque")
}
// 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.typeOpaque(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.typeOpaque(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.typeOpaque(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.typeOpaque(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.typeOpaque(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.typeOpaque(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.typeOpaque(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.Unt = owner.Unit()
// methods cannot be marked as opaque
if method.Acc == entity.AccessOpaque {
return nil, errors.Errorf(pos, "cannot mark method as opaque")
}
// 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 {
Pos: method.Position(),
Name: "this",
Ty: &entity.TypePointer {
Pos: method.Position(),
Referenced: &entity.TypeNamed {
Pos: 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 TestUnitAssignOpaque (test *testing.T) {
testUnits (test,
`[main] = {
x:other::OpaqueInt
y:other::OpaqueInt
x = y
}`,
"other.fspl",
`# OpaqueInt:Int`,
)}
func TestUnitAssignLiteralOpaqueErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::OpaqueInt is opaque", 1, 27,
`[main]:other::OpaqueInt = 5`,
"other.fspl",
`# OpaqueInt:Int`,
)}
func TestAssignLiteralOpaque (test *testing.T) {
testString (test,
`# OpaqueInt:Int
[main]:OpaqueInt = 5`,
)}
func TestUnitMemberAccessOpaqueErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::OpaqueStruct is opaque", 3, 3,
`[main] = {
x:other::OpaqueStruct
x.x = 5
}`,
"other.fspl",
`# OpaqueStruct:(. x:Int y:Int)`,
)}
func TestMemberAccessOpaque (test *testing.T) {
testString (test,
`# OpaqueStruct:(. x:Int y:Int)
[main] = {
x:OpaqueStruct
x.x = 5
}`,
)}
func TestUnitSubscriptOpaqueErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::OpaqueArr is opaque", 3, 4,
`[main] = {
x:other::OpaqueArr
[.x 0] = 5
}`,
"other.fspl",
`# OpaqueArr:5:Int`,
)}
func TestSubscriptOpaque (test *testing.T) {
testString (test,
`# OpaqueArr:5:Int
[main] = {
x:OpaqueArr
[.x 0] = 5
}`,
)}
func TestUnitMathOpaqueErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::OpaqueInt is opaque", 2, 23,
`[main] = {
x:other::OpaqueInt = [+
y:other::OpaqueInt
z:other::OpaqueInt]
}`,
"other.fspl",
`# OpaqueInt:Int`,
)}
func TestMathOpaque (test *testing.T) {
testString (test,
`# OpaqueInt:Int
[main] = {
x:OpaqueInt = [+
y:OpaqueInt
z:OpaqueInt]
}`,
)}
func TestNestedUnitTypedef (test *testing.T) {
testUnits (test,
`[main]:layer1::X = 5`,
"layer0.fspl",
`+ X:Int`,
"layer1.fspl",
`+ X:layer0::X`,
)}
func TestUnitBehaviorCallOpaqueErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::OpaqueInterface is opaque", 3, 3,
`[main] = {
x:other::OpaqueInterface
x.[y]
}`,
"other.fspl",
`# OpaqueInterface:(& [y])`,
)}
func TestBehaviorCallOpaque (test *testing.T) {
testString (test,
`[main] = {
x:OpaqueInterface
x.[y]
}
# OpaqueInterface:(& [y])`,
)}
func TestUnitCastOpaqueErr (test *testing.T) {
testUnitsErr (test,
"main.fspl", "type other::OpaqueInt is opaque", 2, 16,
`[main] = {
x:Int = [~Int y:other::OpaqueInt]
}`,
"other.fspl",
`# OpaqueInt:Int`,
)}
func TestCastOpaque (test *testing.T) {
testString (test,
`[main] = {
x:Int = [~Int y:OpaqueInt]
}
# OpaqueInt:Int`,
)}
func TestFunctionOpaqueErr (test *testing.T) {
testStringErr (test,
"cannot mark function as opaque", 1, 1,
`# [f]`,
)}
func TestMethodOpaqueErr (test *testing.T) {
testStringErr (test,
"cannot mark method as opaque", 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.Breakable) {
this.assertPopulated()
(*this)[len(*this) - 1].pushLoop(loop)
}
func (this *scopeContextManager) popLoop () {
this.assertPopulated()
(*this)[len(*this) - 1].popLoop()
}
func (this *scopeContextManager) topLoop () (entity.Breakable, 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.Breakable
declaration entity.TopLevel
}
func (this *scopeContext) pushLoop (loop entity.Breakable) {
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.Breakable, 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,147 @@ 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 fsplParser.IsReserved(key.Name) {
return errors.Errorf (
currentPos, "cannot shadow reserved identifier %s",
key.Name)
}
if function, isFunction := this.rawFunctions[name]; isFunction {
pos = function.Pos
unique = false
if _, isPrimitive := primitiveTypes[key.Name]; isPrimitive {
return errors.Errorf (
currentPos, "cannot shadow primitive %s",
key.Name)
}
if method, isMethod := this.rawMethods[name]; isMethod {
pos = method.Pos
unique = false
if _, isBuiltin := builtinTypes[key.Name]; isBuiltin {
return errors.Errorf (
currentPos, "cannot shadow builtin %s",
key.Name)
}
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,305 @@
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.Unt = 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,
acceptIncomplete bool,
) (
entity.Type,
error,
) {
return this.analyzeTypeInternal (ty, nil, acceptIncomplete)
}
switch ty.(type) {
case *entity.TypeNamed:
ty := ty.(*entity.TypeNamed)
defined, err := this.analyzeTypedef(ty.Pos, ty.Name)
ty.Type = defined
return ty, err
case *entity.TypePointer:
ty := ty.(*entity.TypePointer)
ty.Referenced, err = this.analyzeType(ty.Referenced)
return ty, err
case *entity.TypeSlice:
ty := ty.(*entity.TypeSlice)
ty.Element, err = this.analyzeType(ty.Element)
return ty, err
case *entity.TypeArray:
ty := ty.(*entity.TypeArray)
if ty.Length < 1 {
return ty, participle.Errorf (
ty.Pos, "array length must be > 1", name)
func (this *Tree) analyzeTypeInternal (
ty entity.Type,
root *entity.Typedef,
acceptIncomplete bool,
) (
entity.Type,
error,
) {
// edge cases
if ty == nil { return nil, nil }
var err error
// if we are analyzing a typedef, we will need to update incomplete type
// information as we go in order for recursive type definitions to work
rootKey := func () entity.Key {
return entity.Key {
Unit: this.unit,
Name: root.Name,
}
ty.Element, err = this.analyzeType(ty.Element)
}
updateIncompleteInfo := func () {
if root != nil { this.incompleteTypes[rootKey()] = root }
}
updatePseudoCompleteInfo := func () {
if root != nil { this.Types[rootKey()] = root }
}
if root != nil { defer delete(this.incompleteTypes, rootKey()) }
// ---------
// determine the access permission for the type
access := entity.AccessPublic; if root != nil {
access = root.Acc
}
switch ty := ty.(type) {
// named type
case *entity.TypeNamed:
ty.Unt = this.unit
ty.Acc = access
updateIncompleteInfo()
// resolve nickname of the unit where the typedef is
if primitive, isPrimitive := primitiveTypes[ty.Name]; isPrimitive {
return primitive, nil
}
unit, err := this.resolveNickname(ty.Position(), ty.UnitNickname)
if err != nil { return nil, err }
// analyze the typedef
def, err := this.analyzeTypedef(ty.Position(), entity.Key {
Unit: unit,
Name: ty.Name,
}, acceptIncomplete)
if err != nil { return nil, err }
ty.Type = def.Type
// check access permissions
err = this.typePrivate(ty.Position(), ty)
if err != nil { return nil, err }
return ty, nil
// pointer type
case *entity.TypePointer:
ty.Unt = this.unit
ty.Acc = access
updateIncompleteInfo()
ty.Referenced, err = this.analyzeType(ty.Referenced, true)
return ty, err
// slice type
case *entity.TypeSlice:
ty.Unt = this.unit
ty.Acc = access
updateIncompleteInfo()
ty.Element, err = this.analyzeType(ty.Element, false)
return ty, err
// array type
case *entity.TypeArray:
ty.Unt = this.unit
ty.Acc = access
updateIncompleteInfo()
if ty.Length < 1 {
return ty, errors.Errorf (
ty.Position(), "array length must be > 0")
}
ty.Element, err = this.analyzeType(ty.Element, false)
return ty, err
// struct type
case *entity.TypeStruct:
ty := ty.(*entity.TypeStruct)
ty.Unt = this.unit
ty.Acc = access
ty, err = this.assembleStructMap(ty)
if err != nil { return err }
updateIncompleteInfo()
if err != nil { return nil, err }
for name, member := range ty.MemberMap {
ty.MemberMap[name], err = this.analyzeType(member.Type)
if err != nil { return ty, err }
ty.MemberMap[name].Ty,
err = this.analyzeType(member.Ty, false)
if err != nil { return nil, err }
}
return ty, nil
// interface type
case *entity.TypeInterface:
ty := ty.(*entity.TypeInterface)
ty.Unt = this.unit
ty.Acc = access
ty, err = this.assembleInterfaceMap(ty)
if err != nil { return err }
for name, method := range ty.MethodMap {
ty.MethodMap[name], err = this.analyzeSignature(method)
if err != nil { return ty, err }
updatePseudoCompleteInfo()
if err != nil { return nil, err }
for name, behavior := range ty.BehaviorMap {
ty.BehaviorMap[name], err = this.analyzeBehavior(behavior)
if err != nil { return nil, err }
}
return ty, nil
// union type
case *entity.TypeUnion:
ty.Unt = this.unit
ty.Acc = access
updateIncompleteInfo()
return this.analyzeTypeUnion(ty)
// integer type
case *entity.TypeInt:
ty.Unt = this.unit
ty.Acc = access
updateIncompleteInfo()
if ty.Width < 1 {
return ty, errors.Errorf (
ty.Position(), "integer width must be > 0")
}
return ty, nil
// floating point and word types
case *entity.TypeFloat:
ty.Unt = this.unit
ty.Acc = access
return ty, nil
case *entity.TypeWord:
ty.Unt = this.unit
ty.Acc = access
return ty, nil
default: panic(fmt.Sprint("BUG: analyzer doesnt know about type ", ty))
}
}
// typePrivate checks if the given type is private.
func (this *Tree) typePrivate (pos errors.Position, ty entity.Type) error {
if ty == nil { return nil }
original := ty
if named, ok := ty.(*entity.TypeNamed); ok {
ty = named.Type
}
if ty.Unit() != this.unit && ty.Access() == entity.AccessPrivate {
return errors.Errorf(pos, "type %v is private", entity.FormatType(original))
}
return nil
}
// typeOpaque checks if the given type is opaque or private.
func (this *Tree) typeOpaque (pos errors.Position, ty entity.Type) error {
if ty == nil { return nil }
err := this.typePrivate(pos, ty)
if err != nil { return err }
original := ty
if named, ok := ty.(*entity.TypeNamed); ok {
ty = named.Type
}
if ty.Unit() != this.unit && ty.Access() == entity.AccessOpaque {
return errors.Errorf(pos, "type %v is opaque", entity.FormatType(original))
}
return nil
}
func (this *Tree) assembleStructMap (ty *entity.TypeStruct) (*entity.TypeStruct, error) {
ty.MemberMap = make(map[string] *entity.Declaration)
ty.MemberOrder = make([]string, len(ty.Members))
for index, member := range ty.Members {
if previous, exists := ty.MemberMap[member.Name]; exists {
return ty, participle.Errorf (
member.Pos, "%s already listed in struct at %v",
member.Name, previous.Pos)
return ty, errors.Errorf (
member.Position(), "%s already listed in struct at %v",
member.Name, previous.Position())
}
ty.MemberMap [member.Name] = member
ty.MemberOrder[index] = member.Name
ty.Members [index] = member
}
ty.Members = nil
return ty, nil
}
func (this *Tree) assembleInterfaceMap (ty *entity.TypeStruct) (*entity.TypeStruct, error) {
ty.MethodMap = make(map[string] *entity.Signature)
ty.MethodOrder = make([]string, len(ty.Methods))
for index, method := range ty.Methods {
if previous, exists := ty.MethodMap[method.Name]; exists {
return ty, participle.Errorf (
method.Pos, "%s already listed in interface at %v",
method.Name, previous.Pos)
func (this *Tree) assembleInterfaceMap (ty *entity.TypeInterface) (*entity.TypeInterface, error) {
ty.BehaviorMap = make(map[string] *entity.Signature)
ty.BehaviorOrder = make([]string, len(ty.Behaviors))
for index, method := range ty.Behaviors {
if previous, exists := ty.BehaviorMap[method.Name]; exists {
return ty, errors.Errorf (
method.Position(), "%s already listed in interface at %v",
method.Name, previous.Position())
}
ty.MethodMap [method.Name] = member
ty.MethodOrder[index] = method.Name
ty.BehaviorMap [method.Name] = method
ty.BehaviorOrder[index] = method.Name
ty.Behaviors [index] = method
}
ty.Methods = nil
return ty, nil
}
func (this *Tree) analyzeTypeUnion (ty *entity.TypeUnion) (*entity.TypeUnion, error) {
ty.AllowedMap = make(map[entity.Hash] entity.Type)
ty.AllowedOrder = make([]entity.Hash, len(ty.Allowed))
for index, allowed := range ty.Allowed {
allowed, err := this.analyzeType(allowed, true)
if err != nil { return nil, err }
hash := allowed.Hash()
if previous, exists := ty.AllowedMap[hash]; exists {
return ty, errors.Errorf (
allowed.Position(), "%v already listed in union at %v",
allowed, previous.Position())
}
ty.AllowedMap [hash] = allowed
ty.AllowedOrder[index] = hash
ty.Allowed [index] = allowed
}
return ty, nil
}
func (this *Tree) analyzeBehavior (behavior *entity.Signature) (*entity.Signature, error) {
behavior, err := this.assembleSignatureMap(behavior)
behavior.Unt = this.unit
if err != nil { return behavior, nil }
for name, argument := range behavior.ArgumentMap {
behavior.ArgumentMap[name].Ty, err = this.analyzeType(argument.Ty, false)
if err != nil { return behavior, err }
}
behavior.Return, err = this.analyzeType(behavior.Return, false)
return behavior, err
}

224
analyzer/type_test.go Normal file
View File

@ -0,0 +1,224 @@
package analyzer
import "testing"
func TestTypedefUniqueErr (test *testing.T) {
testStringErr (test,
"Hello already declared at stream0.fspl:2:1", 3, 1,
`
Hello: *Int
Hello: (. x:Int y:Int)
`)
}
func TestTypedefUniqueErrShadowPrimitive (test *testing.T) {
testStringErr (test,
"cannot shadow primitive U8", 2, 1,
`
U8: Int
`)
}
func TestTypedefUniqueErrShadowBuiltin (test *testing.T) {
testStringErr (test,
"cannot shadow builtin String", 2, 1,
`
String: Int
`)
}
func TestTypedefUnique (test *testing.T) {
testString (test,
`
Hello: *Int
World: (. x:Int y:Int)
`)
}
func TestTypedefRecursiveErr (test *testing.T) {
testStringErr (test,
"type List cannot be used in this context", 4, 9,
`
List: (.
value: Int
next: List)
`)
}
func TestTypedefRecursive (test *testing.T) {
testString (test,
`
List: (.
value: Int
next: *List)
`)
}
func TestTypeNamedErrMissing (test *testing.T) {
testStringErr (test,
"no type named Missing", 2, 10,
`
Present: Missing
`)
}
func TestTypeNamed (test *testing.T) {
testString (test,
`
Example: Int
AllBuiltin: (.
int: Int
uint: UInt
byte: Byte
rune: Rune
string: String
i8: I8
i16: I16
i32: I32
i64: I64
u8: U8
u16: U16
u32: U32
u64: U64
f32: F32
f64: F64
bool: Bool)
[main] = {
example:Example
allBuiltin:AllBuiltin
}
`)
}
func TestTypePointer (test *testing.T) {
testString (test,
`
Ptr: *Int
PtrPtr: **Int
PtrPtrPtr: *PtrPtr
ArrPtr: *5:Int
SlicePtr: **:Int
`)
}
func TestTypeSlice (test *testing.T) {
testString (test,
`
Slice: *:Int
RaggedSlice: *:*:Int
`)
}
func TestTypeArrayErrSizeZero (test *testing.T) {
testStringErr (test,
"array length must be > 0", 2, 8,
`
Array: 0:Int
`)
}
func TestTypeArray (test *testing.T) {
testString (test,
`
Array: 5:Int
Matrix: 5:6:Int
`)
}
func TestTypeStructMemberUniqueErr (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 TestTypeStructMemberUnique (test *testing.T) {
testString (test,
`
Bird: (.
x:Int
y:Int
z:Int
a:U8)
`)
}
func TestTypeUnion (test *testing.T) {
testString (test,
`
U: (| I8 I16 I32 I64 Int)
Point: (. x:Int y:Int)
Error: (& [error]:String)
PointOrError: (| Point Error)
`)
}
func TestTypeUnionAllowedUnique (test *testing.T) {
testString (test,
`
SomeInt: Int
U: (|
U8
U16
(. x:Int)
Int
(. x:SomeInt))
`)
}
func TestTypeUnionAllowedUniqueErrPrimitive (test *testing.T) {
testStringErr (test,
"Int already listed in union at stream0.fspl:2:10", 2, 26,
`
U: (| I8 Int I16 I32 I64 Int I64)
`)
}
func TestTypeUnionAllowedUniqueErrComposite (test *testing.T) {
testStringErr (test,
"(. x:Int) already listed in union at stream0.fspl:5:3", 7, 3,
`
U: (|
U8
U16
(. x:Int)
Int
(. x:Int))
`)
}
func TestTypeInterfaceBehaviorUniqueErr (test *testing.T) {
testStringErr (test,
"fly already listed in interface at stream0.fspl:2:10", 2, 16,
`
Bird: (& [fly] [fly])
`)
}
func TestTypeInterfaceBehaviorUnique (test *testing.T) {
testString (test,
`
Bird: (& [fly] [land])
`)
}
func TestTypeIntegerLiteralVoid (test *testing.T) {
testStringErr (test,
"cannot use integer literal as Void", 2, 10,
`
[main] = 5
`)
}
func TestTypeStringLiteralVoid (test *testing.T) {
testStringErr (test,
"cannot use string literal as Void", 2, 10,
`
[main] = 'hello'
`)
}

122
assets/fspl.svg Normal file
View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="1024"
height="1024"
viewBox="0 0 1024 1024"
version="1.1"
id="svg1"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
sodipodi:docname="fspl.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#FF0000"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="false"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#646464"
inkscape:document-units="mm"
labelstyle="default"
showgrid="true"
inkscape:zoom="0.69531497"
inkscape:cx="342.29092"
inkscape:cy="574.55976"
inkscape:window-width="1886"
inkscape:window-height="1000"
inkscape:window-x="16"
inkscape:window-y="1119"
inkscape:window-maximized="0"
inkscape:current-layer="layer1">
<inkscape:grid
id="grid1"
units="px"
originx="0"
originy="0"
spacingx="32"
spacingy="32"
empcolor="#0099e5"
empopacity="0.30196078"
color="#0099e5"
opacity="0.14901961"
empspacing="4"
dotted="false"
gridanglex="30"
gridanglez="30"
visible="true" />
</sodipodi:namedview>
<defs
id="defs1">
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect6"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,0,0,1"
radius="128"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
<inkscape:path-effect
effect="fillet_chamfer"
id="path-effect5"
is_visible="true"
lpeversion="1"
nodesatellites_param="F,0,0,1,0,0,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,33.866667,0,1 @ F,0,0,1,0,0,0,1"
radius="128"
unit="px"
method="auto"
mode="F"
chamfer_steps="1"
flexible="false"
use_knot_distance="true"
apply_no_radius="true"
apply_with_radius="true"
only_selected="false"
hide_knots="false" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<path
style="fill:none;stroke:#FF0000;stroke-width:48;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
d="M 592.50809,272 H 369.86667 A 33.866667,33.866667 135 0 0 336,305.86667 v 28.26666 96 96 A 33.866667,33.866667 45 0 0 369.86667,560 H 654.13333 A 33.866667,33.866667 45 0 1 688,593.86667 v 28.26666 96 A 33.866667,33.866667 135 0 1 654.13333,752 H 336"
id="path1"
inkscape:path-effect="#path-effect5"
inkscape:original-d="M 592.50809,272 H 336 v 96 96 96 h 352 v 96 96 H 336"
sodipodi:nodetypes="ccccccccc" />
<path
style="fill:none;stroke:#FF0000;stroke-width:48;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill;stroke-linejoin:miter"
d="M 336.03852,368.00001 H 495.98905"
id="path2" />
<path
style="fill:none;stroke:#FF0000;stroke-width:48;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
d="M 336.02172,464 H 623.97828"
id="path3" />
<path
style="fill:none;stroke:#FF0000;stroke-width:48;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill"
d="M 688,656 H 369.86667 A 33.866667,33.866667 135 0 0 336,689.86667 V 814.13333 A 33.866667,33.866667 45 0 0 369.86667,848 h 257.05452"
id="path4"
inkscape:path-effect="#path-effect6"
inkscape:original-d="M 688,656 H 336 v 192 h 290.92119"
sodipodi:nodetypes="cccc" />
<path
style="fill:none;stroke:#FF0000;stroke-width:48;stroke-linecap:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke markers fill;stroke-linejoin:miter"
d="M 560.08272,176 H 463.94476"
id="path5" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.5 KiB

418
cli/cli.go Normal file
View File

@ -0,0 +1,418 @@
package cli
import "os"
import "io"
import "fmt"
import "sort"
import "errors"
import "strings"
import "strconv"
// Cli represents a command line interface. It is not tied to os.Args, and it is
// possible to create multiple Cli's to have so many sub-commands in one
// program.
type Cli struct {
Logger
// Description describes the application.
Description string
// The syntax description of the command. It appears in Usage after the
// name of the program (os.Args[0]). If not specified, "[OPTIONS]..."
// will be used instead.
Syntax string
// Flag is a set of flags to parse.
Flags []*Flag
// Args is a list of leftover arguments that were not parsed.
Args []string
// Sub contains sub-commands.
Sub map[string] *Cli
// Super contains the string of commands used to invoke this Cli.
// For example, a command invoked using
// <program> foo bar
// Would have Super set to
// "foo bar"
// This is automatically filled out by Cli.AddSub.
Super string
}
// New creates a new Cli from a command description and a set of flags. These
// flags should be created as variables before passing them to New(), so that
// their values can be extracted after Parse is called.
func New (description string, flags ...*Flag) *Cli {
return &Cli {
Description: description,
Flags: flags,
Sub: make(map[string] *Cli),
}
}
// AddSub adds a sub-command to this command. It automatically fills out the
// sub-command's Super field, and alters the prefix of its Logger to match.
func (this *Cli) AddSub (name string, sub *Cli) {
super := this.Super
if super != "" { super += " " }
super += name
sub.Super = super
sub.Prefix = os.Args[0] + " " + super
this.Sub[name] = sub
}
// Parse parses the given set of command line arguments according to the flags
// defined within the Cli. The first element of the argument slice is always
// dropped, because it is assumed that it just contains the command name. If the
// second element matches up with the name of a sub-command, parsing is
// deferred to that sub-command instead. The sub-command will be given all but
// the first element of args. This method will return the address of the
// command that did the parsing.
func (this *Cli) Parse (args []string) (*Cli, error) {
// try to find a sub-command
if this.Sub != nil && len(args) > 1 {
if sub, ok := this.Sub[args[1]]; ok {
return sub.Parse(args[1:])
}
}
// strip out program name
if len(args) > 0 { args = args[1:] }
next := func () string {
args = args[1:]
if len(args) > 0 {
return args[0]
} else {
return ""
}
}
processFlag := func (flag *Flag, allowInput bool) error {
if flag.Validate == nil {
flag.Value = "true"
} else {
if len(args) < 1 || !allowInput {
return errors.New(
flag.String() +
" requires a value")
}
value := next()
err := flag.Validate(value)
if err != nil {
return errors.New(fmt.Sprint (
"bad value for ", flag.String(), ": ",
err))
}
flag.Value = value
}
if flag.Found != nil { flag.Found(this, flag.Value) }
return nil
}
// process args one by one
for ; len(args) > 0; next() {
arg := args[0]
switch {
case arg == "--":
// halt parsing
this.Args = append(this.Args, args[1:]...)
return nil, nil
case strings.HasPrefix(arg, "--"):
// long flag
flag, ok := this.LongFlag(arg[2:])
if !ok {
return nil, errors.New("unknown flag: " + arg)
}
err := processFlag(flag, true)
if err != nil { return nil, err }
case strings.HasPrefix(arg, "-") && len(arg) > 1:
// one or more short flags
arg := arg[1:]
for _, part := range arg {
flag, ok := this.ShortFlag(part)
if !ok {
return nil, errors.New (
"unknown flag: -" +
string(part))
}
err := processFlag(flag, len(arg) == 1)
if err != nil { return nil, err }
}
default:
// not a flag
this.Args = append(this.Args, arg)
}
}
return this, nil
}
// ParseOrExit is like Parse, but if an error occurs while parsing the argument
// list, it prints out Usage and exits with error status 2.
func (this *Cli) ParseOrExit (args []string) *Cli {
command, err := this.Parse(args)
if err != nil {
fmt.Fprintf(this, "%s: %v\n\n", os.Args[0], err)
this.Usage()
os.Exit(2)
}
return command
}
// ShortFlag searches for and returns the flag with the given short form. If it
// was found, it returns true. If it was not found, it returns nil, false.
func (this *Cli) ShortFlag (short rune) (*Flag, bool) {
if short == 0 { return nil, false }
for _, flag := range this.Flags {
if flag.Short == short { return flag, true }
}
return nil, false
}
// LongFlag searches for and returns the flag with the given long form. If it
// was found, it returns true. If it was not found, it returns nil, false.
func (this *Cli) LongFlag (long string) (*Flag, bool) {
if long == "" { return nil, false }
for _, flag := range this.Flags {
if flag.Long == long { return flag, true }
}
return nil, false
}
// Usage prints out usage/help information.
func (this *Cli) Usage () {
// syntax
fmt.Fprint(this, "Usage:")
fmt.Fprint(this, " ", os.Args[0])
if this.Super != "" {
fmt.Fprint(this, " ", this.Super)
}
hasSubCommands := this.Sub != nil && len(this.Sub) > 0
if hasSubCommands {
fmt.Fprint(this, " [COMMAND]")
}
if this.Syntax == "" {
fmt.Fprint(this, " [OPTION]...")
} else {
fmt.Fprint(this, " ", this.Syntax)
}
fmt.Fprint(this, "\n\n")
// description
if this.Description != "" {
fmt.Fprintf(this, "%s\n\n", this.Description)
}
// fit the longest flag
longest := 0
for _, flag := range this.Flags {
if len(flag.Long) > longest {
longest = len(flag.Long)
}
}
format := fmt.Sprint("\t%-", longest + 8, "s%s\n")
// flags
for _, flag := range this.Flags {
shortLong := ""
if flag.Short == 0 {
shortLong += " "
} else {
shortLong += "-" + string(flag.Short)
}
if flag.Short != 0 && flag.Long != "" {
shortLong += ", "
}
if flag.Long != "" {
shortLong += "--" + flag.Long
}
fmt.Fprintf(this, format, shortLong, flag.Help)
}
// commands
if hasSubCommands {
fmt.Fprint(this, "\nCommands:\n\n")
names := sortMapKeys(this.Sub)
// fit the longest command
longest := 0
for _, name := range names {
if len(name) > longest {
longest = len(name)
}
}
format := fmt.Sprint("\t%-", longest + 2, "s%s\n")
for _, name := range names {
fmt.Fprintf(this, format, name, this.Sub[name].Description)
}
}
}
// Logger prints messages to an output writer.
type Logger struct {
// Writer specifies the writer to output to. If it is nil, the Logger
// will output to os.Stderr. If you wish to silence the output, set it
// to io.Discard.
io.Writer
// Debug determines whether or not debug messages will be logged.
Debug bool
// Prefix is printed before all messages. If this is an empty string,
// os.Args[0] will be used.
Prefix string
}
// Println logs a normal message.
func (this *Logger) Println (v ...any) {
fmt.Fprintf(this, "%s: %s", this.prefix(), fmt.Sprintln(v...))
}
// Errorln logs an error message.
func (this *Logger) Errorln (v ...any) {
fmt.Fprintf(this, "%s: %s", this.prefix(), fmt.Sprintln(v...))
}
// Warnln logs a warning.
func (this *Logger) Warnln (v ...any) {
fmt.Fprintf(this, "%s: warning: %s", this.prefix(), fmt.Sprintln(v...))
}
// Debugln logs debugging information. It will only print the message if the
// Debug field is set to true.
func (this *Logger) Debugln (v ...any) {
if !this.Debug { return }
fmt.Fprintf(this, "%s: debug: %s", this.prefix(), fmt.Sprintln(v...))
}
func (this *Logger) prefix () string {
if this.Prefix == "" {
return os.Args[0]
} else {
return this.Prefix
}
}
// Write writes to the Logger's writer. If it is nil, it writes to os.Stderr.
func (this *Logger) Write (data []byte) (n int, err error) {
if this.Writer == nil {
return os.Stderr.Write(data)
} else {
return this.Writer.Write(data)
}
}
// Flag is a command line option.
type Flag struct {
Short rune // The short form of the flag (-l)
Long string // Long form of the flag (--long-form)
Help string // Help text to display by the flag
// Validate is an optional function that is called when an input is
// passed to the flag. It checks whether or not the input is valid, and
// returns an error if it isn't. If this function is nil, the flag is
// assumed to have no input.
Validate func (string) error
// Value contains the input given to the flag, and is filled out when
// the flags are parsed. If this is a non-input flag (if Validate is
// nil), this will be set to true. If the flag was not specified, Value
// will be unchanged.
Value string
// Found is an optional function that is called each time the flag is
// found, and has a valid argument (if applicable). The value is passed
// to the function, as well as the command that it was found in.
Found func (*Cli, string)
}
// String returns --<LongForm> if specified, and if not returns -<ShortForm>
func (this *Flag) String () string {
if this.Long == "" {
return fmt.Sprintf("-%c", this.Short)
} else {
return fmt.Sprintf("--%s", this.Long)
}
}
// NewFlag creates a new flag that does not take in a value.
func NewFlag (short rune, long string, help string) *Flag {
return &Flag {
Short: short,
Long: long,
Help: help,
}
}
// NewInputFlag creates a new flag that does take in a value. This function will
// panic if the given validation function is nil.
func NewInputFlag (short rune, long string, help string, defaul string, validate func (string) error) *Flag {
if validate == nil {
panic("validate must be non-nil for a flag to take in a value")
}
return &Flag {
Short: short,
Long: long,
Help: help,
Value: defaul,
Validate: validate,
}
}
// NewHelp creates a new help flag activated by --help or -h. It shows help
// information for the command that it is a part of, and causes the program to
// exit with a status of 2.
func NewHelp () *Flag {
flag := NewFlag('h', "help", "Display usage information and exit")
flag.Found = func (command *Cli, value string) {
command.Usage()
os.Exit(2)
}
return flag
}
// ValString is a validation function that always returns nil, accepting any
// string.
func ValString (value string) error {
return nil
}
// ValInt is a validation function that returns an error if the value is not an
// integer.
func ValInt (value string) error {
_, err := strconv.Atoi(value)
return err
}
// NewValSet returns a validation function that accepts a set of strings.
func NewValSet (allowed ...string) func (value string) error {
return func (value string) error {
allowedFmt := ""
for index, test := range allowed {
if test == value { return nil }
if index > 0 { allowedFmt += ", " }
allowedFmt += test
}
return errors.New (
"value must be one of (" + allowedFmt + ")")
}
}
func sortMapKeys[T any] (unsorted map[string] T) []string {
keys := make([]string, len(unsorted))
index := 0
for key := range unsorted {
keys[index] = key
index ++
}
sort.Strings(keys)
return keys
}

15
cmd/fsplc/doc.go Normal file
View File

@ -0,0 +1,15 @@
// fsplc compiles FSPL programs.
//
// Its job is to take in FSPL code, and compile it to one of the supported
// output formats. Currently it supports native object files, native ASM, and
// LLVM IR code.
//
// Usage: fsplc [OPTION]... ADDRESS
//
// -h, --help Display usage information and exit
// --debug Print extra debug information while compiling
// -q, --quiet Don't print warnings, errors, etc.
// -t, --filetype Output format (asm, obj, ir)
// -o, --output Output filename
// -O, --optimization Optimization level (0-3)
package main

88
cmd/fsplc/main.go Normal file
View File

@ -0,0 +1,88 @@
package main
import "os"
import "io"
import "fmt"
import "git.tebibyte.media/fspl/fspl/cli"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/compiler"
import ferrors "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/compiler/native"
func main () {
// instantiate the compiler
comp := new(compiler.Compiler)
comp.Writer = os.Stderr
resolver, err := native.NativeResolver()
if err != nil {
comp.Errorln(err)
os.Exit(2)
}
comp.Resolver = resolver
// take in CLI flags
debug := cli.NewFlag (
0, "debug",
"Print extra debug information while compiling")
quiet := cli.NewFlag (
'q', "quiet",
"Don't print warnings, errors, etc.")
filetype := cli.NewInputFlag (
't', "filetype",
fmt.Sprintf (
"Output format (%s, %s, %s)",
compiler.FiletypeAssembly,
compiler.FiletypeObject,
compiler.FiletypeIR), "",
cli.NewValSet (
compiler.FiletypeAssembly.String(),
compiler.FiletypeObject.String(),
compiler.FiletypeIR.String()))
output := cli.NewInputFlag (
'o', "output",
"Output filename", "",
cli.ValString)
optimization := cli.NewInputFlag (
'O', "optimization",
"Optimization level (0-3)", "0",
cli.NewValSet("0", "1", "2", "3"))
includePath := cli.NewInputFlag (
'u', "unit-directory",
"Extra directory(s) to search for units in", "",
cli.ValString)
includePath.Found = func (application *cli.Cli, value string) {
comp.Resolver.AddPathFront(value)
}
application := cli.New (
"Compile FSPL source files",
cli.NewHelp(),
debug,
quiet,
filetype,
output,
optimization,
includePath)
application.Syntax = "[OPTION]... ADDRESS"
application.ParseOrExit(os.Args)
if len(application.Args) != 1 {
comp.Errorln("please specify one unit address")
application.Usage()
os.Exit(2)
}
// configure the compiler based on user input
comp.Output = output.Value
comp.Optimization = optimization.Value
comp.Filetype, _ = compiler.FiletypeFromString(filetype.Value)
comp.Debug = debug.Value != ""
if quiet.Value != "" { comp.Writer = io.Discard }
err = comp.CompileUnit(entity.Address(application.Args[0]))
if err != nil {
comp.Errorln(ferrors.Format(err))
os.Exit(1)
}
}

10
cmd/fsplmod/doc.go Normal file
View File

@ -0,0 +1,10 @@
// fsplmod manages FSPL modules.
// Usage: fsplmod [COMMAND] [OPTION]...
//
// -h, --help Display usage information and exit
//
// Commands:
//
// bump Re-generate the UUID of a module
// new Create a new module in the specified directory
package main

113
cmd/fsplmod/main.go Normal file
View File

@ -0,0 +1,113 @@
package main
import "io"
import "os"
import "path/filepath"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/cli"
import "git.tebibyte.media/fspl/fspl/lexer"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/parser/meta"
import ferrors "git.tebibyte.media/fspl/fspl/errors"
func main () {
// CLI: application
applicationCli := cli.New (
"Manage FSPL modules",
cli.NewHelp())
// CLI: new
newCli := cli.New (
"Create a new module in the specified directory",
cli.NewHelp())
newCli.Syntax = "[OPTION]... DIRECTORY"
applicationCli.AddSub("new", newCli)
// CLI: bump
bumpCli := cli.New (
"Re-generate the UUID of a module",
cli.NewHelp())
newCli.Syntax = "[OPTION]... DIRECTORY"
applicationCli.AddSub("bump", bumpCli)
handleErr := func (cli *cli.Cli, err error, code int) {
if err == nil { return }
cli.Errorln(ferrors.Format(err))
os.Exit(code)
}
switch cli := applicationCli.ParseOrExit(os.Args); cli {
case newCli:
// Create a new module in the specified directory
if len(cli.Args) != 1 {
cli.Errorln("please specify one module directory")
cli.Usage()
os.Exit(2)
}
dir := cli.Args[0]
if abs, err := filepath.Abs(dir); err == nil {
dir = abs
}
_, err := os.Stat(dir)
if err != nil {
err := os.Mkdir(dir, 0755)
handleErr(cli, err, 1)
}
moduleId, err := uuid.NewRandom()
handleErr(cli, err, 1)
cli.Println("creating module", moduleId, "in", dir)
metadataPath := filepath.Join(dir, "fspl.mod")
file, err := os.OpenFile (
metadataPath,
os.O_CREATE | os.O_WRONLY | os.O_TRUNC,
0644)
handleErr(cli, err, 1)
defer file.Close()
meta := entity.Metadata {
UUID: moduleId,
}
_, err = io.WriteString(file, meta.String() + "\n")
handleErr(cli, err, 1)
case bumpCli:
// Re-generate the UUID of a module
if len(cli.Args) != 1 {
cli.Errorln("please specify one module directory")
cli.Usage()
os.Exit(2)
}
dir := cli.Args[0]
if abs, err := filepath.Abs(dir); err == nil {
dir = abs
}
metadataPath := filepath.Join(dir, "fspl.mod")
file, err := os.OpenFile(metadataPath, os.O_RDWR, 0644)
handleErr(cli, err, 1)
defer file.Close()
lx, err := lexer.LexReader(metadataPath, file)
handleErr(cli, err, 1)
meta := metaParser.Tree { }
err = meta.Parse(lx)
handleErr(cli, err, 1)
newModuleId, err := uuid.NewRandom()
handleErr(cli, err, 1)
cli.Println("changing uuid of", dir + ":", meta.UUID, "->", newModuleId)
meta.UUID = newModuleId
file.Truncate(0)
file.Seek(0, 0)
_, err = io.WriteString(file, meta.String() + "\n")
handleErr(cli, err, 1)
default:
applicationCli.Usage()
os.Exit(2)
}
}

160
compiler/common_test.go Normal file
View File

@ -0,0 +1,160 @@
package compiler
import "time"
import "io/fs"
import "embed"
import "strings"
import "testing"
import "context"
import "os/exec"
import "path/filepath"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/errors"
import "git.tebibyte.media/fspl/fspl/testcommon"
import "git.tebibyte.media/fspl/fspl/generator/native"
//go:embed all:test-data/*
var testData embed.FS
func defaultCompiler () *Compiler {
// instantiate and configure the compiler
comp := new(Compiler)
comp.Prefix = "compiler"
comp.Resolver = NewResolver (
"/test-data/usr/local/src/fspl",
"/test-data/usr/src/fspl",
"/test-data/usr/local/incude/fspl",
"/test-data/usr/include/fspl")
comp.Resolver.FS = testData
comp.Filetype = FiletypeObject
comp.Debug = true
nativeTarget := native.NativeTarget()
comp.Target = &nativeTarget
return comp
}
func compileDependency (
test *testing.T,
address entity.Address,
) string {
// create temporary directory
temp := test.TempDir()
// instantiate and configure the compiler
compOutputBuilder := new(strings.Builder)
comp := defaultCompiler()
defer func () {
test.Logf (
"COMPILER LOG (dependency %s):\n%s",
address, compOutputBuilder)
} ()
comp.Writer = compOutputBuilder
nickname, ok := address.Nickname()
if !ok {
test.Fatal("could not generate nickname for", address)
}
comp.Output = filepath.Join(temp, FiletypeObject.Extend(*comp.Target, nickname))
// compile to object file
err := comp.CompileUnit(address)
if err != nil {
test.Fatal("compiler returned error:", errors.Format(err))
}
return comp.Output
}
func testUnit (
test *testing.T,
address entity.Address,
clangArgs []string,
stdin, stdout string,
exit int,
args ...string,
) {
// create temporary directory
temp := test.TempDir()
// instantiate and configure the compiler
compOutputBuilder := new(strings.Builder)
comp := defaultCompiler()
outputCompilerLog := func () {
test.Log("COMPILER LOG (main unit):\n" + compOutputBuilder.String())
}
comp.Writer = compOutputBuilder
comp.Output = filepath.Join(temp, FiletypeObject.Extend(*comp.Target, "output"))
// compile to object file
err := comp.CompileUnit(address)
if err != nil {
outputCompilerLog()
test.Fatal("compiler returned error:", errors.Format(err))
}
outputCompilerLog()
// link the object file into an executable
linkOutputBuilder := new(strings.Builder)
executablePath := FiletypeExecutable.Extend(*comp.Target, filepath.Join(temp, "output"))
linkCommand := exec.Command("clang", append (
clangArgs,
comp.Output,
"-v",
"-o",
executablePath)...)
linkCommand.Stdout = linkOutputBuilder
linkCommand.Stderr = linkOutputBuilder
test.Log("running link command: ", linkCommand)
err = linkCommand.Run()
test.Log("LINKER LOG (main unit):\n" + linkOutputBuilder.String())
if err != nil {
test.Fatal("error linking executable:", err)
}
// run the executable file (with timeout) and check its output
timeoutDuration := 4000 * time.Millisecond
ctx, cancel := context.WithTimeout(context.Background(), timeoutDuration)
defer cancel()
executableCommand := exec.CommandContext(ctx, executablePath, args...)
stdoutBuilder := new(strings.Builder)
executableCommand.Stdin = strings.NewReader(stdin)
executableCommand.Stdout = stdoutBuilder
test.Log("running executable command: ", executableCommand)
err = executableCommand.Run()
if ctx.Err() == context.DeadlineExceeded {
test.Errorf("timeout of %v exceeded!", timeoutDuration)
}
// check error, compare exit code
if exitErr, ok := err.(*exec.ExitError); ok {
code := exitErr.ExitCode()
if code != exit {
test.Errorf (
"expecting exit code %d, got %d",
exit, code)
}
} else if err != nil {
test.Fatalf("error running %s: %v", executablePath, err)
} else {
if 0 != exit {
test.Errorf("expecting exit code %d, got 0", exit)
}
}
// compare stdout
gotStdout := stdoutBuilder.String()
if gotStdout != stdout {
testcommon.CompareHex(test, stdout, gotStdout)
test.Fail()
}
}
func TestEmbedOk (test *testing.T) {
err := fs.WalkDir(testData, ".", func (path string, d fs.DirEntry, err error) error {
test.Log("file:", path)
return err
})
if err != nil {
test.Error("walk dir failed: ", err)
}
}

165
compiler/compile-unit.go Normal file
View File

@ -0,0 +1,165 @@
package compiler
import "os"
import "fmt"
import "errors"
import "os/exec"
import "path/filepath"
import "git.tebibyte.media/fspl/fspl/llvm"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/analyzer"
import "git.tebibyte.media/fspl/fspl/generator/native"
func (this *Compiler) CompileUnit (address entity.Address) error {
this.Debugln("using search path", this.Resolver.Path)
path, err := this.ResolveCwd(address)
if err != nil { return err }
this.Debugln("compiling unit", path)
var semanticTree analyzer.Tree
_, err = this.AnalyzeUnit(&semanticTree, path, false)
if err != nil { return err }
if this.Target == nil {
target := native.NativeTarget()
this.Target = &target
}
irModule, err := this.Target.Generate(semanticTree)
if err != nil {
return this.bug(err)
}
// if the format isn't specified, try to get it from the filename
// extension of the input. if that isn't specified, default to .o
if this.Filetype == FiletypeUnknown {
this.Debugln("filetype was not specified")
if this.Output == "" {
this.Filetype = FiletypeObject
} else {
var ok bool
this.Filetype, ok = FiletypeFromExt (
*this.Target,
filepath.Ext(this.Output))
if !ok {
this.Filetype = FiletypeObject
}
}
}
// if the output file is unspecified, generate a nickname from the
// input address. if that doesn't work, default to "output"
if this.Output == "" {
nickname, ok := entity.Address(path).Nickname()
if !ok { nickname = "output" }
this.Output = this.Filetype.Extend(*this.Target, nickname)
}
// do something based on the output extension
// TODO: add .so
switch this.Filetype {
case FiletypeAssembly, FiletypeObject:
return this.CompileIRModule(irModule, this.Filetype)
case FiletypeIR:
file, err := os.Create(this.Output)
if err != nil { return err }
defer file.Close()
_, err = irModule.WriteTo(file)
return err
default:
return errors.New(fmt.Sprintf (
"unknown output type %s", this.Filetype))
}
}
func (this *Compiler) CompileIRModule (module *llvm.Module, filetype Filetype) error {
this.Debugln("compiling ir module to filetype", filetype)
commandName, args, err := this.FindBackend(filetype)
if err != nil { return err }
command := exec.Command(commandName, args...)
this.Debugln("compiling ir using:", command)
command.Stdout = os.Stdout
command.Stderr = os.Stderr
pipe, err := command.StdinPipe()
if err != nil { return err }
err = command.Start()
if err != nil { return err }
_, err = module.WriteTo(pipe)
if err != nil { return err }
pipe.Close()
return command.Wait()
}
// FindBackend returns the name of an LLVM backend command, and a list of
// arguments to pass to it. It tries commands in this order:
// - llc
// - llc-<latest> -> llc-14
// - clang
// If none were found, it returns an error.
func (this *Compiler) FindBackend (filetype Filetype) (string, []string, error) {
optimization := "0"
if this.Optimization != "" { optimization = this.Optimization }
llcArgs := []string {
"-",
fmt.Sprintf("-filetype=%s", filetype),
"-o", this.Output,
fmt.Sprintf("-O=%s", optimization),
}
// attempt to use llc command
commandName := "llc"
_, err := exec.LookPath(commandName)
this.Debugln("trying", commandName)
if err == nil {
return commandName, llcArgs, nil
}
// attempt to find a versioned llc command, counting down from the
// latest known version
llcVersion := 17 // TODO change this number to the latest llc version in
// the future
for err != nil && llcVersion >= 14 {
commandName := fmt.Sprintf("llc-%d", llcVersion)
this.Debugln("trying", commandName)
_, err = exec.LookPath(commandName)
if err == nil {
if llcVersion == 14 {
// -opaque-pointers is needed in version 14
llcArgs = append(llcArgs, "-opaque-pointers")
this.Warnln (
"using llvm llc version 14, which",
"does not have proper support for",
"opaque pointers. expect bugs")
}
return commandName, llcArgs, nil
}
llcVersion --
}
// attempt to use clang
commandName = "clang"
_, err = exec.LookPath(commandName)
this.Debugln("trying", commandName)
if err == nil {
if filetype != FiletypeObject {
return "", nil, errors.New(fmt.Sprint (
"need 'llc' to compile to ", filetype))
}
this.Warnln("falling back to clang to compile llvm ir. expect bugs")
return commandName, []string {
"-c",
"-x", "ir",
"-o", this.Output,
fmt.Sprintf("-O%s", this.Optimization),
"-",
}, nil
}
return "", nil, errors.New (
"no suitable backends found: please make sure either 'llc' " +
"or 'clang' are accessable from your PATH")
}

250
compiler/compiler.go Normal file
View File

@ -0,0 +1,250 @@
package compiler
import "fmt"
import "sort"
import "io/fs"
import "errors"
import "path/filepath"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/cli"
import "git.tebibyte.media/fspl/fspl/lexer"
import "git.tebibyte.media/fspl/fspl/entity"
import "git.tebibyte.media/fspl/fspl/analyzer"
import "git.tebibyte.media/fspl/fspl/generator"
import "git.tebibyte.media/fspl/fspl/parser/fspl"
import "git.tebibyte.media/fspl/fspl/parser/meta"
import ferrors "git.tebibyte.media/fspl/fspl/errors"
type Compiler struct {
*Resolver
cli.Logger
Target *generator.Target
Output string
Optimization string
Filetype Filetype
}
func (this *Compiler) bug (err error) error {
return errors.New(fmt.Sprintln (
"Bug detected in the compiler!\n" +
"The FSPL compiler has experienced an error that should not",
"happen.\n" +
"Please submit a report with this info and the code you were",
"compiling to:",
"https://git.tebibyte.media/sashakoshka/fspl/issues\n" +
"The error is:", err))
}
func (this *Compiler) AnalyzeUnit (
semanticTree *analyzer.Tree,
path string,
skim bool,
) (
uuid.UUID,
error,
) {
this.Debugln("entering unit analysis", path)
filePath, isFile := entity.Address(path).SourceFile()
modulePath, isModule := entity.Address(path).Module()
if !isFile && !isModule {
return uuid.UUID { }, errors.New(fmt.Sprintf (
"%v is not a module, nor a source file",
path))
}
if isModule {
return this.AnalyzeModule(semanticTree, modulePath, skim)
} else {
return this.AnalyzeSourceFile(semanticTree, filePath, skim)
}
}
func (this *Compiler) AnalyzeModule (
semanticTree *analyzer.Tree,
path string,
skim bool,
) (
uuid.UUID,
error,
) {
this.Debugln("entering module analysis", path)
// parse module metadata file
var metaTree metaParser.Tree
metaPath := filepath.Join(path, "fspl.mod")
metaFile, err := openAbsolute(this.FS, metaPath)
if err != nil { return uuid.UUID { }, err }
defer metaFile.Close()
lx, err := lexer.LexReader(metaPath, metaFile)
if err != nil { return uuid.UUID { }, err }
err = metaTree.Parse(lx)
if err != nil { return uuid.UUID { }, err }
// ensure metadata is well formed
dependencies := make(map[string] *entity.Dependency)
for _, dependency := range metaTree.Dependencies {
this.Debugln(dependency)
nickname := dependency.Nickname
if nickname == "" {
newNickname, ok := dependency.Address.Nickname()
if !ok {
return uuid.UUID { }, ferrors.Errorf (
dependency.Position(),
"cannot generate nickname for %v, " +
"please add one after the address",
dependency.Address)
}
nickname = newNickname
}
if previous, exists := dependencies[nickname]; exists {
return uuid.UUID { }, ferrors.Errorf (
dependency.Position(),
"unit with nickname %v already listed at %v",
nickname, previous.Position())
}
dependencies[nickname] = dependency
}
// analyze dependency units, building a nickname translation table
nicknames := make(map[string] uuid.UUID)
dependencyKeys := sortMapKeys(dependencies)
for _, nickname := range dependencyKeys {
dependency := dependencies[nickname]
resolved, err := this.Resolve(path, dependency.Address)
if err != nil { return uuid.UUID { }, err }
dependencyUUID, err := this.AnalyzeUnit(semanticTree, resolved, true)
if err != nil { return uuid.UUID { }, err }
nicknames[nickname] = dependencyUUID
}
// parse this unit
var syntaxTree fsplParser.Tree
err = this.ParseUnit(&syntaxTree, path, skim)
if err != nil { return uuid.UUID { }, err}
// analyze this unit
this.Debugln("analyzing", path, metaTree.UUID)
err = semanticTree.Analyze(metaTree.UUID, nicknames, syntaxTree)
if err != nil { return uuid.UUID { }, err}
return metaTree.UUID, nil
}
func (this *Compiler) AnalyzeSourceFile (
semanticTree *analyzer.Tree,
path string,
skim bool,
) (
uuid.UUID,
error,
) {
this.Debugln("entering source file analysis", path)
// parse this unit
var syntaxTree fsplParser.Tree
err := this.ParseUnit(&syntaxTree, path, skim)
if err != nil { return uuid.UUID { }, err}
// analyze this unit
unitId := entity.Address(path).UUID()
this.Debugln("analyzing", path, unitId)
err = semanticTree.Analyze(unitId, nil, syntaxTree)
if err != nil { return uuid.UUID { }, err}
return unitId, nil
}
func (this *Compiler) ParseUnit (
syntaxTree *fsplParser.Tree,
path string,
skim bool,
) (
error,
) {
filePath, isFile := entity.Address(path).SourceFile()
modulePath, isModule := entity.Address(path).Module()
if !isFile && !isModule {
return errors.New(fmt.Sprintf (
"%v is not a module, nor a source file",
path))
}
if isModule {
return this.ParseModule(syntaxTree, modulePath, skim)
} else {
return this.ParseSourceFile(syntaxTree, filePath, skim)
}
}
func (this *Compiler) ParseModule (
syntaxTree *fsplParser.Tree,
path string,
skim bool,
) (
error,
) {
this.Debugln("parsing module", path)
// parse all files in the module
file, err := openAbsolute(this.FS, path)
if err != nil { return err }
defer file.Close()
dir, ok := file.(fs.ReadDirFile)
if !ok { return errors.New(fmt.Sprintf("%s is not a directory", path)) }
entries, err := dir.ReadDir(0)
if err != nil { return err }
for _, entry := range entries {
if filepath.Ext(entry.Name()) != ".fspl" { continue }
filePath := filepath.Join(path, entry.Name())
file, err := openAbsolute(this.FS, filePath)
if err != nil { return err }
defer file.Close()
lx, err := lexer.LexReader(filePath, file)
if err != nil { return err }
if skim {
err = syntaxTree.Skim(lx)
} else {
err = syntaxTree.Parse(lx)
}
if err != nil { return err }
}
return nil
}
func (this *Compiler) ParseSourceFile (
syntaxTree *fsplParser.Tree,
path string,
skim bool,
) (
error,
) {
this.Debugln("parsing source file", path)
file, err := openAbsolute(this.FS, path)
if err != nil { return err }
defer file.Close()
lx, err := lexer.LexReader(path, file)
if err != nil { return err }
if skim {
err = syntaxTree.Skim(lx)
} else {
err = syntaxTree.Parse(lx)
}
if err != nil { return err }
return nil
}
func sortMapKeys[T any] (unsorted map[string] T) []string {
keys := make([]string, len(unsorted))
index := 0
for key := range unsorted {
keys[index] = key
index ++
}
sort.Strings(keys)
return keys
}

177
compiler/compiler_test.go Normal file
View File

@ -0,0 +1,177 @@
package compiler
import "testing"
import "runtime"
var nativeLineBreak = "\n"
func init () {
if runtime.GOOS == "windows" {
nativeLineBreak = "\r\n"
}
}
func TestHelloWorld (test *testing.T) {
testUnit (test,
"/test-data/data/hello", nil,
"", "Hello, world!" + nativeLineBreak,
0,
)}
func TestPuts (test *testing.T) {
testUnit (test,
"/test-data/data/puts", nil,
"", "hello" + nativeLineBreak,
0,
)}
func TestExitCode13 (test *testing.T) {
testUnit (test,
"/test-data/data/exit-code-13", nil,
"", "",
13,
)}
func TestSystemInclude (test *testing.T) {
testUnit (test,
"/test-data/data/system-include", nil,
"", "Hello, /usr/include!" + nativeLineBreak,
0,
)}
func TestSystemSrc (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/system-src", dependencies,
"", "Hello, /usr/src!" + nativeLineBreak,
0,
)}
func TestArgC (test *testing.T) {
testUnit (test,
"/test-data/data/argc", nil,
"", "",
2, "arg1", "arg2",
)}
// TODO: uncomment once #47 has been dealt with
// func TestArgV (test *testing.T) {
// testUnit (test,
// "/test-data/data/argv", nil,
// "", "This is an argument\n",
// 0, "This is an argument",
// )}
func TestSimpleInterface (test *testing.T) {
testUnit (test,
"/test-data/data/simple-interface", nil,
"", "",
9,
)}
func TestWriterInterface (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/writer", dependencies,
"", "well hello their" + nativeLineBreak,
0,
)}
func TestMatchExitCode (test *testing.T) {
testUnit (test,
"/test-data/data/match-exit-code", nil,
"", "",
7,
)}
func TestMatchPrint (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/match-print", dependencies,
"", "F64" + nativeLineBreak,
0,
)}
func TestMatchDefaultPrint (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/match-default-print", dependencies,
"", "something else" + nativeLineBreak,
0,
)}
func TestSwitchExitCode (test *testing.T) {
testUnit (test,
"/test-data/data/switch-exit-code", nil,
"", "",
4,
)}
func TestReturnAssign (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/return-assign", dependencies,
"", "false" + nativeLineBreak,
0,
)}
func TestLoopBreakExitCode (test *testing.T) {
testUnit (test,
"/test-data/data/loop-break-exit-code", nil,
"", "",
5,
)}
func TestLoopBreakBranchExitCode (test *testing.T) {
testUnit (test,
"/test-data/data/loop-break-branch-exit-code", nil,
"", "",
2,
)}
func TestForSimple (test *testing.T) {
testUnit (test,
"/test-data/data/for-simple", nil,
"", "",
0,
)}
func TestForStringArray (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/for-string-array", dependencies,
"", "a" + nativeLineBreak + "b" + nativeLineBreak + "c" + nativeLineBreak +
"a" + nativeLineBreak + "b" + nativeLineBreak + "c" + nativeLineBreak,
0,
)}
func TestForStringArrayOnce (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/for-string-array-once", dependencies,
"", "abc" + nativeLineBreak,
0,
)}
func TestForBreakBranch (test *testing.T) {
dependencies := []string {
compileDependency(test, "io"),
}
testUnit (test,
"/test-data/data/for-break-branch", dependencies,
"", "iter" + nativeLineBreak + "iter" + nativeLineBreak,
0,
)}

3
compiler/doc.go Normal file
View File

@ -0,0 +1,3 @@
// Package compiler is responsible for orchestrating the different FSPL
// compilation stages, as well as invoking the LLVM IR compiler.
package compiler

95
compiler/filetype.go Normal file
View File

@ -0,0 +1,95 @@
package compiler
import "fmt"
import "git.tebibyte.media/fspl/fspl/generator"
// Filetype represents an output filetype.
type Filetype int; const (
FiletypeUnknown Filetype = iota
FiletypeObject
FiletypeLibrary
FiletypeAssembly
FiletypeIR
FiletypeExecutable
)
// FiletypeFromString returns a filetype based on the given name.
func FiletypeFromString (ext string) (Filetype, bool) {
switch ext {
case "": return FiletypeUnknown, true
case "obj": return FiletypeObject, true
case "lib": return FiletypeLibrary, true
case "asm": return FiletypeAssembly, true
case "ir": return FiletypeIR, true
case "exe": return FiletypeExecutable, true
default: return FiletypeUnknown, false
}
}
// FiletypeFromExt returns a filetype based on the given filename extension.
func FiletypeFromExt (target generator.Target, ext string) (Filetype, bool) {
switch ext {
case targetObjExt(target): return FiletypeObject, true
case targetSoExt(target): return FiletypeLibrary, true
case ".s": return FiletypeAssembly, true
case ".ll": return FiletypeIR, true
case targetExeExt(target): return FiletypeExecutable, true
default: return FiletypeUnknown, false
}
}
// String returns a string representation of the filetype.
func (filetype Filetype) String () string {
switch filetype {
case FiletypeUnknown: return ""
case FiletypeObject: return "obj"
case FiletypeLibrary: return "lib"
case FiletypeAssembly: return "asm"
case FiletypeIR: return "ir"
case FiletypeExecutable: return "exe"
default: return fmt.Sprintf("Filetype(%d)", filetype)
}
}
// Ext returns the standard filename extension of the filetype.
func (filetype Filetype) Ext (target generator.Target) string {
switch filetype {
case FiletypeUnknown: return ""
case FiletypeObject: return targetObjExt(target)
case FiletypeLibrary: return targetSoExt(target)
case FiletypeAssembly: return ".s"
case FiletypeIR: return ".ll"
case FiletypeExecutable: return targetExeExt(target)
default: return ""
}
}
// Extend adds the extension of the filetype onto the specified path.
func (filetype Filetype) Extend (target generator.Target, path string) string {
return path + filetype.Ext(target)
}
func targetSoExt (target generator.Target) string {
// TODO: more research is required here
switch target.OS {
case "win32": return ".dll"
default: return ".so"
}
}
func targetObjExt (target generator.Target) string {
// TODO: more research is required here
switch target.OS {
case "win32": return ".obj"
default: return ".o"
}
}
func targetExeExt (target generator.Target) string {
// TODO: more research is required here
switch target.OS {
case "win32": return ".exe"
default: return ""
}
}

57
compiler/fs.go Normal file
View File

@ -0,0 +1,57 @@
package compiler
import "os"
import "io/fs"
import "runtime"
import "strings"
import "path/filepath"
// OsFS returns an fs.FS that represents the operating system's filesystem. In
// Windows, volume names (A:, B:, C:, etc.) are treated as subdirectories within
// the root.
type OsFS struct { }
// win
// C:/Something -> C:\Something
// "" -> ""
// unix
// C:/Something -> /C:/Something
// "" -> /
func (this OsFS) Open (name string) (fs.File, error) {
fullname, err := this.nativize(name)
if err != nil {
return nil, this.err("open", name, err)
}
file, err := os.Open(fullname)
if err != nil {
return nil, this.err("open", name, err.(*os.PathError).Err)
}
return file, nil
}
func (this OsFS) nativize (name string) (string, error) {
if !fs.ValidPath(name) {
return "", fs.ErrInvalid
}
if filepath.Separator != '/' {
if strings.Contains(name, string(filepath.Separator)) {
return "", fs.ErrInvalid
}
}
if runtime.GOOS != "windows" {
// TODO is this correct for all non-windows systems?
name = "/" + name
}
name = filepath.FromSlash(name)
return name, nil
}
func (this OsFS) err (op, path string, err error) error {
return &fs.PathError {
Op: "open",
Path: path,
Err: err,
}
}

55
compiler/native/native.go Normal file
View File

@ -0,0 +1,55 @@
// Package native provides OS native parameters for the compilation process.
package native
import "os"
import "errors"
import "runtime"
import "path/filepath"
import "git.tebibyte.media/fspl/fspl/compiler"
// NativeResolver returns a resolver that resolves paths native to the operating
// system.
func NativeResolver () (*compiler.Resolver, error) {
switch runtime.GOOS {
case "windows": return windowsNativeResolver()
default: return unixNativeResolver()
}
}
func unixNativeResolver () (*compiler.Resolver, error) {
resolver := compiler.NewResolver (
"/usr/local/src/fspl",
"/usr/src/fspl",
"/usr/local/incude/fspl",
"/usr/include/fspl")
homeDir, err := os.UserHomeDir()
if err != nil { return nil, err }
resolver.AddPathFront (
filepath.Join(homeDir, ".local/src/fspl"),
filepath.Join(homeDir, ".local/include/fspl"))
return resolver, nil
}
func windowsNativeResolver () (*compiler.Resolver, error) {
localAppData := os.Getenv("LOCALAPPDATA")
if localAppData == "" {
return nil, errors.New("could not get %LOCALAPPDATA%")
}
allUsersProfile := os.Getenv("ALLUSERSPROFILE")
if allUsersProfile == "" {
return nil, errors.New("could not get %ALLUSERSPROFILE%")
}
programFiles := os.Getenv("ProgramFiles")
if programFiles == "" {
return nil, errors.New("could not get %ProgramFiles%")
}
resolver := compiler.NewResolver (
filepath.Join(localAppData, "fspl\\src"),
filepath.Join(localAppData, "fspl\\include"),
filepath.Join(allUsersProfile, "fspl\\src"),
filepath.Join(allUsersProfile, "fspl\\include"),
filepath.Join(programFiles, "fspl\\src"),
filepath.Join(programFiles, "fspl\\include"))
return resolver, nil
}

113
compiler/resolver.go Normal file
View File

@ -0,0 +1,113 @@
package compiler
import "os"
import "fmt"
import "io/fs"
import "errors"
import "strings"
import "path/filepath"
import "git.tebibyte.media/fspl/fspl/entity"
// Resolver turns addresses into absolute filepaths.
type Resolver struct {
// FS specifies a filesystem to search.
FS fs.FS
// Path specifies a list of paths that a unit may exist directly in. The
// Resolver will search the FS for each path listed, starting at the
// first and ending at the last. Thus, paths nearer the start will have
// a higher priority.
Path []string
}
// NewResolver creates a new resolver with OsFS.
func NewResolver (path ...string) *Resolver {
return &Resolver {
FS: OsFS { },
Path: path,
}
}
// AddPath adds one or more items to the resolver's search path.
func (resolver *Resolver) AddPath (path ...string) {
resolver.Path = append(resolver.Path, path...)
}
// AddPathFront adds one or more items to the beginning of the resolver's search
// path.
func (resolver *Resolver) AddPathFront (path ...string) {
// i know how memory works in go babyyyyyy
newPath := make([]string, len(path))
copy(newPath, path)
resolver.Path = append(newPath, resolver.Path...)
}
// Resolve resolves an address into an absolute filepath starting at the
// filesystem root. It follows these rules:
// - If the address starts with '.', '..', it is joined with context
// - If the address starts with '/', it is treated as an absolute path from
// the fs root
// - Else, the address is searched for in the resolver's paths
func (resolver *Resolver) Resolve (context string, address entity.Address) (string, error) {
strAddr := string(address)
switch {
case strings.HasPrefix(strAddr, "."):
return filepath.Join(context, strAddr), nil
case strings.HasPrefix(strAddr, "/"):
return strAddr, nil
default:
if resolver.Path == nil || resolver.FS == nil {
return "", errors.New(fmt.Sprintf (
"could not find unit %v: %v",
address, "no search path specified"))
}
location, err := resolver.search(strAddr)
if err != nil {
return "", errors.New(fmt.Sprintf (
"could not find unit %v: %v",
address, err))
}
return location, nil
}
}
func (resolver *Resolver) search (needle string) (string, error) {
for _, dirPath := range resolver.Path {
// attempt to open the file as dir.
// if we can't open the dir, just skip it, because it is
// perfectly reasonable that the user might not have
// /usr/local/include/fspl etc.
file, err := openAbsolute(resolver.FS, dirPath)
if err != nil { continue }
dir, ok := file.(fs.ReadDirFile)
if !ok { continue }
entries, err := dir.ReadDir(0)
if err != nil { continue }
dir.Close()
// search through the entries
for _, entry := range entries {
if entry.Name() == needle {
return filepath.Join(dirPath, entry.Name()), nil
}
}
}
return "", fs.ErrNotExist
}
// ResolveCwd resolves the address within the context of the current working
// directory.
func (resolver *Resolver) ResolveCwd (address entity.Address) (string, error) {
wd, err := os.Getwd()
if err != nil { return "", err }
return resolver.Resolve(wd, address)
}
// openAbsolute exists because fs.FS implementations do not understand absolute
// paths, which the FSPL compiler runs on. It converts an absolute path to a
// slash path relative to "/" and opens the file.
func openAbsolute (filesystem fs.FS, path string) (fs.File, error) {
path = strings.TrimPrefix(filepath.ToSlash(path), "/")
return filesystem.Open(path)
}

View File

@ -0,0 +1 @@
'2b4a9e61-1f87-4173-b6d5-c0174b0abe43'

View File

@ -0,0 +1 @@
[main argc:I32 argv:**Byte]: I32 'main' = [- argc 1]

View File

@ -0,0 +1,2 @@
'ad9c62ae-c580-4c53-86e1-d5bf3ce251e5'
+ 'cstdio'

View File

@ -0,0 +1,5 @@
[main argc:I32 argv:**Byte]: I32 'main' = {
argv = [~~ **Byte [+ [~~Index argv] 1]]
cstdio::[puts [.argv]]
0
}

View File

@ -0,0 +1 @@
'2380d7e1-00a4-44a8-ae4a-30285a158788'

View File

@ -0,0 +1 @@
[main]: Int 'main' = 13

View File

@ -0,0 +1,2 @@
'61b23750-b3c5-4cbd-85f7-71322a23f980'
+ 'io'

View File

@ -0,0 +1,8 @@
[main]:I32 'main' = {
arr:3:Int = (5 6 7)
for i:Index e:Int in arr {
if [>= i 2] then [break]
io::[println 'iter']
}
0
}

View File

@ -0,0 +1,2 @@
'11371094-76a7-424f-b9ae-14636962073b'
+ 'io'

View File

@ -0,0 +1,5 @@
[main]:I32 'main' = {
str:String = 'a'
for c:Byte in str { }
0
}

View File

@ -0,0 +1,3 @@
'c9024148-f4ec-4d69-84c1-c838f9c68073'
+ 'io'
+ 'cstdio'

View File

@ -0,0 +1,5 @@
[main]:I32 'main' = {
arr:5:String = ('abc' 'def' 'ghi')
for e:String in arr { io::[println e] [break] }
0
}

View File

@ -0,0 +1,2 @@
'cfb45339-4c4d-4519-9578-3abf0c698867'
+ 'io'

View File

@ -0,0 +1,10 @@
[main]:I32 'main' = {
; array
arr:3:String = ('a' 'b' 'c')
for e:String in arr io::[println e]
; slice
slice:*:String = arr
for e:String in slice io::[println e]
0
}

View File

@ -0,0 +1 @@
'241467f1-219a-4f13-8cdf-32ef2ad88277'

View File

@ -0,0 +1,6 @@
[puts string:*Byte]: Index 'puts'
[main]: I32 'main' = {
[puts 'Hello, world!']
0
}

View File

@ -0,0 +1 @@
'e8962449-6214-4622-afcf-f6b58046fab2'

View File

@ -0,0 +1,10 @@
[main]:I32 'main' = {
y:I32 = 6
loop {
if [< y 3]
then [break y]
else {
y = [-- y]
}
}
}

View File

@ -0,0 +1 @@
'779e632d-88a6-46c4-bfa2-7db4fc919f07'

View File

@ -0,0 +1,3 @@
[main]:I32 'main' = loop {
[break 5]
}

View File

@ -0,0 +1,2 @@
'4f3d6ccb-c233-4648-abd2-72e3f9a82efd'
+ 'io'

View File

@ -0,0 +1,11 @@
[main]: I32 'main' = {
x:UInt = 7
[matchToInt x]
0
}
U: (| Int F64 UInt)
[matchToInt u:U] = match u
| u:Int io::[println 'Int']
| u:F64 io::[println 'F64']
* io::[println 'something else']

View File

@ -0,0 +1 @@
'839f0104-91f5-4db0-be52-6a17c571ebb1'

View File

@ -0,0 +1,9 @@
[main]: I32 'main' = {
x:F32 = 7.7
[matchToInt x]
}
U: (| I32 F32)
[matchToInt u:U]:I32 = match u
| u:I32 u
| u:F32 [~I32 u]

View File

@ -0,0 +1,2 @@
'624d4557-5291-4ad7-9283-7c200b9c2942'
+ 'io'

View File

@ -0,0 +1,10 @@
[main]: I32 'main' = {
x:F64 = 7.7
[matchToInt x]
0
}
U: (| Int F64 UInt)
[matchToInt u:U] = match u
| u:Int io::[println 'Int']
| u:F64 io::[println 'F64']

View File

@ -0,0 +1,2 @@
'aaad83b9-6610-45c4-ad89-4c28cf3a3b26'
+ 'cstdio'

View File

@ -0,0 +1,4 @@
[main]: I32 'main' = {
cstdio::[puts 'hello']
0
}

View File

@ -0,0 +1,2 @@
'678748d4-ce9e-45db-bccb-07eecf067770'
+ 'io'

View File

@ -0,0 +1,10 @@
[main]:I32 'main' = {
if [is5 7]
then io::[println 'true']
else io::[println 'false']
0
}
[is5 x:Int]:Bool = if [= x 5]
then true
else [return false]

View File

@ -0,0 +1 @@
'1cabcb3c-ebac-4bc1-8610-76cbc47fde3a'

View File

@ -0,0 +1,9 @@
Numbered: (& [number]:Int)
Number: Int
Number.[number]: Int = [.this]
[main]: I32 'main' = {
num:Number = 9
ifa:Numbered = num
[~I32 ifa.[number]]
}

View File

@ -0,0 +1 @@
'433ed4ee-be14-4d0f-baef-a13919ec3b6c'

View File

@ -0,0 +1,10 @@
[main]: I32 'main' = {
x:I32 = 1
[switch x]
}
[switch x:I32]:I32 = switch x
| 0 5
| 1 4
| 2 3
* 0

View File

@ -0,0 +1,2 @@
'15059679-90a0-468a-be57-62f8b958d46b'
+ 'cstdio'

View File

@ -0,0 +1,4 @@
[main]: I32 'main' = {
cstdio::[puts 'Hello, /usr/include!']
0
}

View File

@ -0,0 +1,2 @@
'fb8bddc7-2db1-45f6-b81c-d58a28298ab0'
+ 'io'

View File

@ -0,0 +1,4 @@
[main]: I32 'main' = {
io::[println 'Hello, /usr/src!']
0
}

View File

@ -0,0 +1,2 @@
'7e7ee20f-30fc-441a-943c-2acdb40c9df1'
+ 'io'

View File

@ -0,0 +1,7 @@
[sayHello writer:io::Writer] = writer.[write 'well hello their\n']
[main]: I32 'main' = {
stdout:io::File = 1
[sayHello stdout]
0
}

View File

@ -0,0 +1 @@
'f95aaa14-612c-45cd-b3ae-fd24049cc81b'

View File

@ -0,0 +1,4 @@
+ FileDescriptor: Int
+ [write file:FileDescriptor buffer:*Byte count:Index]: Index 'write'
+ [read file:FileDescriptor buffer:*Byte count:Index]: Index 'read'
+ [puts string:*Byte]: Index 'puts'

View File

@ -0,0 +1,2 @@
'431b60dd-c990-4086-ae30-aad6246b207d'
+ 'cstdio'

View File

@ -0,0 +1,9 @@
+ [print string:String]: Index = cstdio::[write 1 [~*Byte string] [#string]]
+ [println string:String]: Index = [+ [print string] cstdio::[write 1 '\n' 1]]
+ Writer: (& [write buffer:*:Byte]: Index)
+ File: cstdio::FileDescriptor
+ File.[write buffer:*:Byte]:Index =
cstdio::[write
[~cstdio::FileDescriptor [.this]]
[~*Byte buffer] [#buffer]]

View File

@ -1,8 +1,18 @@
# Semantic entities
## Top level
Top level entities are defined directly within the source file(s) of a unit, and
can be made available to other units via access control modes. The three modes
are:
- Public access: Allows other modules to access an entity normally.
- Opaque access: Causes a top-level entity to appear opaque to other units.
Values of opaque types can be passed around, assigned to each-other, and their
methods can be called, but the implementation of the type is entirely hidden.
This access mode cannot be applied to functions or methods.
- Private access: Disallows other modules from accessing a top-level entity.
This mode is the default when one isn't supplied.
### Type definition
Type definitions bind a type to a global identifier.
Type definitions bind a type to a global type identifier.
### Function
Functions bind a global identifier and argument list to an expression which is
evaluated each time the function is called. If no expression is specified, the
@ -17,7 +27,7 @@ the type they are defined on.
## Types
### Named
Named refers to a user-defined or built in named type.
Named refers to a user-defined, primitive, or built-in named type.
### Pointer
Pointer is a pointer to another type.
### Array
@ -37,6 +47,44 @@ except it must have at least the methods defined within the interface.
Interfaces are always passed by reference. When assigning a value to an
interface, it will be referenced automatically. When assigning a pointer to an
interface, the pointer's reference will be used instead.
### Union
Union is a polymorphic type that can hold any value as long as it is one of a
list of allowed types. It is not a pointer. It holds the hash of the actual type
of the value stored within it, followed by the value. The hash field is computed
using the type's name, and the UUID that it was defined in. If it is not named,
then the hash is computed using the structure of the type. The value field is
always big enough to hold the largest type in the allowed list. The value of a
union can only be extracted using a match expression.
## Primitive types
### Int
Int is defined as a signed system word.
### UInt
UInt is defined as an unsigned system word.
### I8, I16, I32, I64
I8-I64 are defined as signed 8, 16, 32, and 64 bit integers respectively.
### U8, U16, U32, U64
U8-U64 are defined as unsigned 8, 16, 32, and 64 bit integers respectively.
### F32, F64
F32 and F64 are defined as single-precision and double-precision floating point
types respectively.
## Built-in types
### Index
Index is defined as an unsigned system word. It is used to describe the size of
chunks of memory, and to index arrays and such.
### Byte
Byte is defined as the smallest addressable integer. It is unsigned. It is
usually equivalent to U8.
### Bool
Bool is a boolean type. It is equivalent to U1. For now, since arbitrary width
integers are not supported, it is the only way to get a U1 type.
### Rune
Rune is defined as a U32. It represents a single UTF-32 code point.
### String
String is defined as a slice of U8's. It represents a UTF-8 string. It is not
conventionally null-terminated, but a null can be added at the end manually if
desired.
## Expressions
### Location expressions
@ -47,7 +95,8 @@ with a star (*).
### Literals
#### Integer
An integer literal specifies an integer value. It can be assigned to any type
that is derived from an integer, or a float.
that is derived from an integer or a float, as long as the value of the literal
can fit within the range of the type.
It cannot be directly assigned to an interface because it contains no inherent
type information. A value cast may be used for this purpose.
#### Float
@ -55,6 +104,22 @@ A float literal specifies a floating point value. It can be assigned to any type
that is derived from a float.
It cannot be directly assigned to an interface because it contains no inherent
type information. A value cast may be used for this purpose.
#### String
A string literal specifies a string value. It takes on different data
representations depending on what the base type of what it is assigned to is
structurally equivalent to:
- Integer: Single unicode code point. When assigning to an integer, the
string literal may not be longer than one code point, and that code point
must fit in the integer.
- Slice of 8 bit integers: UTF-8 string.
- Slice of 16 bit integers: UTF-16 string.
- Slice of 32 bit (or larger) integers: UTF-32 string.
- Array of integers: The same as slices of integers, but the string literal
must fit inside of the array.
- Pointer to 8 bit integer: Null-terminated UTF-8 string (AKA C-string).
A string literal cannot be directly assigned to an interface because it
contains no inherent type information. A value cast may be used for this
purpose.
#### Array
Array is a composite array literal. It can contain any number of values. It can
be assigned to any array type that:
@ -73,6 +138,11 @@ pairs. It can be assigned to any struct type that:
It cannot be directly assigned to an interface because it contains no inherent
type information. A value cast may be used for this purpose.
#### Boolean
Boolean is a boolean literal. It may be either true or false. It can be assigned
to any type derived from a boolean. It cannot be directly assigned to an
interface because it contains no inherent type information. A value cast may be
used for this purpose.
### Variable *
Variable specifies a named variable. It can be assigned to a type matching the
variable declaration's type. Since it contains inherent type information, it may
@ -89,19 +159,22 @@ value, and any assignment rules of the block are equivalent to those of its last
expression.
### Call
Call calls upon the function specified by the first argument, and passes the
rest of that argument to the function. The first argument must be a function
type, usually the name of a function. The result of a call may be assigned to
rest of that argument to the function. The first argument must be an identifier
referring to usually the name of a function. The result of a call may be assigned to
any type matching the function's return type. Since it contains inherent type
information, it may be directly assigned to an interface.
### Method call
Method call calls upon the method (of the expression before the dot) that is
specified by the first argument, passing the rest of the arguments to the
method. The first argument must be a method name. The result of a call may be
assigned to any type matching the method's return type. Since it contains
inherent type information, it may be directly assigned to an interface.
### Member access *
Member access allows referring to a specific member of a value with a struct
type. It accepts any struct type that contains the specified member name, and
may be assigned to any type that matches the type of the selected member. Since
it contains inherent type information, it may be directly assigned to an
interface.
### Method access
Method access allows referring to a specific method of a type, or a behavior of
an interface. It can only be assigned to the first argument of a call.
### Array subscript *
Array subscripting allows referring to a specific element of an array. It
accepts any array, and any offset of type Size. It may be assigned to any type
@ -112,6 +185,9 @@ Slice adjusts the start and end points of a slice relative to its current
starting index, and returns an adjusted copy pointing to the same data. Any
assignment rules of this expression are equivalent to those of the slice it is
operating on.
### Length
Length returns the length of an array or a slice. It always returns a value
of type Index.
### Pointer dereference *
Pointer dereferencing allows retrieving the value of a pointer. It accepts any
pointer. It may be assigned to any type matching the pointer's pointed type.
@ -183,6 +259,22 @@ If/else is a control flow branching expression that executes one of two
expressions depending on a boolean value. If the value of the if/else is unused,
the else expression need not be specified. It may be assigned to any type that
satisfies the assignment rules of both the true and false expressions.
### Match
Match is a control flow branching expression that executes one of several case
expressions depending on the input. It can be used to check the type of a union
value. Each case takes the form of a declaration, and an associated expression.
If the type of the union matches the type of the declaration in the case, the
expression is executed and the value of the union is made available to it
through the declaration. If the value of the match expression is used, all
possible types in the union must be accounted for, or it must have a default
case. It may be assigned to any type that satisfies the assignment rules of its
first case.
### Switch
Switch is a control flow branching expression that executes one of several case
expressions depending on the value of the input. It accepts any pointer or
integer type. If the value of the switch expression is used, a default case must
be present. It may be assigned to any type that satisfies the assignment rules
of its first case.
### Loop
Loop is a control flow expression that repeats an expression until a break
statement is called from within it. The break statement must be given a value
@ -191,26 +283,40 @@ statement. The result of the loop may be assigned to any type that satisfies the
assignment rules of all of its break statements. Loops may be nested, and break
statements only apply to the closest containing loop. The value of the loop's
expression is never used.
### For
For is a special kind of loop that evaluates an expression for each element of
an array or slice. It accepts an index declaration and an element declaration,
which are scoped to the loop's body and are set to the index of the current
element and the element itself respectively at the beginning of each iteration.
The assignment rules of a for statement are identical to that of a normal loop.
### Break
Break allows breaking out of loops. It has no value and may not be assigned to
anything.
Break allows breaking out of loops. It may be assigned to anything, but the
assignment will have no effect as execution of the code after and surrounding it
will cease.
### Return
Return allows terminating functions before they have reached their end. It
accepts values that may be assigned to the function's return type. If a function
does not return anything, the return statement does not accept a value. In all
cases, return statements have no value and may not be assigned to anything.
does not return anything, the return statement does not accept a value. It may
be assigned to anything, but the assignment will have no effect as execution of
the function or method will cease.
### Assignment
Assignment allows assigning the result of one expression to one or more location
expressions. The assignment statement itself has no value and may not be
expressions. The assignment expression itself has no value and may not be
assigned to anything.
# Syntax entities
Below is a rough syntax description of the language. Note that `<assignment>`
is right-associative, and `<memberAccess>` and `<methodCall>` are
left-associative. I invite you to torture yourself by attempting to implement
this without hand-writing a parser.
```
<file> -> (<typedef> | <function> | <method>)*
<typedef> -> <identifier> ":" <type>
<function> -> <signature> ["=" <expression>]
<method> -> <identifier> "." <function>
<access> -> "+" | "#" | "-"
<typedef> -> [<access>] <typeIdentifier> ":" <type>
<function> -> [<access>] <signature> ["=" <expression>]
<method> -> [<access>] <typeIdentifier> "." <function>
<type> -> <namedType>
| <pointerType>
@ -218,21 +324,25 @@ assigned to anything.
| <arrayType>
| <structType>
| <interfaceType>
<namedType> -> <identifier>
<namedType> -> <typeIdentifier>
<pointerType> -> "*" <type>
<sliceType> -> "*" ":" <type>
<arrayType> -> <intLiteral> ":" <type>
<structType> -> "(" <declaration>* ")"
<interfaceType> -> "(" <signature> ")"
<structType> -> "(" "." <declaration>* ")"
<interfaceType> -> "(" "&" <signature>* ")"
<unionType> -> "(" "|" <type>* ")"
<expression> -> <intLiteral>
| <floatLiteral>
| <stringLiteral>
| <arrayLiteral>
| <structLiteral>
| <booleanLiteral>
| <variable>
| <declaration>
| <call>
| <subscript>
| <length>
| <dereference>
| <reference>
| <valueCast>
@ -240,28 +350,33 @@ assigned to anything.
| <operation>
| <block>
| <memberAccess>
| <methodCall>
| <ifelse>
| <loop>
| <break>
| <return>
<statement> -> <expression> | <assignment>
| <assignment>
<variable> -> <identifier>
<declaration> -> <identifier> ":" <type>
<call> -> "[" <expression>+ "]"
<subscript> -> "[" "." <expression> <expression> "]"
<slice> -> "[" "\" <expression> <expression>? ":" <expression>? "]"
<slice> -> "[" "\" <expression> <expression>? "/" <expression>? "]"
<length> -> "[" "#" <expression> "]"
<dereference> -> "[" "." <expression> "]"
<reference> -> "[" "@" <expression> "]"
<valueCast> -> "[" "~" <type> <expression> "]"
<valueCast> -> "[" "~" <type> <expression> "]"
<bitCast> -> "[" "~~" <type> <expression> "]"
<operation> -> "[" <operator> <expression>* "]"
<block> -> "{" <statement>* "}"
<memberAccess> -> <variable> "." <identifier>
<methodAccess> -> <variable> "::" <identifier>
<block> -> "{" <expression>* "}"
<memberAccess> -> <expression> "." <identifier>
<methodCall> -> <expression> "." <call>
<ifelse> -> "if" <expression>
"then" <expression>
["else" <expression>]
<match> -> "match" <expression> <matchCase>* [<defaultCase>]
<switch> -> "switch" <expression> <switchCase>* [<defaultCase>]
<loop> -> "loop" <expression>
<for> -> "for" <expression> [<expression>] in <expression> <expression>
<break> -> "[" "break" [<expression>] "]"
<return> -> "[" "return" [<expression>] "]"
<assignment> -> <expression> "=" <expression>
@ -270,16 +385,21 @@ assigned to anything.
| /-?0[0-7]*/
| /-?0x[0-9a-fA-F]*/
| /-?0b[0-1]*/
<floatLiteral> -> /-?[0-9]*\.[0-9]+/
<arrayLiteral> -> "(*" <expression>* ")"
<structLiteral> -> "(" <member>* ")"
<floatLiteral> -> /-?[0-9]*\.[0-9]+/
<stringLiteral> -> /'.*'/
<arrayLiteral> -> "(*" <expression>* ")"
<structLiteral> -> "(." <member>* ")"
<booleanLiteral> -> "true" | "false"
<member> -> <identifier> ":" <expression>
<signature> -> "[" <identifier> <declaration>* "]" [":" <type>]
<identifier> -> /[A-Za-z]+/
<operator> -> "+" | "++" | "-" | "--" | "*" | "/" | "%"
| "!!" | "||" | "&&" | "^^"
| "!" | "|" | "&" | "^" | "<<" | ">>"
| "<" | ">" | "<=" | ">=" | "="
<member> -> <identifier> ":" <expression>
<matchCase> -> "|" <declaration> <expression>
<switchCase> -> "|" <expression> <expression>
<defaultCase> -> "*" <expression>
<signature> -> "[" <identifier> <declaration>* "]" [":" <type>]
<identifier> -> /[a-z][A-Za-z]*/
<typeIdentifier> -> /[A-Z][A-Za-z]*/
<operator> -> "+" | "++" | "-" | "--" | "*" | "/" | "%"
| "!!" | "||" | "&&" | "^^"
| "!" | "|" | "&" | "^" | "<<" | ">>"
| "<" | ">" | "<=" | ">=" | "="
```

191
design/units.md Normal file
View File

@ -0,0 +1,191 @@
# Units
## Modules - Concept
- Equivalent to a package in Go
- Contains one or more FSPL source files
- Uniqued by a UUIDv4
- Depends on zero or more other units
- Source files in a module can access functionality of dependencies
## Addressing
When compiling source files, depending on a module, etc. an *address* is used to
refer to a *unit*, which is a module or file. An *addresser* is anything that
addresses a unit, the *addressee*. An addresser can be a module, a user invoking
the compiler, or something else. An address is represented by a string. If the
string ends in `.fspl`, the address refers to an FSPL source file. If not, the
address refers to a module.
If the address begins in a `/`, `./` or `../`, the address is interpreted as an
absolute path beginning from the filesystem root, the current directory of the
addressee, or the directory above that respectively. Otherwise, the unit is
searched for within a set of standard or configured paths.
For example, if the search path is `/usr/include/fspl`, and the address is
`foo`, then the unit will be located at `/usr/include/fspl/foo`. If the address
is `foo/bar`, then the unit will be located at `/usr/include/fspl/foo/bar`. If
there is an additional directory in the search path, such as
`/usr/local/include/fspl`, then the unit will be searched for in each one (in
order) until it is found.
There are standard paths that the compiler will search for units in. These are,
in order of preference:
- `$HOME/.local/src/fspl`
- `$HOME/.local/include/fspl`
- `/usr/local/src/fspl`
- `/usr/local/include/fspl`
- `/usr/src/fspl`
- `/usr/include/fspl`
On Windows, these are used instead:
- `%LOCALAPPDATA%\fspl\src`
- `%LOCALAPPDATA%\fspl\include`
- `%ALLUSERSPROFILE%\fspl\src`
- `%ALLUSERSPROFILE%\fspl\include`
- `%ProgramFiles%\fspl\src`
- `%ProgramFiles%\fspl\include`
Files in `include` directories *should not* include program code, and should
only define types and external functions and methods, similar to header files in
C. They may have a corresponding shared object file that programs can
dynamically link against.
Files in `src` directories *may* contain program code, and may be compiled into
an object file if the user wishes to link them statically. Because of FSPL's
ability to "skim" units (discussed later in this document), files in `src` may
be used in the same way that files in `include` are. Thus, `src` files are
effectively more "complete" versions of `include` files with extended
capability, and that is why they are searched first.
## Uniqueness
Each unit is uniqued by a UUID. Most users will never directly use UUIDs, but
they are essential in order to prevent name collisions within the compiler or
linker. For modules, the UUID is specified in the metadata file. For other
units, the UUID is a UUIDv3 (md5) generated using the zero-UUID as a namespace
and the basename of the file (with the extension) as the data.
When creating a module, a UUID should be randomly generated for it. Keep in mind
that altering the UUID of a library will cause programs that used to dynamically
load it to no longer function until they are re-compiled. Therefore, UUIDs
should only be altered if you are introducing breaking ABI changes to your
library. If you are forking an existing module and making changes to it, a
similar rule applies: only keep the same UUID if you intend on keeping an
entirely backwards compatible ABI.
Built-in entities that can be accessed globally from any module (such as the
`String` type) are given a zero-UUID, which represents the "global" unit.
Anything that is a part of this unit is accesisble from any other unit, without
having to use a nickname to refer to it.
When generating code, top-level entities must be named like this if their link
name was not specified manually:
`<uuid>::<name>`
Where `<uuid>` is the base64 encoding of the UUID. For example, the built-in
String type would be assigned the following link name:
`AAAAAAAAAAAAAAAAAAAAAA==::String`
And a type `Bird` in a lone source file with the name `bird.fspl` would be:
`eT/CnSopFDlFwpDCnSEAThjDsBw=::Bird`
Methods are named as follows:
`<uuid>::<name>.<method>`
Where `<uuid>` and `<name>` correspond to the base64 UUID of the unit and the
name of the method's owner type respectively, and `<method>` corresponds to the
method name.
## Module Structure
Each module is represented by a directory, which contains source files along
with a metadata file called `fspl.mod`. The metadata file is of the form:
```
<file> -> <UUIDv4> <directive>*
<directive> -> <depedency>
<dependency> -> "+" <stringLiteral> [<ident>]
<UUIDv4> -> <stringLiteral>
```
Metadata files only make use of tokens defined in the FSPL lexer, and are
designed to make use of the same parsing and lexing infrastructure used to parse
and tokenize source files. A sample metadata file might look like:
```
'5a8353f8-cad8-4604-be60-29a2575996bc'
+ 'io'
+ '../io' customIo
```
The UUID is represented as a string, and so are addresses. When depending on a
unit, it may be "nicknamed" by supplying an identifier after the address. This
changes how the unit is referred to within the module.
## Referencing Units
Compiled by itself, an FSPL source file has no access to other units. However,
when compiling a module as a whole, all source files within the module have
access to units depended on by the module's metadata file. Note that no actual
data or code is imported into the module from the units it depends on, because
all methods and functions defined within them are automatically turned into
prototypes. The module must be linked either statically or dynamically to the
unit's object code after compilation. This is why the FSPL compiler outputs
object files by default.
FSPL source files may reference functions or types from dependencies by
prefixing them with a unit name and a double colon (`::`), like this:
```
reader: io::Reader = x
data: *:Byte = io::[readAll reader]
```
The name of a unit depends on the associated dependency directive used in the
module metadata file. If a nickname is listed, then that is used as the unit
name. Otherwise, the unit name is the basename of the address, which is
normalized and formatted into a valid identifier by the the following rules:
- If the name contains at least one dot, the last dot and everything after it
are removed
- All non-alphabetical and non-numeric characters are removed, and any
alphabetical characters that were directly after them are converted to
uppercase
- All numeric digits at the start of the string are removed
- The first character is converted to lowercase
For example:
- `100-bottles-of-glue_test`
- `Picture.jpg`
- `Just a straight up sentence`
Would become:
- `bottlesOfGlueTest`
- `picture`
- `justAStraightUpSentence`
If the unit name is still not a valid identifier or is empty, the compiler will
refuse to process the module and it is up to the user to either nickname the
unit, or change the unit's basename to something workable.
The compiler will also refuse to process the module if one or more units end up
with the same unit name. However, a function or a variable may have the same
name as a unit because units are only ever used within the context of their own
special syntax (`::`).
## Future Work
Addresses do not necessarily have to refer to units. They could also refer to
arbitrary blobs to embed into a compiled program, similarly to how Go's embed
system works. There of course would need to be a distinction between depending
on units and embedding data, because someone might want to embed an FSPL source
file. Thus, there would need to be a separate metadata file directive, possibly
starting with an `!` or something like that.

4
entity/doc.go Normal file
View File

@ -0,0 +1,4 @@
// Package entity provides data representations of language concepts.
// They are used to construct syntax trees, and semantic trees. It additionally
// provides some utility functions to compare certain entities.
package entity

View File

@ -1,125 +1,235 @@
package entity
import "fmt"
import "github.com/alecthomas/participle/v2/lexer"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/errors"
// Expression is any construct that can be evaluated.
type Expression interface {
fmt.Stringer
// Position returns the position of the expression within its source
// file.
Position () errors.Position
// Type returns the type of the expression. The return value of this is
// only considered valid if the expression has its semantic information
// fields filled.
Type () Type
// HasExplicitType returns true if the expression carries inherent type
// information. If it returns false, the expression must be given a type
// to conform to. The value of this does not depend on semantic
// information fields.
HasExplicitType () bool
expression ()
Statement
}
// Statement is any construct that can be placed inside of a block expression.
type Statement interface {
statement()
}
var _ Expression = &Variable { }
// Variable specifies a named variable. It can be assigned to a type matching
// the variable declaration's type. Since it contains inherent type information,
// it may be directly assigned to an interface. A variable is always a valid
// location expression.
type Variable struct {
Pos lexer.Position
Name string `parser:" @Ident "`
// Syntax
Pos errors.Position
Name string
// Semantics
Declaration *Declaration
}
func (*Variable) expression(){}
func (*Variable) statement(){}
func (this *Variable) Position () errors.Position { return this.Pos }
func (this *Variable) Type () Type { return this.Declaration.Type() }
func (this *Variable) HasExplicitType () bool { return true }
func (this *Variable) String () string {
return this.Name
}
var _ Expression = &Declaration { }
// Declaration binds a local identifier to a typed variable, but also acts as a
// variable expression allowing the variable to be used the moment it is
// defined. Since it contains inherent type information, it may be directly
// assigned to an interface. A declaration is always a valid location
// expression.
type Declaration struct {
Pos lexer.Position
Name string `parser:" @Ident "`
Type Type `parser:" ':' @@ "`
Pos errors.Position
Name string
Ty Type
}
func (*Declaration) expression(){}
func (*Declaration) statement(){}
func (this *Declaration) Position () errors.Position { return this.Pos }
func (this *Declaration) Type () Type { return this.Ty }
func (this *Declaration) HasExplicitType () bool { return true }
func (this *Declaration) String () string {
return fmt.Sprint(this.Name, ":", this.Type)
return fmt.Sprint(this.Name, ":", this.Ty)
}
// Call calls upon the function specified by the first argument, and passes the
// rest of that argument to the function. The first argument must be a function
// type, usually the name of a function. The result of a call may be assigned to
// any type matching the function's return type. Since it contains inherent type
// information, it may be directly assigned to an interface. A call is never a
// valid location expression.
var _ Expression = &Call { }
// Call calls upon the function specified by the first argument, passing the
// rest of the arguments to that function. The first argument must be a function
// name. The result of a call may be assigned to any type matching the
// function's return type. Since it contains inherent type information, it may
// be directly assigned to an interface. A call is never a valid location
// expression.
type Call struct {
Pos lexer.Position
Function Expression `parser:" '[' @@ "`
Arguments []Expression `parser:" @@* ']' "`
// Syntax
Pos errors.Position
UnitNickname string
Name string
Arguments []Expression
// Semantics
Function *Function
Unit uuid.UUID
}
func (*Call) expression(){}
func (*Call) statement(){}
func (this *Call) Position () errors.Position { return this.Pos }
func (this *Call) Type () Type { return this.Function.Signature.Return }
func (this *Call) HasExplicitType () bool { return true }
func (this *Call) String () string {
out := fmt.Sprint("[", this.Function)
out := ""
if this.UnitNickname != "" {
out += fmt.Sprint(this.UnitNickname, "::")
}
out += fmt.Sprint("[", this.Name)
for _, argument := range this.Arguments {
out += fmt.Sprint(" ", argument)
}
return out + "]"
}
// Array subscripting allows referring to a specific element of an array. It
// accepts any array, and any offset of type Size. It may be assigned to any
// type matching the array's element type. Since it contains inherent type
var _ Expression = &MethodCall { }
// MethodCall calls upon the method of the variable before the dot that is
// specified by the first argument, passing the rest of the arguments to the
// method. The first argument must be a method name. The result of a call may be
// assigned to any type matching the method's return type. Since it contains
// inherent type information, it may be directly assigned to an interface.
// A method call is never a valid location expression.
type MethodCall struct {
// Syntax
Pos errors.Position
Source Expression
Name string
Arguments []Expression
// Semantics
Method *Method
Behavior *Signature
Ty Type
}
func (*MethodCall) expression(){}
func (this *MethodCall) Position () errors.Position { return this.Pos }
func (this *MethodCall) Type () Type {
if this.Method != nil {
return this.Method.Signature.Return
} else {
return this.Behavior.Return
}
}
func (this *MethodCall) HasExplicitType () bool { return true }
func (this *MethodCall) String () string {
out := fmt.Sprint(this.Source, ".[", this.Name)
for _, argument := range this.Arguments {
out += fmt.Sprint(" ", argument)
}
return out + "]"
}
var _ Expression = &Subscript { }
// Slice subscripting allows referring to a specific element of a slice. It
// accepts any slice, and any offset of type Size. It may be assigned to any
// type matching the slice's element type. Since it contains inherent type
// information, it may be directly assigned to an interface. A subscript is a
// valid location expression only if the array being subscripted is.
type Subscript struct {
Pos lexer.Position
Array Expression `parser:" '[' '.' @@ "`
Offset Expression `parser:" @@ ']' "`
// Syntax
Pos errors.Position
Slice Expression
Offset Expression
// Semantics
Ty Type
}
func (*Subscript) expression(){}
func (*Subscript) statement(){}
func (this *Subscript) Position () errors.Position { return this.Pos }
func (this *Subscript) Type () Type { return this.Ty }
func (this *Subscript) HasExplicitType () bool { return true }
func (this *Subscript) String () string {
return fmt.Sprint("[.", this.Array, " ", this.Offset, "]")
return fmt.Sprint("[.", this.Slice, " ", this.Offset, "]")
}
var _ Expression = &Slice { }
// Slice adjusts the start and end points of a slice relative to its current
// starting index, and returns an adjusted copy pointing to the same data. Any
// assignment rules of this expression are equivalent to those of the slice it
// is operating on. A slice is never a valid location expression.
type Slice struct {
Pos lexer.Position
Slice Expression `parser:" '[' '\\\\' @@ "`
Start Expression `parser:" @@? "`
End Expression `parser:" ':' @@? ']' "`
// Syntax
Pos errors.Position
Slice Expression
Start Expression
End Expression
}
func (*Slice) expression(){}
func (*Slice) statement(){}
func (this *Slice) Position () errors.Position { return this.Pos }
func (this *Slice) Type () Type { return this.Slice.Type() }
func (this *Slice) HasExplicitType () bool { return true }
func (this *Slice) String () string {
out := fmt.Sprint("[\\", this.Slice, " ")
if this.Start != nil {
out += fmt.Sprint(this.Start)
}
out += ":"
out += "/" // TODO have trailing decimals pop off of numbers, replace this with ..
if this.End != nil {
out += fmt.Sprint(this.End)
}
return out + "]"
}
var _ Expression = &Length { }
// Length returns the length of an array or a slice. It always returns a value
// of type Index. A length is never a valid location expression.
type Length struct {
// Syntax
Pos errors.Position
Slice Expression
// Semantics
Ty Type
}
func (*Length) expression(){}
func (this *Length) Position () errors.Position { return this.Pos }
func (this *Length) Type () Type { return this.Ty }
func (this *Length) HasExplicitType () bool { return true }
func (this *Length) String () string {
return fmt.Sprint("[#", this.Slice, "]")
}
var _ Expression = &Dereference { }
// Pointer dereferencing allows retrieving the value of a pointer. It accepts
// any pointer. It may be assigned to any type matching the pointer's pointed
// type. Since it contains inherent type information, it may be directly
// assigned to an interface. A dereference is a valid location expression only
// if the pointer being dereferenced is.
type Dereference struct {
Pos lexer.Position
Pointer Expression `parser:" '[' '.' @@ ']' "`
// Syntax
Pos errors.Position
Pointer Expression
// Semantics
Ty Type
}
func (*Dereference) expression(){}
func (*Dereference) statement(){}
func (this *Dereference) Position () errors.Position { return this.Pos }
func (this *Dereference) Type () Type { return this.Ty }
func (this *Dereference) HasExplicitType () bool { return true }
func (this *Dereference) String () string {
return fmt.Sprint("[.", this.Pointer, "]")
}
var _ Expression = &Reference { }
// Value referencing allows retrieving the location of a value in memory. It
// accepts any location expression, and can be assigned to any type that is a
// pointer to the location expression's type. Since it contains inherent type
@ -128,56 +238,76 @@ func (this *Dereference) String () string {
// automatically references it anyway. A reference is never a valid location
// expression.
type Reference struct {
Pos lexer.Position
Value Expression `parser:" '[' '@' @@ ']' "`
// Syntax
Pos errors.Position
Value Expression
// Semantics
Ty Type
}
func (*Reference) expression(){}
func (*Reference) statement(){}
func (this *Reference) Position () errors.Position { return this.Pos }
func (this *Reference) Type () Type { return this.Ty }
func (this *Reference) HasExplicitType () bool { return true }
func (this *Reference) String () string {
return fmt.Sprint("[@", this.Value, "]")
}
var _ Expression = &ValueCast { }
// Vaue casting converts a value of a certain type to another type. Since it
// contains inherent type information, it may be directly assigned to an
// interface. A value cast is never a valid location expression.
type ValueCast struct {
Pos lexer.Position
Type Type `parser:" '[' '~' @@ "`
Value Expression `parser:" @@ ']' "`
Pos errors.Position
Ty Type
Value Expression
}
func (*ValueCast) expression(){}
func (*ValueCast) statement(){}
func (this *ValueCast) Position () errors.Position { return this.Pos }
func (this *ValueCast) Type () Type { return this.Ty }
func (this *ValueCast) HasExplicitType () bool { return true }
func (this *ValueCast) String () string {
return fmt.Sprint("[~ ", this.Type, this.Value, "]")
return fmt.Sprint("[~ ", this.Ty, this.Value, "]")
}
var _ Expression = &BitCast { }
// Bit casting takes the raw data in memory of a certain value and re-interprets
// it as a value of another type. Since it contains inherent type information,
// it may be directly assigned to an interface. A bit cast is never a valid
// location expression.
type BitCast struct {
Pos lexer.Position
Type Type `parser:" '[' '~''~' @@ "`
Value Expression `parser:" @@ ']' "`
Pos errors.Position
Ty Type
Value Expression
}
func (*BitCast) expression(){}
func (*BitCast) statement(){}
func (this *BitCast) Position () errors.Position { return this.Pos }
func (this *BitCast) Type () Type { return this.Ty }
func (this *BitCast) HasExplicitType () bool { return true }
func (this *BitCast) String () string {
return fmt.Sprint("[~~ ", this.Type, this.Value, "]")
return fmt.Sprint("[~~ ", this.Ty, this.Value, "]")
}
var _ Expression = &Operation { }
// Operations perform math, logic, or bit manipulation on values. They accept
// values of the same type as the type they are being assigned to, except in
// special cases. Since they contain no inherent type information, they may not
// be assigned to interfaces. An operation is never a valid location expression.
type Operation struct {
Pos lexer.Position
// FIXME super janky, need to make a custom lexer at some point
Operator Operator `parser:" '[' @('+''+' | '+' | '-''-' | '-' | '*' | '/' | '%' | '!''!' | '|''|' | '&''&' | '^''^' | '!' | '|' | '&' | '^' | '<''<' | '>''>' | '<' | '>' | '<''=' | '>''=' | '=') "`
Arguments []Expression `parser:" @@+ ']' "`
// Syntax
Pos errors.Position
Operator Operator
Arguments []Expression
// Semantics
Ty Type
}
func (*Operation) expression(){}
func (*Operation) statement(){}
func (this *Operation) Position () errors.Position { return this.Pos }
func (this *Operation) Type () Type { return this.Ty }
func (this *Operation) HasExplicitType () bool {
return this.Operator.ResultsInBoolean()
}
func (this *Operation) String () string {
out := fmt.Sprint("[", this.Operator)
for _, argument := range this.Arguments {
@ -186,6 +316,7 @@ func (this *Operation) String () string {
return out + "]"
}
var _ Expression = &Block { }
// Block is an ordered collection of expressions that are evaluated
// sequentially. It has its own scope. The last expression in the block
// specifies the block's value, and any assignment rules of the block are
@ -193,14 +324,20 @@ func (this *Operation) String () string {
// expression.
type Block struct {
// Syntax
Pos lexer.Position
Steps []Statement `parser:" '{' @@* '}' "`
Pos errors.Position
Steps []Expression
// Semantics
Ty Type
Scope
}
func (*Block) expression(){}
func (*Block) statement(){}
func (this *Block) Position () errors.Position { return this.Pos }
func (this *Block) Type () Type { return this.Ty }
func (this *Block) HasExplicitType () bool {
if len(this.Steps) == 0 { return false }
return this.Steps[len(this.Steps) - 1].HasExplicitType()
}
func (this *Block) String () string {
out := "{"
for index, step := range this.Steps {
@ -210,6 +347,7 @@ func (this *Block) String () string {
return out + "}"
}
var _ Expression = &MemberAccess { }
// Member access allows referring to a specific member of a value with a struct
// type. It accepts any struct type that contains the specified member name, and
// may be assigned to any type that matches the type of the selected member.
@ -217,43 +355,44 @@ func (this *Block) String () string {
// an interface. A member access is a valid location expression only when the
// struct being accessed is.
type MemberAccess struct {
Pos lexer.Position
Source *Variable `parser:" @@ "`
Member string `parser:" '.' @Ident "`
// Syntax
Pos errors.Position
Source Expression
Member string
// Semantics
Ty Type
}
func (*MemberAccess) expression(){}
func (*MemberAccess) statement(){}
func (this *MemberAccess) Position () errors.Position { return this.Pos }
func (this *MemberAccess) Type () Type { return this.Ty }
func (this *MemberAccess) HasExplicitType () bool { return true }
func (this *MemberAccess) String () string {
return fmt.Sprint(this.Source, ".", this.Member)
}
// Method access allows referring to a specific method of a type, or a behavior
// of an interface. It can only be assigned to the first argument of a call. A
// method access is never a valid location expression.
type MethodAccess struct {
Pos lexer.Position
Source *Variable `parser:" @@ "`
Member string `parser:" ':' ':' @Ident "`
}
func (*MethodAccess) expression(){}
func (*MethodAccess) statement(){}
func (this *MethodAccess) String () string {
return fmt.Sprint(this.Source, "::", this.Member)
}
var _ Expression = &IfElse { }
// If/else is a control flow branching expression that executes one of two
// expressions depending on a boolean value. If the value of the if/else is
// unused, the else expression need not be specified. It may be assigned to any
// type that satisfies the assignment rules of both the true and false
// expressions. An If/else is never a valid location expression.
type IfElse struct {
Pos lexer.Position
Condition Expression `parser:" 'if' @@ "`
True Expression `parser:" 'then' @@ "`
False Expression `parser:" ('else' @@)? "`
// Syntax
Pos errors.Position
Condition Expression
True Expression
False Expression
// Semantics
Ty Type
}
func (*IfElse) expression(){}
func (*IfElse) statement(){}
func (this *IfElse) Position () errors.Position { return this.Pos }
func (this *IfElse) Type () Type { return this.Ty }
func (this *IfElse) HasExplicitType () bool {
return this.True.HasExplicitType()
}
func (this *IfElse) String () string {
out := fmt.Sprint("if ", this.Condition, " then ", this.True)
if this.False != nil {
@ -262,6 +401,95 @@ func (this *IfElse) String () string {
return out
}
var _ Expression = &Match { }
// Match is a control flow branching expression that executes one of several
// case expressions depending on the input. It can be used to check the type of
// a union value. Each case takes the form of a declaration, and an associated
// expression. If the type of the union matches the type of the declaration in
// the case, the expression is executed and the value of the union is made
// available to it through the declaration. If the value of the match expression
// is used, all possible types in the union must be accounted for. It may be
// assigned to any type that satisfies the assignment rules of its first case.
type Match struct {
// Syntax
Pos errors.Position
Value Expression
Cases []*MatchCase
Default *DefaultCase
// Semantics
Ty Type
CaseOrder []Hash
CaseMap map[Hash] *MatchCase
}
func (*Match) expression(){}
func (this *Match) Position () errors.Position { return this.Pos }
func (this *Match) Type () Type { return this.Ty }
func (this *Match) HasExplicitType () bool {
if len(this.Cases) == 0 {
return true
} else {
return this.Cases[0].HasExplicitType()
}
}
func (this *Match) String () string {
out := fmt.Sprint("match ", this.Value)
for _, cas := range this.Cases {
out += fmt.Sprint(" ", cas)
}
if this.Default != nil {
out += fmt.Sprint(" ", this.Default)
}
return out
}
var _ Expression = &Switch { }
// Switch is a control flow branching expression that executes one of several
// case expressions depending on the value of the input. It accepts any pointer
// or integer type. If the value of the switch expression is used, a default
// case must be present. It may be assigned to any type that satisfies the
// assignment rules of its first case.
type Switch struct {
// Syntax
Pos errors.Position
Value Expression
Cases []*SwitchCase
Default *DefaultCase
// Semantics
Ty Type
CaseOrder []int64
CaseMap map[int64] *SwitchCase
}
func (*Switch) expression(){}
func (this *Switch) Position () errors.Position { return this.Pos }
func (this *Switch) Type () Type { return this.Ty }
func (this *Switch) HasExplicitType () bool {
if len(this.Cases) == 0 {
return true
} else {
return this.Cases[0].HasExplicitType()
}
}
func (this *Switch) String () string {
out := fmt.Sprint("switch ", this.Value)
for _, cas := range this.Cases {
out += fmt.Sprint(" ", cas)
}
if this.Default != nil {
out += fmt.Sprint(" ", this.Default)
}
return out
}
// Breakable is any expression that can be halted using a break.
type Breakable interface {
Expression
breakable()
}
var _ Expression = &Loop { }
var _ Breakable = &Loop { }
// Loop is a control flow expression that repeats an expression until a break
// statement is called from within it. The break statement must be given a value
// if the value of the loop is used. Otherwise, it need not even have a break
@ -270,23 +498,79 @@ func (this *IfElse) String () string {
// break statements only apply to the closest containing loop. The value of the
// loop's expression is never used. A loop is never a valid location expression.
type Loop struct {
Pos lexer.Position
Body Expression `parser:" 'loop' @@ "`
// Syntax
Pos errors.Position
Body Expression
// Semantics
Ty Type
}
func (*Loop) expression(){}
func (*Loop) statement(){}
func (*Loop) breakable(){}
func (this *Loop) Position () errors.Position { return this.Pos }
func (this *Loop) Type () Type { return this.Ty }
func (this *Loop) HasExplicitType () bool {
// this is as accurate as possible without doing a full analysis of the
// loop
return false
}
func (this *Loop) String () string {
return fmt.Sprint("loop ", this.Body)
}
var _ Expression = &For { }
var _ Breakable = &For { }
// For is a special kind of loop that evaluates an expression for each element
// of an array or slice. It accepts an index declaration and an element
// declaration, which are scoped to the loop's body and are set to the index of
// the current element and the element itself respectively at the beginning of
// each iteration. The assignment rules of a for statement are identical to that
// of a normal loop.
type For struct {
// Syntax
Pos errors.Position
Index *Declaration
Element *Declaration
Over Expression
Body Expression
// Semantics
Ty Type
Scope
}
func (*For) expression(){}
func (*For) breakable(){}
func (this *For) Position () errors.Position { return this.Pos }
func (this *For) Type () Type { return this.Ty }
func (this *For) HasExplicitType () bool {
// this is as accurate as possible without doing a full analysis of the
// loop
return false
}
func (this *For) String () string {
out := "for"
if this.Index != nil {
out += " " + this.Index.String()
}
out += fmt.Sprintf(" %v in %v %v", this.Element, this.Over, this.Body)
return out
}
var _ Expression = &Break { }
// Break allows breaking out of loops. It has no value and may not be assigned
// to anything. It is never a valid location expression.
type Break struct {
Pos lexer.Position
Value Expression `parser:" '[' 'break' @@? ']' "`
// Syntax
Pos errors.Position
Value Expression
// Semantics
Loop Breakable
}
func (*Break) expression(){}
func (*Break) statement(){}
func (this *Break) Position () errors.Position { return this.Pos }
func (this *Break) Type () Type { return nil }
func (this *Break) HasExplicitType () bool { return false }
func (this *Break) String () string {
if this.Value == nil {
return "[break]"
@ -294,17 +578,25 @@ func (this *Break) String () string {
return fmt.Sprint("[break ", this.Value, "]")
}
}
var _ Expression = &Return { }
// Return allows terminating functions before they have reached their end. It
// accepts values that may be assigned to the function's return type. If a
// function does not return anything, the return statement does not accept a
// value. In all cases, return statements have no value and may not be assigned
// to anything. A return statement is never a valid location expression.
type Return struct {
Pos lexer.Position
Value Expression `parser:" '[' 'return' @@? ']' "`
// Syntax
Pos errors.Position
Value Expression
// Semantics
Declaration TopLevel
}
func (*Return) expression(){}
func (*Return) statement(){}
func (this *Return) Position () errors.Position { return this.Pos }
func (this *Return) Type () Type { return nil }
func (this *Return) HasExplicitType () bool { return false }
func (this *Return) String () string {
if this.Value == nil {
return "[return]"
@ -313,16 +605,20 @@ func (this *Return) String () string {
}
}
var _ Expression = &Assignment { }
// Assignment allows assigning the result of one expression to one or more
// location expressions. The assignment statement itself has no value and may
// not be assigned to anything. An assignment statement is never a valid
// location expression.
type Assignment struct {
Pos lexer.Position
Location Expression `parser:" @@ "`
Value Expression `parser:" '=' @@ "`
Pos errors.Position
Location Expression
Value Expression
}
func (*Assignment) statement(){}
func (*Assignment) expression(){}
func (this *Assignment) Position () errors.Position { return this.Pos }
func (this *Assignment) Type () Type { return nil }
func (this *Assignment) HasExplicitType () bool { return false }
func (this *Assignment) String () string {
return fmt.Sprint(this.Location, "=", this.Value)
}

70
entity/key.go Normal file
View File

@ -0,0 +1,70 @@
package entity
import "fmt"
import "crypto/md5"
import "encoding/base64"
import "github.com/google/uuid"
// A hash represents a hash sum that fits within a uint64.
type Hash [8]byte
// NewHash creates a new truncated md5 hash using the specified data.
func NewHash (data []byte) Hash {
sum := md5.Sum(data)
hash := Hash { }
copy(hash[:], sum[:8])
return hash
}
// Number converts the hash into a uint64.
func (hash Hash) Number () uint64 {
var number uint64
for _, part := range hash {
number <<= 8
number |= uint64(part)
}
return number
}
// Key globally indexes top level entities in contexts where modules matter.
type Key struct {
Unit uuid.UUID
Name string
Method string
}
func (key Key) String () string {
out := fmt.Sprintf("%v::%v", key.Unit, key.Name)
if key.Method != "" {
out = fmt.Sprintf("%s.%s", out, key.Method)
}
return out
}
// Hash returns a representation of the hash of this key that fits within a
// uint64.
func (key Key) Hash () Hash {
return NewHash([]byte("Key:" + key.String()))
}
// LinkName returns the name that the entity it refers to will be given when
// compiled.
func (key Key) LinkName () string {
data := [16]byte(key.Unit)
out := fmt.Sprintf(
"%s::%s",
base64.StdEncoding.EncodeToString(data[:]),
key.Name)
if key.Method != "" {
out = fmt.Sprintf("%s.%s", out, key.Method)
}
return out
}
// StripMethod returns a copy of the key that refers to a type instead of a
// method.
func (key Key) StripMethod () Key {
key.Method = ""
return key
}

View File

@ -1,58 +1,116 @@
package entity
import "fmt"
import "github.com/alecthomas/participle/v2/lexer"
import "git.tebibyte.media/fspl/fspl/errors"
var _ Expression = &LiteralInt { }
// LiteralInt specifies an integer value. It can be assigned to any type that is
// derived from an integer, or a float. It cannot be directly assigned to an
// derived from an integer or a float, as long as the value fo the literal can
// fit within the range of the type. It cannot be directly assigned to an
// interface because it contains no inherent type information. A value cast may
// be used for this purpose.
type LiteralInt struct {
Pos lexer.Position
Value int `parser:" @Int "`
// Syntax
Pos errors.Position
Value int
// Semantics
Ty Type
}
func (*LiteralInt) expression(){}
func (*LiteralInt) statement(){}
func (this *LiteralInt) Position () errors.Position { return this.Pos }
func (this *LiteralInt) Type () Type { return this.Ty }
func (this *LiteralInt) HasExplicitType () bool { return false }
func (this *LiteralInt) String () string {
return fmt.Sprint(this.Value)
}
var _ Expression = &LiteralFloat { }
// LiteralFloat specifies a floating point value. It can be assigned to any type
// that is derived from a float. It cannot be directly assigned to an interface
// because it contains no inherent type information. A value cast may be used
// for this purpose.
type LiteralFloat struct {
Pos lexer.Position
Value float64 `parser:" @Float "`
// Syntax
Pos errors.Position
Value float64
// Semantics
Ty Type
}
func (*LiteralFloat) expression(){}
func (*LiteralFloat) statement(){}
func (this *LiteralFloat) Position () errors.Position { return this.Pos }
func (this *LiteralFloat) Type () Type { return this.Ty }
func (this *LiteralFloat) HasExplicitType () bool { return false }
func (this *LiteralFloat) String () string {
return fmt.Sprint(this.Value)
}
// Array is a composite array literal. It can contain any number of values. It
// can be assigned to any array type that:
var _ Expression = &LiteralString { }
// LiteralString specifies a string value. It takes on different data
// representations depending on what the base type of what it is assigned to is
// structurally equivalent to:
// - Integer: Single unicode code point. When assigning to an integer, the
// string literal may not be longer than one code point, and that code point
// must fit in the integer.
// - Slice of 8 bit integers: UTF-8 string.
// - Slice of 16 bit integers: UTF-16 string.
// - Slice of 32 bit (or larger) integers: UTF-32 string.
// - Array of integers: The same as slices of integers, but the string literal
// must fit inside of the array.
// - Pointer to 8 bit integer: Null-terminated UTF-8 string (AKA C-string).
// A string literal cannot be directly assigned to an interface because it
// contains no inherent type information. A value cast may be used for this
// purpose.
type LiteralString struct {
// Syntax
Pos errors.Position
ValueUTF8 string
ValueUTF16 []uint16
ValueUTF32 []rune
// Semantics
Ty Type
}
func (*LiteralString) expression(){}
func (this *LiteralString) Position () errors.Position { return this.Pos }
func (this *LiteralString) Type () Type { return this.Ty }
func (this *LiteralString) HasExplicitType () bool { return false }
func (this *LiteralString) String () string {
return Quote(this.ValueUTF8)
}
var _ Expression = &LiteralArray { }
// LiteralArray is a composite array literal. It can contain any number of
// values. It can be assigned to any array type that:
// 1. has an identical length, and
// 2. who's element type can be assigned to by all the element values in the
// literal.
// It cannot be directly assigned to an interface because it contains no
// inherent type information. A value cast may be used for this purpose.
type LiteralArray struct {
Pos lexer.Position
Elements []Expression `parser:" '(' '*' @@* ')' "`
// Syntax
Pos errors.Position
Elements []Expression
// Semantics
Ty Type
}
func (*LiteralArray) expression(){}
func (*LiteralArray) statement(){}
func (this *LiteralArray) Position () errors.Position { return this.Pos }
func (this *LiteralArray) Type () Type { return this.Ty }
func (this *LiteralArray) HasExplicitType () bool { return false }
func (this *LiteralArray) String () string {
out := "(*"
for _, element := range this.Elements {
out += fmt.Sprint(" ", element)
out := "("
for index, element := range this.Elements {
if index > 0 { out += " " }
out += fmt.Sprint(element)
}
return out + ")"
}
// Struct is a composite structure literal. It can contain any number of
var _ Expression = &LiteralStruct { }
// LiteralStruct is a composite structure literal. It can contain any number of
// name:value pairs. It can be assigned to any struct type that:
// 1. has at least the members specified in the literal
// 2. who's member types can be assigned to by the corresponding member
@ -60,16 +118,63 @@ func (this *LiteralArray) String () string {
// It cannot be directly assigned to an interface because it contains no
// inherent type information. A value cast may be used for this purpose.
type LiteralStruct struct {
Pos lexer.Position
Members []Member `parser:" '(' @@* ')' "`
// Syntax
Pos errors.Position
Members []*Member
// Semantics
Ty Type
MemberOrder []string
MemberMap map[string] *Member
}
func (*LiteralStruct) expression(){}
func (*LiteralStruct) statement(){}
func (this *LiteralStruct) Position () errors.Position { return this.Pos }
func (this *LiteralStruct) Type () Type { return this.Ty }
func (this *LiteralStruct) HasExplicitType () bool { return false }
func (this *LiteralStruct) String () string {
out := "("
for index, member := range this.Members {
if index > 1 { out += " " }
out += fmt.Sprint(member)
out := "(."
for _, member := range this.Members {
out += fmt.Sprint(" ", member)
}
return out + ")"
}
var _ Expression = &LiteralBoolean { }
// LiteralBoolean is a boolean literal. It may be either true or false. It can
// be assigned to any type derived from a boolean. It cannot be directly
// assigned to an interface because it contains no inherent type information. A
// value cast may be used for this purpose.
type LiteralBoolean struct {
// Syntax
Pos errors.Position
Value bool
// Semantics
Ty Type
}
func (*LiteralBoolean) expression(){}
func (this *LiteralBoolean) Position () errors.Position { return this.Pos }
func (this *LiteralBoolean) Type () Type { return this.Ty }
func (this *LiteralBoolean) HasExplicitType () bool { return false }
func (this *LiteralBoolean) String () string {
if this.Value {
return "true"
} else {
return "false"
}
}
var _ Expression = &LiteralNil { }
// TODO: document, put in spec, fully implement nil for slices interfaces etc
type LiteralNil struct {
// Syntax
Pos errors.Position
// Semantics
Ty Type
}
func (*LiteralNil) expression(){}
func (this *LiteralNil) Position () errors.Position { return this.Pos }
func (this *LiteralNil) Type () Type { return this.Ty }
func (this *LiteralNil) HasExplicitType () bool { return false }
func (this *LiteralNil) String () string { return "nil" }

129
entity/meta.go Normal file
View File

@ -0,0 +1,129 @@
package entity
import "fmt"
import "strings"
import "unicode"
import "unicode/utf8"
import "path/filepath"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/errors"
// Metadata represents a module metadata file.
type Metadata struct {
Pos errors.Position
UUID uuid.UUID
Dependencies []*Dependency
}
func (this *Metadata) Position () errors.Position { return this.Pos }
func (this *Metadata) String () string {
out := fmt.Sprint(Quote(this.UUID.String()))
for _, dependency := range this.Dependencies {
out += fmt.Sprint("\n", dependency)
}
return out
}
// Directive is a declaration within a module metadata file.
type Directive interface {
fmt.Stringer
// Position returns the position of the directive within its metadata
// file.
Position () errors.Position
directive()
}
var _ Directive = &Dependency { }
// Dependency is a metadata dependency listing.
type Dependency struct {
Pos errors.Position
Address Address
Nickname string
}
func (*Dependency) directive () { }
func (this *Dependency) Position () errors.Position { return this.Pos }
func (this *Dependency) String () string {
out := fmt.Sprint("+ ", this.Address)
if this.Nickname != "" {
out += fmt.Sprint(" ", this.Nickname)
}
return out
}
// Address is the address of a unit.
type Address string
func (addr Address) String () string {
return Quote(string(addr))
}
// SourceFile returns the FSPL source file associated with the address. If the
// address does not point to an FSPL source file, it returns "", false.
func (addr Address) SourceFile () (string, bool) {
ext := filepath.Ext(string(addr))
if ext == ".fspl" {
return filepath.Clean(string(addr)), true
} else {
return "", false
}
}
// Module returns the module associated with the address. If the address does
// does not point to a module, it returns "", false.
func (addr Address) Module () (string, bool) {
path := string(addr)
switch {
case filepath.Base(path) == "fspl.mod":
return filepath.Dir(path), true
case filepath.Ext(path) == "" || filepath.Ext(path) == ".":
return filepath.Clean(path), true
default:
return "", false
}
}
// Nickname automatically generates a nickname from the address, which is a
// valid Ident token. On failure "", false is returned. The nickname is
// generated according to the folowing rules:
//
// - If the name contains at least one dot, the last dot and everything after
// it is removed
// - All non-letter, non-numeric characters are removed, and any letters that
// were directly after them are converted to uppercase
// - All numeric digits at the start of the string are removed
// - The first character is converted to lowercase
func (addr Address) Nickname () (string, bool) {
// get the normalized basename with no extension
base := filepath.Base(string(addr))
base = strings.TrimSuffix(base, filepath.Ext(base))
// remove all non-letter, non-digit characters and convert to camel case
nickname := ""
uppercase := false
for _, char := range base {
if unicode.IsLetter(char) || unicode.IsDigit(char) {
if uppercase {
uppercase = false
char = unicode.ToUpper(char)
}
nickname += string(char)
} else {
uppercase = true
}
}
// remove numeric digits
for len(nickname) > 0 {
char, size := utf8.DecodeRuneInString(nickname)
nickname = nickname[size:]
if unicode.IsLetter(char) {
// lowercase the first letter
nickname = string(unicode.ToLower(char)) + nickname
break
}
}
return nickname, nickname != ""
}
// UUID automatically generates a UUID from the address. It is the MD5 hash of
// the basename, with a space of the zero UUID.
func (addr Address) UUID () uuid.UUID {
return uuid.NewMD5(uuid.UUID { }, []byte(filepath.Base(string(addr))))
}

View File

@ -2,18 +2,55 @@ package entity
import "fmt"
import "strings"
import "github.com/alecthomas/participle/v2/lexer"
import "unicode"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/errors"
// Access determines the external access control mode for a top-level entity.
type Access int; const (
// AccessPublic allows other modules to access an entity normally.
AccessPublic Access = iota
// AccessOpaque causes a top-level entity to appear opaque to other
// units. Values of opaque types can be passed around, assigned to each-
// other, and their methods can be called, but the implementation of the
// type is entirely hidden. This access mode cannot be applied to
// functions or methods.
AccessOpaque
// AccessPrivate disallows other modules from accessing a top-level
// entity.
AccessPrivate
)
func (this Access) String () string {
switch this {
case AccessPrivate: return "-"
case AccessOpaque: return "#"
case AccessPublic: return "+"
default: return fmt.Sprintf("entity.Access(%d)", this)
}
}
// Signature is a function or method signature that is used in functions,
// methods, and specifying interface behaviors. It defines the type of a
// function.
type Signature struct {
Pos lexer.Position
Name string `parser:" '[' @Ident "`
Arguments []*Declaration `parser:" @@* ']' "`
Return Type `parser:" ( ':' @@ )? "`
// Syntax
Pos errors.Position
Name string
Arguments []*Declaration
Return Type
// Semantics
Acc Access
Unt uuid.UUID
ArgumentOrder []string
ArgumentMap map[string] *Declaration
}
func (*Signature) ty(){}
func (this *Signature) Position () errors.Position { return this.Pos }
func (this *Signature) Access () Access { return this.Acc }
func (this *Signature) Unit () uuid.UUID { return this.Unt }
func (this *Signature) String () string {
out := "[" + this.Name
for _, argument := range this.Arguments {
@ -25,18 +62,80 @@ func (this *Signature) String () string {
}
return out
}
func (this *Signature) Hash () Hash {
data := new(strings.Builder)
data.WriteString("Signature:")
for _, argument := range this.Arguments {
argumentHash := HashType(argument.Type())
data.Write(argumentHash[:])
}
data.WriteString(":")
returnHash := HashType(this.Return)
data.Write(returnHash[:])
return NewHash([]byte(data.String()))
}
func (this *Signature) Equals (ty Type) bool {
real, ok := ty.(*Signature)
if !ok ||
len(real.Arguments) != len(this.Arguments) ||
!TypesEqual(real.Return, this.Return) ||
real.Name != this.Name { return false }
for index, argument := range this.Arguments {
if !argument.Type().Equals(real.Arguments[index].Type()) {
return false
}
}
return true
}
// Member is a syntactical construct that is used to list members in struct
// literals.
type Member struct {
Pos lexer.Position
Name string `parser:" @Ident "`
Value Expression `parser:" ':' @@ "`
Pos errors.Position
Name string
Value Expression
}
func (this *Member) Position () errors.Position { return this.Pos }
func (this *Member) String () string {
return fmt.Sprint(this.Name, ":", this.Value)
}
// MatchCase represents a case in a match expression.
type MatchCase struct {
Pos errors.Position
Declaration *Declaration
Expression
Scope
}
func (this *MatchCase) Position () errors.Position { return this.Pos }
func (this *MatchCase) String () string {
return fmt.Sprint("| ", this.Declaration, this.Expression)
}
// SwitchCase represents a case in a switch expression.
type SwitchCase struct {
Pos errors.Position
Key Expression
Expression
Scope
}
func (this *SwitchCase) Position () errors.Position { return this.Pos }
func (this *SwitchCase) String () string {
return fmt.Sprint("| ", this.Key, this.Expression)
}
// DefaultCase represents the default case in a switch or match expression.
type DefaultCase struct {
Pos errors.Position
Expression
Scope
}
func (this *DefaultCase) Position () errors.Position { return this.Pos }
func (this *DefaultCase) String () string {
return fmt.Sprint("* ", this.Expression)
}
// Operator determines which operation to apply to a value in an operation
// expression.
type Operator int; const (
@ -71,6 +170,25 @@ type Operator int; const (
OperatorEqual
)
// ResultsInBoolean determines whether or not this operation will always result
// in a boolean value.
func (operator Operator) ResultsInBoolean () bool {
switch operator {
case OperatorLogicalNot,
OperatorLogicalOr,
OperatorLogicalAnd,
OperatorLogicalXor,
OperatorLess,
OperatorGreater,
OperatorLessEqual,
OperatorGreaterEqual,
OperatorEqual:
return true
default:
return false
}
}
var operatorToString = []string {
// Math
"+",
@ -86,6 +204,7 @@ var operatorToString = []string {
"|",
"&",
"^",
// Bit manipulation
"!!",
"||",
@ -111,11 +230,25 @@ func init () {
}
}
func (this *Operator) Capture (str []string) error {
*this = stringToOperator[strings.Join(str, "")]
return nil
func OperatorFromString (str string) Operator {
return stringToOperator[str]
}
func (this Operator) String () string {
return operatorToString[this]
}
// Quote puts quotes around a string to turn it into a string literal.
func Quote (in string) string {
out := "'"
for _, char := range in {
if char == '\'' {
out += "\\'"
} else if unicode.IsPrint(char) {
out += string(char)
} else {
out += fmt.Sprintf("\\%03o", char)
}
}
return out + "'"
}

View File

@ -8,7 +8,11 @@ type Scoped interface {
Variable (name string) *Declaration
// AddVariable adds a variable to this entity's scope.
AddVariable (name string, declaration *Declaration)
AddVariable (declaration *Declaration)
// OverVariables calls callback for each defined variable, stopping if
// returns false.
OverVariables (callback func (*Declaration) bool)
}
// Scope implements a scope.
@ -23,9 +27,15 @@ func (this *Scope) Variable (name string) *Declaration {
return this.variables[name]
}
func (this *Scope) AddVariable (name string, declaration *Declaration) {
func (this *Scope) AddVariable (declaration *Declaration) {
if this.variables == nil {
this.variables = make(map[string] *Declaration)
}
this.variables[name] = declaration
this.variables[declaration.Name] = declaration
}
func (this *Scope) OverVariables (callback func (*Declaration) bool) {
for _, declaration := range this.variables {
if !callback(declaration) { break }
}
}

View File

@ -1,35 +1,55 @@
package entity
import "fmt"
import "github.com/alecthomas/participle/v2/lexer"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/errors"
// TopLevel is any construct that is placed at the root of a file.
type TopLevel interface {
fmt.Stringer
// Position returns the position of the entity within its source file.
Position () errors.Position
// Access returns the access control mode of the entity.
Access () Access
// Unit returns the unit that the entity was defined in.
Unit () uuid.UUID
topLevel ()
}
var _ TopLevel = &Typedef { }
// Typedef binds a type to a global identifier.
type Typedef struct {
// Syntax
Pos lexer.Position
Public bool `parser:" @'+'? "`
Name string `parser:" @Ident "`
Type Type `parser:" ':' @@ "`
Pos errors.Position
Acc Access
Name string
Type Type
// Semantics
Methods map[string] Method
Unt uuid.UUID
Methods map[string] *Method
}
func (*Typedef) topLevel(){}
func (this *Typedef) Position () errors.Position { return this.Pos }
func (this *Typedef) Access () Access { return this.Acc }
func (this *Typedef) Unit () uuid.UUID { return this.Unt }
func (this *Typedef) String () string {
out := fmt.Sprint(this.Name, ": ", this.Type)
output := ""
output += fmt.Sprint(this.Acc, " ")
output += fmt.Sprint(this.Name, ": ", this.Type)
if this.Methods != nil {
for _, method := range this.Methods {
out += fmt.Sprint("\n", method)
output += fmt.Sprint("\n", method)
}
}
return out
return output
}
var _ TopLevel = &Function { }
// Function binds a global identifier and argument list to an expression which
// is evaluated each time the function is called. If no expression is specified,
// the function is marked as external. Functions have an argument list, where
@ -37,46 +57,66 @@ func (this *Typedef) String () string {
// these are typed.
type Function struct {
// Syntax
Pos lexer.Position
Public bool `parser:" @'+'? "`
Signature *Signature `parser:" @@ "`
Body Expression `parser:" ( '=' @@ )? "`
Pos errors.Position
Acc Access
LinkName string
Signature *Signature
Body Expression
// Semantics
Unt uuid.UUID
Scope
}
func (*Function) topLevel(){}
func (this *Function) Position () errors.Position { return this.Pos }
func (this *Function) Access () Access { return this.Acc }
func (this *Function) Unit () uuid.UUID { return this.Unt }
func (this *Function) String () string {
if this.Body == nil {
return fmt.Sprint(this.Signature)
} else {
return fmt.Sprint(this.Signature, " = ", this.Body)
output := ""
output += fmt.Sprint(this.Acc, " ")
output += this.Signature.String()
if this.LinkName != "" {
output += fmt.Sprint(" '", this.LinkName, "'")
}
if this.Body != nil {
output += fmt.Sprint(" = ", this.Body)
}
return output
}
var _ TopLevel = &Method { }
// Method is like a function, except localized to a defined type. Methods are
// called on an instance of that type, and receive a pointer to that instance
// via the "this" variable. Method names are not globally unique, bur are unique
// within the type they are defined on.
// via the "this" variable when they are run. Method names are not globally
// unique, but are unique within the type they are defined on.
type Method struct {
// Syntax
Pos lexer.Position
Public bool `parser:" @'+'? "`
TypeName string `parser:" @Ident "`
Signature *Signature `parser:" ':' ':' @@ "`
Body Expression `parser:" ( '=' @@ )? "`
Pos errors.Position
Acc Access
TypeName string
LinkName string
Signature *Signature
Body Expression
// Semantics
Unt uuid.UUID
Type Type
This *Declaration
Scope
}
func (*Method) topLevel(){}
func (this *Method) Position () errors.Position { return this.Pos }
func (this *Method) Access () Access { return this.Acc }
func (this *Method) Unit () uuid.UUID { return this.Unt }
func (this *Method) String () string {
if this.Body == nil {
return fmt.Sprint(this.TypeName, ".", this.Signature)
} else {
return fmt.Sprint (
this.TypeName, "::",
this.Signature, " = ",
this.Body)
output := ""
output += fmt.Sprint(this.Acc, " ")
output += fmt.Sprint(this.TypeName, ".", this.Signature)
if this.LinkName != "" {
output += fmt.Sprint(" '", this.LinkName, "'")
}
if this.Body != nil {
output += fmt.Sprint(" = ", this.Body)
}
return output
}

View File

@ -1,108 +1,403 @@
package entity
import "fmt"
import "github.com/alecthomas/participle/v2/lexer"
import "strings"
import "github.com/google/uuid"
import "git.tebibyte.media/fspl/fspl/errors"
// Type is any type notation.
type Type interface {
fmt.Stringer
// Position returns the position of the type within its source file.
Position () errors.Position
// Equals returns whether this type is equivalent to another type.
Equals (ty Type) bool
// Access returns the access control mode of the type.
Access () Access
// Unit returns the unit that the type was defined in.
Unit () uuid.UUID
// Hash returns a hash of this type that fits within a uint64.
Hash () Hash
ty ()
}
type void { }
func (void) ty(){}
// Void returns the absence of a type. It can be compared to a Type to find out
// if it is filled in, but has no type.
func Void () Type {
return void { }
}
// TypeNamed refers to a user-defined or built in named type.
type TypeNamed struct {
Pos lexer.Position
Name string `parser:" @Ident "`
Type Type
// Syntax
Pos errors.Position
UnitNickname string
Name string
Type Type
// Semantics
Acc Access
Unt uuid.UUID
}
func (*TypeNamed) ty(){}
func (this *TypeNamed) String () string { return this.Name }
func (this *TypeNamed) Position () errors.Position { return this.Pos }
func (this *TypeNamed) Access () Access { return this.Acc }
func (this *TypeNamed) Unit () uuid.UUID { return this.Unt }
func (this *TypeNamed) String () string {
if this.UnitNickname == "" {
return this.Name
} else {
return fmt.Sprint(this.UnitNickname, "::", this.Name)
}
}
func (this *TypeNamed) Hash () Hash {
return Key {
Unit: this.Type.Unit(),
Name: this.Name,
}.Hash()
}
func (this *TypeNamed) Equals (ty Type) bool {
real, ok := ty.(*TypeNamed)
return ok && TypesEqual(real.Type, this.Type)
}
// TypePointer is a pointer to another type.
type TypePointer struct {
Pos lexer.Position
Referenced Type `parser:" '*' @@ "`
Pos errors.Position
Referenced Type
// Semantics
Acc Access
Unt uuid.UUID
}
func (*TypePointer) ty(){}
func (this *TypePointer) Position () errors.Position { return this.Pos }
func (this *TypePointer) Access () Access { return this.Acc }
func (this *TypePointer) Unit () uuid.UUID { return this.Unt }
func (this *TypePointer) String () string {
return fmt.Sprint("*", this.Referenced)
}
func (this *TypePointer) Hash () Hash {
referenced := HashType(this.Referenced)
return NewHash(append([]byte("TypePointer:"), referenced[:]...))
}
func (this *TypePointer) Equals (ty Type) bool {
real, ok := ty.(*TypePointer)
return ok && TypesEqual(real.Referenced, this.Referenced)
}
// TypeSlice is a pointer to several values of a given type stored next to
// eachother. Its length is not built into its type and can be changed at
// runtime.
type TypeSlice struct {
Pos lexer.Position
Element Type `parser:" '*' ':' @@ "`
Pos errors.Position
Element Type
// Semantics
Acc Access
Unt uuid.UUID
}
func (*TypeSlice) ty(){}
func (this *TypeSlice) Position () errors.Position { return this.Pos }
func (this *TypeSlice) Access () Access { return this.Acc }
func (this *TypeSlice) Unit () uuid.UUID { return this.Unt }
func (this *TypeSlice) String () string {
return fmt.Sprint("*:", this.Element)
}
func (this *TypeSlice) Hash () Hash {
referenced := HashType(this.Element)
return NewHash(append([]byte("TypeSlice:"), referenced[:]...))
}
func (this *TypeSlice) Equals (ty Type) bool {
real, ok := ty.(*TypeSlice)
return ok && TypesEqual(real.Element, this.Element)
}
// TypeArray is a group of values of a given type stored next to eachother. The
// length of an array is fixed and is part of its type. Arrays are passed by
// value unless a pointer is used.
type TypeArray struct {
Pos lexer.Position
Length int `parser:" @Int "`
Element Type `parser:" ':' @@ "`
Pos errors.Position
Length int
Element Type
// Semantics
Acc Access
Unt uuid.UUID
}
func (*TypeArray) ty(){}
func (this *TypeArray) Position () errors.Position { return this.Pos }
func (this *TypeArray) Access () Access { return this.Acc }
func (this *TypeArray) Unit () uuid.UUID { return this.Unt }
func (this *TypeArray) String () string {
return fmt.Sprint(this.Length, ":", this.Element)
}
func (this *TypeArray) Hash () Hash {
referenced := HashType(this.Element)
return NewHash(append (
[]byte(fmt.Sprintf("TypeArray:%d:", this.Length)),
referenced[:]...))
}
func (this *TypeArray) Equals (ty Type) bool {
real, ok := ty.(*TypeArray)
return ok &&
real.Length == this.Length &&
TypesEqual(real.Element, this.Element)
}
// Struct is a composite type that stores keyed values. The positions of the
// TypeStruct is a composite type that stores keyed values. The positions of the
// values within the struct are decided at compile time, based on the order they
// are specified in. Structs are passed by value unless a pointer is used.
type TypeStruct struct {
// Syntax
Pos lexer.Position
Members []*Declaration `parser:" '(' @@+ ')' "`
Pos errors.Position
Members []*Declaration
// Semantics
Acc Access
Unt uuid.UUID
MemberOrder []string
MemberMap map[string] *Declaration
MemberMap map[string] *Declaration
}
func (*TypeStruct) ty(){}
func (this *TypeStruct) Position () errors.Position { return this.Pos }
func (this *TypeStruct) Access () Access { return this.Acc }
func (this *TypeStruct) Unit () uuid.UUID { return this.Unt }
func (this *TypeStruct) String () string {
out := "("
for index, member := range this.Members {
if index > 0 { out += " " }
out += fmt.Sprint(member)
out := "(."
for _, member := range this.Members {
out += fmt.Sprint(" ", member)
}
return out + ")"
}
func (this *TypeStruct) Hash () Hash {
data := new(strings.Builder)
data.WriteString("TypeStruct:")
for _, member := range this.Members {
memberHash := HashType(member.Type())
data.Write(memberHash[:])
}
return NewHash([]byte(data.String()))
}
func (this *TypeStruct) Equals (ty Type) bool {
real, ok := ty.(*TypeStruct)
if !ok || len(real.Members) != len(this.Members) { return false }
// Interface is a polymorphic pointer that allows any value of any type through,
// except it must have at least the methods defined within the interface.
// Interfaces are always passed by reference. When assigning a value to an
// interface, it will be referenced automatically. When assigning a pointer to
// an interface, the pointer's reference will be used instead.
for index, member := range this.Members {
if !TypesEqual(member.Type(), real.Members[index].Type()) {
return false
}
}
return true
}
// TypeInterface is a polymorphic pointer that allows any value of any type
// through, except it must have at least the methods defined within the
// interface. Interfaces are always passed by reference. When assigning a value
// to an interface, it will be referenced automatically. When assigning a
// pointer to an interface, the pointer's reference will be used instead.
type TypeInterface struct {
// Syntax
Pos lexer.Position
Behaviors []*Signature `parser:" '(' @@+ ')' "`
Pos errors.Position
Behaviors []*Signature
// Semantics
Acc Access
Unt uuid.UUID
BehaviorOrder []string
BehaviorMap map[string] *Signature
BehaviorMap map[string] *Signature
}
func (*TypeInterface) ty(){}
func (this *TypeInterface) Position () errors.Position { return this.Pos }
func (this *TypeInterface) Access () Access { return this.Acc }
func (this *TypeInterface) Unit () uuid.UUID { return this.Unt }
func (this *TypeInterface) String () string {
out := "("
for index, behavior := range this.Behaviors {
if index > 0 { out += " " }
out += fmt.Sprint(behavior)
out := "(&"
for _, behavior := range this.Behaviors {
out += fmt.Sprint(" ", behavior)
}
return out + ")"
}
func (this *TypeInterface) Hash () Hash {
data := new(strings.Builder)
data.WriteString("TypeInterface:")
for _, behavior := range this.Behaviors {
behaviorHash := HashType(behavior)
data.Write(behaviorHash[:])
}
return NewHash([]byte(data.String()))
}
func (this *TypeInterface) Equals (ty Type) bool {
real, ok := ty.(*TypeInterface)
if !ok || len(real.Behaviors) != len(this.Behaviors) { return false }
for index, behavior := range this.Behaviors {
if !behavior.Equals(real.Behaviors[index]) {
return false
}
}
return true
}
// TypeUnion is a polymorphic type that can hold any value as long as it is one
// of a list of allowed types. It is not a pointer. It holds the hash of the
// actual type of the value stored within it, followed by the value. The hash
// field is computed using the type's name, and the UUID that it was defined in.
// If it is not named, then the hash is computed using the structure of the
// type. The value field is always big enough to hold the largest type in the
// allowed list.
type TypeUnion struct {
// Syntax
Pos errors.Position
Allowed []Type
// Semantics
Acc Access
Unt uuid.UUID
AllowedOrder []Hash
AllowedMap map[Hash] Type
}
func (*TypeUnion) ty(){}
func (this *TypeUnion) Position () errors.Position { return this.Pos }
func (this *TypeUnion) Access () Access { return this.Acc }
func (this *TypeUnion) Unit () uuid.UUID { return this.Unt }
func (this *TypeUnion) String () string {
out := "(|"
for _, ty := range this.Allowed {
out += fmt.Sprint(" ", ty)
}
return out + ")"
}
func (this *TypeUnion) Hash () Hash {
data := new(strings.Builder)
data.WriteString("TypeUnion:")
for _, ty := range this.Allowed {
typeHash := HashType(ty)
data.Write(typeHash[:])
}
return NewHash([]byte(data.String()))
}
func (this *TypeUnion) Equals (ty Type) bool {
real, ok := ty.(*TypeUnion)
if !ok || len(real.Allowed) != len(this.Allowed) { return false }
for index, ty := range this.Allowed {
if !ty.Equals(real.Allowed[index]) {
return false
}
}
return true
}
// TypeInt represents any signed or unsigned integer type.
type TypeInt struct {
Pos errors.Position
Width int
Signed bool
// Semantics
Acc Access
Unt uuid.UUID
}
func (*TypeInt) ty(){}
func (this *TypeInt) Position () errors.Position { return this.Pos }
func (this *TypeInt) Access () Access { return this.Acc }
func (this *TypeInt) Unit () uuid.UUID { return this.Unt }
func (this *TypeInt) String () string {
if this.Signed {
return fmt.Sprint("I", this.Width)
} else {
return fmt.Sprint("U", this.Width)
}
}
func (this *TypeInt) Hash () Hash {
return NewHash([]byte(fmt.Sprintf (
"TypeInt:%d:%t",
this.Width, this.Signed)))
}
func (this *TypeInt) Equals (ty Type) bool {
real, ok := ty.(*TypeInt)
return ok && real.Width == this.Width && real.Signed == this.Signed
}
// TypeFloat represents any floating point type.
type TypeFloat struct {
Pos errors.Position
Width int
// Semantics
Acc Access
Unt uuid.UUID
}
func (*TypeFloat) ty(){}
func (this *TypeFloat) Position () errors.Position { return this.Pos }
func (this *TypeFloat) Access () Access { return this.Acc }
func (this *TypeFloat) Unit () uuid.UUID { return this.Unt }
func (this *TypeFloat) String () string {
return fmt.Sprint("F", this.Width)
}
func (this *TypeFloat) Hash () Hash {
return NewHash([]byte(fmt.Sprintf("TypeFloat:%d", this.Width)))
}
func (this *TypeFloat) Equals (ty Type) bool {
real, ok := ty.(*TypeFloat)
return ok && real.Width == this.Width
}
// TypeWord represents an integer type of unspecified width. The optimal width
// is chosen based on the machine word size (32 on 32 bit systems, 64 on 64 bit
// systems, etc)
type TypeWord struct {
Pos errors.Position
Signed bool
// Semantics
Acc Access
Unt uuid.UUID
}
func (*TypeWord) ty(){}
func (this *TypeWord) Position () errors.Position { return this.Pos }
func (this *TypeWord) Access () Access { return this.Acc }
func (this *TypeWord) Unit () uuid.UUID { return this.Unt }
func (this *TypeWord) String () string {
if this.Signed {
return "Int"
} else {
return "UInt"
}
}
func (this *TypeWord) Hash () Hash {
return NewHash([]byte(fmt.Sprintf("TypeWord:%t", this.Signed)))
}
func (this *TypeWord) Equals (ty Type) bool {
real, ok := ty.(*TypeWord)
return ok && real.Signed == this.Signed
}
// TypesEqual checks if two types are equal to eachother, even if one or both
// are nil.
func TypesEqual (left, right Type) bool {
if (left == nil) != (right == nil) { return false }
if left == nil { return true }
return left.Equals(right)
}
// FormatType returns a string representing a type. If the type is nil, it
// returns "Void".
func FormatType (ty Type) string {
if ty == nil {
return "Void"
} else {
return ty.String()
}
}
// HashType returns a hash representing a type. If the type is nil, it returns
// NewHash([]byte("TypeVoid"))
func HashType (ty Type) Hash {
if ty == nil {
return NewHash([]byte("TypeVoid"))
} else {
return ty.Hash()
}
}

5
errors/doc.go Normal file
View File

@ -0,0 +1,5 @@
// Package errors provides a location tracking and error formatting system.
// It provides a way to keep track of the in-file position of the physical text
// that in-memory data structures were derived from, and a way to use this
// positional information to create informative and easy to read errors.
package errors

62
errors/errors.go Normal file
View File

@ -0,0 +1,62 @@
package errors
import "fmt"
// Error is an error that contains positional information.
type Error interface {
error
Position () Position
}
type errInternal struct {
position Position
message string
}
var _ Error = new(errInternal)
func (err *errInternal) Error () string {
return err.String()
}
func (err *errInternal) String () string {
return err.message
}
func (err *errInternal) Position () Position {
return err.position
}
// Errorf produces an error with a fmt'd message.
func Errorf (position Position, format string, variables ...any) Error {
return &errInternal {
position: position,
message: fmt.Sprintf(format, variables...),
}
}
// Format returns a formatted string representing an error. This string may take
// up multiple lines and contain ANSI escape codes. If err does not fulfill the
// Error interface, err.Error() is returned instead.
//
// When the error fulfills the Error interface, the formatted output will
// be of the general form:
// FILE:ROW+1:START+1: MESSAGE
// ROW+1 | LINE
// ^^^^
// Where the position of the underline (^^^^) corresponds to the start and end
// fields in the error's position. Note that the row and column numbers as
// displayed are increased by one from their normal zero-indexed state, because
// most editors display row and column numbers starting at 1:1. Additionally,
// because normal error messages do not produce trailing line breaks, neither
// does this function.
func Format (err error) string {
if e, ok := err.(Error); ok {
return fmt.Sprintf (
"%v: %v\n%v",
e.Position(),
e.Error(),
e.Position().Format())
} else {
return err.Error()
}
}

Some files were not shown because too many files have changed in this diff Show More