Merge pull request 'func-section' (#1) from func-section into main

Reviewed-on: arf/arf#1
This commit is contained in:
Sasha Koshka 2022-09-04 00:09:13 +00:00
commit 845c12c78b
27 changed files with 1200 additions and 149 deletions

View File

@ -2,7 +2,7 @@ package arfc
import "os"
import "fmt"
import "git.tebibyte.media/sashakoshka/arf"
import "git.tebibyte.media/arf/arf"
func main () {
if len(os.Args) != 2 {

View File

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

2
go.mod
View File

@ -1,3 +1,3 @@
module git.tebibyte.media/sashakoshka/arf
module git.tebibyte.media/arf/arf
go 1.18

View File

@ -2,7 +2,7 @@ package infoerr
import "os"
import "fmt"
import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/arf/arf/file"
type ErrorKind int

View File

@ -1,9 +1,9 @@
package lexer
import "io"
import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/file"
import "git.tebibyte.media/arf/arf/types"
import "git.tebibyte.media/arf/arf/infoerr"
// LexingOperation holds information about an ongoing lexing operataion.
type LexingOperation struct {
@ -253,14 +253,26 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
lexer.addToken(token)
case '%':
token := lexer.newToken()
token.kind = TokenKindPercent
lexer.addToken(token)
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 '~':
token := lexer.newToken()
token.kind = TokenKindTilde
lexer.addToken(token)
err = lexer.nextRune()
if err != nil { return }
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
@ -275,6 +287,11 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
token.kind = TokenKindLShift
err = lexer.nextRune()
token.location.SetWidth(2)
if lexer.char == '=' {
token.kind = TokenKindLShiftAssignment
err = lexer.nextRune()
token.location.SetWidth(3)
}
} else if lexer.char == '=' {
token.kind = TokenKindLessThanEqualTo
err = lexer.nextRune()
@ -290,6 +307,11 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
token.kind = TokenKindRShift
err = lexer.nextRune()
token.location.SetWidth(2)
if lexer.char == '=' {
token.kind = TokenKindRShiftAssignment
err = lexer.nextRune()
token.location.SetWidth(3)
}
} else if lexer.char == '=' {
token.kind = TokenKindGreaterThanEqualTo
err = lexer.nextRune()
@ -305,6 +327,10 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
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)
case '&':
@ -316,6 +342,21 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
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)
case '^':
token := lexer.newToken()
err = lexer.nextRune()
if err != nil { return }
token.kind = TokenKindBinaryXor
if lexer.char == '=' {
token.kind = TokenKindBinaryXorAssignment
err = lexer.nextRune()
token.location.SetWidth(2)
}
lexer.addToken(token)
default:
@ -331,11 +372,11 @@ func (lexer *LexingOperation) tokenizeSymbolBeginning () (err error) {
}
func (lexer *LexingOperation) tokenizeDashBeginning () (err error) {
token := lexer.newToken()
err = lexer.nextRune()
if err != nil { return }
if lexer.char == '-' {
token := lexer.newToken()
token.kind = TokenKindDecrement
token.location.SetWidth(2)
@ -349,18 +390,16 @@ func (lexer *LexingOperation) tokenizeDashBeginning () (err error) {
}
lexer.addToken(token)
} else if lexer.char == '>' {
token := lexer.newToken()
token.kind = TokenKindReturnDirection
token.location.SetWidth(2)
err = lexer.nextRune()
err = lexer.nextRune()
if err != nil { return }
lexer.addToken(token)
} else if lexer.char >= '0' && lexer.char <= '9' {
lexer.tokenizeNumberBeginning(true)
} else {
token := lexer.newToken()
token.kind = TokenKindMinus
lexer.addToken(token)
}

View File

@ -1,9 +1,9 @@
package lexer
import "testing"
import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/file"
import "git.tebibyte.media/arf/arf/types"
import "git.tebibyte.media/arf/arf/infoerr"
func quickToken (width int, kind TokenKind, value any) (token Token) {
token.location.SetWidth(width)
@ -152,19 +152,27 @@ func TestTokenizeAll (test *testing.T) {
quickToken(1, TokenKindAt, nil),
quickToken(1, TokenKindExclamation, nil),
quickToken(1, TokenKindPercent, nil),
quickToken(2, TokenKindPercentAssignment, nil),
quickToken(1, TokenKindTilde, nil),
quickToken(2, TokenKindTildeAssignment, nil),
quickToken(1, TokenKindEqualTo, nil),
quickToken(2, TokenKindNotEqualTo, nil),
quickToken(1, TokenKindLessThan, nil),
quickToken(2, TokenKindLessThanEqualTo, nil),
quickToken(2, TokenKindLShift, nil),
quickToken(3, TokenKindLShiftAssignment, nil),
quickToken(1, TokenKindGreaterThan, nil),
quickToken(2, TokenKindGreaterThanEqualTo, nil),
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),
)
}

View File

@ -1,7 +1,7 @@
package lexer
import "strconv"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/infoerr"
// tokenizeSymbolBeginning lexes a token that starts with a number.
func (lexer *LexingOperation) tokenizeNumberBeginning (negative bool) (err error) {
@ -36,10 +36,17 @@ func (lexer *LexingOperation) tokenizeNumberBeginning (negative bool) (err error
isFloat, amountRead,
err = lexer.tokenizeNumber(10)
} else if lexer.char >= '0' && lexer.char <= '9' {
} else if lexer.char >= '0' && lexer.char <= '7' {
intNumber, floatNumber,
isFloat, amountRead,
err = lexer.tokenizeNumber(8)
} else if lexer.char != ' ' { // a space should correctly
err = infoerr.NewError ( // terminate this
lexer.file.Location(1),
"unexpected rune '" + string(lexer.char) +
"' in integer literal",
infoerr.ErrorKindError)
return
}
} else {
intNumber, floatNumber,

View File

@ -1,7 +1,7 @@
package lexer
import "strconv"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/infoerr"
// tokenizeString tokenizes a string or rune literal.
func (lexer *LexingOperation) tokenizeString (isRuneLiteral bool) (err error) {

View File

@ -1,8 +1,8 @@
package lexer
import "fmt"
import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/file"
import "git.tebibyte.media/arf/arf/infoerr"
// TokenKind is an enum represzenting what role a token has.
type TokenKind int
@ -43,20 +43,28 @@ const (
TokenKindAt
TokenKindExclamation
TokenKindPercent
TokenKindPercentAssignment
TokenKindTilde
TokenKindTildeAssignment
TokenKindEqualTo
TokenKindNotEqualTo
TokenKindLessThanEqualTo
TokenKindLessThan
TokenKindLShift
TokenKindLShiftAssignment
TokenKindGreaterThan
TokenKindGreaterThanEqualTo
TokenKindRShift
TokenKindRShiftAssignment
TokenKindBinaryOr
TokenKindBinaryOrAssignment
TokenKindLogicalOr
TokenKindBinaryAnd
TokenKindBinaryAndAssignment
TokenKindLogicalAnd
TokenKindBinaryXor
TokenKindBinaryXorAssignment
)
// Token represents a single token. It holds its location in the file, as well
@ -67,6 +75,14 @@ type Token struct {
value any
}
// NewToken provides a way for a new token to be created by other modules for
// comparison purposes.
func NewToken (kind TokenKind, value any) (token Token) {
token.kind = kind
token.value = value
return
}
// Kind returns the semantic role of the token.
func (token Token) Kind () (kind TokenKind) {
return token.kind
@ -175,8 +191,12 @@ func (tokenKind TokenKind) Describe () (description string) {
description = "Exclamation"
case TokenKindPercent:
description = "Percent"
case TokenKindPercentAssignment:
description = "PercentAssignment"
case TokenKindTilde:
description = "Tilde"
case TokenKindTildeAssignment:
description = "TildeAssignment"
case TokenKindEqualTo:
description = "EqualTo"
case TokenKindNotEqualTo:
@ -187,20 +207,32 @@ func (tokenKind TokenKind) Describe () (description string) {
description = "LessThanEqualTo"
case TokenKindLShift:
description = "LShift"
case TokenKindLShiftAssignment:
description = "LShiftAssignment"
case TokenKindGreaterThan:
description = "GreaterThan"
case TokenKindGreaterThanEqualTo:
description = "GreaterThanEqualTo"
case TokenKindRShift:
description = "RShift"
case TokenKindRShiftAssignment:
description = "RShiftAssignment"
case TokenKindBinaryOr:
description = "BinaryOr"
case TokenKindBinaryOrAssignment:
description = "BinaryOrAssignment"
case TokenKindLogicalOr:
description = "LogicalOr"
case TokenKindBinaryAnd:
description = "BinaryAnd"
case TokenKindBinaryAndAssignment:
description = "BinaryAndAssignment"
case TokenKindLogicalAnd:
description = "LogicalAnd"
case TokenKindBinaryXor:
description = "BinaryXor"
case TokenKindBinaryXorAssignment:
description = "BinaryXorAssignment"
}
return

View File

@ -1,9 +0,0 @@
package arf
import "io"
import "git.tebibyte.media/sashakoshka/arf/parser"
func CompileModule (modulePath string, output io.Writer) (err error) {
_, err = parser.Parse(modulePath)
return
}

View File

@ -1,7 +1,7 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/lexer"
import "git.tebibyte.media/arf/arf/infoerr"
var validArgumentStartTokens = []lexer.TokenKind {
lexer.TokenKindName,
@ -12,7 +12,6 @@ var validArgumentStartTokens = []lexer.TokenKind {
lexer.TokenKindString,
lexer.TokenKindRune,
lexer.TokenKindLBrace,
lexer.TokenKindLBracket,
}
@ -29,6 +28,8 @@ func (parser *ParsingOperation) parseArgument () (argument Argument, err error)
if err != nil { return }
if parser.token.Is(lexer.TokenKindColon) {
err = parser.nextToken()
if err != nil { return }
var what Type
what, err = parser.parseType()
if err != nil { return }
@ -77,9 +78,9 @@ func (parser *ParsingOperation) parseArgument () (argument Argument, err error)
argument.value = parser.token.Value().(rune)
parser.nextToken()
// case lexer.TokenKindLBrace:
// case lexer.TokenKindLBracket:
case lexer.TokenKindLBracket:
argument.kind = ArgumentKindPhrase
argument.value, err = parser.parseArgumentLevelPhrase()
default:
panic (

View File

@ -1,7 +1,7 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/lexer"
import "git.tebibyte.media/arf/arf/infoerr"
// parse body parses the body of an arf file, after the metadata header.
func (parser *ParsingOperation) parseBody () (err error) {
@ -57,6 +57,14 @@ func (parser *ParsingOperation) parseBody () (err error) {
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 + "\"",

View File

@ -1,8 +1,8 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/types"
import "git.tebibyte.media/arf/arf/lexer"
import "git.tebibyte.media/arf/arf/infoerr"
// parseData parses a data section.
func (parser *ParsingOperation) parseDataSection () (
@ -73,7 +73,7 @@ func (parser *ParsingOperation) parseInitializationValues (
var initializationValues ObjectInitializationValues
initializationValues, err = parser.parseObjectInitializationValues()
initializationArgument.kind = ArgumentKindObjectInitializationValues
initializationArgument.value = &initializationValues
initializationArgument.value = initializationValues
} else {
@ -82,7 +82,7 @@ func (parser *ParsingOperation) parseInitializationValues (
var initializationValues ArrayInitializationValues
initializationValues, err = parser.parseArrayInitializationValues()
initializationArgument.kind = ArgumentKindArrayInitializationValues
initializationArgument.value = &initializationValues
initializationArgument.value = initializationValues
}
return

View File

@ -1,8 +1,8 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/types"
import "git.tebibyte.media/arf/arf/lexer"
import "git.tebibyte.media/arf/arf/infoerr"
func (parser *ParsingOperation) parseEnumSection () (
section *EnumSection,

View File

@ -1,8 +1,8 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/types"
import "git.tebibyte.media/arf/arf/lexer"
import "git.tebibyte.media/arf/arf/infoerr"
// parseFaceSection parses an interface section.
func (parser *ParsingOperation) parseFaceSection () (
@ -64,7 +64,6 @@ func (parser *ParsingOperation) parseFaceSection () (
if err != nil { return }
}
return
}
// parseFaceBehavior parses a single interface behavior. Indentation level is
@ -127,6 +126,4 @@ func (parser *ParsingOperation) parseFaceBehavior () (
declaration)
}
}
return
}

205
parser/func.go Normal file
View File

@ -0,0 +1,205 @@
package parser
import "git.tebibyte.media/arf/arf/types"
import "git.tebibyte.media/arf/arf/lexer"
import "git.tebibyte.media/arf/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() }
// 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)
// get arguments
err = parser.nextToken(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
err = parser.parseFuncArguments(section)
if err != nil { return }
// check to see if the function is external
if !parser.token.Is(lexer.TokenKindIndent) { return }
if parser.token.Value().(int) != 1 { return }
err = parser.nextToken()
if err != nil { return }
if parser.token.Is(lexer.TokenKindName) &&
parser.token.Value().(string) == "external" {
section.external = true
err = parser.nextToken(lexer.TokenKindNewline)
if err != nil { return }
if err != nil { return }
err = parser.nextToken()
return
}
// if it isn't, backtrack to the start of the line
parser.previousToken()
// parse root block
section.root, err = parser.parseBlock(1)
if err != nil { return }
if len(section.root) == 0 {
infoerr.NewError (section.location,
"this function has nothing in it",
infoerr.ErrorKindWarn).Print()
}
return
}
// parseFuncArguments parses a function's inputs, outputs, and reciever if that
// exists.
func (parser *ParsingOperation) parseFuncArguments (
into *FuncSection,
) (
err error,
) {
for {
// if we've left the block, stop parsing
if !parser.token.Is(lexer.TokenKindIndent) ||
parser.token.Value().(int) != 1 {
if into.receiver != nil {
err = parser.token.NewError (
"func section terminated without a " +
"separator token",
infoerr.ErrorKindError)
}
return
}
// determine whether this is an input, output, or the method
// reciever
err = parser.nextToken (
lexer.TokenKindAt,
lexer.TokenKindLessThan,
lexer.TokenKindGreaterThan,
lexer.TokenKindSeparator)
if err != nil { return }
startToken := parser.token
if parser.token.Is(lexer.TokenKindSeparator) {
// if we have encountered a separator, that means our
// work is done here.
err = parser.nextToken(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
return
}
switch startToken.Kind() {
case lexer.TokenKindAt:
reciever := Declaration { }
reciever.location = parser.token.Location()
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
reciever.name = parser.token.Value().(string)
// get type
err = parser.nextToken(lexer.TokenKindColon)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
reciever.what, err = parser.parseType()
if err != nil { return }
if into.receiver != nil {
err = startToken.NewError (
"cannot have more than one method " +
"receiver",
infoerr.ErrorKindError)
return
} else {
into.receiver = &reciever
}
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
case lexer.TokenKindGreaterThan:
input := Declaration { }
input.location = parser.token.Location()
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
input.name = parser.token.Value().(string)
// get type
err = parser.nextToken(lexer.TokenKindColon)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
input.what, err = parser.parseType()
if err != nil { return }
into.inputs = append(into.inputs, input)
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
case lexer.TokenKindLessThan:
output := FuncOutput { }
output.location = parser.token.Location()
// get name
err = parser.nextToken(lexer.TokenKindName)
if err != nil { return }
output.name = parser.token.Value().(string)
// get type
err = parser.nextToken(lexer.TokenKindColon)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
output.what, err = parser.parseType()
if err != nil { return }
// parse default value
if parser.token.Is(lexer.TokenKindNewline) {
err = parser.nextToken()
if err != nil { return }
output.defaultValue, err =
parser.parseInitializationValues(1)
into.outputs = append(into.outputs, output)
if err != nil { return }
} else {
output.defaultValue, err =
parser.parseArgument()
into.outputs = append(into.outputs, output)
if err != nil { return }
err = parser.expect(lexer.TokenKindNewline)
if err != nil { return }
err = parser.nextToken()
if err != nil { return }
}
}
}
}

119
parser/func_test.go Normal file
View File

@ -0,0 +1,119 @@
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 7]
[fn 329 983 7]
[fn 329 983 57]
[fn [gn 329 983 57] 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 [+ [* 250 0] 98 x [/ 9832 y] 930]]
[! true]
[~ 1]
[~= x]
[% 873 32]
[= 5 5]
[!= 4 4]
[<= 4 98]
[< 4 98]
[<< 15 4]
[<<= x 4]
[>= 98 4]
[> 98 4]
[>> 240 4]
[>>= x 4]
[| 1 2]
[|= x 2]
[& 6 3]
[&= x 3]
[&& true true]
[|| true false]
func ro fReturnDirection
< err:Error
---
[someFunc 498 2980 90] -> thing:Int err
[otherFunc] -> thing err:Error
[fn 329 983 57] -> thing:Int err
func ro gControlFlow
---
[defer]
[something]
[otherThing]
[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 ro hSetPhrase
---
[set x:Int 3]
[set y:{Int} [loc x]]
[set z:{Int 8}]
398
9
2309
983
-2387
478
555
123
[set bird:Bird]
.that
.whenYou 99999
.this 324
`, test)
}

View File

@ -1,7 +1,7 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/lexer"
import "git.tebibyte.media/arf/arf/infoerr"
// parseMeta parsese the metadata header at the top of an arf file.
func (parser *ParsingOperation) parseMeta () (err error) {

View File

@ -1,8 +1,8 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/types"
import "git.tebibyte.media/arf/arf/lexer"
import "git.tebibyte.media/arf/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.
@ -94,8 +94,6 @@ func (parser *ParsingOperation) parseObjtMember () (
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)

View File

@ -3,9 +3,9 @@ package parser
import "io"
import "os"
import "path/filepath"
import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/lexer"
import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/file"
import "git.tebibyte.media/arf/arf/lexer"
import "git.tebibyte.media/arf/arf/infoerr"
// ParsingOperation holds information about an ongoing parsing operation.
type ParsingOperation struct {

354
parser/phrase.go Normal file
View File

@ -0,0 +1,354 @@
package parser
import "git.tebibyte.media/arf/arf/lexer"
// operatorTokens lists all symbolic tokens that can act as a command to a
// phrase.
var operatorTokens = []lexer.TokenKind {
lexer.TokenKindColon,
lexer.TokenKindPlus,
lexer.TokenKindMinus,
lexer.TokenKindIncrement,
lexer.TokenKindDecrement,
lexer.TokenKindAsterisk,
lexer.TokenKindSlash,
lexer.TokenKindExclamation,
lexer.TokenKindPercent,
lexer.TokenKindPercentAssignment,
lexer.TokenKindTilde,
lexer.TokenKindTildeAssignment,
lexer.TokenKindEqualTo,
lexer.TokenKindNotEqualTo,
lexer.TokenKindLessThanEqualTo,
lexer.TokenKindLessThan,
lexer.TokenKindLShift,
lexer.TokenKindLShiftAssignment,
lexer.TokenKindGreaterThan,
lexer.TokenKindGreaterThanEqualTo,
lexer.TokenKindRShift,
lexer.TokenKindRShiftAssignment,
lexer.TokenKindBinaryOr,
lexer.TokenKindBinaryOrAssignment,
lexer.TokenKindLogicalOr,
lexer.TokenKindBinaryAnd,
lexer.TokenKindBinaryAndAssignment,
lexer.TokenKindLogicalAnd,
lexer.TokenKindBinaryXor,
lexer.TokenKindBinaryXorAssignment,
}
// isTokenOperator returns whether or not the token is an operator token.
func isTokenOperator (token lexer.Token) (isOperator bool) {
for _, kind := range operatorTokens {
if token.Is(kind) {
isOperator = true
return
}
}
return
}
// validPhraseStartTokens lists all tokens that are expected when parsing the
// first part of a phrase.
var validPhraseStartTokens = append (
operatorTokens,
lexer.TokenKindLBracket,
lexer.TokenKindName,
lexer.TokenKindString)
// validBlockLevelPhraseTokens lists all tokens that are expected when parsing
// a block level phrase.
var validBlockLevelPhraseTokens = append (
validArgumentStartTokens,
lexer.TokenKindNewline,
lexer.TokenKindReturnDirection)
// validDelimitedPhraseTokens is like validBlockLevelPhraseTokens, but it also
// includes a right brace token.
var validDelimitedPhraseTokens = append (
validArgumentStartTokens,
lexer.TokenKindNewline,
lexer.TokenKindIndent,
lexer.TokenKindRBracket,
lexer.TokenKindReturnDirection)
// controlFlowKinds contains a list of all phrase kinds that must have a block
// underneath them.
var controlFlowKinds = []PhraseKind {
PhraseKindIf,
PhraseKindElse,
PhraseKindElseIf,
PhraseKindFor,
PhraseKindWhile,
PhraseKindDefer,
PhraseKindCase,
}
// parseBlock parses an indented block of phrases
func (parser *ParsingOperation) parseBlock (
indent int,
) (
block Block,
err error,
) {
for {
// if we've left the block, stop parsing
if !parser.token.Is(lexer.TokenKindIndent) { return }
if parser.token.Value().(int) != indent { return }
var phrase Phrase
phrase, err = parser.parseBlockLevelPhrase(indent)
block = append(block, phrase)
if err != nil { return }
}
return
}
// parseBlockLevelPhrase parses a phrase that is not being used as an argument
// to something else. This method is allowed to do things like parse return
// directions, and indented blocks beneath the phrase.
func (parser *ParsingOperation) parseBlockLevelPhrase (
indent int,
) (
phrase Phrase,
err error,
) {
if !parser.token.Is(lexer.TokenKindIndent) { return }
if parser.token.Value().(int) != indent { return }
err = parser.nextToken(validPhraseStartTokens...)
if err != nil { return }
expectRightBracket := false
if parser.token.Is(lexer.TokenKindLBracket) {
expectRightBracket = true
err = parser.nextToken()
if err != nil { return }
}
// get command
err = parser.expect(validPhraseStartTokens...)
if err != nil { return }
phrase.command, phrase.kind, err = parser.parsePhraseCommand()
if err != nil { return }
for {
if expectRightBracket {
// delimited
// [someFunc arg1 arg2 arg3] -> someVariable
err = parser.expect(validDelimitedPhraseTokens...)
if err != nil { return }
if parser.token.Is(lexer.TokenKindRBracket) {
// this is an ending delimiter
err = parser.nextToken()
if err != nil { return }
break
} else if parser.token.Is(lexer.TokenKindNewline) {
// we are delimited, so we can safely skip
// newlines
err = parser.nextToken()
if err != nil { return }
continue
} else if parser.token.Is(lexer.TokenKindIndent) {
// we are delimited, so we can safely skip
// indents
err = parser.nextToken()
if err != nil { return }
continue
}
} else {
// not delimited
// someFunc arg1 arg2 arg3 -> someVariable
err = parser.expect(validBlockLevelPhraseTokens...)
if err != nil { return }
if parser.token.Is(lexer.TokenKindReturnDirection) {
// we've reached a return direction, so that
// means this is the end of the phrase
break
} else if parser.token.Is(lexer.TokenKindNewline) {
// we've reached the end of the line, so that
// means this is the end of the phrase.
break
}
}
// this is an argument
var argument Argument
argument, err = parser.parseArgument()
phrase.arguments = append(phrase.arguments, argument)
}
// expect newline or return direction
err = parser.expect (
lexer.TokenKindNewline,
lexer.TokenKindReturnDirection)
if err != nil { return }
expectReturnDirection := parser.token.Is(lexer.TokenKindReturnDirection)
// if we have hit a return direction, parse it...
if expectReturnDirection {
err = parser.nextToken()
if err != nil { return }
for {
err = parser.expect (
lexer.TokenKindNewline,
lexer.TokenKindName)
if err != nil { return }
// ...until we hit a newline
if parser.token.Is(lexer.TokenKindNewline) { break }
var returnTo Argument
returnTo, err = parser.parseArgument()
if err != nil { return }
phrase.returnsTo = append(phrase.returnsTo, returnTo)
}
}
err = parser.nextToken()
if err != nil { return }
// if this is a set phrase, parse initialization values under it
if phrase.kind == PhraseKindSet {
var values Argument
values, err = parser.parseInitializationValues(indent)
if values.kind != ArgumentKindNil {
phrase.arguments = append(phrase.arguments, values)
}
return
}
// if this is a control flow phrase, parse block under it
isControlFlow := false
for _, kind := range controlFlowKinds {
if phrase.kind == kind {
isControlFlow = true
break
}
}
if !isControlFlow { return }
// if it is any of those, parse the block under it
phrase.block, err = parser.parseBlock(indent + 1)
return
}
// parseArgumentLevelPhrase parses a phrase that is being used as an argument to
// something. It is forbidden from using return direction, and it must be
// delimited by brackets.
func (parser *ParsingOperation) parseArgumentLevelPhrase () (
phrase Phrase,
err error,
) {
err = parser.expect(lexer.TokenKindLBracket)
if err != nil { return }
// get command
err = parser.nextToken(validPhraseStartTokens...)
if err != nil { return }
phrase.command, phrase.kind, err = parser.parsePhraseCommand()
if err != nil { return }
for {
// delimited
// [someFunc arg1 arg2 arg3] -> someVariable
err = parser.expect(validDelimitedPhraseTokens...)
if err != nil { return }
if parser.token.Is(lexer.TokenKindRBracket) {
// this is an ending delimiter
err = parser.nextToken()
if err != nil { return }
return
} else if parser.token.Is(lexer.TokenKindNewline) {
// we are delimited, so we can safely skip
// newlines
err = parser.nextToken()
if err != nil { return }
continue
} else if parser.token.Is(lexer.TokenKindIndent) {
// we are delimited, so we can safely skip
// indents
err = parser.nextToken()
if err != nil { return }
continue
}
// this is an argument
var argument Argument
argument, err = parser.parseArgument()
phrase.arguments = append(phrase.arguments, argument)
}
}
// parsePhraseCommand parses the command argument of a phrase.
func (parser *ParsingOperation) parsePhraseCommand () (
command Argument,
kind PhraseKind,
err error,
) {
if isTokenOperator(parser.token) {
err = parser.expect(operatorTokens...)
if err != nil { return }
command.location = parser.token.Location()
command.kind = ArgumentKindOperator
command.value = parser.token.Kind()
if parser.token.Is(lexer.TokenKindColon) {
kind = PhraseKindCase
} else {
kind = PhraseKindOperator
}
err = parser.nextToken()
return
}
// phrase command is not an operator, it is just a normal argument
command, err = parser.parseArgument()
if err != nil { return }
// determine semantic role of phrase
if command.kind == ArgumentKindString {
kind = PhraseKindCallExternal
} else if command.kind == ArgumentKindIdentifier {
identifier := command.value.(Identifier)
if len(identifier.trail) == 1 {
switch identifier.trail[0] {
case "set":
kind = PhraseKindSet
case "loc":
kind = PhraseKindReference
case "defer":
kind = PhraseKindDefer
case "if":
kind = PhraseKindIf
case "elseif":
kind = PhraseKindElseIf
case "else":
kind = PhraseKindElse
case "switch":
kind = PhraseKindSwitch
case "while":
kind = PhraseKindWhile
case "for":
kind = PhraseKindFor
}
}
}
return
}

View File

@ -3,10 +3,20 @@ package parser
import "io"
import "strings"
import "testing"
// import "git.tebibyte.media/sashakoshka/arf/types"
// import "git.tebibyte.media/arf/arf/types"
func checkTree (modulePath string, correct string, test *testing.T) {
tree, err := Parse(modulePath)
tree, err := Parse(modulePath)
if tree == nil {
test.Log("TREE IS NIL!")
if err != io.EOF && err != nil {
test.Log("returned error:")
test.Log(err)
}
test.Fail()
return
}
treeString := tree.ToString(0)
treeRunes := []rune(treeString)

View File

@ -2,6 +2,7 @@ package parser
import "fmt"
import "sort"
import "git.tebibyte.media/arf/arf/lexer"
func doIndent (indent int, input ...string) (output string) {
for index := 0; index < indent; index ++ {
@ -70,10 +71,15 @@ func (tree *SyntaxTree) ToString (indent int) (output string) {
for _, name := range dataSectionKeys {
output += tree.dataSections[name].ToString(indent)
}
funcSectionKeys := sortMapKeysAlphabetically(tree.funcSections)
for _, name := range funcSectionKeys {
output += tree.funcSections[name].ToString(indent)
}
return
}
func (identifier *Identifier) ToString () (output string) {
func (identifier Identifier) ToString () (output string) {
for index, trailItem := range identifier.trail {
if index > 0 {
output += "."
@ -110,13 +116,13 @@ func (what *Type) ToString () (output string) {
return
}
func (declaration *Declaration) ToString () (output string) {
func (declaration Declaration) ToString () (output string) {
output += declaration.name + ":"
output += declaration.what.ToString()
return
}
func (attributes *ObjectInitializationValues) ToString (
func (attributes ObjectInitializationValues) ToString (
indent int,
) (
output string,
@ -136,7 +142,7 @@ func (attributes *ObjectInitializationValues) ToString (
return
}
func (values *ArrayInitializationValues) ToString (
func (values ArrayInitializationValues) ToString (
indent int,
) (
output string,
@ -148,39 +154,6 @@ func (values *ArrayInitializationValues) ToString (
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 {
@ -191,30 +164,30 @@ func (argument *Argument) ToString (indent int, breakLine bool) (output string)
switch argument.kind {
case ArgumentKindPhrase:
output += argument.value.(*Phrase).ToString (
output += argument.value.(Phrase).ToString (
indent,
breakLine)
case ArgumentKindObjectInitializationValues:
// this should only appear in contexts where breakLine is true
output += argument.value.(*ObjectInitializationValues).
output += argument.value.(ObjectInitializationValues).
ToString(indent)
case ArgumentKindArrayInitializationValues:
// this should only appear in contexts where breakLine is true
output += argument.value.(*ArrayInitializationValues).
output += argument.value.(ArrayInitializationValues).
ToString(indent)
case ArgumentKindIdentifier:
output += doIndent (
indent,
argument.value.(*Identifier).ToString())
argument.value.(Identifier).ToString())
if breakLine { output += "\n" }
case ArgumentKindDeclaration:
output += doIndent (
indent,
argument.value.(*Declaration).ToString())
argument.value.(Declaration).ToString())
if breakLine { output += "\n" }
case ArgumentKindInt, ArgumentKindUInt, ArgumentKindFloat:
@ -234,10 +207,71 @@ func (argument *Argument) ToString (indent int, breakLine bool) (output string)
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.
var stringValue string
switch argument.value.(lexer.TokenKind) {
case lexer.TokenKindColon:
stringValue = ":"
case lexer.TokenKindPlus:
stringValue = "+"
case lexer.TokenKindMinus:
stringValue = "-"
case lexer.TokenKindIncrement:
stringValue = "++"
case lexer.TokenKindDecrement:
stringValue = "--"
case lexer.TokenKindAsterisk:
stringValue = "*"
case lexer.TokenKindSlash:
stringValue = "/"
case lexer.TokenKindExclamation:
stringValue = "!"
case lexer.TokenKindPercent:
stringValue = "%"
case lexer.TokenKindPercentAssignment:
stringValue = "%="
case lexer.TokenKindTilde:
stringValue = "~"
case lexer.TokenKindTildeAssignment:
stringValue = "~="
case lexer.TokenKindEqualTo:
stringValue = "="
case lexer.TokenKindNotEqualTo:
stringValue = "!="
case lexer.TokenKindLessThanEqualTo:
stringValue = "<="
case lexer.TokenKindLessThan:
stringValue = "<"
case lexer.TokenKindLShift:
stringValue = "<<"
case lexer.TokenKindLShiftAssignment:
stringValue = "<<="
case lexer.TokenKindGreaterThan:
stringValue = ">"
case lexer.TokenKindGreaterThanEqualTo:
stringValue = ">="
case lexer.TokenKindRShift:
stringValue = ">>"
case lexer.TokenKindRShiftAssignment:
stringValue = ">>="
case lexer.TokenKindBinaryOr:
stringValue = "|"
case lexer.TokenKindBinaryOrAssignment:
stringValue = "|="
case lexer.TokenKindLogicalOr:
stringValue = "||"
case lexer.TokenKindBinaryAnd:
stringValue = "&"
case lexer.TokenKindBinaryAndAssignment:
stringValue = "&="
case lexer.TokenKindLogicalAnd:
stringValue = "&&"
case lexer.TokenKindBinaryXor:
stringValue = "^"
case lexer.TokenKindBinaryXorAssignment:
stringValue = "^="
}
output += doIndent(indent, stringValue)
if breakLine { output += "\n" }
}
return
@ -389,3 +423,87 @@ func (behavior *FaceBehavior) ToString (indent int) (output string) {
return
}
func (phrase Phrase) ToString (indent int, ownLine bool) (output string) {
if ownLine {
output += doIndent(indent)
}
var initializationValues Argument
output += "[" + phrase.command.ToString(0, false)
for _, argument := range phrase.arguments {
isInitializationValue :=
argument.kind == ArgumentKindObjectInitializationValues ||
argument.kind == ArgumentKindArrayInitializationValues
if isInitializationValue {
initializationValues = argument
} else {
output += " " + argument.ToString(0, false)
}
}
output += "]"
if len(phrase.returnsTo) > 0 {
output += " ->"
for _, returnItem := range phrase.returnsTo {
output += " " + returnItem.ToString(0, false)
}
}
if ownLine {
output += "\n"
if initializationValues.kind != ArgumentKindNil {
output += initializationValues.ToString(indent + 1, true)
}
output += phrase.block.ToString(indent + 1)
}
return
}
func (block Block) ToString (indent int) (output string) {
for _, phrase := range block {
output += phrase.ToString(indent, true)
}
return
}
func (funcOutput FuncOutput) ToString () (output string) {
output += funcOutput.Declaration.ToString()
if funcOutput.defaultValue.kind != ArgumentKindNil {
output += " " + funcOutput.defaultValue.ToString(0, false)
}
return
}
func (section *FuncSection) ToString (indent int) (output string) {
output += doIndent (
indent,
"func ",
section.permission.ToString(), " ",
section.name, "\n")
if section.receiver != nil {
output += doIndent (
indent + 1,
"@ ", section.receiver.ToString(), "\n")
}
for _, inputItem := range section.inputs {
output += doIndent(indent + 1, "> ", inputItem.ToString(), "\n")
}
for _, outputItem := range section.outputs {
output += doIndent(indent + 1, "< ", outputItem.ToString(), "\n")
}
output += doIndent(indent + 1, "---\n")
if section.external {
output += doIndent(indent + 1, "external\n")
}
output += section.root.ToString(indent + 1)
return
}

View File

@ -1,7 +1,7 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/file"
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/arf/arf/file"
import "git.tebibyte.media/arf/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
@ -16,6 +16,7 @@ type SyntaxTree struct {
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.
@ -78,15 +79,6 @@ type ArrayInitializationValues struct {
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
@ -97,7 +89,7 @@ const (
// [name argument]
// [name argument argument]
// etc...
ArgumentKindPhrase = iota
ArgumentKindPhrase
// {name}
ArgumentKindDereference
@ -185,7 +177,7 @@ type ObjtMember struct {
defaultValue Argument
}
// ObjtSection represents an object type definition
// ObjtSection represents an object type definition.
type ObjtSection struct {
location file.Location
name string
@ -195,6 +187,7 @@ type ObjtSection struct {
members []ObjtMember
}
// EnumMember represents a member of an enum section.
type EnumMember struct {
location file.Location
name string
@ -229,3 +222,60 @@ type FaceSection struct {
permission types.Permission
behaviors map[string] FaceBehavior
}
// PhraseKind determines what semantic role a phrase plays.
type PhraseKind int
const (
PhraseKindCall = iota
PhraseKindCallExternal
PhraseKindOperator
PhraseKindSet
PhraseKindReference
PhraseKindDefer
PhraseKindIf
PhraseKindElseIf
PhraseKindElse
PhraseKindSwitch
PhraseKindCase
PhraseKindWhile
PhraseKindFor
)
// 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
kind PhraseKind
// only applicable for control flow phrases
block Block
}
// Block represents a scoped/indented block of code.
type Block []Phrase
// FuncOutput represents an input a function section. It is unlike an input in
// that it can have a default value.
type FuncOutput struct {
Declaration
defaultValue Argument
}
// FuncSection represents a function section.
type FuncSection struct {
location file.Location
name string
permission types.Permission
receiver *Declaration
inputs []Declaration
outputs []FuncOutput
root Block
external bool
}

View File

@ -1,8 +1,8 @@
package parser
import "git.tebibyte.media/sashakoshka/arf/types"
import "git.tebibyte.media/sashakoshka/arf/lexer"
// import "git.tebibyte.media/sashakoshka/arf/infoerr"
import "git.tebibyte.media/arf/arf/types"
import "git.tebibyte.media/arf/arf/lexer"
// import "git.tebibyte.media/arf/arf/infoerr"
// parseTypeSection parses a blind type definition, meaning it can inherit from
// anything including primitives, but cannot define structure.

View File

@ -1,3 +1,3 @@
:arf
--- rw -> -349820394 932748397 239485.37520 "hello world!\n" 'E' helloWorld:.,..[]{}
+ - ++ -- * / @ ! % ~ = != < <= << > >= >> | || & &&
+ - ++ -- * / @ ! % %= ~ ~= = != < <= << <<= > >= >> >>= | |= || & &= && ^ ^=

135
tests/parser/func/main.arf Normal file
View File

@ -0,0 +1,135 @@
: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 07
[fn 329 983 07]
[fn
329
983
071]
fn [gn
329 983
071] 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 [+ [* 0372 00] 98 x [/ 9832 y] 930]
! 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
071] -> thing:Int err
func ro gControlFlow
---
defer
something
otherThing
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 ro hSetPhrase
---
set x:Int 3
# loc is a reference, similar to * in C
set y:{Int} [loc x]
set z:{Int 8}
398 9 2309 983 -2387
478 555 123
set bird:Bird
.that
.whenYou 99999
.this 324