Compare commits

..

43 Commits

Author SHA1 Message Date
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
17 changed files with 994 additions and 62 deletions

View File

@ -29,6 +29,8 @@ 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

View File

@ -52,20 +52,30 @@ func (err Error) Error () (formattedMessage string) {
if err.width > 0 { if err.width > 0 {
// print erroneous line // print erroneous line
line := err.Location.file.lines[err.Location.row]
formattedMessage += formattedMessage +=
err.Location.file.lines[err.Location.row] + "\n" err.Location.file.lines[err.Location.row] + "\n"
// position error marker
var index int
for index = 0; index < err.Location.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 columnCountdown > 1 {
// TODO: for tabs, print out a teb instead.
formattedMessage += " "
columnCountdown --
}
for err.width > 1 { for err.width > 1 {
// TODO: for tabs, print out 8 of these instead. if line[index] == '\t' {
formattedMessage += "-" formattedMessage += "--------"
} else {
formattedMessage += "-"
}
index ++
} }
formattedMessage += "^\n" formattedMessage += "^\n"
} }
formattedMessage += err.message + "\n" formattedMessage += err.message + "\n"

View File

@ -176,7 +176,17 @@ 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()
}
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 +210,15 @@ 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()
} }
lexer.addToken(token) lexer.addToken(token)
err = lexer.nextRune()
case '-': case '-':
err = lexer.tokenizeDashBeginning() err = lexer.tokenizeDashBeginning()
case '*': case '*':
@ -242,45 +252,45 @@ 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 = TokenKindLessThan token.kind = TokenKindLessThan
if lexer.char == '<' { if lexer.char == '<' {
token.kind = TokenKindLShift token.kind = TokenKindLShift
err = lexer.nextRune()
} }
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()
} }
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()
} }
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()
} }
lexer.addToken(token) lexer.addToken(token)
err = lexer.nextRune()
default: default:
err = file.NewError ( err = file.NewError (
lexer.file.Location(1), lexer.file.Location(1),

View File

@ -62,6 +62,8 @@ func TestTokenizeAll (test *testing.T) {
Token { kind: TokenKindName, value: "helloWorld" }, Token { kind: TokenKindName, value: "helloWorld" },
Token { kind: TokenKindColon }, Token { kind: TokenKindColon },
Token { kind: TokenKindDot }, Token { kind: TokenKindDot },
Token { kind: TokenKindComma },
Token { kind: TokenKindElipsis },
Token { kind: TokenKindLBracket }, Token { kind: TokenKindLBracket },
Token { kind: TokenKindRBracket }, Token { kind: TokenKindRBracket },
Token { kind: TokenKindLBrace }, Token { kind: TokenKindLBrace },
@ -91,6 +93,10 @@ func TestTokenizeAll (test *testing.T) {
func TestTokenizeNumbers (test *testing.T) { func TestTokenizeNumbers (test *testing.T) {
checkTokenSlice("../tests/lexer/numbers.arf", []Token { checkTokenSlice("../tests/lexer/numbers.arf", []Token {
Token { kind: TokenKindUInt, value: uint64(0) },
Token { kind: TokenKindNewline },
Token { kind: TokenKindUInt, value: uint64(8) },
Token { kind: TokenKindNewline },
Token { kind: TokenKindUInt, value: uint64(83628266) }, Token { kind: TokenKindUInt, value: uint64(83628266) },
Token { kind: TokenKindNewline }, Token { kind: TokenKindNewline },
Token { kind: TokenKindUInt, value: uint64(83628266) }, Token { kind: TokenKindUInt, value: uint64(83628266) },

View File

@ -1,7 +1,5 @@
package lexer package lexer
import "git.tebibyte.media/sashakoshka/arf/file"
// 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 number uint64
@ -23,11 +21,6 @@ func (lexer *LexingOperation) tokenizeNumberBeginning (negative bool) (err error
number, fragment, isFloat, err = lexer.tokenizeNumber(10) number, fragment, isFloat, 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) number, fragment, isFloat, err = lexer.tokenizeNumber(8)
} else {
return file.NewError (
lexer.file.Location(1),
"unexpected character in number literal",
file.ErrorKindError)
} }
} else { } else {
number, fragment, isFloat, err = lexer.tokenizeNumber(10) number, fragment, isFloat, err = lexer.tokenizeNumber(10)

View File

@ -24,6 +24,8 @@ const (
TokenKindColon TokenKindColon
TokenKindDot TokenKindDot
TokenKindElipsis
TokenKindComma
TokenKindLBracket TokenKindLBracket
TokenKindRBracket TokenKindRBracket
@ -133,6 +135,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:

91
parser/argument.go Normal file
View File

@ -0,0 +1,91 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/lexer"
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",
file.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
}

View File

@ -1,18 +1,34 @@
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"
// 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":
case "face":
case "enum":
case "func":
default:
err = parser.token.NewError (
"unknown section type \"" + sectionType + "\"",
file.ErrorKindError)
return
}
} }
return
} }

View File

@ -1,6 +1,312 @@
package parser package parser
// parseData parses a data section import "git.tebibyte.media/sashakoshka/arf/file"
func (parser *ParsingOperation) parseData () (err error) { import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
// 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,
) {
println("BEGIN")
defer println("END")
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 }
println("HIT")
// 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",
file.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 + "\"",
file.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 {
// TODO: eat up newlines and tabs after the dot, but not before
// it.
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 }
}
return
} }

View File

@ -111,3 +111,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
}

View File

@ -1,33 +1,75 @@
package parser package parser
import "reflect" import "io"
import "testing" import "testing"
// import "git.tebibyte.media/sashakoshka/arf/types"
func checkTree (modulePath string, correct *SyntaxTree, test *testing.T) { func checkTree (modulePath string, correct string, test *testing.T) {
tree, err := Parse(modulePath) tree, err := Parse(modulePath)
treeString := tree.ToString(0)
if err != nil { test.Log("CORRECT TREE:")
test.Log(correct)
test.Log("WHAT WAS PARSED:")
test.Log(treeString)
if err != io.EOF && err != nil {
test.Log("returned error:") test.Log("returned error:")
test.Log(err.Error()) test.Log(err.Error())
test.Fail() test.Fail()
return return
} }
if !reflect.DeepEqual(tree, correct) { if treeString != correct {
test.Log("trees not equal") test.Log("trees not equal!")
test.Fail() test.Fail()
return return
} }
} }
func TestMeta (test *testing.T) { func TestMeta (test *testing.T) {
checkTree("../tests/parser/meta",&SyntaxTree { checkTree ("../tests/parser/meta",
license: "GPLv3", `:arf
author: "Sasha Koshka", author "Sasha Koshka"
license "GPLv3"
requires: []string { require "someModule"
"someModule", require "otherModule"
"otherModule", ---
}, `, test)
}, test)
} }
func TestData (test *testing.T) {
checkTree ("../tests/parser/data",
`:arf
---
data wr integer:Int 3202
data wr integerArray16:{Int 16}
data wr integerArrayInitialized:{Int 16}
3948
293
293049
948
912
340
0
2304
0
4785
92
data wr integerArrayVariable:{Int ..}
data wr integerPointer:{Int}
data wr mutInteger:Int:mut 3202
data wr mutIntegerPointer:{Int}:mut
data wr nestedObject:Obj
.that
.bird2 123.8439
.bird3 9328.21348239
.this
.bird0 324
.bird1 "hello world"
data wr object:Obj
.that 2139
.this 324
`, test)
}

248
parser/tree-tostring.go Normal file
View File

@ -0,0 +1,248 @@
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")
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
}

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,152 @@ type SyntaxTree struct {
license string license string
author string author string
requires []string requires []string
dataSections map[string] *DataSection
}
// 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
value Argument
permission types.Permission
} }

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:.,..[]{}
+ - ++ -- * / @ ! % ~ < << > >> | || & && + - ++ -- * / @ ! % ~ < << > >> | || & &&

View File

@ -1,4 +1,6 @@
:arf :arf
0
8
83628266 83628266
0b100111111000001000011101010 0b100111111000001000011101010
0x4Fc10Ea 0x4Fc10Ea

View File

@ -3,25 +3,50 @@
data wr integer:Int 3202 data wr integer:Int 3202
data wr mutInteger:Int:mut 3202
data wr integerPointer:{Int} data wr integerPointer:{Int}
# TODO: data wr integerPointer:{Int} [& integer]
data wr mutIntegerPointer:{Int}:mut
data wr integerArray16:{Int 16} data wr integerArray16:{Int 16}
data wr integerArrayVariable:{Int ...} data wr integerArrayVariable:{Int ..}
data wr integerArrayInitialized:{Int 16} data wr 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
# TODO: reinstate these two after phrase parsing is implemented
# data wr integerPointerInit:{Int} [& integer]
# data wr mutIntegerPointerInit:{Int}:mut [& integer]
data wr object:Obj data wr object:Obj
: this 324 .this 324
: that 2139 .that 2139
data wr nestedObject:Obj data wr nestedObject:Obj
: this .this
: bird0 324 .bird0 324
: bird1 "hello world" .bird1 "hello world"
: that .that
: bird2 123.8439 .bird2 123.8439
: bird3 9328.21348239 .bird3 9328.21348239
# func rr 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

View File

@ -30,3 +30,19 @@ func PermissionFrom (data string) (permission Permission) {
permission.External = ModeFrom(rune(data[1])) permission.External = ModeFrom(rune(data[1]))
return return
} }
func (mode Mode) ToString () (output string) {
switch mode {
case ModeNone: output = "n"
case ModeRead: output = "r"
case ModeWrite: output = "w"
}
return
}
func (permission Permission) ToString () (output string) {
output += permission.Internal.ToString()
output += permission.External.ToString()
return
}