Merge pull request 'data-section' (#3) from data-section into main
Reviewed-on: #3
This commit is contained in:
commit
e069569c3c
@ -29,6 +29,8 @@ These are some design goals that I have followed/am following:
|
||||
- Language syntax must have zero ambiguity
|
||||
- The compiler should not generate new functions or complex logic that the user
|
||||
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
|
||||
|
||||
|
@ -52,20 +52,30 @@ func (err Error) Error () (formattedMessage string) {
|
||||
|
||||
if err.width > 0 {
|
||||
// print erroneous line
|
||||
line := err.Location.file.lines[err.Location.row]
|
||||
formattedMessage +=
|
||||
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
|
||||
columnCountdown := err.Location.column
|
||||
for columnCountdown > 1 {
|
||||
// TODO: for tabs, print out a teb instead.
|
||||
formattedMessage += " "
|
||||
columnCountdown --
|
||||
}
|
||||
for err.width > 1 {
|
||||
// TODO: for tabs, print out 8 of these instead.
|
||||
formattedMessage += "-"
|
||||
if line[index] == '\t' {
|
||||
formattedMessage += "--------"
|
||||
} else {
|
||||
formattedMessage += "-"
|
||||
}
|
||||
index ++
|
||||
}
|
||||
|
||||
formattedMessage += "^\n"
|
||||
}
|
||||
formattedMessage += err.message + "\n"
|
||||
|
@ -176,7 +176,17 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
|
||||
err = lexer.nextRune()
|
||||
case '.':
|
||||
token := lexer.newToken()
|
||||
err = lexer.nextRune()
|
||||
if err != nil { return }
|
||||
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)
|
||||
err = lexer.nextRune()
|
||||
case '[':
|
||||
@ -200,15 +210,15 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
|
||||
lexer.addToken(token)
|
||||
err = lexer.nextRune()
|
||||
case '+':
|
||||
token := lexer.newToken()
|
||||
err = lexer.nextRune()
|
||||
if err != nil { return }
|
||||
token := lexer.newToken()
|
||||
token.kind = TokenKindPlus
|
||||
if lexer.char == '+' {
|
||||
token.kind = TokenKindIncrement
|
||||
err = lexer.nextRune()
|
||||
}
|
||||
lexer.addToken(token)
|
||||
err = lexer.nextRune()
|
||||
case '-':
|
||||
err = lexer.tokenizeDashBeginning()
|
||||
case '*':
|
||||
@ -242,45 +252,45 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
|
||||
lexer.addToken(token)
|
||||
err = lexer.nextRune()
|
||||
case '<':
|
||||
token := lexer.newToken()
|
||||
err = lexer.nextRune()
|
||||
if err != nil { return }
|
||||
token := lexer.newToken()
|
||||
token.kind = TokenKindLessThan
|
||||
if lexer.char == '<' {
|
||||
token.kind = TokenKindLShift
|
||||
err = lexer.nextRune()
|
||||
}
|
||||
lexer.addToken(token)
|
||||
err = lexer.nextRune()
|
||||
case '>':
|
||||
token := lexer.newToken()
|
||||
err = lexer.nextRune()
|
||||
if err != nil { return }
|
||||
token := lexer.newToken()
|
||||
token.kind = TokenKindGreaterThan
|
||||
if lexer.char == '>' {
|
||||
token.kind = TokenKindRShift
|
||||
err = lexer.nextRune()
|
||||
}
|
||||
lexer.addToken(token)
|
||||
err = lexer.nextRune()
|
||||
case '|':
|
||||
token := lexer.newToken()
|
||||
err = lexer.nextRune()
|
||||
if err != nil { return }
|
||||
token := lexer.newToken()
|
||||
token.kind = TokenKindBinaryOr
|
||||
if lexer.char == '|' {
|
||||
token.kind = TokenKindLogicalOr
|
||||
err = lexer.nextRune()
|
||||
}
|
||||
lexer.addToken(token)
|
||||
err = lexer.nextRune()
|
||||
case '&':
|
||||
token := lexer.newToken()
|
||||
err = lexer.nextRune()
|
||||
if err != nil { return }
|
||||
token := lexer.newToken()
|
||||
token.kind = TokenKindBinaryAnd
|
||||
if lexer.char == '&' {
|
||||
token.kind = TokenKindLogicalAnd
|
||||
err = lexer.nextRune()
|
||||
}
|
||||
lexer.addToken(token)
|
||||
err = lexer.nextRune()
|
||||
default:
|
||||
err = file.NewError (
|
||||
lexer.file.Location(1),
|
||||
|
@ -62,6 +62,8 @@ func TestTokenizeAll (test *testing.T) {
|
||||
Token { kind: TokenKindName, value: "helloWorld" },
|
||||
Token { kind: TokenKindColon },
|
||||
Token { kind: TokenKindDot },
|
||||
Token { kind: TokenKindComma },
|
||||
Token { kind: TokenKindElipsis },
|
||||
Token { kind: TokenKindLBracket },
|
||||
Token { kind: TokenKindRBracket },
|
||||
Token { kind: TokenKindLBrace },
|
||||
@ -91,6 +93,10 @@ func TestTokenizeAll (test *testing.T) {
|
||||
|
||||
func TestTokenizeNumbers (test *testing.T) {
|
||||
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: TokenKindNewline },
|
||||
Token { kind: TokenKindUInt, value: uint64(83628266) },
|
||||
|
@ -1,7 +1,5 @@
|
||||
package lexer
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/arf/file"
|
||||
|
||||
// tokenizeSymbolBeginning lexes a token that starts with a number.
|
||||
func (lexer *LexingOperation) tokenizeNumberBeginning (negative bool) (err error) {
|
||||
var number uint64
|
||||
@ -23,11 +21,6 @@ func (lexer *LexingOperation) tokenizeNumberBeginning (negative bool) (err error
|
||||
number, fragment, isFloat, err = lexer.tokenizeNumber(10)
|
||||
} else if lexer.char >= '0' && lexer.char <= '9' {
|
||||
number, fragment, isFloat, err = lexer.tokenizeNumber(8)
|
||||
} else {
|
||||
return file.NewError (
|
||||
lexer.file.Location(1),
|
||||
"unexpected character in number literal",
|
||||
file.ErrorKindError)
|
||||
}
|
||||
} else {
|
||||
number, fragment, isFloat, err = lexer.tokenizeNumber(10)
|
||||
|
@ -24,6 +24,8 @@ const (
|
||||
|
||||
TokenKindColon
|
||||
TokenKindDot
|
||||
TokenKindElipsis
|
||||
TokenKindComma
|
||||
|
||||
TokenKindLBracket
|
||||
TokenKindRBracket
|
||||
@ -133,6 +135,10 @@ func (tokenKind TokenKind) Describe () (description string) {
|
||||
description = "Colon"
|
||||
case TokenKindDot:
|
||||
description = "Dot"
|
||||
case TokenKindElipsis:
|
||||
description = "Elipsis"
|
||||
case TokenKindComma:
|
||||
description = "Comma"
|
||||
case TokenKindLBracket:
|
||||
description = "LBracket"
|
||||
case TokenKindRBracket:
|
||||
|
91
parser/argument.go
Normal file
91
parser/argument.go
Normal 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
|
||||
}
|
@ -1,18 +1,34 @@
|
||||
package parser
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/arf/file"
|
||||
import "git.tebibyte.media/sashakoshka/arf/lexer"
|
||||
|
||||
// parse body parses the body of an arf file, after the metadata header.
|
||||
func (parser *ParsingOperation) parseBody () (err error) {
|
||||
err = parser.nextToken(lexer.TokenKindName)
|
||||
if err != nil { return }
|
||||
for {
|
||||
err = parser.expect(lexer.TokenKindName)
|
||||
if err != nil { return }
|
||||
|
||||
switch parser.token.Value().(string) {
|
||||
case "data":
|
||||
case "type":
|
||||
case "func":
|
||||
case "face":
|
||||
sectionType := parser.token.Value().(string)
|
||||
switch sectionType {
|
||||
case "data":
|
||||
var section *DataSection
|
||||
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
|
||||
}
|
||||
|
310
parser/data.go
310
parser/data.go
@ -1,6 +1,312 @@
|
||||
package parser
|
||||
|
||||
// parseData parses a data section
|
||||
func (parser *ParsingOperation) parseData () (err error) {
|
||||
import "git.tebibyte.media/sashakoshka/arf/file"
|
||||
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
|
||||
}
|
||||
|
@ -111,3 +111,12 @@ func (parser *ParsingOperation) nextToken (allowed ...lexer.TokenKind) (err erro
|
||||
err = parser.expect(allowed...)
|
||||
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
|
||||
}
|
||||
|
@ -1,33 +1,75 @@
|
||||
package parser
|
||||
|
||||
import "reflect"
|
||||
import "io"
|
||||
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)
|
||||
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(err.Error())
|
||||
test.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(tree, correct) {
|
||||
test.Log("trees not equal")
|
||||
if treeString != 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)
|
||||
checkTree ("../tests/parser/meta",
|
||||
`:arf
|
||||
author "Sasha Koshka"
|
||||
license "GPLv3"
|
||||
require "someModule"
|
||||
require "otherModule"
|
||||
---
|
||||
`, 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
248
parser/tree-tostring.go
Normal 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
|
||||
}
|
152
parser/tree.go
152
parser/tree.go
@ -1,5 +1,8 @@
|
||||
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
|
||||
// can be expected to be syntactically correct, but it might not be semantically
|
||||
// correct (because it has not been analyzed yet.)
|
||||
@ -7,5 +10,152 @@ type SyntaxTree struct {
|
||||
license 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
|
||||
}
|
||||
|
@ -1,3 +1,3 @@
|
||||
:arf
|
||||
--- rw -> -349820394 932748397 239485.37520 "hello world!\n" 'E' helloWorld:.[]{}
|
||||
--- rw -> -349820394 932748397 239485.37520 "hello world!\n" 'E' helloWorld:.,..[]{}
|
||||
+ - ++ -- * / @ ! % ~ < << > >> | || & &&
|
||||
|
@ -1,4 +1,6 @@
|
||||
:arf
|
||||
0
|
||||
8
|
||||
83628266
|
||||
0b100111111000001000011101010
|
||||
0x4Fc10Ea
|
||||
|
@ -3,25 +3,50 @@
|
||||
|
||||
data wr integer:Int 3202
|
||||
|
||||
data wr mutInteger:Int:mut 3202
|
||||
|
||||
data wr integerPointer:{Int}
|
||||
# TODO: data wr integerPointer:{Int} [& integer]
|
||||
|
||||
data wr mutIntegerPointer:{Int}:mut
|
||||
|
||||
data wr integerArray16:{Int 16}
|
||||
|
||||
data wr integerArrayVariable:{Int ...}
|
||||
data wr integerArrayVariable:{Int ..}
|
||||
|
||||
data wr integerArrayInitialized:{Int 16}
|
||||
3948 293 293049 948 912
|
||||
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
|
||||
: this 324
|
||||
: that 2139
|
||||
.this 324
|
||||
.that 2139
|
||||
|
||||
data wr nestedObject:Obj
|
||||
: this
|
||||
: bird0 324
|
||||
: bird1 "hello world"
|
||||
: that
|
||||
: bird2 123.8439
|
||||
: bird3 9328.21348239
|
||||
.this
|
||||
.bird0 324
|
||||
.bird1 "hello world"
|
||||
.that
|
||||
.bird2 123.8439
|
||||
.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
|
||||
|
@ -30,3 +30,19 @@ func PermissionFrom (data string) (permission Permission) {
|
||||
permission.External = ModeFrom(rune(data[1]))
|
||||
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
|
||||
}
|
||||
|
Reference in New Issue
Block a user