Compare commits

...
150 Commits
Author SHA1 Message Date
sashakoshka 06a99ce232 Implement new tokens
I think this is all of them. The test will tell.
2022-08-30 01:02:49 -04:00
Sasha Koshka 1c2194b68a Added text descriptions for new tokens 2022-08-25 23:21:00 -04:00
Sasha Koshka 453a596587 Added test case for new tokens 2022-08-25 23:17:42 -04:00
sashakoshka c3b6330b22 Added base function parsing 2022-08-25 20:01:12 -04:00
sashakoshka 723b506005 Added test code for function sections 2022-08-25 16:08:18 -04:00
sashakoshka 6bbee2e13b Created comprehensive test case 2022-08-25 15:46:35 -04:00
Sasha Koshka 9fd285920b Began writing test case for function sections 2022-08-25 13:31:09 -04:00
Sasha Koshka e630ec6f04 Added function section to tree 2022-08-25 12:02:43 -04:00
Sasha Koshka 0ac71fa1c3 Added progress heatmap 2022-08-25 00:51:54 -04:00
Sasha Koshka 9232432c35 Implemented thos bad boys 2022-08-25 00:05:40 -04:00
Sasha Koshka b536b01eeb Added new tokens to test case 2022-08-25 00:01:28 -04:00
Sasha Koshka 8175a9d4c5 Added some more tokens to the TokenKind enum 2022-08-24 23:58:21 -04:00
Sasha Koshka 3dd2ea83d3 I forgot the 2022-08-24 23:55:34 -04:00
Sasha Koshka b7631530bc yeah 2022-08-24 23:54:06 -04:00
Sasha Koshka fa1d8efe55 Its just as I feared. Identifier parsing doesn't work! 2022-08-24 23:50:16 -04:00
sashakoshka e74aff3299 Merge pull request 'tree-cleanup' (#9) from tree-cleanup into main
Reviewed-on: #9
2022-08-25 00:24:41 +00:00
Sasha Koshka 89a60e620e Altered objt section test case to not be alphabetically organized 2022-08-24 20:20:55 -04:00
Sasha Koshka cd528552c8 Object sections now parse members into array 2022-08-24 20:19:14 -04:00
Sasha Koshka 067bf2f4df Altered tree so that object members are stored in an array 2022-08-24 20:09:57 -04:00
sashakoshka 777c8df6a4 Changed the logo color because why not lol 2022-08-24 18:57:45 -04:00
sashakoshka c470997887 Did the same thing to interfaces 2022-08-24 18:57:07 -04:00
sashakoshka 715766edb4 Objects can only inherit by specifiying an identifier 2022-08-24 18:52:31 -04:00
sashakoshka 821fa0ecb3 Merge pull request 'objt-bitfields' (#8) from objt-bitfields into main
Reviewed-on: #8
2022-08-24 22:46:31 +00:00
sashakoshka e316eb7791 Changed bit field syntax to use an & symbol 2022-08-24 18:37:44 -04:00
sashakoshka 731cc828ce Added untested bit width parsing 2022-08-24 18:29:15 -04:00
sashakoshka 05aa0e6177 Added bitfields to object section test case 2022-08-24 18:23:11 -04:00
sashakoshka fb43f96acc Added bit fields to tree and ToString for object member 2022-08-24 18:22:47 -04:00
Sasha Koshka b64fbd9fc4 Split tests into multiple files
This should make it easier to work on sections independantly of one another
without creating merge conflicts
2022-08-24 01:22:24 -04:00
Sasha Koshka 0d366964ca Enum members are now ordered 2022-08-24 01:16:44 -04:00
sashakoshka a5477717eb Merge pull request 'face-section' (#7) from face-section into main
Reviewed-on: #7
2022-08-24 04:57:14 +00:00
Sasha Koshka 0b80a55f79 Repaired output formatting of interface section 2022-08-24 00:53:42 -04:00
Sasha Koshka 08935d69c0 Parser actually adds interface behavior to interface 2022-08-24 00:52:37 -04:00
Sasha Koshka 39f8d7e4ac Fixed parsing of interface section behaviors 2022-08-24 00:25:52 -04:00
Sasha Koshka 1f88b54eaa Face sections are actually ToString'd now 2022-08-23 22:25:21 -04:00
sashakoshka b0d4ecc83f Added interface section parsing to body 2022-08-23 15:14:44 -04:00
sashakoshka 4eac5c67aa Added untested interface section parsing 2022-08-23 15:13:00 -04:00
sashakoshka 441b036a1c Updated test case to reflect previous commit 2022-08-23 14:07:56 -04:00
sashakoshka 8817d72cb3 Interfaces can inherit other interfaces 2022-08-23 13:56:59 -04:00
sashakoshka 3ef1e706b3 Added ToString method to face section 2022-08-23 13:54:44 -04:00
sashakoshka 944fc8514e Add correct output for face test case 2022-08-23 13:46:20 -04:00
Sasha Koshka cd55a0ad8d Add interface section to tree 2022-08-23 10:56:37 -04:00
Sasha Koshka f95c7e0b1c Basic test file for interface section 2022-08-23 10:55:50 -04:00
sashakoshka 15d1b602b3 Merge pull request 'enum-section' (#6) from enum-section into main
Reviewed-on: #6
2022-08-23 05:38:55 +00:00
Sasha Koshka c29efd97ba Organized test case members alphabetically 2022-08-23 01:36:40 -04:00
Sasha Koshka aa84d9a429 Removed space alignment and hex literals from test case check
ToString is not capable of producing this
2022-08-23 01:35:35 -04:00
Sasha Koshka 5dcf3b3d1a Fixed ToString formatting of enum 2022-08-23 01:33:28 -04:00
Sasha Koshka d8074fa5cb Enum default values are now parsed properly
Previously the parser would stay on the member name and parse it the default
value. It now moves forward and catches the actual default value.
2022-08-23 01:30:56 -04:00
Sasha Koshka 6a6fe8353e Add untested enum parsing 2022-08-21 11:17:56 -04:00
Sasha Koshka c4f763af5b Added test case for enum section 2022-08-21 02:48:36 -04:00
Sasha Koshka 6fbda34300 Add base enum parsing method 2022-08-21 02:42:25 -04:00
Sasha Koshka 59126f60cc Added enum sections to tree 2022-08-21 02:40:04 -04:00
Sasha Koshka ca80a5968d Cleaned up example code and made it up-to-date 2022-08-20 15:54:10 -04:00
sashakoshka 61819311e9 Merge pull request 'objt-section' (#5) from objt-section into main
Reviewed-on: #5
2022-08-20 19:47:44 +00:00
Sasha Koshka f3b2d11f59 I swear its not my code thats wrong its the test
No like literally this keeps happening
2022-08-20 15:45:45 -04:00
Sasha Koshka 3900bbe7bf Parser test cases now print out line numbers 2022-08-20 15:45:01 -04:00
Sasha Koshka b878017b81 The last item of object sections is now saved. 2022-08-20 15:22:25 -04:00
Sasha Koshka 5271876196 Changed data in object test to use objt keyword instead of type 2022-08-20 13:46:10 -04:00
Sasha Koshka 617d76fc46 Object sections now parse properly 2022-08-20 13:43:10 -04:00
Sasha Koshka 0ceaedbcd8 Object sections now ToString properly 2022-08-20 13:42:09 -04:00
Sasha Koshka edb9c1a0b6 Fixed assignment to entry in nil map 2022-08-20 13:29:04 -04:00
Sasha Koshka bd433fc65d Untested object section parsing 2022-08-20 13:26:24 -04:00
Sasha Koshka c847d2187d Fixed the object section test 2022-08-20 13:25:43 -04:00
Sasha Koshka cb2264977a Added object sections to the tree for real lol 2022-08-20 13:24:56 -04:00
Sasha Koshka 790e7e632e Removed recursive member parsing nonsense from type section 2022-08-20 12:50:32 -04:00
Sasha Koshka fc1568aece Updated ToString methods to match new tree structure 2022-08-20 12:40:44 -04:00
Sasha Koshka 222c47ced9 Altered tree to separate object and blind type definitions 2022-08-20 02:46:40 -04:00
Sasha Koshka da6d587a48 Split test cases between blind types and objt types 2022-08-20 02:42:52 -04:00
sashakoshka 018499310c Merge pull request 'type-section' (#4) from type-section into main
Reviewed-on: #4
2022-08-20 02:06:44 +00:00
Sasha Koshka 78b8b9dacd Fixed test case for parser
The correct output string was missing a type specifier. The lexer now passes
this test.
2022-08-19 11:37:30 -04:00
Sasha Koshka 2605d1fb09 Fixed nested complex initialization values not parsing 2022-08-19 11:36:30 -04:00
Sasha Koshka 9dce9b2f75 Fixed test formatting 2022-08-19 03:05:25 -04:00
Sasha Koshka 9b4279c052 Fixed ToString of type nodes 2022-08-19 03:03:36 -04:00
Sasha Koshka 2296765e81 Added recursive parsing of type nodes 2022-08-19 03:01:47 -04:00
Sasha Koshka 19d0b3f455 Complex default values of type nodes now ToString properly 2022-08-19 02:36:56 -04:00
Sasha Koshka e25e7bdf14 Parser can now parse array and object initializations 2022-08-19 02:34:17 -04:00
Sasha Koshka 63419165dd Moved most of type section parsing into reusable type node parsing method 2022-08-19 02:08:18 -04:00
Sasha Koshka 69aaae8f14 Restructured type definitions to use a node tree 2022-08-18 23:38:32 -04:00
Sasha Koshka 717474a59e Removed unnescessary println statements (oopsie) 2022-08-18 20:09:27 -04:00
Sasha Koshka ef90115a1b Fixed some test case formatting 2022-08-18 20:09:04 -04:00
Sasha Koshka cced825f74 Changed this one thing to the other thing 2022-08-18 19:40:35 -04:00
Sasha Koshka 9fd3fb1263 Added basic ToString method to TypeSection 2022-08-18 17:45:34 -04:00
Sasha Koshka 5c2a7aeb07 Created base for type section parsing 2022-08-18 17:39:19 -04:00
Sasha Koshka bc9beb0317 Created test case for type section 2022-08-18 16:56:42 -04:00
Sasha Koshka a548dcc585 Changed permission codes to only determine private/public/readonly
Changing permissions within the module was redundant and would have just
conflicted with the :mut type qualifier. This is easier to understand.
2022-08-18 12:09:17 -04:00
Sasha Koshka 15eb96e8ac Lexer passes all width tests 2022-08-18 11:35:48 -04:00
Sasha Koshka 120976a0f3 Numbers now tokenize with the correct width 2022-08-18 11:32:50 -04:00
Sasha Koshka bde4bf8493 String and rune literals now have correct width 2022-08-18 11:25:40 -04:00
Sasha Koshka a013d4caad Lexer tests now check token width 2022-08-18 11:14:42 -04:00
Sasha Koshka be9a3603d2 Made structural change to lexer test definitions 2022-08-18 11:02:49 -04:00
Sasha Koshka 54de3d1270 Fixed test columns and widths 2022-08-18 02:06:00 -04:00
Sasha Koshka a87973c141 Error widths now work properly 2022-08-18 02:04:49 -04:00
Sasha Koshka 85996b2554 Added more error test cases 2022-08-18 01:47:35 -04:00
Sasha Koshka 4780d9cc28 Fixed bug in file where it would report its location one step ahead 2022-08-18 01:35:46 -04:00
Sasha Koshka bb89009742 Add description method to Location 2022-08-18 01:31:01 -04:00
Sasha Koshka 9e66305001 Created test to check lexer errors 2022-08-18 01:25:02 -04:00
Sasha Koshka 39e4fbe844 Replaced references to file.Error with infoerr.Error 2022-08-18 00:58:40 -04:00
Sasha Koshka d42d0c5b34 Renamed error module to infoerr 2022-08-18 00:56:45 -04:00
Sasha Koshka ca5f8202bb Put Error in its own module 2022-08-18 00:51:19 -04:00
Sasha Koshka abc6e44fb2 Removed Location's dependency on Error 2022-08-18 00:50:57 -04:00
Sasha Koshka cce841f48e Add getters to File 2022-08-18 00:50:39 -04:00
Sasha Koshka 568134a7e9 Add getter functions to Location 2022-08-18 00:42:47 -04:00
Sasha Koshka 57f02ad6f6 Added getter functions to Error 2022-08-18 00:39:08 -04:00
Sasha Koshka dd8bbf8b13 Lexer now uses go's strconv number parsing functions
This allows for better accuracy when parsing floats, at the cost of only
supporting decimal floating point literals. The lexer now passes all tests.
2022-08-18 00:31:27 -04:00
Sasha Koshka ac85c2c725 Remove line break at top of readme 2022-08-17 21:48:06 -04:00
Sasha Koshka 9b62a9b9ed Added logo to README 2022-08-17 20:16:48 -04:00
Sasha Koshka 63110d9a32 Removed bad spacing in ObjectInitializationValues.ToString
If a value had more initialzation values inside of it, it would leave a space
before the newline. This was causing the parser test to believe the parser to be
failing. The parser now passes the test.
2022-08-17 19:40:16 -04:00
Sasha Koshka 6a5851c9eb Parser tests now show exact line and column where data is mismatched 2022-08-17 19:36:33 -04:00
sashakoshka e069569c3c Merge pull request 'data-section' (#3) from data-section into main
Reviewed-on: #3
2022-08-17 18:21:19 +00:00
Sasha Koshka d78f150336 Removed data test case initializing pointers with phrases
Phrase parsing is out of scope for this branch. It will be implemented at the
same time as function parsing.
2022-08-17 14:16:54 -04:00
Sasha Koshka e5b92009f0 Re-arranged data parsing test case to be alphabetical 2022-08-17 13:53:08 -04:00
Sasha Koshka 98fb4e9c66 In ToString methods, maps are sorted alphabetically before output
This makes the output of ToString methods deterministic, and as such they can be
used for testing.
2022-08-17 13:50:33 -04:00
Sasha Koshka aee90757e3 Object initialization value parsing is now done recursively 2022-08-17 13:26:18 -04:00
Sasha Koshka 31bb36a4f7 Reworked parsing object initialization values 2022-08-17 12:53:35 -04:00
Sasha Koshka 384de58d41 Added previousToken method to parser 2022-08-17 12:39:26 -04:00
Sasha Koshka 8c03aa880b Reworked array initialization value parsing 2022-08-17 11:30:17 -04:00
Sasha Koshka 7bb6582e01 Added default nil argument kind 2022-08-17 01:07:12 -04:00
Sasha Koshka 0ad1c0b2f4 Fixed extraneous newlines after complex initialization values 2022-08-17 01:04:52 -04:00
Sasha Koshka 9ca1be2204 Fixed array initialization value parsing 2022-08-17 00:55:53 -04:00
Sasha Koshka 0dd9368393 Reworked data section so it stores single initialization argument 2022-08-17 00:49:49 -04:00
Sasha Koshka bd42c95de0 Parser can now sort of parse object member initialization values 2022-08-17 00:14:55 -04:00
Sasha Koshka bd456b72e9 Argument.ToString can now recover on nil interface value 2022-08-17 00:13:14 -04:00
Sasha Koshka eb3fb65c9b Changed object initialization values to be a map 2022-08-16 23:45:25 -04:00
Sasha Koshka bb2948d397 Added parsing array initialization values 2022-08-16 21:31:23 -04:00
Sasha Koshka 210e527b3a parseType method now understands arrays with undefined length 2022-08-16 20:55:43 -04:00
Sasha Koshka 97cb6e54eb Type.ToString now understands array lengths 2022-08-16 20:53:27 -04:00
Sasha Koshka 3407aa7c59 Fixed lexing digraph tokens
Lexer gave wrong token locations and would skip an extra rune when digraph was
not found.
2022-08-16 20:29:00 -04:00
Sasha Koshka 9e01eef45b Added elipsis token 2022-08-16 20:24:27 -04:00
Sasha Koshka efb3bbe21b Added base for parsing initialization values 2022-08-16 20:10:47 -04:00
Sasha Koshka c172c111d8 Rethought how object and array literals will work 2022-08-16 17:45:31 -04:00
Sasha Koshka 16bca57e36 Fixed numerous problems related to type parsing 2022-08-16 17:21:10 -04:00
Sasha Koshka 5e2d8c9955 Parser can now ToString data sections properly 2022-08-16 16:37:20 -04:00
Sasha Koshka 5c23c59c92 Added parsing primitive arguments 2022-08-16 16:27:52 -04:00
Sasha Koshka 0025d03a18 Modified parser data test to have uninitialized pointer cases 2022-08-16 16:16:39 -04:00
Sasha Koshka 45bc798d19 Added identifier and declaration parsing 2022-08-16 15:53:32 -04:00
Sasha Koshka 9cb2f68581 Argument kind is now called .kind instead of .what 2022-08-16 15:50:34 -04:00
Sasha Koshka 4dfb327558 Add subscript and dereference argument kinds 2022-08-16 13:43:36 -04:00
Sasha Koshka f978621673 Parse basic information about data sections (name, type) 2022-08-16 10:44:02 -04:00
Sasha Koshka ac40fa96e5 Improved parser test
Created new cases involving mutable data and phrase initialization arguments,
and always print out the correct and parsed trees.
2022-08-16 01:55:22 -04:00
Sasha Koshka 85b7938843 ParseBody now has a loop, and errors on unrecognized section type 2022-08-15 21:20:13 -04:00
Sasha Koshka d081f363b1 Add guideline I forgot to add earlier 2022-08-15 17:21:34 -04:00
Sasha Koshka e42bad5810 Identifiers can no longer have arguments in them
Previously [[something something].something] would have been syntactically
correct. This can lead to ugly and cluttered syntax due to violating the one
thing per line guideline (that I've forgotten to write down) and would make the
parser incredibly convoluded. Member selection in arf is not an operator and
should not be treated as such. It would be much better to just use variables for
this.
2022-08-15 17:05:57 -04:00
Sasha Koshka 614b5664fc Parser calls ParseDataSection 2022-08-15 15:09:07 -04:00
Sasha Koshka 8b28fe5a4c Added a comma token 2022-08-15 14:50:09 -04:00
Sasha Koshka 608162fa92 Fixed bug in number lexing function that prevented reading zero 2022-08-15 14:42:32 -04:00
Sasha Koshka d27c0ff07c Added single digit zero and eight to lexer number test 2022-08-15 14:32:59 -04:00
Sasha Koshka 7fc51c278f Fixed issue with Error.Error not positioning marker correctly 2022-08-15 14:30:54 -04:00
Sasha Koshka 3a3c588023 Added data test 2022-08-15 14:23:53 -04:00
Sasha Koshka 00bcfaab0b Parser tests now work by checking the ToString() of the parsed tree 2022-08-15 14:17:29 -04:00
Sasha Koshka d91423863b Added ToString methods for syntax tree nodes 2022-08-15 14:04:57 -04:00
Sasha Koshka b02ff6cda6 Created structs needed to represent a data section 2022-08-15 01:47:42 -04:00
48 changed files with 2820 additions and 360 deletions
+13 -3
View File
@@ -1,4 +1,4 @@
# ARF # ![ARF](assets/logo.svg)
The ARF programming language. The ARF programming language.
@@ -16,7 +16,7 @@ A directory of ARF files is called a module, and modules will compile to object
files (one per module) using C as an intermediate language (maybe LLVM IR in the files (one per module) using C as an intermediate language (maybe LLVM IR in the
future). future).
## Design aspects ## Design Aspects
These are some design goals that I have followed/am following: These are some design goals that I have followed/am following:
@@ -29,8 +29,10 @@ These are some design goals that I have followed/am following:
- Language syntax must have zero ambiguity - Language syntax must have zero ambiguity
- The compiler should not generate new functions or complex logic that the user - The compiler should not generate new functions or complex logic that the user
has not written has not written
- One line at a time - the language's syntax should encourage writing code that
flows vertically and not horizontally, with minimal nesting
## Planned features ## Planned Features
- Type definition through inheritence - Type definition through inheritence
- Struct member functions - Struct member functions
@@ -47,3 +49,11 @@ These are some design goals that I have followed/am following:
- [ ] Semantic tree -> C -> object file - [ ] Semantic tree -> C -> object file
- [ ] Figure out HOW to implement generics - [ ] Figure out HOW to implement generics
- [ ] Create a standard library - [ ] Create a standard library
## Compiler Progress
<img src="assets/heatmap.png" alt="Progress heatmap" width="400">
- Yellow: needs to be completed for the MVP
- Lime: ongoing progress in this area
- Green: Already completed
Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

+8
View File
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 288 80" width="288" height="80">
<path d="M48 0L112 0L112 32L96 32L96 16L56 16L40 32L16 32L48 0Z" fill="#b81414" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M96 64L136 64L136 80L104 80L96 72L96 64Z" fill="#b81414" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M120 0L120 32L136 32L136 16L184 16L184 32L176 40L8 40L0 48L0 56L184 56L200 40L200 32L200 0L120 0Z" fill="#b81414" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M191 61L204 48L236 80L210 80L191 61Z" fill="#b81414" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M256 40L208 40L224 56L256 56L256 40Z" fill="#b81414" fill-rule="evenodd" opacity="1" stroke="none"/>
<path d="M208 0L288 0L288 16L224 16L224 32L208 32L208 0Z" fill="#b81414" fill-rule="evenodd" opacity="1" stroke="none"/>
</svg>

After

Width:  |  Height:  |  Size: 847 B

+7 -1
View File
@@ -1,3 +1,9 @@
:arf :arf
require "io"
---
func rr main func ro main
> arguments:{String}
< status:Int 0
---
io.println "hello world"
+19 -25
View File
@@ -5,37 +5,31 @@ require "io"
--- ---
# this is a global variable # this is a global variable
data wn helloText:String "Hello, world!" data pv helloText:String "Hello, world!"
# this is a struct definition # this is a struct definition
type rr Greeter:Obj objt ro Greeter:Obj
# "Hi." is a string constant. all Greeters will be initialized with a rw text:String "Hi."
# pointer to it. I don't know really it depends on what I decide that
# a String type even is.
wr text:String "Hi."
"sdfdsf" "ahh"
"asdf"
# this is a function # this is a function
func rr main func ro main
> argc:Int > arguments:{String}
> argv:{String} < status:Int 0
< status:Int 0 ---
--- set greeter:Greeter:mut
let greeter:Greeter:mut greeter.setText helloText
greeter.setText helloText greeter.greet
greeter.greet
# this is a member function # this is a member function
func rr greet func ro greet
@ greeter:{Greeter} @ greeter:{Greeter}
--- ---
io.println greeter.text io.println greeter.text
# this is mutator member function # this is mutator member function
func rr setText func ro setText
@ greeter:{Greeter} @ greeter:{Greeter}
> text:String > text:String
--- ---
greeter.text.set text greeter.text.set text
-11
View File
@@ -1,11 +0,0 @@
:arf
require io
---
func rr main
> argc:Int
> argv:{String}
< status:Int
---
io.println [io.readln]
= status 0
-13
View File
@@ -1,13 +0,0 @@
:arf
---
data:{Int 6}
-39480 398 29 0x3AFe3 0b10001010110 0o666
func rr literals
---
= stringLiteral:String "skadjlsakdj"
= intArrayLiteral:{Int 3} 2398
-2938 324
= runeLiteral:Rune 'a'
= floatArrayLiteral:{F64 5} 3248.23 0.324 -94.29
+21
View File
@@ -0,0 +1,21 @@
package parser
import "testing"
func TestFace (test *testing.T) {
checkTree ("../tests/parser/face",
`:arf
---
face ro Destroyer:Face
destroy
face ro ReadWriter:Face
read
> into:{Byte ..}
< read:Int
< err:Error
write
> data:{Byte ..}
< wrote:Int
< err:Error
`, test)
}
+20 -2
View File
@@ -8,6 +8,8 @@ type File struct {
path string path string
file *os.File file *os.File
reader *bufio.Reader reader *bufio.Reader
realLine int
realColumn int
currentLine int currentLine int
currentColumn int currentColumn int
lines []string lines []string
@@ -42,6 +44,9 @@ func (file *File) Read (bytes []byte) (amountRead int, err error) {
// store the character in the file // store the character in the file
for _, char := range bytes { for _, char := range bytes {
file.realLine = file.currentLine
file.realColumn = file.currentColumn
if char == '\n' { if char == '\n' {
file.lines = append(file.lines, "") file.lines = append(file.lines, "")
file.currentLine ++ file.currentLine ++
@@ -61,6 +66,9 @@ func (file *File) Read (bytes []byte) (amountRead int, err error) {
func (file *File) ReadRune () (char rune, size int, err error) { func (file *File) ReadRune () (char rune, size int, err error) {
char, size, err = file.reader.ReadRune() char, size, err = file.reader.ReadRune()
file.realLine = file.currentLine
file.realColumn = file.currentColumn
if char == '\n' { if char == '\n' {
file.lines = append(file.lines, "") file.lines = append(file.lines, "")
file.currentLine ++ file.currentLine ++
@@ -106,8 +114,18 @@ func (file *File) Close () {
func (file *File) Location (width int) (location Location) { func (file *File) Location (width int) (location Location) {
return Location { return Location {
file: file, file: file,
row: file.currentLine, row: file.realLine,
column: file.currentColumn, column: file.realColumn,
width: width, width: width,
} }
} }
// Path returns the path that teh file is located at.
func (file *File) Path () (path string) {
return file.path
}
// GetLine returns the line at the specified index.
func (file *File) GetLine (index int) (line string) {
return file.lines[index]
}
+37 -3
View File
@@ -1,5 +1,7 @@
package file package file
import "fmt"
// Location represents a specific point in a file. It is used for error // Location represents a specific point in a file. It is used for error
// reporting. // reporting.
type Location struct { type Location struct {
@@ -9,7 +11,39 @@ type Location struct {
width int width int
} }
// NewError creates a new error at this location. // File returns the file the location is in
func (location Location) NewError (message string, kind ErrorKind) (err Error) { func (location Location) File () (file *File) {
return NewError(location, message, kind) return location.file
}
// Row returns the row the location is positioned at in the file, starting at
// zero.
func (location Location) Row () (row int) {
return location.row
}
// Column returns the column the location is positioned at in the file, starting
// at zero.
func (location Location) Column () (column int) {
return location.column
}
// Width returns the amount of runes spanned by the location, starting at row
// and column.
func (location Location) Width () (width int) {
return location.width
}
// SetWidth sets the location's width
func (location *Location) SetWidth (width int) {
location.width = width
}
// Describe generates a description of the location for debug purposes
func (location Location) Describe () (description string) {
return fmt.Sprint (
"in ", location.file.Path(),
" row ", location.row,
" column ", location.column,
" width ", location.width)
} }
+39 -19
View File
@@ -1,7 +1,8 @@
package file package infoerr
import "os" import "os"
import "fmt" import "fmt"
import "git.tebibyte.media/sashakoshka/arf/file"
type ErrorKind int type ErrorKind int
@@ -11,14 +12,14 @@ const (
) )
type Error struct { type Error struct {
Location file.Location
message string message string
kind ErrorKind kind ErrorKind
} }
// NewError creates a new error at the specified location. // NewError creates a new error at the specified location.
func NewError ( func NewError (
location Location, location file.Location,
message string, message string,
kind ErrorKind, kind ErrorKind,
) ( ) (
@@ -41,31 +42,40 @@ func (err Error) Error () (formattedMessage string) {
} }
// print information about the location of the mistake // print information about the location of the mistake
if err.width > 0 { if err.Width() > 0 {
formattedMessage += fmt.Sprint ( formattedMessage += fmt.Sprint (
" \033[34m", err.Location.row + 1, " \033[34m", err.Row() + 1,
":", err.Location.column + 1) ":", err.Column() + 1)
} }
formattedMessage += formattedMessage +=
" \033[90min\033[0m " + " \033[90min\033[0m " +
err.Location.file.path + "\n" err.File().Path() + "\n"
if err.width > 0 { if err.Width() > 0 {
// print erroneous line // print erroneous line
formattedMessage += line := err.File().GetLine(err.Row())
err.Location.file.lines[err.Location.row] + "\n" formattedMessage += line + "\n"
// position error marker
var index int
for index = 0; index < err.Column(); index ++ {
if line[index] == '\t' {
formattedMessage += "\t"
} else {
formattedMessage += " "
}
}
// print an arrow with a tail spanning the width of the mistake // print an arrow with a tail spanning the width of the mistake
columnCountdown := err.Location.column for index < err.Column() + err.Width() - 1 {
for columnCountdown > 1 { if line[index] == '\t' {
// TODO: for tabs, print out a teb instead. formattedMessage += "--------"
formattedMessage += " " } else {
columnCountdown -- formattedMessage += "-"
} }
for err.width > 1 { index ++
// TODO: for tabs, print out 8 of these instead.
formattedMessage += "-"
} }
formattedMessage += "^\n" formattedMessage += "^\n"
} }
formattedMessage += err.message + "\n" formattedMessage += err.message + "\n"
@@ -77,3 +87,13 @@ func (err Error) Error () (formattedMessage string) {
func (err Error) Print () { func (err Error) Print () {
os.Stderr.Write([]byte(err.Error())) os.Stderr.Write([]byte(err.Error()))
} }
// Message returns the error's message string
func (err Error) Message () (message string) {
return err.message
}
// Kind returns what kind of error the error is.
func (err Error) Kind () (kind ErrorKind) {
return err.kind
}
+96 -26
View File
@@ -3,6 +3,7 @@ package lexer
import "io" import "io"
import "git.tebibyte.media/sashakoshka/arf/file" import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/types" import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
// LexingOperation holds information about an ongoing lexing operataion. // LexingOperation holds information about an ongoing lexing operataion.
type LexingOperation struct { type LexingOperation struct {
@@ -34,10 +35,10 @@ func (lexer *LexingOperation) tokenize () (err error) {
err = lexer.nextRune() err = lexer.nextRune()
if err != nil || shebangCheck[index] != lexer.char { if err != nil || shebangCheck[index] != lexer.char {
err = file.NewError ( err = infoerr.NewError (
lexer.file.Location(1), lexer.file.Location(1),
"not an arf file", "not an arf file",
file.ErrorKindError) infoerr.ErrorKindError)
return return
} }
} }
@@ -92,14 +93,14 @@ func (lexer *LexingOperation) tokenizeAlphaBeginning () (err error) {
} }
token.value = got token.value = got
token.location.SetWidth(len(got))
if len(got) == 2 { if len(got) == 2 {
firstValid := got[0] == 'n' || got[0] == 'r' || got[0] == 'w' permission, isPermission := types.PermissionFrom(got)
secondValid := got[1] == 'n' || got[1] == 'r' || got[1] == 'w'
if firstValid && secondValid { if isPermission {
token.kind = TokenKindPermission token.kind = TokenKindPermission
token.value = types.PermissionFrom(got) token.value = permission
} }
} }
@@ -123,10 +124,10 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
if !previousToken.Is(TokenKindNewline) { if !previousToken.Is(TokenKindNewline) {
err = lexer.nextRune() err = lexer.nextRune()
file.NewError ( infoerr.NewError (
lexer.file.Location(1), lexer.file.Location(1),
"tab not used as indent", "tab not used as indent",
file.ErrorKindWarn).Print() infoerr.ErrorKindWarn).Print()
return return
} }
@@ -142,6 +143,7 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
} }
token.value = indentLevel token.value = indentLevel
token.location.SetWidth(indentLevel)
lexer.addToken(token) lexer.addToken(token)
case '\n': case '\n':
// line break // line break
@@ -176,7 +178,18 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
err = lexer.nextRune() err = lexer.nextRune()
case '.': case '.':
token := lexer.newToken() token := lexer.newToken()
err = lexer.nextRune()
if err != nil { return }
token.kind = TokenKindDot token.kind = TokenKindDot
if lexer.char == '.' {
token.kind = TokenKindElipsis
err = lexer.nextRune()
token.location.SetWidth(2)
}
lexer.addToken(token)
case ',':
token := lexer.newToken()
token.kind = TokenKindComma
lexer.addToken(token) lexer.addToken(token)
err = lexer.nextRune() err = lexer.nextRune()
case '[': case '[':
@@ -200,15 +213,16 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
lexer.addToken(token) lexer.addToken(token)
err = lexer.nextRune() err = lexer.nextRune()
case '+': case '+':
token := lexer.newToken()
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return } if err != nil { return }
token := lexer.newToken()
token.kind = TokenKindPlus token.kind = TokenKindPlus
if lexer.char == '+' { if lexer.char == '+' {
token.kind = TokenKindIncrement token.kind = TokenKindIncrement
err = lexer.nextRune()
token.location.SetWidth(2)
} }
lexer.addToken(token) lexer.addToken(token)
err = lexer.nextRune()
case '-': case '-':
err = lexer.tokenizeDashBeginning() err = lexer.tokenizeDashBeginning()
case '*': case '*':
@@ -228,65 +242,118 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
err = lexer.nextRune() err = lexer.nextRune()
case '!': case '!':
token := lexer.newToken() token := lexer.newToken()
token.kind = TokenKindExclamation
lexer.addToken(token)
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return }
token.kind = TokenKindExclamation
if lexer.char == '=' {
token.kind = TokenKindNotEqualTo
err = lexer.nextRune()
token.location.SetWidth(2)
}
lexer.addToken(token)
case '%': case '%':
token := lexer.newToken() token := lexer.newToken()
token.kind = TokenKindPercent
lexer.addToken(token)
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return }
token.kind = TokenKindPercent
if lexer.char == '=' {
token.kind = TokenKindPercentAssignment
err = lexer.nextRune()
token.location.SetWidth(2)
}
lexer.addToken(token)
case '~': case '~':
token := lexer.newToken() token := lexer.newToken()
err = lexer.nextRune()
if err != nil { return }
token.kind = TokenKindTilde token.kind = TokenKindTilde
if lexer.char == '=' {
token.kind = TokenKindTildeAssignment
err = lexer.nextRune()
token.location.SetWidth(2)
}
lexer.addToken(token)
case '=':
token := lexer.newToken()
token.kind = TokenKindEqualTo
lexer.addToken(token) lexer.addToken(token)
err = lexer.nextRune() err = lexer.nextRune()
case '<': case '<':
token := lexer.newToken()
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return } if err != nil { return }
token := lexer.newToken()
token.kind = TokenKindLessThan token.kind = TokenKindLessThan
if lexer.char == '<' { if lexer.char == '<' {
token.kind = TokenKindLShift token.kind = TokenKindLShift
err = lexer.nextRune()
token.location.SetWidth(2)
if lexer.char == '=' {
token.kind = TokenKindLShiftAssignment
err = lexer.nextRune()
token.location.SetWidth(2)
}
} else if lexer.char == '=' {
token.kind = TokenKindLessThanEqualTo
err = lexer.nextRune()
token.location.SetWidth(2)
} }
lexer.addToken(token) lexer.addToken(token)
err = lexer.nextRune()
case '>': case '>':
token := lexer.newToken()
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return } if err != nil { return }
token := lexer.newToken()
token.kind = TokenKindGreaterThan token.kind = TokenKindGreaterThan
if lexer.char == '>' { if lexer.char == '>' {
token.kind = TokenKindRShift token.kind = TokenKindRShift
err = lexer.nextRune()
token.location.SetWidth(2)
if lexer.char == '=' {
token.kind = TokenKindRShiftAssignment
err = lexer.nextRune()
token.location.SetWidth(2)
}
} else if lexer.char == '=' {
token.kind = TokenKindGreaterThanEqualTo
err = lexer.nextRune()
token.location.SetWidth(2)
} }
lexer.addToken(token) lexer.addToken(token)
err = lexer.nextRune()
case '|': case '|':
token := lexer.newToken()
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return } if err != nil { return }
token := lexer.newToken()
token.kind = TokenKindBinaryOr token.kind = TokenKindBinaryOr
if lexer.char == '|' { if lexer.char == '|' {
token.kind = TokenKindLogicalOr token.kind = TokenKindLogicalOr
err = lexer.nextRune()
token.location.SetWidth(2)
} else if lexer.char == '=' {
token.kind = TokenKindBinaryOrAssignment
err = lexer.nextRune()
token.location.SetWidth(2)
} }
lexer.addToken(token) lexer.addToken(token)
err = lexer.nextRune()
case '&': case '&':
token := lexer.newToken()
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return } if err != nil { return }
token := lexer.newToken()
token.kind = TokenKindBinaryAnd token.kind = TokenKindBinaryAnd
if lexer.char == '&' { if lexer.char == '&' {
token.kind = TokenKindLogicalAnd token.kind = TokenKindLogicalAnd
err = lexer.nextRune()
token.location.SetWidth(2)
} else if lexer.char == '=' {
token.kind = TokenKindBinaryAndAssignment
err = lexer.nextRune()
token.location.SetWidth(2)
} }
lexer.addToken(token) lexer.addToken(token)
err = lexer.nextRune()
default: default:
err = file.NewError ( err = infoerr.NewError (
lexer.file.Location(1), lexer.file.Location(1),
"unexpected symbol character " + "unexpected symbol character " +
string(lexer.char), string(lexer.char),
file.ErrorKindError) infoerr.ErrorKindError)
return return
} }
@@ -300,6 +367,7 @@ func (lexer *LexingOperation) tokenizeDashBeginning () (err error) {
if lexer.char == '-' { if lexer.char == '-' {
token := lexer.newToken() token := lexer.newToken()
token.kind = TokenKindDecrement token.kind = TokenKindDecrement
token.location.SetWidth(2)
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return } if err != nil { return }
@@ -307,11 +375,13 @@ func (lexer *LexingOperation) tokenizeDashBeginning () (err error) {
if lexer.char == '-' { if lexer.char == '-' {
token.kind = TokenKindSeparator token.kind = TokenKindSeparator
lexer.nextRune() lexer.nextRune()
token.location.SetWidth(3)
} }
lexer.addToken(token) lexer.addToken(token)
} else if lexer.char == '>' { } else if lexer.char == '>' {
token := lexer.newToken() token := lexer.newToken()
token.kind = TokenKindReturnDirection token.kind = TokenKindReturnDirection
token.location.SetWidth(2)
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return } if err != nil { return }
@@ -352,9 +422,9 @@ func (lexer *LexingOperation) skipSpaces () (err error) {
func (lexer *LexingOperation) nextRune () (err error) { func (lexer *LexingOperation) nextRune () (err error) {
lexer.char, _, err = lexer.file.ReadRune() lexer.char, _, err = lexer.file.ReadRune()
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return file.NewError ( return infoerr.NewError (
lexer.file.Location(1), lexer.file.Location(1),
err.Error(), file.ErrorKindError) err.Error(), infoerr.ErrorKindError)
} }
return return
} }
+212 -97
View File
@@ -3,8 +3,17 @@ package lexer
import "testing" import "testing"
import "git.tebibyte.media/sashakoshka/arf/file" import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/types" import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
func checkTokenSlice (filePath string, correct []Token, test *testing.T) { func quickToken (width int, kind TokenKind, value any) (token Token) {
token.location.SetWidth(width)
token.kind = kind
token.value = value
return
}
func checkTokenSlice (filePath string, test *testing.T, correct ...Token) {
test.Log("checking lexer results for", filePath)
file, err := file.Open(filePath) file, err := file.Open(filePath)
if err != nil { if err != nil {
test.Log(err) test.Log(err)
@@ -34,6 +43,15 @@ func checkTokenSlice (filePath string, correct []Token, test *testing.T) {
test.Log("token slice length match", len(tokens), "=", len(correct)) test.Log("token slice length match", len(tokens), "=", len(correct))
for index, token := range tokens { for index, token := range tokens {
if token.location.Width() != correct[index].location.Width() {
test.Log("token", index, "has bad width")
test.Log (
"have", token.location.Width(),
"want", correct[index].location.Width())
test.Fail()
return
}
if !token.Equals(correct[index]) { if !token.Equals(correct[index]) {
test.Log("token", index, "not equal") test.Log("token", index, "not equal")
test.Log ( test.Log (
@@ -46,112 +64,209 @@ func checkTokenSlice (filePath string, correct []Token, test *testing.T) {
test.Log("token slice content match") test.Log("token slice content match")
} }
func compareErr (
filePath string,
correctKind infoerr.ErrorKind,
correctMessage string,
correctRow int,
correctColumn int,
correctWidth int,
test *testing.T,
) {
test.Log("testing errors in", filePath)
file, err := file.Open(filePath)
if err != nil {
test.Log(err)
test.Fail()
return
}
_, err = Tokenize(file)
check := err.(infoerr.Error)
test.Log("error that was recieved:")
test.Log(check)
if check.Kind() != correctKind {
test.Log("mismatched error kind")
test.Log("- want:", correctKind)
test.Log("- have:", check.Kind())
test.Fail()
}
if check.Message() != correctMessage {
test.Log("mismatched error message")
test.Log("- want:", correctMessage)
test.Log("- have:", check.Message())
test.Fail()
}
if check.Row() != correctRow {
test.Log("mismatched error row")
test.Log("- want:", correctRow)
test.Log("- have:", check.Row())
test.Fail()
}
if check.Column() != correctColumn {
test.Log("mismatched error column")
test.Log("- want:", correctColumn)
test.Log("- have:", check.Column())
test.Fail()
}
if check.Width() != correctWidth {
test.Log("mismatched error width")
test.Log("- want:", check.Width())
test.Log("- have:", correctWidth)
test.Fail()
}
}
func TestTokenizeAll (test *testing.T) { func TestTokenizeAll (test *testing.T) {
checkTokenSlice("../tests/lexer/all.arf", []Token { checkTokenSlice("../tests/lexer/all.arf", test,
Token { kind: TokenKindSeparator }, quickToken(3, TokenKindSeparator, nil),
Token { kind: TokenKindPermission, value: types.Permission { quickToken(2, TokenKindPermission, types.PermissionReadWrite),
Internal: types.ModeRead, quickToken(2, TokenKindReturnDirection, nil),
External: types.ModeWrite, quickToken(10, TokenKindInt, int64(-349820394)),
}}, quickToken(9, TokenKindUInt, uint64(932748397)),
Token { kind: TokenKindReturnDirection }, quickToken(12, TokenKindFloat, 239485.37520),
Token { kind: TokenKindInt, value: int64(-349820394) }, quickToken(16, TokenKindString, "hello world!\n"),
Token { kind: TokenKindUInt, value: uint64(932748397) }, quickToken(3, TokenKindRune, 'E'),
Token { kind: TokenKindFloat, value: 239485.37520 }, quickToken(10, TokenKindName, "helloWorld"),
Token { kind: TokenKindString, value: "hello world!\n" }, quickToken(1, TokenKindColon, nil),
Token { kind: TokenKindRune, value: 'E' }, quickToken(1, TokenKindDot, nil),
Token { kind: TokenKindName, value: "helloWorld" }, quickToken(1, TokenKindComma, nil),
Token { kind: TokenKindColon }, quickToken(2, TokenKindElipsis, nil),
Token { kind: TokenKindDot }, quickToken(1, TokenKindLBracket, nil),
Token { kind: TokenKindLBracket }, quickToken(1, TokenKindRBracket, nil),
Token { kind: TokenKindRBracket }, quickToken(1, TokenKindLBrace, nil),
Token { kind: TokenKindLBrace }, quickToken(1, TokenKindRBrace, nil),
Token { kind: TokenKindRBrace }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindPlus, nil),
Token { kind: TokenKindPlus }, quickToken(1, TokenKindMinus, nil),
Token { kind: TokenKindMinus }, quickToken(2, TokenKindIncrement, nil),
Token { kind: TokenKindIncrement }, quickToken(2, TokenKindDecrement, nil),
Token { kind: TokenKindDecrement }, quickToken(1, TokenKindAsterisk, nil),
Token { kind: TokenKindAsterisk }, quickToken(1, TokenKindSlash, nil),
Token { kind: TokenKindSlash }, quickToken(1, TokenKindAt, nil),
Token { kind: TokenKindAt }, quickToken(1, TokenKindExclamation, nil),
Token { kind: TokenKindExclamation }, quickToken(1, TokenKindPercent, nil),
Token { kind: TokenKindPercent }, quickToken(2, TokenKindPercentAssignment, nil),
Token { kind: TokenKindTilde }, quickToken(1, TokenKindTilde, nil),
Token { kind: TokenKindLessThan }, quickToken(2, TokenKindTildeAssignment, nil),
Token { kind: TokenKindLShift }, quickToken(1, TokenKindEqualTo, nil),
Token { kind: TokenKindGreaterThan }, quickToken(2, TokenKindNotEqualTo, nil),
Token { kind: TokenKindRShift }, quickToken(1, TokenKindLessThan, nil),
Token { kind: TokenKindBinaryOr }, quickToken(2, TokenKindLessThanEqualTo, nil),
Token { kind: TokenKindLogicalOr }, quickToken(2, TokenKindLShift, nil),
Token { kind: TokenKindBinaryAnd }, quickToken(3, TokenKindLShiftAssignment, nil),
Token { kind: TokenKindLogicalAnd }, quickToken(1, TokenKindGreaterThan, nil),
Token { kind: TokenKindNewline }, quickToken(2, TokenKindGreaterThanEqualTo, nil),
}, test) quickToken(2, TokenKindRShift, nil),
quickToken(3, TokenKindRShiftAssignment, nil),
quickToken(1, TokenKindBinaryOr, nil),
quickToken(2, TokenKindBinaryOrAssignment, nil),
quickToken(2, TokenKindLogicalOr, nil),
quickToken(1, TokenKindBinaryAnd, nil),
quickToken(2, TokenKindBinaryAndAssignment, nil),
quickToken(2, TokenKindLogicalAnd, nil),
quickToken(1, TokenKindBinaryXor, nil),
quickToken(2, TokenKindBinaryXorAssignment, nil),
quickToken(1, TokenKindNewline, nil),
)
} }
func TestTokenizeNumbers (test *testing.T) { func TestTokenizeNumbers (test *testing.T) {
checkTokenSlice("../tests/lexer/numbers.arf", []Token { checkTokenSlice("../tests/lexer/numbers.arf", test,
Token { kind: TokenKindUInt, value: uint64(83628266) }, quickToken(1, TokenKindUInt, uint64(0)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindUInt, value: uint64(83628266) }, quickToken(1, TokenKindUInt, uint64(8)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindUInt, value: uint64(83628266) }, quickToken(8, TokenKindUInt, uint64(83628266)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindUInt, value: uint64(83628266) }, quickToken(29, TokenKindUInt, uint64(83628266)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
quickToken(9, TokenKindUInt, uint64(83628266)),
quickToken(1, TokenKindNewline, nil),
quickToken(10, TokenKindUInt, uint64(83628266)),
quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindInt, value: int64(-83628266) }, quickToken(9, TokenKindInt, int64(-83628266)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindInt, value: int64(-83628266) }, quickToken(30, TokenKindInt, int64(-83628266)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindInt, value: int64(-83628266) }, quickToken(10, TokenKindInt, int64(-83628266)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindInt, value: int64(-83628266) }, quickToken(11, TokenKindInt, int64(-83628266)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindFloat, value: float64(0.123478) }, quickToken(8, TokenKindFloat, float64(0.123478)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindFloat, value: float64(234.3095) }, quickToken(8, TokenKindFloat, float64(234.3095)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindFloat, value: float64(-2.312) }, quickToken(6, TokenKindFloat, float64(-2.312)),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
}, test) )
} }
func TestTokenizeText (test *testing.T) { func TestTokenizeText (test *testing.T) {
checkTokenSlice("../tests/lexer/text.arf", []Token { checkTokenSlice("../tests/lexer/text.arf", test,
Token { kind: TokenKindString, value: "hello world!\a\b\f\n\r\t\v'\"\\" }, quickToken(34, TokenKindString, "hello world!\a\b\f\n\r\t\v'\"\\"),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindRune, value: '\a' }, quickToken(4, TokenKindRune, '\a'),
Token { kind: TokenKindRune, value: '\b' }, quickToken(4, TokenKindRune, '\b'),
Token { kind: TokenKindRune, value: '\f' }, quickToken(4, TokenKindRune, '\f'),
Token { kind: TokenKindRune, value: '\n' }, quickToken(4, TokenKindRune, '\n'),
Token { kind: TokenKindRune, value: '\r' }, quickToken(4, TokenKindRune, '\r'),
Token { kind: TokenKindRune, value: '\t' }, quickToken(4, TokenKindRune, '\t'),
Token { kind: TokenKindRune, value: '\v' }, quickToken(4, TokenKindRune, '\v'),
Token { kind: TokenKindRune, value: '\'' }, quickToken(4, TokenKindRune, '\''),
Token { kind: TokenKindRune, value: '"' }, quickToken(4, TokenKindRune, '"' ),
Token { kind: TokenKindRune, value: '\\' }, quickToken(4, TokenKindRune, '\\'),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindString, value: "hello world \x40\u0040\U00000040!" }, quickToken(35, TokenKindString, "hello world \x40\u0040\U00000040!"),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
}, test) )
} }
func TestTokenizeIndent (test *testing.T) { func TestTokenizeIndent (test *testing.T) {
checkTokenSlice("../tests/lexer/indent.arf", []Token { checkTokenSlice("../tests/lexer/indent.arf", test,
Token { kind: TokenKindName, value: "line1" }, quickToken(5, TokenKindName, "line1"),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindIndent, value: 1 }, quickToken(1, TokenKindIndent, 1),
Token { kind: TokenKindName, value: "line2" }, quickToken(5, TokenKindName, "line2"),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindIndent, value: 4 }, quickToken(4, TokenKindIndent, 4),
Token { kind: TokenKindName, value: "line3" }, quickToken(5, TokenKindName, "line3"),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindName, value: "line4" }, quickToken(5, TokenKindName, "line4"),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
Token { kind: TokenKindIndent, value: 2 }, quickToken(2, TokenKindIndent, 2),
Token { kind: TokenKindName, value: "line5" }, quickToken(5, TokenKindName, "line5"),
Token { kind: TokenKindNewline }, quickToken(1, TokenKindNewline, nil),
}, test) )
}
func TestTokenizeErr (test *testing.T) {
compareErr (
"../tests/lexer/error/unexpectedSymbol.arf",
infoerr.ErrorKindError,
"unexpected symbol character ;",
1, 5, 1,
test)
compareErr (
"../tests/lexer/error/excessDataRune.arf",
infoerr.ErrorKindError,
"excess data in rune literal",
1, 1, 7,
test)
compareErr (
"../tests/lexer/error/unknownEscape.arf",
infoerr.ErrorKindError,
"unknown escape character g",
1, 2, 1,
test)
} }
+74 -52
View File
@@ -1,43 +1,60 @@
package lexer package lexer
import "git.tebibyte.media/sashakoshka/arf/file" import "strconv"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
// tokenizeSymbolBeginning lexes a token that starts with a number. // tokenizeSymbolBeginning lexes a token that starts with a number.
func (lexer *LexingOperation) tokenizeNumberBeginning (negative bool) (err error) { func (lexer *LexingOperation) tokenizeNumberBeginning (negative bool) (err error) {
var number uint64 var intNumber uint64
var fragment float64 var floatNumber float64
var isFloat bool var isFloat bool
var amountRead int
var totalRead int
token := lexer.newToken() token := lexer.newToken()
if lexer.char == '0' { if lexer.char == '0' {
lexer.nextRune() lexer.nextRune()
totalRead ++
if lexer.char == 'x' { if lexer.char == 'x' {
lexer.nextRune() lexer.nextRune()
number, fragment, isFloat, err = lexer.tokenizeNumber(16) totalRead ++
intNumber, floatNumber,
isFloat, amountRead,
err = lexer.tokenizeNumber(16)
} else if lexer.char == 'b' { } else if lexer.char == 'b' {
lexer.nextRune() lexer.nextRune()
number, fragment, isFloat, err = lexer.tokenizeNumber(2) totalRead ++
intNumber, floatNumber,
isFloat, amountRead,
err = lexer.tokenizeNumber(2)
} else if lexer.char == '.' { } else if lexer.char == '.' {
number, fragment, isFloat, err = lexer.tokenizeNumber(10) intNumber, floatNumber,
isFloat, amountRead,
err = lexer.tokenizeNumber(10)
} else if lexer.char >= '0' && lexer.char <= '9' { } else if lexer.char >= '0' && lexer.char <= '9' {
number, fragment, isFloat, err = lexer.tokenizeNumber(8) intNumber, floatNumber,
} else { isFloat, amountRead,
return file.NewError ( err = lexer.tokenizeNumber(8)
lexer.file.Location(1),
"unexpected character in number literal",
file.ErrorKindError)
} }
} else { } else {
number, fragment, isFloat, err = lexer.tokenizeNumber(10) intNumber, floatNumber,
isFloat, amountRead,
err = lexer.tokenizeNumber(10)
}
totalRead += amountRead
if negative {
totalRead += 1
} }
if err != nil { return } if err != nil { return }
if isFloat { if isFloat {
floatNumber := float64(number) + fragment
token.kind = TokenKindFloat token.kind = TokenKindFloat
if negative { if negative {
token.value = floatNumber * -1 token.value = floatNumber * -1
@@ -47,35 +64,36 @@ func (lexer *LexingOperation) tokenizeNumberBeginning (negative bool) (err error
} else { } else {
if negative { if negative {
token.kind = TokenKindInt token.kind = TokenKindInt
token.value = int64(number) * -1 token.value = int64(intNumber) * -1
} else { } else {
token.kind = TokenKindUInt token.kind = TokenKindUInt
token.value = uint64(number) token.value = uint64(intNumber)
} }
} }
token.location.SetWidth(totalRead)
lexer.addToken(token) lexer.addToken(token)
return return
} }
// runeToDigit converts a rune from 0-F to a corresponding digit, with a maximum // runeIsDigit checks to see if the rune is a valid digit within the given
// radix. If the character is invalid, or the digit is too big, it will return // radix, up to 16. A '.' rune will also be treated as valid.
// false for worked. func runeIsDigit (char rune, radix uint64) (isDigit bool) {
func runeToDigit (char rune, radix uint64) (digit uint64, worked bool) { isDigit = true
worked = true
var digit uint64
if char >= '0' && char <= '9' { if char >= '0' && char <= '9' {
digit = uint64(char - '0') digit = uint64(char - '0')
} else if char >= 'A' && char <= 'F' { } else if char >= 'A' && char <= 'F' {
digit = uint64(char - 'A' + 10) digit = uint64(char - 'A' + 10)
} else if char >= 'a' && char <= 'f' { } else if char >= 'a' && char <= 'f' {
digit = uint64(char - 'a' + 10) digit = uint64(char - 'a' + 10)
} else { } else if char != '.' {
worked = false isDigit = false
} }
if digit >= radix { if digit >= radix {
worked = false isDigit = false
} }
return return
@@ -85,41 +103,45 @@ func runeToDigit (char rune, radix uint64) (digit uint64, worked bool) {
func (lexer *LexingOperation) tokenizeNumber ( func (lexer *LexingOperation) tokenizeNumber (
radix uint64, radix uint64,
) ( ) (
number uint64, intNumber uint64,
fragment float64, floatNumber float64,
isFloat bool, isFloat bool,
err error, amountRead int,
err error,
) { ) {
got := ""
for { for {
digit, worked := runeToDigit(lexer.char, radix) if !runeIsDigit(lexer.char, radix) { break }
if !worked { break } if lexer.char == '.' {
if radix != 10 {
number *= radix err = infoerr.NewError (
number += digit lexer.file.Location(1),
"floats must have radix of 10",
infoerr.ErrorKindError)
return
}
isFloat = true
}
got += string(lexer.char)
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return } if err != nil { return }
} }
// TODO: increase accuracy of this so that TestTokenizeNumbers is amountRead = len(got)
// passed.
if lexer.char == '.' {
isFloat = true
err = lexer.nextRune()
if err != nil { return }
coef := 1 / float64(radix) if isFloat {
for { floatNumber, err = strconv.ParseFloat(got, 64)
digit, worked := runeToDigit(lexer.char, radix) } else {
if !worked { break } intNumber, err = strconv.ParseUint(got, int(radix), 64)
}
fragment += float64(digit) * coef if err != nil {
err = infoerr.NewError (
coef /= float64(radix) lexer.file.Location(1),
"could not parse number: " + err.Error(),
err = lexer.nextRune() infoerr.ErrorKindError)
if err != nil { return } return
}
} }
return return
+31 -16
View File
@@ -1,7 +1,7 @@
package lexer package lexer
import "strconv" import "strconv"
import "git.tebibyte.media/sashakoshka/arf/file" import "git.tebibyte.media/sashakoshka/arf/infoerr"
// tokenizeString tokenizes a string or rune literal. // tokenizeString tokenizes a string or rune literal.
func (lexer *LexingOperation) tokenizeString (isRuneLiteral bool) (err error) { func (lexer *LexingOperation) tokenizeString (isRuneLiteral bool) (err error) {
@@ -9,17 +9,20 @@ func (lexer *LexingOperation) tokenizeString (isRuneLiteral bool) (err error) {
if err != nil { return } if err != nil { return }
token := lexer.newToken() token := lexer.newToken()
got := ""
tokenWidth := 2
got := "" beginning := lexer.file.Location(1)
for { for {
// TODO: add hexadecimal escape codes
if lexer.char == '\\' { if lexer.char == '\\' {
err = lexer.nextRune() err = lexer.nextRune()
tokenWidth ++
if err != nil { return } if err != nil { return }
var actual rune var actual rune
actual, err = lexer.getEscapeSequence() var amountRead int
actual, amountRead, err = lexer.getEscapeSequence()
tokenWidth += amountRead
if err != nil { return } if err != nil { return }
got += string(actual) got += string(actual)
@@ -27,6 +30,7 @@ func (lexer *LexingOperation) tokenizeString (isRuneLiteral bool) (err error) {
got += string(lexer.char) got += string(lexer.char)
err = lexer.nextRune() err = lexer.nextRune()
tokenWidth ++
if err != nil { return } if err != nil { return }
} }
@@ -40,12 +44,13 @@ func (lexer *LexingOperation) tokenizeString (isRuneLiteral bool) (err error) {
err = lexer.nextRune() err = lexer.nextRune()
if err != nil { return } if err != nil { return }
beginning.SetWidth(len(got))
if isRuneLiteral { if isRuneLiteral {
if len(got) > 1 { if len(got) > 1 {
err = file.NewError ( err = infoerr.NewError (
lexer.file.Location(1), beginning,
"excess data in rune literal", "excess data in rune literal",
file.ErrorKindError) infoerr.ErrorKindError)
return return
} }
@@ -56,6 +61,7 @@ func (lexer *LexingOperation) tokenizeString (isRuneLiteral bool) (err error) {
token.value = got token.value = got
} }
token.location.SetWidth(tokenWidth)
lexer.addToken(token) lexer.addToken(token)
return return
} }
@@ -76,16 +82,22 @@ var escapeSequenceMap = map[rune] rune {
} }
// getEscapeSequence reads an escape sequence in a string or rune literal. // getEscapeSequence reads an escape sequence in a string or rune literal.
func (lexer *LexingOperation) getEscapeSequence () (result rune, err error) { func (lexer *LexingOperation) getEscapeSequence () (
result rune,
amountRead int,
err error,
) {
result, exists := escapeSequenceMap[lexer.char] result, exists := escapeSequenceMap[lexer.char]
if exists { if exists {
err = lexer.nextRune() err = lexer.nextRune()
amountRead ++
return return
} else if lexer.char >= '0' && lexer.char <= '7' { } else if lexer.char >= '0' && lexer.char <= '7' {
// octal escape sequence // octal escape sequence
number := string(lexer.char) number := string(lexer.char)
err = lexer.nextRune() err = lexer.nextRune()
amountRead ++
if err != nil { return } if err != nil { return }
for len(number) < 3 { for len(number) < 3 {
@@ -94,14 +106,15 @@ func (lexer *LexingOperation) getEscapeSequence () (result rune, err error) {
number += string(lexer.char) number += string(lexer.char)
err = lexer.nextRune() err = lexer.nextRune()
amountRead ++
if err != nil { return } if err != nil { return }
} }
if len(number) < 3 { if len(number) < 3 {
err = file.NewError ( err = infoerr.NewError (
lexer.file.Location(1), lexer.file.Location(1),
"octal escape sequence too short", "octal escape sequence too short",
file.ErrorKindError) infoerr.ErrorKindError)
return return
} }
@@ -117,6 +130,7 @@ func (lexer *LexingOperation) getEscapeSequence () (result rune, err error) {
number := "" number := ""
err = lexer.nextRune() err = lexer.nextRune()
amountRead ++
if err != nil { return } if err != nil { return }
for len(number) < want { for len(number) < want {
@@ -128,24 +142,25 @@ func (lexer *LexingOperation) getEscapeSequence () (result rune, err error) {
number += string(lexer.char) number += string(lexer.char)
err = lexer.nextRune() err = lexer.nextRune()
amountRead ++
if err != nil { return } if err != nil { return }
} }
if len(number) < want { if len(number) < want {
err = file.NewError ( err = infoerr.NewError (
lexer.file.Location(1), lexer.file.Location(1),
"hex escape sequence too short ", "hex escape sequence too short ",
file.ErrorKindError) infoerr.ErrorKindError)
return return
} }
parsedNumber, _ := strconv.ParseInt(number, 16, want * 4) parsedNumber, _ := strconv.ParseInt(number, 16, want * 4)
result = rune(parsedNumber) result = rune(parsedNumber)
} else { } else {
err = file.NewError ( err = infoerr.NewError (
lexer.file.Location(1), lexer.file.Location(1),
"unknown escape character " + "unknown escape character " +
string(lexer.char), file.ErrorKindError) string(lexer.char), infoerr.ErrorKindError)
return return
} }
+50 -2
View File
@@ -2,6 +2,7 @@ package lexer
import "fmt" import "fmt"
import "git.tebibyte.media/sashakoshka/arf/file" import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
// TokenKind is an enum represzenting what role a token has. // TokenKind is an enum represzenting what role a token has.
type TokenKind int type TokenKind int
@@ -24,6 +25,8 @@ const (
TokenKindColon TokenKindColon
TokenKindDot TokenKindDot
TokenKindElipsis
TokenKindComma
TokenKindLBracket TokenKindLBracket
TokenKindRBracket TokenKindRBracket
@@ -40,16 +43,28 @@ const (
TokenKindAt TokenKindAt
TokenKindExclamation TokenKindExclamation
TokenKindPercent TokenKindPercent
TokenKindPercentAssignment
TokenKindTilde TokenKindTilde
TokenKindTildeAssignment
TokenKindEqualTo
TokenKindNotEqualTo
TokenKindLessThanEqualTo
TokenKindLessThan TokenKindLessThan
TokenKindLShift TokenKindLShift
TokenKindLShiftAssignment
TokenKindGreaterThan TokenKindGreaterThan
TokenKindGreaterThanEqualTo
TokenKindRShift TokenKindRShift
TokenKindRShiftAssignment
TokenKindBinaryOr TokenKindBinaryOr
TokenKindBinaryOrAssignment
TokenKindLogicalOr TokenKindLogicalOr
TokenKindBinaryAnd TokenKindBinaryAnd
TokenKindBinaryAndAssignment
TokenKindLogicalAnd TokenKindLogicalAnd
TokenKindBinaryXor
TokenKindBinaryXorAssignment
) )
// Token represents a single token. It holds its location in the file, as well // Token represents a single token. It holds its location in the file, as well
@@ -87,8 +102,13 @@ func (token Token) Location () (location file.Location) {
} }
// NewError creates a new error at this token's location. // NewError creates a new error at this token's location.
func (token Token) NewError (message string, kind file.ErrorKind) (err file.Error) { func (token Token) NewError (
return token.location.NewError(message, kind) message string,
kind infoerr.ErrorKind,
) (
err infoerr.Error,
) {
return infoerr.NewError(token.location, message, kind)
} }
// Describe generates a textual description of the token to be used in debug // Describe generates a textual description of the token to be used in debug
@@ -133,6 +153,10 @@ func (tokenKind TokenKind) Describe () (description string) {
description = "Colon" description = "Colon"
case TokenKindDot: case TokenKindDot:
description = "Dot" description = "Dot"
case TokenKindElipsis:
description = "Elipsis"
case TokenKindComma:
description = "Comma"
case TokenKindLBracket: case TokenKindLBracket:
description = "LBracket" description = "LBracket"
case TokenKindRBracket: case TokenKindRBracket:
@@ -159,24 +183,48 @@ func (tokenKind TokenKind) Describe () (description string) {
description = "Exclamation" description = "Exclamation"
case TokenKindPercent: case TokenKindPercent:
description = "Percent" description = "Percent"
case TokenKindPercentAssignment:
description = "PercentAssignment"
case TokenKindTilde: case TokenKindTilde:
description = "Tilde" description = "Tilde"
case TokenKindTildeAssignment:
description = "TildeAssignment"
case TokenKindEqualTo:
description = "EqualTo"
case TokenKindNotEqualTo:
description = "NotEqualTo"
case TokenKindLessThan: case TokenKindLessThan:
description = "LessThan" description = "LessThan"
case TokenKindLessThanEqualTo:
description = "LessThanEqualTo"
case TokenKindLShift: case TokenKindLShift:
description = "LShift" description = "LShift"
case TokenKindLShiftAssignment:
description = "LShiftAssignment"
case TokenKindGreaterThan: case TokenKindGreaterThan:
description = "GreaterThan" description = "GreaterThan"
case TokenKindGreaterThanEqualTo:
description = "GreaterThanEqualTo"
case TokenKindRShift: case TokenKindRShift:
description = "RShift" description = "RShift"
case TokenKindRShiftAssignment:
description = "RShiftAssignment"
case TokenKindBinaryOr: case TokenKindBinaryOr:
description = "BinaryOr" description = "BinaryOr"
case TokenKindBinaryOrAssignment:
description = "BinaryOrAssignment"
case TokenKindLogicalOr: case TokenKindLogicalOr:
description = "LogicalOr" description = "LogicalOr"
case TokenKindBinaryAnd: case TokenKindBinaryAnd:
description = "BinaryAnd" description = "BinaryAnd"
case TokenKindBinaryAndAssignment:
description = "BinaryAndAssignment"
case TokenKindLogicalAnd: case TokenKindLogicalAnd:
description = "LogicalAnd" description = "LogicalAnd"
case TokenKindBinaryXor:
description = "BinaryXor"
case TokenKindBinaryXorAssignment:
description = "BinaryXorAssignment"
} }
return return
+91
View File
@@ -0,0 +1,91 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
var validArgumentStartTokens = []lexer.TokenKind {
lexer.TokenKindName,
lexer.TokenKindInt,
lexer.TokenKindUInt,
lexer.TokenKindFloat,
lexer.TokenKindString,
lexer.TokenKindRune,
lexer.TokenKindLBrace,
lexer.TokenKindLBracket,
}
func (parser *ParsingOperation) parseArgument () (argument Argument, err error) {
argument.location = parser.token.Location()
err = parser.expect(validArgumentStartTokens...)
if err != nil { return }
switch parser.token.Kind() {
case lexer.TokenKindName:
var identifier Identifier
identifier, err = parser.parseIdentifier()
if err != nil { return }
if parser.token.Is(lexer.TokenKindColon) {
var what Type
what, err = parser.parseType()
if err != nil { return }
if len(identifier.trail) != 1 {
err = parser.token.NewError (
"cannot use member selection in " +
"a variable definition",
infoerr.ErrorKindError)
return
}
argument.kind = ArgumentKindDeclaration
argument.value = Declaration {
location: argument.location,
name: identifier.trail[0],
what: what,
}
} else {
argument.kind = ArgumentKindIdentifier
argument.value = identifier
}
case lexer.TokenKindInt:
argument.kind = ArgumentKindInt
argument.value = parser.token.Value().(int64)
err = parser.nextToken()
case lexer.TokenKindUInt:
argument.kind = ArgumentKindUInt
argument.value = parser.token.Value().(uint64)
err = parser.nextToken()
case lexer.TokenKindFloat:
argument.kind = ArgumentKindFloat
argument.value = parser.token.Value().(float64)
err = parser.nextToken()
case lexer.TokenKindString:
argument.kind = ArgumentKindString
argument.value = parser.token.Value().(string)
parser.nextToken()
case lexer.TokenKindRune:
argument.kind = ArgumentKindRune
argument.value = parser.token.Value().(rune)
parser.nextToken()
// case lexer.TokenKindLBrace:
// case lexer.TokenKindLBracket:
default:
panic (
"unimplemented argument kind " +
parser.token.Kind().Describe())
}
return
}
+66 -9
View File
@@ -1,18 +1,75 @@
package parser package parser
import "git.tebibyte.media/sashakoshka/arf/lexer" import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
// parse body parses the body of an arf file, after the metadata header. // parse body parses the body of an arf file, after the metadata header.
func (parser *ParsingOperation) parseBody () (err error) { func (parser *ParsingOperation) parseBody () (err error) {
err = parser.nextToken(lexer.TokenKindName) for {
if err != nil { return } err = parser.expect(lexer.TokenKindName)
if err != nil { return }
switch parser.token.Value().(string) { sectionType := parser.token.Value().(string)
case "data": switch sectionType {
case "type": case "data":
case "func": var section *DataSection
case "face": section, err = parser.parseDataSection()
if parser.tree.dataSections == nil {
parser.tree.dataSections =
make(map[string] *DataSection)
}
parser.tree.dataSections[section.name] = section
if err != nil { return }
case "type":
var section *TypeSection
section, err = parser.parseTypeSection()
if parser.tree.typeSections == nil {
parser.tree.typeSections =
make(map[string] *TypeSection)
}
parser.tree.typeSections[section.name] = section
if err != nil { return }
case "objt":
var section *ObjtSection
section, err = parser.parseObjtSection()
if parser.tree.objtSections == nil {
parser.tree.objtSections =
make(map[string] *ObjtSection)
}
parser.tree.objtSections[section.name] = section
if err != nil { return }
case "face":
var section *FaceSection
section, err = parser.parseFaceSection()
if parser.tree.faceSections == nil {
parser.tree.faceSections =
make(map[string] *FaceSection)
}
parser.tree.faceSections[section.name] = section
if err != nil { return }
case "enum":
var section *EnumSection
section, err = parser.parseEnumSection()
if parser.tree.enumSections == nil {
parser.tree.enumSections =
make(map[string] *EnumSection)
}
parser.tree.enumSections[section.name] = section
if err != nil { return }
case "func":
var section *FuncSection
section, err = parser.parseFuncSection()
if parser.tree.funcSections == nil {
parser.tree.funcSections =
make(map[string] *FuncSection)
}
parser.tree.funcSections[section.name] = section
if err != nil { return }
default:
err = parser.token.NewError (
"unknown section type \"" + sectionType + "\"",
infoerr.ErrorKindError)
return
}
} }
return
} }
+313 -2
View File
@@ -1,6 +1,317 @@
package parser package parser
// parseData parses a data section import "git.tebibyte.media/sashakoshka/arf/types"
func (parser *ParsingOperation) parseData () (err error) { import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
// parseData parses a data section.
func (parser *ParsingOperation) parseDataSection () (
section *DataSection,
err error,
) {
err = parser.expect(lexer.TokenKindName)
if err != nil { return }
section = &DataSection { location: parser.token.Location() }
err = parser.nextToken(lexer.TokenKindPermission)
if err != nil { return }
section.permission = parser.token.Value().(types.Permission)
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
section.name = parser.token.Value().(string)
err = parser.nextToken(lexer.TokenKindColon)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
section.what, err = parser.parseType()
if err != nil { return }
if parser.token.Is(lexer.TokenKindNewline) {
err = parser.nextToken()
if err != nil { return }
section.value, err = parser.parseInitializationValues(0)
if err != nil { return }
} else {
section.value, err = parser.parseArgument()
if err != nil { return }
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
}
return
}
// parseInitializationValues starts on the line after a data section, or a set
// phrase. It checks for an indent greater than the indent of the aforementioned
// data section or set phrase (passed through baseIndent), and if there is,
// it parses initialization values.
func (parser *ParsingOperation) parseInitializationValues (
baseIndent int,
) (
initializationArgument Argument,
err error,
) {
// check if line is indented one more than baseIndent
if !parser.token.Is(lexer.TokenKindIndent) { return }
if parser.token.Value().(int) != baseIndent + 1 { return }
initializationArgument.location = parser.token.Location()
err = parser.nextToken()
if err != nil { return }
if parser.token.Is(lexer.TokenKindDot) {
// object initialization
parser.previousToken()
var initializationValues ObjectInitializationValues
initializationValues, err = parser.parseObjectInitializationValues()
initializationArgument.kind = ArgumentKindObjectInitializationValues
initializationArgument.value = &initializationValues
} else {
// array initialization
parser.previousToken()
var initializationValues ArrayInitializationValues
initializationValues, err = parser.parseArrayInitializationValues()
initializationArgument.kind = ArgumentKindArrayInitializationValues
initializationArgument.value = &initializationValues
}
return
}
// parseObjectInitializationValues parses a list of object initialization
// values until the indentation level drops.
func (parser *ParsingOperation) parseObjectInitializationValues () (
initializationValues ObjectInitializationValues,
err error,
) {
initializationValues.attributes = make(map[string] Argument)
baseIndent := 0
begin := true
for {
// if there is no indent we can just stop parsing
if !parser.token.Is(lexer.TokenKindIndent) { break}
indent := parser.token.Value().(int)
if begin == true {
initializationValues.location = parser.token.Location()
baseIndent = indent
begin = false
}
// do not parse any further if the indent has changed
if indent != baseIndent { break }
// move on to the beginning of the line, which must contain
// a member initialization value
err = parser.nextToken(lexer.TokenKindDot)
if err != nil { return }
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
name := parser.token.Value().(string)
// if the member has already been listed, throw an error
_, exists := initializationValues.attributes[name]
if exists {
err = parser.token.NewError (
"duplicate member \"" + name + "\" in object " +
"member initialization",
infoerr.ErrorKindError)
return
}
// parse the argument determining the member initialization
// value
err = parser.nextToken()
if err != nil { return }
var value Argument
if parser.token.Is(lexer.TokenKindNewline) {
// recurse
err = parser.nextToken(lexer.TokenKindIndent)
if err != nil { return }
value, err = parser.parseInitializationValues(baseIndent)
initializationValues.attributes[name] = value
if err != nil { return }
} else {
// parse as normal argument
value, err = parser.parseArgument()
initializationValues.attributes[name] = value
if err != nil { return }
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
}
}
return
}
// parseArrayInitializationValues parses a list of array initialization values
// until the indentation lexel drops.
func (parser *ParsingOperation) parseArrayInitializationValues () (
initializationValues ArrayInitializationValues,
err error,
) {
baseIndent := 0
begin := true
for {
// if there is no indent we can just stop parsing
if !parser.token.Is(lexer.TokenKindIndent) { break}
indent := parser.token.Value().(int)
if begin == true {
initializationValues.location = parser.token.Location()
baseIndent = indent
begin = false
}
// do not parse any further if the indent has changed
if indent != baseIndent { break }
// move on to the beginning of the line, which must contain
// arguments
err = parser.nextToken(validArgumentStartTokens...)
if err != nil { return }
for {
// stop parsing this line and go on to the next if a
// newline token is encountered
if parser.token.Is(lexer.TokenKindNewline) {
err = parser.nextToken()
if err != nil { return }
break
}
// otherwise, parse the argument
var argument Argument
argument, err = parser.parseArgument()
if err != nil { return }
initializationValues.values = append (
initializationValues.values,
argument)
}
}
return
}
// parseType parses a type notation of the form Name, {Name}, etc.
func (parser *ParsingOperation) parseType () (what Type, err error) {
err = parser.expect(lexer.TokenKindName, lexer.TokenKindLBrace)
if err != nil { return }
what.location = parser.token.Location()
if parser.token.Is(lexer.TokenKindLBrace) {
what.kind = TypeKindPointer
err = parser.nextToken()
if err != nil { return }
var points Type
points, err = parser.parseType()
if err != nil { return }
what.points = &points
err = parser.expect (
lexer.TokenKindUInt,
lexer.TokenKindRBrace,
lexer.TokenKindElipsis)
if err != nil { return }
if parser.token.Is(lexer.TokenKindUInt) {
what.kind = TypeKindArray
what.length = parser.token.Value().(uint64)
err = parser.nextToken(lexer.TokenKindRBrace)
if err != nil { return }
} else if parser.token.Is(lexer.TokenKindElipsis) {
what.kind = TypeKindArray
err = parser.nextToken(lexer.TokenKindRBrace)
if err != nil { return }
}
err = parser.nextToken()
if err != nil { return }
} else {
what.name, err = parser.parseIdentifier()
if err != nil { return }
}
if parser.token.Is(lexer.TokenKindColon) {
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
qualifier := parser.token.Value().(string)
switch qualifier {
case "mut":
what.mutable = true
default:
err = parser.token.NewError (
"unknown type qualifier \"" + qualifier + "\"",
infoerr.ErrorKindError)
return
}
err = parser.nextToken()
if err != nil { return }
}
return
}
// parseIdentifier parses an identifier made out of dot separated names.
func (parser *ParsingOperation) parseIdentifier () (
identifier Identifier,
err error,
) {
err = parser.expect(lexer.TokenKindName)
if err != nil { return }
identifier.location = parser.token.Location()
for {
if !parser.token.Is(lexer.TokenKindName) { break }
identifier.trail = append (
identifier.trail,
parser.token.Value().(string))
err = parser.nextToken()
if err != nil { return }
if !parser.token.Is(lexer.TokenKindDot) { break }
err = parser.nextToken()
if err != nil { return }
// allow the identifier to continue on to the next line if there
// is a line break right after the dot
for parser.token.Is(lexer.TokenKindNewline) ||
parser.token.Is(lexer.TokenKindIndent) {
err = parser.nextToken()
if err != nil { return }
}
}
return
} }
+38
View File
@@ -0,0 +1,38 @@
package parser
import "testing"
func TestData (test *testing.T) {
checkTree ("../tests/parser/data",
`:arf
---
data ro integer:Int 3202
data ro integerArray16:{Int 16}
data ro integerArrayInitialized:{Int 16}
3948
293
293049
948
912
340
0
2304
0
4785
92
data ro integerArrayVariable:{Int ..}
data ro integerPointer:{Int}
data ro mutInteger:Int:mut 3202
data ro mutIntegerPointer:{Int}:mut
data ro nestedObject:Obj
.that
.bird2 123.8439
.bird3 9328.21348239
.this
.bird0 324
.bird1 "hello world"
data ro object:thing.thing.thing.thing
.that 2139
.this 324
`, test)
}
+93
View File
@@ -0,0 +1,93 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
func (parser *ParsingOperation) parseEnumSection () (
section *EnumSection,
err error,
) {
err = parser.expect(lexer.TokenKindName)
if err != nil { return }
section = &EnumSection { location: parser.token.Location() }
// get permission
err = parser.nextToken(lexer.TokenKindPermission)
if err != nil { return }
section.permission = parser.token.Value().(types.Permission)
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
section.name = parser.token.Value().(string)
// parse inherited type
err = parser.nextToken(lexer.TokenKindColon)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
section.what, err = parser.parseType()
if err != nil { return }
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
// parse members
err = parser.parseEnumMembers(section)
if err != nil { return }
if len(section.members) == 0 {
infoerr.NewError (
section.location,
"defining an enum with no members",
infoerr.ErrorKindWarn).Print()
}
return
}
// parseEnumMembers parses a list of members for an enum section. Indentation
// level is assumed.
func (parser *ParsingOperation) parseEnumMembers (
into *EnumSection,
) (
err error,
) {
for {
// if we've left the block, stop parsing
if !parser.token.Is(lexer.TokenKindIndent) { return }
if parser.token.Value().(int) != 1 { return }
member := EnumMember { }
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
member.location = parser.token.Location()
member.name = parser.token.Value().(string)
err = parser.nextToken()
if err != nil { return }
// parse default value
if parser.token.Is(lexer.TokenKindNewline) {
err = parser.nextToken()
if err != nil { return }
member.value, err = parser.parseInitializationValues(1)
into.members = append(into.members, member)
if err != nil { return }
} else {
member.value, err = parser.parseArgument()
into.members = append(into.members, member)
if err != nil { return }
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
}
}
}
+38
View File
@@ -0,0 +1,38 @@
package parser
import "testing"
func TestEnum (test *testing.T) {
checkTree ("../tests/parser/enum",
`:arf
---
enum ro AffrontToGod:{Int 4}
bird0
28394
9328
398
9
bird1
23
932832
398
2349
bird2
1
2
3
4
enum ro NamedColor:U32
red 16711680
green 65280
blue 255
enum ro Weekday:Int
sunday
monday
tuesday
wednesday
thursday
friday
saturday
`, test)
}
+132
View File
@@ -0,0 +1,132 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
// parseFaceSection parses an interface section.
func (parser *ParsingOperation) parseFaceSection () (
section *FaceSection,
err error,
) {
err = parser.expect(lexer.TokenKindName)
if err != nil { return }
section = &FaceSection {
location: parser.token.Location(),
behaviors: make(map[string] FaceBehavior),
}
// get permission
err = parser.nextToken(lexer.TokenKindPermission)
if err != nil { return }
section.permission = parser.token.Value().(types.Permission)
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
section.name = parser.token.Value().(string)
// parse inherited interface
err = parser.nextToken(lexer.TokenKindColon)
if err != nil { return }
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
section.inherits, err = parser.parseIdentifier()
if err != nil { return }
err = parser.nextToken(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
// parse members
for {
// if we've left the block, stop parsing
if !parser.token.Is(lexer.TokenKindIndent) { return }
if parser.token.Value().(int) != 1 { return }
// parse behavior
behaviorBeginning := parser.token.Location()
var behavior FaceBehavior
behavior, err = parser.parseFaceBehavior()
// add to section
_, exists := section.behaviors[behavior.name]
if exists {
err = infoerr.NewError (
behaviorBeginning,
"multiple behaviors named " + behavior.name +
" in this interface",
infoerr.ErrorKindError)
return
}
section.behaviors[behavior.name] = behavior
if err != nil { return }
}
return
}
// parseFaceBehavior parses a single interface behavior. Indentation level is
// assumed.
func (parser *ParsingOperation) parseFaceBehavior () (
behavior FaceBehavior,
err error,
) {
err = parser.expect(lexer.TokenKindIndent)
if err != nil { return }
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
behavior.name = parser.token.Value().(string)
err = parser.nextToken(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
for {
// if we've left the block, stop parsing
if !parser.token.Is(lexer.TokenKindIndent) { return }
if parser.token.Value().(int) != 2 { return }
// get preceding symbol
err = parser.nextToken (
lexer.TokenKindGreaterThan,
lexer.TokenKindLessThan)
if err != nil { return }
kind := parser.token.Kind()
var declaration Declaration
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
declaration.name = parser.token.Value().(string)
// parse inherited type
err = parser.nextToken(lexer.TokenKindColon)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
declaration.what, err = parser.parseType()
if err != nil { return }
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
if kind == lexer.TokenKindGreaterThan {
behavior.inputs = append (
behavior.inputs,
declaration)
} else {
behavior.outputs = append (
behavior.outputs,
declaration)
}
}
return
}
+30
View File
@@ -0,0 +1,30 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
// import "git.tebibyte.media/sashakoshka/arf/infoerr"
// parseFunc parses a function section.
func (parser *ParsingOperation) parseFuncSection () (
section *FuncSection,
err error,
) {
err = parser.expect(lexer.TokenKindName)
if err != nil { return }
section = &FuncSection { location: parser.token.Location() }
err = parser.nextToken(lexer.TokenKindPermission)
if err != nil { return }
section.permission = parser.token.Value().(types.Permission)
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
section.name = parser.token.Value().(string)
err = parser.nextToken(lexer.TokenKindNewline)
if err != nil { return }
return
}
+111
View File
@@ -0,0 +1,111 @@
package parser
import "testing"
func TestFunc (test *testing.T) {
checkTree ("../tests/parser/func",
`:arf
---
func ro aBasicExternal
> someInput:Int:mut
< someOutput:Int 4
---
external
func ro bMethod
@ bird:{Bird}
> someInput:Int:mut
< someOutput:Int 4
---
external
func ro cBasicPhrases
---
[fn 329 983 09]
[fn 329 983 09]
[fn 329 983 091]
[fn [gn 329 983 091] 123]
func ro dArgumentTypes
---
[bird tree butterfly.wing "hello world" grass:{Int:mut 8}]
func ro eMath
[> x:Int]
[> y:Int]
[< z:Int]
[---]
[++ x]
[-- y]
[set z [+ [* 0392 00] 98 x [/ 9832 y] 930]]
[! true]
[~ 0b01]
[% 873 32]
[= 5 5]
[!= 4 4]
[<= 4 98]
[< 4 98]
[<< 0x0F 4]
[>= 98 4]
[> 98 4]
[>> 0xF0 4]
[| 0b01 0b10]
[& 0b110 0b011]
[&& true true]
[|| true false]
func ro fReturnDirection
< err:Error
---
[someFunc 498 2980 90] -> thing:Int err
[otherFunc] -> thing err:Error
[fn 329 983 091] -> thing:Int err
func ro gControlFlow
---
[if condition]
[something]
[if condition]
[something]
[elseif]
[otherThing]
[else]
[finalThing]
[while [< x 432]]
[something]
[switch value]
[: 324]
[something]
[: 93284]
otherThing
[: 9128 34738 7328]
multipleCases
[:]
[defaultThing]
[for index:Size element:Int someArray]
[something]
[someNextThing]
[justMakingSureBlockParsingWorks]
[if condition]
[if condition]
[nestedThing]
[else]
[otherThing]
[else]
[if condition]
[nestedThing]
[else]
[otherThing]
func hSetPhrase
---
[set x:Int 3]
[set y:{Int} [. x]]
[set z:{Int 8}]
398
9
2309
983
-2387
478
555
123
[set bird:Bird]
.that
.whenYou 99999
.this 324
`, test)
}
+2 -2
View File
@@ -1,7 +1,7 @@
package parser package parser
import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/lexer" import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
// parseMeta parsese the metadata header at the top of an arf file. // parseMeta parsese the metadata header at the top of an arf file.
func (parser *ParsingOperation) parseMeta () (err error) { func (parser *ParsingOperation) parseMeta () (err error) {
@@ -35,7 +35,7 @@ func (parser *ParsingOperation) parseMeta () (err error) {
default: default:
parser.token.NewError ( parser.token.NewError (
"unrecognized metadata field: " + field, "unrecognized metadata field: " + field,
file.ErrorKindError) infoerr.ErrorKindError)
} }
err = parser.nextToken(lexer.TokenKindNewline) err = parser.nextToken(lexer.TokenKindNewline)
+14
View File
@@ -0,0 +1,14 @@
package parser
import "testing"
func TestMeta (test *testing.T) {
checkTree ("../tests/parser/meta",
`:arf
author "Sasha Koshka"
license "GPLv3"
require "someModule"
require "otherModule"
---
`, test)
}
+127
View File
@@ -0,0 +1,127 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
// parseObjtSection parses an object type definition. This allows for structured
// types to be defined, and for member variables to be added and overridden.
func (parser *ParsingOperation) parseObjtSection () (
section *ObjtSection,
err error,
) {
err = parser.expect(lexer.TokenKindName)
if err != nil { return }
section = &ObjtSection { location: parser.token.Location() }
// get permission
err = parser.nextToken(lexer.TokenKindPermission)
if err != nil { return }
section.permission = parser.token.Value().(types.Permission)
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
section.name = parser.token.Value().(string)
// parse inherited type
err = parser.nextToken(lexer.TokenKindColon)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
section.inherits, err = parser.parseIdentifier()
if err != nil { return }
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
// parse members
err = parser.parseObjtMembers(section)
if err != nil { return }
if len(section.members) == 0 {
infoerr.NewError (
section.location,
"defining an object with no members",
infoerr.ErrorKindWarn).Print()
}
return
}
// parseObjtMembers parses a list of members for an object section. Indentation
// level is assumed.
func (parser *ParsingOperation) parseObjtMembers (
into *ObjtSection,
) (
err error,
) {
for {
// if we've left the block, stop parsing
if !parser.token.Is(lexer.TokenKindIndent) { return }
if parser.token.Value().(int) != 1 { return }
// add member to object section
var member ObjtMember
member, err = parser.parseObjtMember()
into.members = append(into.members, member)
if err != nil { return }
}
}
// parseObjtMember parses a single member of an object section. Indentation
// level is assumed.
func (parser *ParsingOperation) parseObjtMember () (
member ObjtMember,
err error,
) {
// get permission
err = parser.nextToken(lexer.TokenKindPermission)
if err != nil { return }
member.permission = parser.token.Value().(types.Permission)
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
member.name = parser.token.Value().(string)
// get type
err = parser.nextToken(lexer.TokenKindColon)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
member.what, err = parser.parseType()
if err != nil { return }
println(parser.token.Describe())
// if there is a bit width, get it
if parser.token.Is(lexer.TokenKindBinaryAnd) {
err = parser.nextToken(lexer.TokenKindUInt)
if err != nil { return }
member.bitWidth = parser.token.Value().(uint64)
err = parser.nextToken()
if err != nil { return }
}
// parse default value
if parser.token.Is(lexer.TokenKindNewline) {
err = parser.nextToken()
if err != nil { return }
member.defaultValue,
err = parser.parseInitializationValues(1)
if err != nil { return }
} else {
member.defaultValue, err = parser.parseArgument()
if err != nil { return }
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
}
return
}
+31
View File
@@ -0,0 +1,31 @@
package parser
import "testing"
func TestObjt (test *testing.T) {
checkTree ("../tests/parser/objt",
`:arf
---
objt ro Basic:Obj
ro that:Basic
ro this:Basic
objt ro BitFields:Obj
ro that:Int & 1
ro this:Int & 24 298
objt ro ComplexInit:Obj
ro whatever:{Int 3}
230984
849
394580
ro complex0:Bird
.that 98
.this 2
ro complex1:Bird
.that 98902
.this 235
ro basic:Int 87
objt ro Init:Obj
ro that:String "hello world"
ro this:Int 23
`, test)
}
+12 -2
View File
@@ -5,6 +5,7 @@ import "os"
import "path/filepath" import "path/filepath"
import "git.tebibyte.media/sashakoshka/arf/file" import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/lexer" import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
// ParsingOperation holds information about an ongoing parsing operation. // ParsingOperation holds information about an ongoing parsing operation.
type ParsingOperation struct { type ParsingOperation struct {
@@ -96,9 +97,9 @@ func (parser *ParsingOperation) expect (allowed ...lexer.TokenKind) (err error)
message += allowedItem.Describe() message += allowedItem.Describe()
} }
err = file.NewError ( err = infoerr.NewError (
parser.token.Location(), parser.token.Location(),
message, file.ErrorKindError) message, infoerr.ErrorKindError)
return return
} }
@@ -111,3 +112,12 @@ func (parser *ParsingOperation) nextToken (allowed ...lexer.TokenKind) (err erro
err = parser.expect(allowed...) err = parser.expect(allowed...)
return return
} }
// previousToken goes back one token. If the parser is already at the beginning,
// this does nothing.
func (parser *ParsingOperation) previousToken () {
parser.tokenIndex --
if parser.tokenIndex < 0 { parser.tokenIndex = 0 }
parser.token = parser.tokens[parser.tokenIndex]
return
}
-33
View File
@@ -1,33 +0,0 @@
package parser
import "reflect"
import "testing"
func checkTree (modulePath string, correct *SyntaxTree, test *testing.T) {
tree, err := Parse(modulePath)
if err != nil {
test.Log("returned error:")
test.Log(err.Error())
test.Fail()
return
}
if !reflect.DeepEqual(tree, correct) {
test.Log("trees not equal")
test.Fail()
return
}
}
func TestMeta (test *testing.T) {
checkTree("../tests/parser/meta",&SyntaxTree {
license: "GPLv3",
author: "Sasha Koshka",
requires: []string {
"someModule",
"otherModule",
},
}, test)
}
+74
View File
@@ -0,0 +1,74 @@
package parser
import "io"
import "strings"
import "testing"
// import "git.tebibyte.media/sashakoshka/arf/types"
func checkTree (modulePath string, correct string, test *testing.T) {
tree, err := Parse(modulePath)
treeString := tree.ToString(0)
treeRunes := []rune(treeString)
test.Log("CORRECT TREE:")
logWithLineNumbers(correct, test)
test.Log("WHAT WAS PARSED:")
logWithLineNumbers(treeString, test)
if err != io.EOF && err != nil {
test.Log("returned error:")
test.Log(err.Error())
test.Fail()
return
}
equal := true
line := 0
column := 0
for index, correctChar := range correct {
if index >= len(treeRunes) {
test.Log (
"parsed is too short at line", line + 1,
"col", column + 1)
test.Fail()
return
}
if correctChar != treeRunes[index] {
test.Log (
"trees not equal at line", line + 1,
"col", column + 1)
test.Log("correct: [" + string(correctChar) + "]")
test.Log("got: [" + string(treeRunes[index]) + "]")
test.Fail()
return
}
if correctChar == '\n' {
line ++
column = 0
} else {
column ++
}
}
if len(treeString) > len(correct) {
test.Log("parsed is too long")
test.Fail()
return
}
if !equal {
return
}
}
func logWithLineNumbers (bigString string, test *testing.T) {
lines := strings.Split (
strings.Replace(bigString, "\t", " ", -1), "\n")
for index, line := range lines {
test.Logf("%3d | %s", index + 1, line)
}
}
+391
View File
@@ -0,0 +1,391 @@
package parser
import "fmt"
import "sort"
func doIndent (indent int, input ...string) (output string) {
for index := 0; index < indent; index ++ {
output += "\t"
}
for _, inputSection := range input {
output += inputSection
}
return
}
func sortMapKeysAlphabetically[KEY_TYPE any] (
unsortedMap map[string] KEY_TYPE,
) (
sortedKeys []string,
) {
sortedKeys = make([]string, len(unsortedMap))
index := 0
for key, _ := range unsortedMap {
sortedKeys[index] = key
index ++
}
sort.Strings(sortedKeys)
return
}
func (tree *SyntaxTree) ToString (indent int) (output string) {
output += doIndent(indent, ":arf\n")
if tree.author != "" {
output += doIndent(indent, "author \"", tree.author, "\"\n")
}
if tree.license != "" {
output += doIndent(indent, "license \"", tree.license, "\"\n")
}
for _, require := range tree.requires {
output += doIndent(indent, "require \"", require, "\"\n")
}
output += doIndent(indent, "---\n")
typeSectionKeys := sortMapKeysAlphabetically(tree.typeSections)
for _, name := range typeSectionKeys {
output += tree.typeSections[name].ToString(indent)
}
objtSectionKeys := sortMapKeysAlphabetically(tree.objtSections)
for _, name := range objtSectionKeys {
output += tree.objtSections[name].ToString(indent)
}
enumSectionKeys := sortMapKeysAlphabetically(tree.enumSections)
for _, name := range enumSectionKeys {
output += tree.enumSections[name].ToString(indent)
}
faceSectionKeys := sortMapKeysAlphabetically(tree.faceSections)
for _, name := range faceSectionKeys {
output += tree.faceSections[name].ToString(indent)
}
dataSectionKeys := sortMapKeysAlphabetically(tree.dataSections)
for _, name := range dataSectionKeys {
output += tree.dataSections[name].ToString(indent)
}
return
}
func (identifier *Identifier) ToString () (output string) {
for index, trailItem := range identifier.trail {
if index > 0 {
output += "."
}
output += trailItem
}
return
}
func (what *Type) ToString () (output string) {
if what.kind == TypeKindBasic {
output += what.name.ToString()
} else {
output += "{"
output += what.points.ToString()
if what.kind == TypeKindArray {
output += " "
if what.length == 0 {
output += ".."
} else {
output += fmt.Sprint(what.length)
}
}
output += "}"
}
if what.mutable {
output += ":mut"
}
return
}
func (declaration *Declaration) ToString () (output string) {
output += declaration.name + ":"
output += declaration.what.ToString()
return
}
func (attributes *ObjectInitializationValues) ToString (
indent int,
) (
output string,
) {
for _, name := range sortMapKeysAlphabetically(attributes.attributes) {
value := attributes.attributes[name]
output += doIndent(indent, ".", name)
if value.kind == ArgumentKindObjectInitializationValues {
output += "\n"
output += value.ToString(indent + 1, true)
} else {
output += " " + value.ToString(0, false) + "\n"
}
}
return
}
func (values *ArrayInitializationValues) ToString (
indent int,
) (
output string,
) {
for _, value := range values.values {
output += value.ToString(indent, true)
}
return
}
func (phrase *Phrase) ToString (indent int, breakLine bool) (output string) {
if breakLine {
output += doIndent (
indent,
"[", phrase.command.ToString(0, false))
output += "\n"
for _, argument := range phrase.arguments {
output += doIndent (
indent,
argument.ToString(indent + 1, true))
}
} else {
output += "[" + phrase.command.ToString(0, false)
for _, argument := range phrase.arguments {
output += " " + argument.ToString(0, false)
}
}
output += "]"
if len(phrase.returnsTo) > 0 {
output += " ->"
for _, returnItem := range phrase.returnsTo {
output += " " + returnItem.ToString(0, false)
}
}
if breakLine {
output += "\n"
}
return
}
func (argument *Argument) ToString (indent int, breakLine bool) (output string) {
if !breakLine { indent = 0 }
if argument.kind == ArgumentKindNil {
output += "NIL-ARGUMENT"
if breakLine { output += "\n" }
return
}
switch argument.kind {
case ArgumentKindPhrase:
output += argument.value.(*Phrase).ToString (
indent,
breakLine)
case ArgumentKindObjectInitializationValues:
// this should only appear in contexts where breakLine is true
output += argument.value.(*ObjectInitializationValues).
ToString(indent)
case ArgumentKindArrayInitializationValues:
// this should only appear in contexts where breakLine is true
output += argument.value.(*ArrayInitializationValues).
ToString(indent)
case ArgumentKindIdentifier:
output += doIndent (
indent,
argument.value.(*Identifier).ToString())
if breakLine { output += "\n" }
case ArgumentKindDeclaration:
output += doIndent (
indent,
argument.value.(*Declaration).ToString())
if breakLine { output += "\n" }
case ArgumentKindInt, ArgumentKindUInt, ArgumentKindFloat:
output += doIndent(indent, fmt.Sprint(argument.value))
if breakLine { output += "\n" }
case ArgumentKindString:
output += doIndent (
indent,
"\"" + argument.value.(string) + "\"")
if breakLine { output += "\n" }
case ArgumentKindRune:
output += doIndent (
indent,
"'" + string(argument.value.(rune)) + "'")
if breakLine { output += "\n" }
case ArgumentKindOperator:
// TODO
// also when parsing this argument kind, don't do it in the
// argument parsing function. do it specifically when parsing a
// phrase command.
}
return
}
func (section *DataSection) ToString (indent int) (output string) {
output += doIndent (
indent,
"data ",
section.permission.ToString(), " ",
section.name, ":",
section.what.ToString())
isComplexInitialization :=
section.value.kind == ArgumentKindObjectInitializationValues ||
section.value.kind == ArgumentKindArrayInitializationValues
if section.value.value == nil {
output += "\n"
} else if isComplexInitialization {
output += "\n"
output += section.value.ToString(indent + 1, true)
} else {
output += " " + section.value.ToString(0, false)
output += "\n"
}
return
}
func (section *TypeSection) ToString (indent int) (output string) {
output += doIndent (
indent,
"type ",
section.permission.ToString(), " ",
section.name, ":",
section.inherits.ToString())
isComplexInitialization :=
section.defaultValue.kind == ArgumentKindObjectInitializationValues ||
section.defaultValue.kind == ArgumentKindArrayInitializationValues
if section.defaultValue.value == nil {
output += "\n"
} else if isComplexInitialization {
output += "\n"
output += section.defaultValue.ToString(indent + 1, true)
} else {
output += " " + section.defaultValue.ToString(0, false)
output += "\n"
}
return
}
func (member ObjtMember) ToString (indent int) (output string) {
output += doIndent(indent)
output += member.permission.ToString() + " "
output += member.name + ":"
output += member.what.ToString()
if member.bitWidth > 0 {
output += fmt.Sprint(" & ", member.bitWidth)
}
isComplexInitialization :=
member.defaultValue.kind == ArgumentKindObjectInitializationValues ||
member.defaultValue.kind == ArgumentKindArrayInitializationValues
if member.defaultValue.value == nil {
output += "\n"
} else if isComplexInitialization {
output += "\n"
output += member.defaultValue.ToString(indent + 1, true)
} else {
output += " " + member.defaultValue.ToString(0, false)
output += "\n"
}
return
}
func (section *ObjtSection) ToString (indent int) (output string) {
output += doIndent (
indent,
"objt ",
section.permission.ToString(), " ",
section.name, ":",
section.inherits.ToString(), "\n")
for _, member := range section.members {
output += member.ToString(indent + 1)
}
return
}
func (section *EnumSection) ToString (indent int) (output string) {
output += doIndent (
indent,
"enum ",
section.permission.ToString(), " ",
section.name, ":",
section.what.ToString(), "\n")
for _, member := range section.members {
output += doIndent(indent + 1, member.name)
isComplexInitialization :=
member.value.kind == ArgumentKindObjectInitializationValues ||
member.value.kind == ArgumentKindArrayInitializationValues
if member.value.value == nil {
output += "\n"
} else if isComplexInitialization {
output += "\n"
output += member.value.ToString(indent + 2, true)
} else {
output += " " + member.value.ToString(0, false)
output += "\n"
}
}
return
}
func (section *FaceSection) ToString (indent int) (output string) {
output += doIndent (
indent,
"face ",
section.permission.ToString(), " ",
section.name, ":",
section.inherits.ToString(), "\n")
for _, name := range sortMapKeysAlphabetically(section.behaviors) {
behavior := section.behaviors[name]
output += behavior.ToString(indent + 1)
}
return
}
func (behavior *FaceBehavior) ToString (indent int) (output string) {
output += doIndent(indent, behavior.name, "\n")
for _, inputItem := range behavior.inputs {
output += doIndent(indent + 1, "> ", inputItem.ToString(), "\n")
}
for _, outputItem := range behavior.outputs {
output += doIndent(indent + 1, "< ", outputItem.ToString(), "\n")
}
return
}
+239 -1
View File
@@ -1,5 +1,8 @@
package parser package parser
import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/types"
// SyntaxTree represents an abstract syntax tree. It covers an entire module. It // SyntaxTree represents an abstract syntax tree. It covers an entire module. It
// can be expected to be syntactically correct, but it might not be semantically // can be expected to be syntactically correct, but it might not be semantically
// correct (because it has not been analyzed yet.) // correct (because it has not been analyzed yet.)
@@ -7,5 +10,240 @@ type SyntaxTree struct {
license string license string
author string author string
requires []string requires []string
typeSections map[string] *TypeSection
objtSections map[string] *ObjtSection
enumSections map[string] *EnumSection
faceSections map[string] *FaceSection
dataSections map[string] *DataSection
funcSections map[string] *FuncSection
}
// Identifier represents a chain of arguments separated by a dot.
type Identifier struct {
location file.Location
trail []string
}
// TypeKind represents what kind of type a type is
type TypeKind int
const (
// TypeKindBasic either means it's a primitive, or it inherits from
// something.
TypeKindBasic TypeKind = iota
// TypeKindPointer means it's a pointer
TypeKindPointer
// TypeKindArray means it's an array.
TypeKindArray
)
// Type represents a type specifier
type Type struct {
location file.Location
mutable bool
kind TypeKind
// only applicable for arrays. a value of zero means it has an
// undefined/dynamic length.
length uint64
// only applicable for basic.
name Identifier
// not applicable for basic.
points *Type
}
// Declaration represents a variable declaration.
type Declaration struct {
location file.Location
name string
what Type
}
// ObjectInitializationValues represents a list of object member initialization
// attributes.
type ObjectInitializationValues struct {
location file.Location
attributes map[string] Argument
}
// ArrayInitializationValues represents a list of attributes initializing an
// array.
type ArrayInitializationValues struct {
location file.Location
values []Argument
}
// Phrase represents a function call or operator. In ARF they are the same
// syntactical concept.
type Phrase struct {
location file.Location
command Argument
arguments []Argument
returnsTo []Argument
}
// ArgumentKind specifies the type of thing the value of an argument should be
// cast to.
type ArgumentKind int
const (
ArgumentKindNil ArgumentKind = iota
// [name argument]
// [name argument argument]
// etc...
ArgumentKindPhrase = iota
// {name}
ArgumentKindDereference
// {name 23}
ArgumentKindSubscript
// .name value
// but like, a lot of them
ArgumentKindObjectInitializationValues
// value value...
ArgumentKindArrayInitializationValues
// name.name
// name.name.name
// etc...
ArgumentKindIdentifier
// name:Type
// name:{Type}
// name:{Type ..}
// name:{Type 23}
// etc...
ArgumentKindDeclaration
// -1337
ArgumentKindInt
// 1337
ArgumentKindUInt
// 0.44
ArgumentKindFloat
// "hello world"
ArgumentKindString
// 'S'
ArgumentKindRune
// + - * / etc...
// this is only used as a phrase command
ArgumentKindOperator
)
// Argument represents a value that can be placed anywhere a value goes. This
// allows things like phrases being arguments to other phrases.
type Argument struct {
location file.Location
kind ArgumentKind
value any
// TODO: if there is an argument expansion operator its existence should
// be stored here in a boolean.
}
// DataSection represents a global variable.
type DataSection struct {
location file.Location
name string
what Type
permission types.Permission
value Argument
}
// TypeSection represents a blind type definition.
type TypeSection struct {
location file.Location
name string
inherits Type
permission types.Permission
defaultValue Argument
}
// ObjtMember represents a part of an object type definition.
type ObjtMember struct {
location file.Location
name string
what Type
bitWidth uint64
permission types.Permission
defaultValue Argument
}
// ObjtSection represents an object type definition
type ObjtSection struct {
location file.Location
name string
inherits Identifier
permission types.Permission
members []ObjtMember
}
type EnumMember struct {
location file.Location
name string
value Argument
}
// EnumSection represents an enumerated type section.
type EnumSection struct {
location file.Location
name string
what Type
permission types.Permission
members []EnumMember
}
// FaceBehavior represents a behavior of an interface section.
type FaceBehavior struct {
location file.Location
name string
inputs []Declaration
outputs []Declaration
}
// FaceSection represents an interface type section.
type FaceSection struct {
location file.Location
name string
inherits Identifier
permission types.Permission
behaviors map[string] FaceBehavior
}
// Block represents a scoped/indented block of code.
// TODO: blocks will not directly nest. nested blocks will be stored as a part
// of certain control flow statements.
type Block []Phrase
// FuncSection represents a function section.
type FuncSection struct {
location file.Location
name string
permission types.Permission
receiver *Declaration
inputs []Declaration
outputs []Declaration
root *Block
} }
+53
View File
@@ -0,0 +1,53 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
// import "git.tebibyte.media/sashakoshka/arf/infoerr"
// parseTypeSection parses a blind type definition, meaning it can inherit from
// anything including primitives, but cannot define structure.
func (parser *ParsingOperation) parseTypeSection () (
section *TypeSection,
err error,
) {
err = parser.expect(lexer.TokenKindName)
if err != nil { return }
section = &TypeSection { location: parser.token.Location() }
// get permission
err = parser.nextToken(lexer.TokenKindPermission)
if err != nil { return }
section.permission = parser.token.Value().(types.Permission)
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
section.name = parser.token.Value().(string)
// parse inherited type
err = parser.nextToken(lexer.TokenKindColon)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
section.inherits, err = parser.parseType()
if err != nil { return }
// parse default values
if parser.token.Is(lexer.TokenKindNewline) {
err = parser.nextToken()
if err != nil { return }
section.defaultValue, err = parser.parseInitializationValues(0)
if err != nil { return }
} else {
section.defaultValue, err = parser.parseArgument()
if err != nil { return }
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
}
return
}
+17
View File
@@ -0,0 +1,17 @@
package parser
import "testing"
func TestType (test *testing.T) {
checkTree ("../tests/parser/type",
`:arf
---
type ro Basic:Int
type ro BasicInit:Int 6
type ro IntArray:{Int ..}
type ro IntArrayInit:{Int 3}
3298
923
92
`, test)
}
+2 -2
View File
@@ -1,3 +1,3 @@
:arf :arf
--- rw -> -349820394 932748397 239485.37520 "hello world!\n" 'E' helloWorld:.[]{} --- rw -> -349820394 932748397 239485.37520 "hello world!\n" 'E' helloWorld:.,..[]{}
+ - ++ -- * / @ ! % ~ < << > >> | || & && + - ++ -- * / @ ! % %= ~ ~= = != < <= << <<= > >= >> >>= | |= || & &= && ^ ^=
+2
View File
@@ -0,0 +1,2 @@
:arf
'aaaaaaa'
+2
View File
@@ -0,0 +1,2 @@
:arf
hello;
+2
View File
@@ -0,0 +1,2 @@
:arf
"\g"
+2
View File
@@ -1,4 +1,6 @@
:arf :arf
0
8
83628266 83628266
0b100111111000001000011101010 0b100111111000001000011101010
0x4Fc10Ea 0x4Fc10Ea
+43 -16
View File
@@ -1,27 +1,54 @@
:arf :arf
--- ---
data wr integer:Int 3202 data ro integer:Int 3202
data wr integerPointer:{Int} data ro mutInteger:Int:mut 3202
# TODO: data wr integerPointer:{Int} [& integer]
data wr integerArray16:{Int 16} data ro integerPointer:{Int}
data wr integerArrayVariable:{Int ...} data ro mutIntegerPointer:{Int}:mut
data wr integerArrayInitialized:{Int 16} data ro integerArray16:{Int 16}
data ro integerArrayVariable:{Int ..}
data ro integerArrayInitialized:{Int 16}
3948 293 293049 948 912 3948 293 293049 948 912
340 0 2304 0 4785 92 340 0 2304 0 4785 92
data wr object:Obj # TODO: reinstate these two after phrase parsing is implemented
: this 324 # data wr integerPointerInit:{Int} [& integer]
: that 2139
data wr nestedObject:Obj # data wr mutIntegerPointerInit:{Int}:mut [& integer]
: this
: bird0 324 # TODO: maybe test identifiers somewhere else?
: bird1 "hello world" data ro object:thing.thing.
: that thing.thing
: bird2 123.8439 .this 324
: bird3 9328.21348239 .that 2139
data ro nestedObject:Obj
.this
.bird0 324
.bird1 "hello world"
.that
.bird2 123.8439
.bird3 9328.21348239
# func ro main
# ---
# # TODO: set should be a special case, checking under itself for object
# member initialization args. it should also check for args in general
# under there which should be treated as array initialization args.
# basically, under a set phrase, it should do the same checks that it
# does under a data section.
#
# [set object:Obj]
# .this 324
# .that 2139
#
# set object:Obj
# .this 324
# .that 2139
+30
View File
@@ -0,0 +1,30 @@
:arf
---
enum ro Weekday:Int
sunday
monday
tuesday
wednesday
thursday
friday
saturday
enum ro NamedColor:U32
red 0xFF0000
green 0x00FF00
blue 0x0000FF
enum ro AffrontToGod:{Int 4}
bird0
28394 9328
398 9
bird1
23 932832
398
2349
bird2
1
2
3
4
+15
View File
@@ -0,0 +1,15 @@
:arf
---
face ro ReadWriter:Face
write
> data:{Byte ..}
< wrote:Int
< err:Error
read
> into:{Byte ..}
< read:Int
< err:Error
face ro Destroyer:Face
destroy
+134
View File
@@ -0,0 +1,134 @@
:arf
---
func ro aBasicExternal
> someInput:Int:mut
< someOutput:Int 4
---
external
func ro bMethod
@ bird:{Bird}
> someInput:Int:mut
< someOutput:Int 4
---
external
func ro cBasicPhrases
---
fn 329 983 09
[fn 329 983 09]
[fn
329
983
091]
fn [gn
329 983
091] 123
func ro dArgumentTypes
---
[bird tree butterfly.wing "hello world"
grass:{Int:mut 8}]
func ro eMath
> x:Int
> y:Int
< z:Int
---
++ x
-- y
set z [+ [* 0392 00] 98 x [/ 9832 y] 930]
# TODO: need tokens ~=
! true
~ 0b01
# ~= x
% 873 32
= 5 5
!= 4 4
<= 4 98
< 4 98
<< 0x0F 4
# <<= x 4
>= 98 4
> 98 4
>> 0xF0 4
# >>= x 4
| 0b01 0b10
# |= x 0b10
& 0b110 0b011
# &= x 0b011
&& true true
|| true false
func ro fReturnDirection
< err:Error
---
someFunc 498 2980 90 -> thing:Int err
otherFunc -> thing err:Error
[fn
329
983
091] -> thing:Int err
func ro gControlFlow
---
if condition
something
if condition
something
elseif
[otherThing]
else
finalThing
while [< x 432]
something
switch value
: 324
something
[: 93284]
otherThing
: 9128 34738 7328
multipleCases
:
[defaultThing]
for index:Size element:Int someArray
something
someNextThing
justMakingSureBlockParsingWorks
[if condition]
if condition
nestedThing
else
otherThing
else
if condition
nestedThing
else
otherThing
func hSetPhrase
---
set x:Int 3
# TODO: this should be the "location of" phrase. update other things to
# match.
set y:{Int} [. x]
set z:{Int 8}
398 9 2309 983 -2387
478 555 123
set bird:Bird
.that
.whenYou 99999
.this 324
+25
View File
@@ -0,0 +1,25 @@
:arf
---
objt ro Basic:Obj
ro that:Basic
ro this:Basic
objt ro BitFields:Obj
ro that:Int & 1
ro this:Int & 24 298
objt ro Init:Obj
ro that:String "hello world"
ro this:Int 23
objt ro ComplexInit:Obj
ro whatever:{Int 3}
230984
849 394580
ro complex0:Bird
.that 98
.this 2
ro complex1:Bird
.that 98902
.this 235
ro basic:Int 87
+10
View File
@@ -0,0 +1,10 @@
:arf
---
type ro Basic:Int
type ro BasicInit:Int 6
type ro IntArray:{Int ..}
type ro IntArrayInit:{Int 3}
3298 923 92
+51 -20
View File
@@ -1,32 +1,63 @@
package types package types
type Mode int type Permission int
const ( const (
ModeNone = iota // Displays as: pv
ModeRead //
ModeWrite // Other modules cannot access the section or member.
PermissionPrivate Permission = iota
// Displays as: ro
//
// Other modules can access the section or member, but can only read its
// value. It is effectively immutable.
//
// Data sections, member variables, etc: The value can be read by other
// modules but not altered by them
//
// Functions: The function can be called by other modules.
//
// Methods: The method can be called by other modules, but cannot be
// overriden by a type defined in another module inheriting from this
// method's reciever.
PermissionReadOnly
// Displays as: rw
//
// Other modules cannot only access the section or member's value, but
// can alter it. It is effectively mutable.
//
// Data sections, member variables, etc: The value can be read and
// altered at will by other modules.
//
// Functions: This permission cannot be applied to non-method functions.
//
// Methods: The method can be called by other modules, and overridden by
// types defined in other modules inheriting from the method's reciever.
PermissionReadWrite
) )
type Permission struct { // PermissionFrom creates a new permission value from the specified text. If the
Internal Mode // input text was not valid, the function returns false for worked. Otherwise,
External Mode // it returns true.
} func PermissionFrom (data string) (permission Permission, worked bool) {
worked = true
func ModeFrom (char rune) (mode Mode) { switch data {
switch (char) { case "pv": permission = PermissionPrivate
case 'n': mode = ModeNone case "ro": permission = PermissionReadOnly
case 'r': mode = ModeRead case "rw": permission = PermissionReadWrite
case 'w': mode = ModeWrite default: worked = false
} }
return return
} }
func PermissionFrom (data string) (permission Permission) { // ToString converts the permission value into a string.
if len(data) != 2 { return } func (permission Permission) ToString () (output string) {
switch permission {
permission.Internal = ModeFrom(rune(data[0])) case PermissionPrivate: output = "pv"
permission.External = ModeFrom(rune(data[1])) case PermissionReadOnly: output = "ro"
case PermissionReadWrite: output = "rw"
}
return return
} }