Merge pull request 'parser-remove-participle' (#7) from parser-remove-participle into remove-participle
Reviewed-on: sashakoshka/fspl#7
This commit is contained in:
commit
090d52b8f8
@ -5,17 +5,10 @@ import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
|
||||
// Expression is any construct that can be evaluated.
|
||||
type Expression interface {
|
||||
Statement
|
||||
Type () Type
|
||||
|
||||
expression ()
|
||||
}
|
||||
|
||||
// Statement is any construct that can be placed inside of a block expression.
|
||||
type Statement interface {
|
||||
statement()
|
||||
}
|
||||
|
||||
// Variable specifies a named variable. It can be assigned to a type matching
|
||||
// the variable declaration's type. Since it contains inherent type information,
|
||||
// it may be directly assigned to an interface. A variable is always a valid
|
||||
@ -29,7 +22,6 @@ type Variable struct {
|
||||
Declaration *Declaration
|
||||
}
|
||||
func (*Variable) expression(){}
|
||||
func (*Variable) statement(){}
|
||||
func (this *Variable) Type () Type { return this.Declaration.Type() }
|
||||
func (this *Variable) String () string {
|
||||
return this.Name
|
||||
@ -46,7 +38,6 @@ type Declaration struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*Declaration) expression(){}
|
||||
func (*Declaration) statement(){}
|
||||
func (this *Declaration) Type () Type { return this.Ty }
|
||||
func (this *Declaration) String () string {
|
||||
return fmt.Sprint(this.Name, ":", this.Ty)
|
||||
@ -69,7 +60,6 @@ type Call struct {
|
||||
Function *Function
|
||||
}
|
||||
func (*Call) expression(){}
|
||||
func (*Call) statement(){}
|
||||
func (this *Call) Type () Type { return this.Function.Signature.Return }
|
||||
func (this *Call) String () string {
|
||||
out := ""
|
||||
@ -92,7 +82,7 @@ func (this *Call) String () string {
|
||||
type MethodCall struct {
|
||||
// Syntax
|
||||
Position errors.Position
|
||||
Source *Variable
|
||||
Source Expression
|
||||
Name string
|
||||
Arguments []Expression
|
||||
|
||||
@ -102,7 +92,6 @@ type MethodCall struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*MethodCall) expression(){}
|
||||
func (*MethodCall) statement(){}
|
||||
func (this *MethodCall) Type () Type {
|
||||
if this.Method != nil {
|
||||
return this.Method.Signature.Return
|
||||
@ -133,7 +122,6 @@ type Subscript struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*Subscript) expression(){}
|
||||
func (*Subscript) statement(){}
|
||||
func (this *Subscript) Type () Type { return this.Ty }
|
||||
func (this *Subscript) String () string {
|
||||
return fmt.Sprint("[.", this.Slice, " ", this.Offset, "]")
|
||||
@ -151,14 +139,13 @@ type Slice struct {
|
||||
End Expression
|
||||
}
|
||||
func (*Slice) expression(){}
|
||||
func (*Slice) statement(){}
|
||||
func (this *Slice) Type () Type { return this.Slice.Type() }
|
||||
func (this *Slice) String () string {
|
||||
out := fmt.Sprint("[\\", this.Slice, " ")
|
||||
if this.Start != nil {
|
||||
out += fmt.Sprint(this.Start)
|
||||
}
|
||||
out += ":"
|
||||
out += "/" // TODO have trailing decimals pop off of numbers, replace this with ..
|
||||
if this.End != nil {
|
||||
out += fmt.Sprint(this.End)
|
||||
}
|
||||
@ -169,13 +156,13 @@ func (this *Slice) String () string {
|
||||
// of type Index. A length is never a valid location expression.
|
||||
type Length struct {
|
||||
// Syntax
|
||||
Slice Expression
|
||||
Position errors.Position
|
||||
Slice Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*Length) expression(){}
|
||||
func (*Length) statement(){}
|
||||
func (this *Length) Type () Type { return this.Ty }
|
||||
func (this *Length) String () string {
|
||||
return fmt.Sprint("[#", this.Slice, "]")
|
||||
@ -195,7 +182,6 @@ type Dereference struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*Dereference) expression(){}
|
||||
func (*Dereference) statement(){}
|
||||
func (this *Dereference) Type () Type { return this.Ty }
|
||||
func (this *Dereference) String () string {
|
||||
return fmt.Sprint("[.", this.Pointer, "]")
|
||||
@ -217,7 +203,6 @@ type Reference struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*Reference) expression(){}
|
||||
func (*Reference) statement(){}
|
||||
func (this *Reference) Type () Type { return this.Ty }
|
||||
func (this *Reference) String () string {
|
||||
return fmt.Sprint("[@", this.Value, "]")
|
||||
@ -232,7 +217,6 @@ type ValueCast struct {
|
||||
Value Expression
|
||||
}
|
||||
func (*ValueCast) expression(){}
|
||||
func (*ValueCast) statement(){}
|
||||
func (this *ValueCast) Type () Type { return this.Ty }
|
||||
func (this *ValueCast) String () string {
|
||||
return fmt.Sprint("[~ ", this.Ty, this.Value, "]")
|
||||
@ -248,7 +232,6 @@ type BitCast struct {
|
||||
Value Expression
|
||||
}
|
||||
func (*BitCast) expression(){}
|
||||
func (*BitCast) statement(){}
|
||||
func (this *BitCast) Type () Type { return this.Ty }
|
||||
func (this *BitCast) String () string {
|
||||
return fmt.Sprint("[~~ ", this.Ty, this.Value, "]")
|
||||
@ -268,7 +251,6 @@ type Operation struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*Operation) expression(){}
|
||||
func (*Operation) statement(){}
|
||||
func (this *Operation) Type () Type { return this.Ty }
|
||||
func (this *Operation) String () string {
|
||||
out := fmt.Sprint("[", this.Operator)
|
||||
@ -286,14 +268,13 @@ func (this *Operation) String () string {
|
||||
type Block struct {
|
||||
// Syntax
|
||||
Position errors.Position
|
||||
Steps []Statement
|
||||
Steps []Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
Scope
|
||||
}
|
||||
func (*Block) expression(){}
|
||||
func (*Block) statement(){}
|
||||
func (this *Block) Type () Type { return this.Ty }
|
||||
func (this *Block) String () string {
|
||||
out := "{"
|
||||
@ -313,14 +294,13 @@ func (this *Block) String () string {
|
||||
type MemberAccess struct {
|
||||
// Syntax
|
||||
Position errors.Position
|
||||
Source *Variable
|
||||
Source Expression
|
||||
Member string
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*MemberAccess) expression(){}
|
||||
func (*MemberAccess) statement(){}
|
||||
func (this *MemberAccess) Type () Type { return this.Ty }
|
||||
func (this *MemberAccess) String () string {
|
||||
return fmt.Sprint(this.Source, ".", this.Member)
|
||||
@ -342,7 +322,6 @@ type IfElse struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*IfElse) expression(){}
|
||||
func (*IfElse) statement(){}
|
||||
func (this *IfElse) Type () Type { return this.Ty }
|
||||
func (this *IfElse) String () string {
|
||||
out := fmt.Sprint("if ", this.Condition, " then ", this.True)
|
||||
@ -368,7 +347,6 @@ type Loop struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*Loop) expression(){}
|
||||
func (*Loop) statement(){}
|
||||
func (this *Loop) Type () Type { return this.Ty }
|
||||
func (this *Loop) String () string {
|
||||
return fmt.Sprint("loop ", this.Body)
|
||||
@ -385,7 +363,6 @@ type Break struct {
|
||||
Loop *Loop
|
||||
}
|
||||
func (*Break) expression(){}
|
||||
func (*Break) statement(){}
|
||||
func (this *Break) Type () Type { return nil }
|
||||
func (this *Break) String () string {
|
||||
if this.Value == nil {
|
||||
@ -408,7 +385,6 @@ type Return struct {
|
||||
Declaration TopLevel
|
||||
}
|
||||
func (*Return) expression(){}
|
||||
func (*Return) statement(){}
|
||||
func (this *Return) Type () Type { return nil }
|
||||
func (this *Return) String () string {
|
||||
if this.Value == nil {
|
||||
@ -427,7 +403,8 @@ type Assignment struct {
|
||||
Location Expression
|
||||
Value Expression
|
||||
}
|
||||
func (*Assignment) statement(){}
|
||||
func (*Assignment) expression(){}
|
||||
func (this *Assignment) Type () Type { return nil }
|
||||
func (this *Assignment) String () string {
|
||||
return fmt.Sprint(this.Location, "=", this.Value)
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ type LiteralInt struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralInt) expression(){}
|
||||
func (*LiteralInt) statement(){}
|
||||
func (this *LiteralInt) Type () Type { return this.Ty }
|
||||
func (this *LiteralInt) String () string {
|
||||
return fmt.Sprint(this.Value)
|
||||
@ -37,7 +36,6 @@ type LiteralFloat struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralFloat) expression(){}
|
||||
func (*LiteralFloat) statement(){}
|
||||
func (this *LiteralFloat) Type () Type { return this.Ty }
|
||||
func (this *LiteralFloat) String () string {
|
||||
return fmt.Sprint(this.Value)
|
||||
@ -69,7 +67,6 @@ type LiteralString struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralString) expression(){}
|
||||
func (*LiteralString) statement(){}
|
||||
func (this *LiteralString) Type () Type { return this.Ty }
|
||||
func (this *LiteralString) String () string {
|
||||
out := "'"
|
||||
@ -101,7 +98,6 @@ type LiteralArray struct {
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralArray) expression(){}
|
||||
func (*LiteralArray) statement(){}
|
||||
func (this *LiteralArray) Type () Type { return this.Ty }
|
||||
func (this *LiteralArray) String () string {
|
||||
out := "(*"
|
||||
@ -129,13 +125,11 @@ type LiteralStruct struct {
|
||||
MemberMap map[string] *Member
|
||||
}
|
||||
func (*LiteralStruct) expression(){}
|
||||
func (*LiteralStruct) statement(){}
|
||||
func (this *LiteralStruct) Type () Type { return this.Ty }
|
||||
func (this *LiteralStruct) String () string {
|
||||
out := "("
|
||||
for index, member := range this.Members {
|
||||
if index > 0 { out += " " }
|
||||
out += fmt.Sprint(member)
|
||||
out := "(."
|
||||
for _, member := range this.Members {
|
||||
out += fmt.Sprint(" ", member)
|
||||
}
|
||||
return out + ")"
|
||||
}
|
||||
@ -147,38 +141,28 @@ func (this *LiteralStruct) String () string {
|
||||
type LiteralBoolean struct {
|
||||
// Syntax
|
||||
Position errors.Position
|
||||
Value *Boolean
|
||||
Value bool
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralBoolean) expression(){}
|
||||
func (*LiteralBoolean) statement(){}
|
||||
func (this *LiteralBoolean) Type () Type { return this.Ty }
|
||||
func (this *LiteralBoolean) String () string {
|
||||
if *this.Value {
|
||||
if this.Value {
|
||||
return "true"
|
||||
} else {
|
||||
return "false"
|
||||
}
|
||||
}
|
||||
|
||||
// Boolean exists because participle hates me.
|
||||
type Boolean bool
|
||||
func (this *Boolean) Capture (values []string) error {
|
||||
*this = values[0] == "true"
|
||||
return nil
|
||||
}
|
||||
|
||||
type LiteralNil struct {
|
||||
// Syntax
|
||||
Position errors.Position
|
||||
Value string
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralNil) expression(){}
|
||||
func (*LiteralNil) statement(){}
|
||||
func (this *LiteralNil) Type () Type { return this.Ty }
|
||||
func (this *LiteralNil) String () string { return this.Value }
|
||||
func (this *LiteralNil) String () string { return "nil" }
|
||||
|
@ -1,7 +1,6 @@
|
||||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "strings"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
|
||||
// Signature is a function or method signature that is used in functions,
|
||||
@ -131,9 +130,8 @@ func init () {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Operator) Capture (str []string) error {
|
||||
*this = stringToOperator[strings.Join(str, "")]
|
||||
return nil
|
||||
func OperatorFromString (str string) Operator {
|
||||
return stringToOperator[str]
|
||||
}
|
||||
|
||||
func (this Operator) String () string {
|
||||
|
@ -15,8 +15,8 @@ type Access int; const (
|
||||
AccessPublic
|
||||
)
|
||||
|
||||
func (this *Access) String () string {
|
||||
switch *this {
|
||||
func (this Access) String () string {
|
||||
switch this {
|
||||
case AccessPrivate: return "-"
|
||||
case AccessRestricted: return "~"
|
||||
case AccessPublic: return "+"
|
||||
@ -24,20 +24,11 @@ func (this *Access) String () string {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Access) Capture (values []string) error {
|
||||
switch values[0] {
|
||||
case "-": *this = AccessPrivate
|
||||
case "~": *this = AccessRestricted
|
||||
case "+": *this = AccessPublic
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Typedef binds a type to a global identifier.
|
||||
type Typedef struct {
|
||||
// Syntax
|
||||
Position errors.Position
|
||||
Acc *Access
|
||||
Acc Access
|
||||
Name string
|
||||
Type Type
|
||||
|
||||
@ -47,9 +38,7 @@ type Typedef struct {
|
||||
func (*Typedef) topLevel(){}
|
||||
func (this *Typedef) String () string {
|
||||
output := ""
|
||||
if this.Acc != nil {
|
||||
output += fmt.Sprint(this.Acc, " ")
|
||||
}
|
||||
output += fmt.Sprint(this.Acc, " ")
|
||||
output += fmt.Sprint(this.Name, ": ", this.Type)
|
||||
if this.Methods != nil {
|
||||
for _, method := range this.Methods {
|
||||
@ -67,7 +56,7 @@ func (this *Typedef) String () string {
|
||||
type Function struct {
|
||||
// Syntax
|
||||
Position errors.Position
|
||||
Acc *Access
|
||||
Acc Access
|
||||
Signature *Signature
|
||||
LinkName string
|
||||
Body Expression
|
||||
@ -78,9 +67,7 @@ type Function struct {
|
||||
func (*Function) topLevel(){}
|
||||
func (this *Function) String () string {
|
||||
output := ""
|
||||
if this.Acc != nil {
|
||||
output += fmt.Sprint(this.Acc, " ")
|
||||
}
|
||||
output += fmt.Sprint(this.Acc, " ")
|
||||
output += this.Signature.String()
|
||||
if this.LinkName != "" {
|
||||
output += fmt.Sprint(" '", this.LinkName, "'")
|
||||
@ -98,7 +85,7 @@ func (this *Function) String () string {
|
||||
type Method struct {
|
||||
// Syntax
|
||||
Position errors.Position
|
||||
Acc *Access
|
||||
Acc Access
|
||||
TypeName string
|
||||
Signature *Signature
|
||||
LinkName string
|
||||
@ -112,9 +99,7 @@ type Method struct {
|
||||
func (*Method) topLevel(){}
|
||||
func (this *Method) String () string {
|
||||
output := ""
|
||||
if this.Acc != nil {
|
||||
output += fmt.Sprint(this.Acc, " ")
|
||||
}
|
||||
output += fmt.Sprint(this.Acc, " ")
|
||||
output += fmt.Sprint(this.TypeName, ".", this.Signature)
|
||||
if this.LinkName != "" {
|
||||
output += fmt.Sprint(" '", this.LinkName, "'")
|
||||
|
@ -96,10 +96,9 @@ type TypeStruct struct {
|
||||
}
|
||||
func (*TypeStruct) ty(){}
|
||||
func (this *TypeStruct) String () string {
|
||||
out := "("
|
||||
for index, member := range this.Members {
|
||||
if index > 0 { out += " " }
|
||||
out += fmt.Sprint(member)
|
||||
out := "(."
|
||||
for _, member := range this.Members {
|
||||
out += fmt.Sprint(" ", member)
|
||||
}
|
||||
return out + ")"
|
||||
}
|
||||
@ -131,10 +130,9 @@ type TypeInterface struct {
|
||||
}
|
||||
func (*TypeInterface) ty(){}
|
||||
func (this *TypeInterface) String () string {
|
||||
out := "("
|
||||
for index, behavior := range this.Behaviors {
|
||||
if index > 0 { out += " " }
|
||||
out += fmt.Sprint(behavior)
|
||||
out := "(?"
|
||||
for _, behavior := range this.Behaviors {
|
||||
out += fmt.Sprint(" ", behavior)
|
||||
}
|
||||
return out + ")"
|
||||
}
|
||||
|
@ -22,10 +22,13 @@ func (pos Position) String () string {
|
||||
// Format produces a formatted printout of where the position is in its file.
|
||||
func (pos Position) Format () string {
|
||||
line := formatTabs(pos.Line)
|
||||
output := fmt.Sprintf("%-4d | %s\n ", pos.Row, line)
|
||||
output := fmt.Sprintf("%-4d | %s\n ", pos.Row + 1, line)
|
||||
|
||||
start := getXInTabbedString(pos.Line, pos.Start)
|
||||
end := getXInTabbedString(pos.Line, pos.End)
|
||||
start := pos.Start
|
||||
end := pos.End
|
||||
if end <= start { end = start + 1 }
|
||||
start = getXInTabbedString(pos.Line, start)
|
||||
end = getXInTabbedString(pos.Line, end)
|
||||
|
||||
for index := range line {
|
||||
if index >= end { break }
|
||||
|
@ -5,8 +5,8 @@ import "strings"
|
||||
|
||||
func TestError (test *testing.T) {
|
||||
testError (test,
|
||||
`example.fspl:10:6: some error
|
||||
10 | lorem ipsum dolor
|
||||
`example.fspl:11:7: some error
|
||||
11 | lorem ipsum dolor
|
||||
^^^^^`,
|
||||
&Error {
|
||||
Position: Position {
|
||||
@ -22,8 +22,8 @@ testError (test,
|
||||
|
||||
func TestErrorTab (test *testing.T) {
|
||||
testError (test,
|
||||
`example.fspl:10:7: some error
|
||||
10 | lorem ipsum dolor
|
||||
`example.fspl:11:8: some error
|
||||
11 | lorem ipsum dolor
|
||||
^^^^^`,
|
||||
&Error {
|
||||
Position: Position {
|
||||
@ -39,8 +39,8 @@ testError (test,
|
||||
|
||||
func TestErrorTabInBetween (test *testing.T) {
|
||||
testError (test,
|
||||
`example.fspl:10:7: some error
|
||||
10 | lorem ipsum dolor
|
||||
`example.fspl:11:8: some error
|
||||
11 | lorem ipsum dolor
|
||||
^^^^^^^^^`,
|
||||
&Error {
|
||||
Position: Position {
|
||||
|
@ -6,9 +6,9 @@ import "git.tebibyte.media/sashakoshka/fspl/testcommon"
|
||||
func testString (test *testing.T, correct string, got string) {
|
||||
if got != correct {
|
||||
test.Logf("results do not match")
|
||||
testcommon.Compare(test, correct, got)
|
||||
test.Fail()
|
||||
}
|
||||
testcommon.Compare(test, correct, got)
|
||||
}
|
||||
|
||||
func testError (test *testing.T, correct string, err *Error) {
|
||||
|
14
go.mod
14
go.mod
@ -4,17 +4,5 @@ go 1.21
|
||||
|
||||
require (
|
||||
github.com/alecthomas/participle/v2 v2.1.0
|
||||
github.com/llir/llvm v0.3.6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/kr/pretty v0.2.0 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/llir/ll v0.0.0-20220802044011-65001c0fb73c // indirect
|
||||
github.com/mewmew/float v0.0.0-20201204173432-505706aa38fa // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
golang.org/x/mod v0.4.2 // indirect
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
|
||||
golang.org/x/tools v0.1.4 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
github.com/mewmew/float v0.0.0-20201204173432-505706aa38fa
|
||||
)
|
||||
|
42
go.sum
42
go.sum
@ -4,49 +4,7 @@ github.com/alecthomas/participle/v2 v2.1.0 h1:z7dElHRrOEEq45F2TG5cbQihMtNTv8vwld
|
||||
github.com/alecthomas/participle/v2 v2.1.0/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
|
||||
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/llir/ll v0.0.0-20220802044011-65001c0fb73c h1:UwtWiaR7Zg/IItv2hEN1EATTY/Hv69llULknaeMgxWo=
|
||||
github.com/llir/ll v0.0.0-20220802044011-65001c0fb73c/go.mod h1:2F+W9dmrXLYy3UZXnii5UM7QDRiVsz4QkMpC0vaBU7M=
|
||||
github.com/llir/llvm v0.3.6 h1:Zh9vd8EOMDgwRAg43+VkOwnXISXIPyTzoNH89LLX5eM=
|
||||
github.com/llir/llvm v0.3.6/go.mod h1:2vIck7uj3cIuZqx5cLXxB9lD6bT2JtgXcMD0u3WbfOo=
|
||||
github.com/mewmew/float v0.0.0-20201204173432-505706aa38fa h1:R27wrYHe8Zik4z/EV8xxfoH3cwMJw3qI4xsI3yYkGDQ=
|
||||
github.com/mewmew/float v0.0.0-20201204173432-505706aa38fa/go.mod h1:O+xb+8ycBNHzJicFVs7GRWtruD4tVZI0huVnw5TM01E=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.4 h1:cVngSRcfgyZCzys3KYOpCFa+4dqX/Oub9tAq00ttGVs=
|
||||
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
130
lexer/lexer.go
130
lexer/lexer.go
@ -1,6 +1,7 @@
|
||||
package lexer
|
||||
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "bufio"
|
||||
import "unicode"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
@ -15,7 +16,7 @@ type TokenKind int; const (
|
||||
Float // [0-9]*\.[0-9]+
|
||||
String // \'.*\'
|
||||
|
||||
Symbol // [~!@#$%^&*-_=+\\|;:,<.>/?]+
|
||||
Symbol // [~!@#$%^&*-_=+\\|;,<>/?]+
|
||||
LParen // \(
|
||||
LBrace // \{
|
||||
LBracket // \[
|
||||
@ -24,9 +25,35 @@ type TokenKind int; const (
|
||||
RBracket // \]
|
||||
Colon // :
|
||||
DoubleColon // ::
|
||||
Dot // .
|
||||
DoubleDot // ..
|
||||
Star // \*
|
||||
)
|
||||
|
||||
func (kind TokenKind) String () string {
|
||||
switch kind {
|
||||
case EOF: return "EOF"
|
||||
case Ident: return "Ident"
|
||||
case TypeIdent: return "TypeIdent"
|
||||
case Int: return "Int"
|
||||
case Float: return "Float"
|
||||
case String: return "String"
|
||||
case Symbol: return "Symbol"
|
||||
case LParen: return "LParen"
|
||||
case LBrace: return "LBrace"
|
||||
case LBracket: return "LBracket"
|
||||
case RParen: return "RParen"
|
||||
case RBrace: return "RBrace"
|
||||
case RBracket: return "RBracket"
|
||||
case Colon: return "Colon"
|
||||
case DoubleColon: return "DoubleColon"
|
||||
case Dot: return "Dot"
|
||||
case DoubleDot: return "DoubleDot"
|
||||
case Star: return "Star"
|
||||
default: return fmt.Sprintf("TokenKind(%d)", kind)
|
||||
}
|
||||
}
|
||||
|
||||
func Symbols () map[string] TokenKind {
|
||||
return map[string] TokenKind {
|
||||
"EOF": EOF,
|
||||
@ -44,6 +71,8 @@ func Symbols () map[string] TokenKind {
|
||||
"RBracket": RBracket,
|
||||
"Colon": Colon,
|
||||
"DoubleColon": DoubleColon,
|
||||
"Dot": Dot,
|
||||
"DoubleDot": DoubleDot,
|
||||
"Star": Star,
|
||||
}
|
||||
}
|
||||
@ -54,28 +83,50 @@ type Token struct {
|
||||
Position errors.Position
|
||||
}
|
||||
|
||||
func (tok Token) String () string {
|
||||
output := tok.Kind.String()
|
||||
if tok.Value != "" {
|
||||
output += fmt.Sprintf(" '%s'", tok.Value)
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
func (tok Token) EOF () bool {
|
||||
return tok.Kind == EOF
|
||||
}
|
||||
|
||||
func (tok Token) Is (kinds ...TokenKind) bool {
|
||||
for _, kind := range kinds {
|
||||
if tok.Kind == kind { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (tok Token) ValueIs (values ...string) bool {
|
||||
for _, value := range values {
|
||||
if tok.Value == value { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type Lexer interface {
|
||||
Next () (Token, error)
|
||||
}
|
||||
|
||||
func NewLexer (filename string, reader io.Reader) (Lexer, error) {
|
||||
lexer := &fsplLexer {
|
||||
filename: filename,
|
||||
reader: bufio.NewReader(reader),
|
||||
row: 1,
|
||||
filename: filename,
|
||||
lineScanner: bufio.NewScanner(reader),
|
||||
}
|
||||
lexer.nextRune()
|
||||
return lexer, nil
|
||||
}
|
||||
|
||||
type fsplLexer struct {
|
||||
filename string
|
||||
reader *bufio.Reader
|
||||
rune rune
|
||||
filename string
|
||||
lineScanner *bufio.Scanner
|
||||
rune rune
|
||||
runeLine []rune
|
||||
|
||||
offset int
|
||||
row int
|
||||
@ -92,9 +143,7 @@ func (this *fsplLexer) Next () (Token, error) {
|
||||
|
||||
func (this *fsplLexer) nextInternal () (token Token, err error) {
|
||||
err = this.skipWhitespace()
|
||||
|
||||
token.Position = this.pos()
|
||||
|
||||
if this.eof {
|
||||
token.Kind = EOF
|
||||
err = nil
|
||||
@ -132,6 +181,12 @@ func (this *fsplLexer) nextInternal () (token Token, err error) {
|
||||
}
|
||||
}
|
||||
|
||||
defer func () {
|
||||
newPos := this.pos()
|
||||
newPos.End -- // TODO figure out why tf we have to do this
|
||||
token.Position = token.Position.Union(newPos)
|
||||
} ()
|
||||
|
||||
switch {
|
||||
// Ident
|
||||
case unicode.IsLower(this.rune):
|
||||
@ -152,6 +207,7 @@ func (this *fsplLexer) nextInternal () (token Token, err error) {
|
||||
// Int, Float
|
||||
case isDigit(this.rune):
|
||||
doNumber()
|
||||
if this.eof { err = nil; return }
|
||||
// String
|
||||
case this.rune == '\'':
|
||||
token.Kind = String
|
||||
@ -192,21 +248,27 @@ func (this *fsplLexer) nextInternal () (token Token, err error) {
|
||||
case this.rune == '(':
|
||||
token.Kind = LParen
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == '{':
|
||||
token.Kind = LBrace
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == '[':
|
||||
token.Kind = LBracket
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == ')':
|
||||
token.Kind = RParen
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == '}':
|
||||
token.Kind = RBrace
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == ']':
|
||||
token.Kind = RBracket
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
// Colon, DoubleColon
|
||||
case this.rune == ':':
|
||||
token.Kind = Colon
|
||||
@ -215,13 +277,24 @@ func (this *fsplLexer) nextInternal () (token Token, err error) {
|
||||
token.Kind = DoubleColon
|
||||
appendRune()
|
||||
}
|
||||
if this.eof { err = nil; return }
|
||||
// Dot, DoubleDot
|
||||
case this.rune == '.':
|
||||
token.Kind = Dot
|
||||
appendRune()
|
||||
if this.rune == '.' {
|
||||
token.Kind = DoubleDot
|
||||
appendRune()
|
||||
}
|
||||
if this.eof { err = nil; return }
|
||||
// Star
|
||||
case this.rune == '*':
|
||||
token.Kind = Star
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case unicode.IsPrint(this.rune):
|
||||
err = errors.Errorf (
|
||||
this.pos(), "unexpected rune \"%c\"",
|
||||
this.pos(), "unexpected rune '%c'",
|
||||
this.rune)
|
||||
default:
|
||||
err = errors.Errorf (
|
||||
@ -233,21 +306,28 @@ func (this *fsplLexer) nextInternal () (token Token, err error) {
|
||||
}
|
||||
|
||||
func (this *fsplLexer) nextRune () error {
|
||||
char, _, err := this.reader.ReadRune()
|
||||
this.rune = char
|
||||
|
||||
this.offset ++
|
||||
if char == '\n' {
|
||||
this.row ++
|
||||
this.column = 0
|
||||
if this.column >= len(this.runeLine) {
|
||||
ok := this.lineScanner.Scan()
|
||||
if ok {
|
||||
this.runeLine = []rune(this.lineScanner.Text())
|
||||
this.rune = '\n'
|
||||
this.column = 0
|
||||
this.row ++
|
||||
} else {
|
||||
err := this.lineScanner.Err()
|
||||
if err == nil {
|
||||
this.eof = true
|
||||
return io.EOF
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.rune = this.runeLine[this.column]
|
||||
this.column ++
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
this.eof = true
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *fsplLexer) escapeSequence () (rune, error) {
|
||||
@ -308,9 +388,9 @@ func (this *fsplLexer) skipComment () error {
|
||||
func (this *fsplLexer) pos () errors.Position {
|
||||
return errors.Position {
|
||||
File: this.filename,
|
||||
Line: "",
|
||||
Row: this.row,
|
||||
Start: this.column,
|
||||
Line: this.lineScanner.Text(),
|
||||
Row: this.row - 1,
|
||||
Start: this.column - 1,
|
||||
End: this.column,
|
||||
}
|
||||
}
|
||||
@ -334,7 +414,7 @@ func isSymbol (char rune) bool {
|
||||
switch char {
|
||||
case
|
||||
'~', '!', '@', '#', '$', '%', '^', '&', '-', '_', '=', '+',
|
||||
'\\', '|', ';', ',', '<', '.', '>', '/', '?':
|
||||
'\\', '|', ';', ',', '<', '>', '/', '?':
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
|
@ -8,7 +8,7 @@ testString(test, "", tok(EOF, ""))
|
||||
|
||||
func TestLexerErrUnexpectedRune (test *testing.T) {
|
||||
testStringErr(test,
|
||||
"unexpected rune \"\"\"", 3, 5,
|
||||
"unexpected rune '\"'", `ooo " hahsdklhasldk`, 2, 4,
|
||||
`
|
||||
valid tokens valid tokens
|
||||
ooo " hahsdklhasldk
|
||||
@ -23,7 +23,7 @@ tok(0, ""),
|
||||
|
||||
func TestLexerErrUnexpectedEOF (test *testing.T) {
|
||||
testStringErr(test,
|
||||
"unexpected EOF", 2, 39,
|
||||
"unexpected EOF", `'this is a string that does not end...`, 1, 38,
|
||||
`
|
||||
'this is a string that does not end...`,
|
||||
tok(String, "this is a string that does not end..."),
|
||||
@ -35,7 +35,7 @@ testString(test,
|
||||
`
|
||||
normalIdent TypeIdent
|
||||
[$$abcdEfGhIjKlMnOpQrStUvWxYz]{+}(-) ; this is a comment
|
||||
~ ! @ # $ % ^ & *- _ = + \ | :** ::, < . > / ?
|
||||
~ ! @ # $ % ^ & *- _ = + \ | :** ::, < . .. > / ?
|
||||
!! ++ -- && || 'some \'string' 'nullterm\0' '\110\117\115\105'
|
||||
1234 -1234 9876540321 (754.2340)
|
||||
`,
|
||||
@ -73,7 +73,8 @@ tok(Star, "*"),
|
||||
tok(DoubleColon, "::"),
|
||||
tok(Symbol, ","),
|
||||
tok(Symbol, "<"),
|
||||
tok(Symbol, "."),
|
||||
tok(Dot, "."),
|
||||
tok(DoubleDot, ".."),
|
||||
tok(Symbol, ">"),
|
||||
tok(Symbol, "/"),
|
||||
tok(Symbol, "?"),
|
||||
@ -98,6 +99,13 @@ tok(RParen, ")"),
|
||||
tok(EOF, ""),
|
||||
)}
|
||||
|
||||
func TestLexerSingleParen (test *testing.T) {
|
||||
testString(test,
|
||||
`)`,
|
||||
tok(RParen, ")"),
|
||||
tok(EOF, ""),
|
||||
)}
|
||||
|
||||
func TestLexerNoTrailingEOLInt (test *testing.T) {
|
||||
testString(test,
|
||||
`12345`,
|
||||
|
@ -17,12 +17,13 @@ func testString (
|
||||
input string,
|
||||
correct ...Token,
|
||||
) {
|
||||
testStringErr(test, "", 0, 0, input, correct...)
|
||||
testStringErr(test, "", "", 0, 0, input, correct...)
|
||||
}
|
||||
|
||||
func testStringErr (
|
||||
test *testing.T,
|
||||
errMessage string,
|
||||
errLine string,
|
||||
errRow int,
|
||||
errStart int,
|
||||
input string,
|
||||
@ -31,26 +32,46 @@ func testStringErr (
|
||||
testError := func (err error) bool {
|
||||
got := err.(*errors.Error)
|
||||
gotMessage := got.Message
|
||||
gotLine := got.Line
|
||||
gotRow := got.Position.Row
|
||||
gotStart := got.Position.Start
|
||||
correct :=
|
||||
gotMessage == errMessage &&
|
||||
gotLine == errLine &&
|
||||
gotRow == errRow &&
|
||||
gotStart == errStart
|
||||
if !correct {
|
||||
test.Log("errors do not match")
|
||||
test.Logf("got:\n%v:%v: %v", gotRow, gotStart, gotMessage)
|
||||
test.Logf("correct:\n%v:%v: %v", errRow, errStart, errMessage)
|
||||
test.Log("got:\n" + got.Format())
|
||||
test.Log("correct:\n" + (&errors.Error {
|
||||
Position: errors.Position {
|
||||
File: "input.fspl",
|
||||
Line: errLine,
|
||||
Row: errRow,
|
||||
Start: errStart,
|
||||
End: errStart,
|
||||
},
|
||||
Message: errMessage,
|
||||
}).Format())
|
||||
test.Fail()
|
||||
}
|
||||
return correct
|
||||
}
|
||||
|
||||
printUnexpectedErr := func (err error) {
|
||||
if err, ok := err.(*errors.Error); ok {
|
||||
test.Log("lexer returned error:\n", err.Format())
|
||||
} else {
|
||||
test.Log("lexer returned error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
reader := strings.NewReader(input)
|
||||
lx, err := NewLexer("stream0.fspl", reader)
|
||||
lx, err := NewLexer("input.fspl", reader)
|
||||
if err != nil {
|
||||
if errMessage == "" {
|
||||
test.Error("lexer returned error: ", err)
|
||||
printUnexpectedErr(err)
|
||||
test.Fail()
|
||||
return
|
||||
} else {
|
||||
if !testError(err) { return }
|
||||
@ -64,8 +85,8 @@ func testStringErr (
|
||||
if got.EOF() { break }
|
||||
if err != nil {
|
||||
if errMessage == "" {
|
||||
test.Error("lexer returned error: ", err)
|
||||
return
|
||||
printUnexpectedErr(err)
|
||||
test.Fail()
|
||||
} else {
|
||||
if !testError(err) { return }
|
||||
}
|
||||
@ -74,11 +95,11 @@ func testStringErr (
|
||||
}
|
||||
|
||||
printColumns := func (left, right string) {
|
||||
test.Logf("%-40v | %-40v", left, right)
|
||||
test.Logf("%-50v | %v", left, right)
|
||||
}
|
||||
|
||||
dumpToken := func (token Token) string {
|
||||
return fmt.Sprintf("%4v: \"%s\"", token.Kind, token.Value)
|
||||
return fmt.Sprintf("%12v: \"%s\"", token.Kind, token.Value)
|
||||
}
|
||||
|
||||
compareTokens := func () {
|
||||
|
608
parser/expression.go
Normal file
608
parser/expression.go
Normal file
@ -0,0 +1,608 @@
|
||||
package parser
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/fspl/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
// Expression decision tree flow:
|
||||
//
|
||||
// | +Ident =Variable
|
||||
// | | 'true' =LiteralBoolean
|
||||
// | | 'false' =LiteralBoolean
|
||||
// | | 'nil' =LiteralNil
|
||||
// | | 'if' =IfElse
|
||||
// | | 'loop' =Loop
|
||||
// | | +Colon =Declaration
|
||||
// | | +DoubleColon =Call
|
||||
// |
|
||||
// | +LParen X
|
||||
// | | +Star =LiteralArray
|
||||
// | | +Dot =LiteralStruct
|
||||
// |
|
||||
// | +LBracket | +Ident =Call
|
||||
// | | | 'break' =Break
|
||||
// | | | 'return' =Return
|
||||
// | |
|
||||
// | | +Dot +Expression =Dereference
|
||||
// | | | +Expression =Subscript
|
||||
// | | +Symbol X
|
||||
// | | '\' =Slice
|
||||
// | | '#' =Length
|
||||
// | | '@' =Reference
|
||||
// | | '~' =ValueCast
|
||||
// | | '~~' =BitCast
|
||||
// | | OPERATOR =Operation
|
||||
// |
|
||||
// | +LBrace =Block
|
||||
// | +Int =LiteralInt
|
||||
// | +Float =LiteralFloat
|
||||
// | +String =LiteralString
|
||||
//
|
||||
// Entities with a star have yet to be implemented
|
||||
|
||||
var descriptionExpression = "expression"
|
||||
var startTokensExpression = []lexer.TokenKind {
|
||||
lexer.Ident,
|
||||
lexer.LParen,
|
||||
lexer.LBracket,
|
||||
lexer.LBrace,
|
||||
lexer.Int,
|
||||
lexer.Float,
|
||||
lexer.String,
|
||||
}
|
||||
|
||||
func (this *Parser) parseExpression () (entity.Expression, error) {
|
||||
// Steps that this function takes to parse expressions:
|
||||
// 1. Run a decision tree to parse the expression.
|
||||
// 2. After that, test for infix operators (. and =). We will not need
|
||||
// to consume more tokens to do this, as if they exist they will be
|
||||
// the very next token AKA the current one after parsing the
|
||||
// expression.
|
||||
// 3. Then, use the previously parsed expression and use it as the left
|
||||
// side, and go on to parse the right side. Assignment (=) is the
|
||||
// only infix expression where both left and right sides are
|
||||
// expressions, so it gets to call parseExpression() and recurse. The
|
||||
// implied associativity matters here.
|
||||
// 4. If not assignment, then this has to do with member access (the
|
||||
// right side is not an expression), so we repeat from step 2 in order
|
||||
// to chain member accesses and method calls. Step 2 will test
|
||||
// whether or not we need to continue repeating afterward. The
|
||||
// implied associativity matters here.
|
||||
// 5. Return the expression, whether it was directly parsed or is a tree
|
||||
// of infix operator(s).
|
||||
|
||||
// run decision tree
|
||||
expression, err := this.parseExpressionRoot()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// test for infix operators
|
||||
for this.token.Is(lexer.Dot) || (this.token.Is(lexer.Symbol) && this.token.ValueIs("=")) {
|
||||
pos := this.pos()
|
||||
|
||||
switch this.kind() {
|
||||
case lexer.Dot:
|
||||
// Dot: member access or method call
|
||||
// this control path must not return, staying within the
|
||||
// loop
|
||||
source := expression
|
||||
err := this.expectNextDesc (
|
||||
"member access or method call",
|
||||
lexer.Ident, lexer.LBracket)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
switch this.kind() {
|
||||
case lexer.Ident:
|
||||
// Ident: member access
|
||||
expression = &entity.MemberAccess {
|
||||
Position: pos.Union(this.pos()),
|
||||
Source: source,
|
||||
Member: this.value(),
|
||||
}
|
||||
this.next()
|
||||
|
||||
case lexer.LBracket:
|
||||
// LBracket: method call
|
||||
expression, err = this.parseMethodCallCore(pos, source)
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
|
||||
case lexer.Symbol:
|
||||
// Symbol '=': assignment
|
||||
// this control path must return, breaking out of the
|
||||
// loop.
|
||||
expression := &entity.Assignment {
|
||||
Position: pos,
|
||||
Location: expression,
|
||||
}
|
||||
|
||||
this.next()
|
||||
value, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
expression.Value = value
|
||||
return expression, nil
|
||||
}
|
||||
}
|
||||
|
||||
return expression, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseExpressionRoot () (entity.Expression, error) {
|
||||
err := this.expectDesc(descriptionExpression, startTokensExpression...)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
switch this.kind() {
|
||||
case lexer.Ident: return this.parseExpressionRootIdent()
|
||||
case lexer.LParen: return this.parseExpressionRootLParen()
|
||||
case lexer.LBracket: return this.parseExpressionRootLBracket()
|
||||
case lexer.LBrace: return this.parseBlock()
|
||||
case lexer.Int: return this.parseLiteralInt()
|
||||
case lexer.Float: return this.parseLiteralFloat()
|
||||
case lexer.String: return this.parseLiteralString()
|
||||
}
|
||||
panic(this.bug())
|
||||
}
|
||||
|
||||
func (this *Parser) parseExpressionRootIdent () (entity.Expression, error) {
|
||||
err := this.expect(lexer.Ident)
|
||||
if err != nil { return nil, err }
|
||||
name := this.value()
|
||||
pos := this.pos()
|
||||
|
||||
switch name {
|
||||
case "true", "false": return this.parseLiteralBoolean ()
|
||||
case "nil": return this.parseLiteralNil()
|
||||
case "if": return this.parseIfElse()
|
||||
case "loop": return this.parseLoop()
|
||||
default:
|
||||
println(this.token.String())
|
||||
this.next()
|
||||
switch this.kind() {
|
||||
case lexer.Colon:
|
||||
// Colon: declaration
|
||||
return this.parseDeclarationCore(pos, name)
|
||||
case lexer.DoubleColon:
|
||||
// DoubleColon: call
|
||||
err := this.expectNext(lexer.LBracket)
|
||||
if err != nil { return nil, err }
|
||||
this.next()
|
||||
return this.parseCallCore(pos, name)
|
||||
default:
|
||||
// *: variable
|
||||
return &entity.Variable {
|
||||
Position: pos,
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
panic(this.bug())
|
||||
}
|
||||
|
||||
func (this *Parser) parseExpressionRootLParen() (entity.Expression, error) {
|
||||
err := this.expect(lexer.LParen)
|
||||
if err != nil { return nil, err }
|
||||
pos := this.pos()
|
||||
|
||||
err = this.expectNext(lexer.Star, lexer.Dot)
|
||||
if err != nil { return nil, err }
|
||||
switch this.kind() {
|
||||
case lexer.Star: return this.parseLiteralArrayCore(pos)
|
||||
case lexer.Dot: return this.parseLiteralStructCore(pos)
|
||||
}
|
||||
panic(this.bug())
|
||||
}
|
||||
|
||||
func (this *Parser) parseExpressionRootLBracket () (entity.Expression, error) {
|
||||
err := this.expect(lexer.LBracket)
|
||||
if err != nil { return nil, err }
|
||||
pos := this.pos()
|
||||
|
||||
err = this.expectNext(lexer.Ident, lexer.Dot, lexer.Symbol)
|
||||
if err != nil { return nil, err }
|
||||
switch this.kind() {
|
||||
case lexer.Ident: return this.parseExpressionRootLBracketIdent(pos)
|
||||
case lexer.Dot: return this.parseDereferenceOrSubscriptCore(pos)
|
||||
case lexer.Symbol: return this.parseExpressionRootLBracketSymbol(pos)
|
||||
}
|
||||
panic(this.bug())
|
||||
}
|
||||
|
||||
func (this *Parser) parseExpressionRootLBracketIdent (pos errors.Position) (entity.Expression, error) {
|
||||
err := this.expect(lexer.Ident)
|
||||
if err != nil { return nil, err }
|
||||
name := this.value()
|
||||
|
||||
switch name {
|
||||
case "break", "return": return this.parseReturnOrBreakCore(pos)
|
||||
default: return this.parseCallCore(pos, "")
|
||||
}
|
||||
panic(this.bug())
|
||||
}
|
||||
|
||||
func (this *Parser) parseDereferenceOrSubscriptCore (pos errors.Position) (entity.Expression, error) {
|
||||
err := this.expect(lexer.Dot)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
this.next()
|
||||
argument, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
err = this.expectDesc (
|
||||
"element offset expression or end of dereference",
|
||||
appendCopy(startTokensExpression, lexer.RBracket)...)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if this.token.Is(lexer.RBracket) {
|
||||
// RBracket: dereference
|
||||
pos = pos.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return &entity.Dereference {
|
||||
Position: pos,
|
||||
Pointer: argument,
|
||||
}, nil
|
||||
} else {
|
||||
// startTokensExpression...: subscript
|
||||
offset, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
err = this.expect(lexer.RBracket)
|
||||
if err != nil { return nil, err }
|
||||
pos = pos.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return &entity.Subscript {
|
||||
Position: pos,
|
||||
Slice: argument,
|
||||
Offset: offset,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Parser) parseExpressionRootLBracketSymbol (pos errors.Position) (entity.Expression, error) {
|
||||
err := this.expectValue (
|
||||
lexer.Symbol,
|
||||
appendCopy(valuesOperator, "\\", "#", "@", "~", "~~")...)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
switch this.value() {
|
||||
case "\\": return this.parseSliceCore(pos)
|
||||
case "#": return this.parseLengthCore(pos)
|
||||
case "@": return this.parseReferenceCore(pos)
|
||||
case "~": return this.parseValueOrBitCastCore(pos)
|
||||
case "~~": return this.parseValueOrBitCastCore(pos)
|
||||
default: return this.parseOperationCore(pos)
|
||||
}
|
||||
panic(this.bug())
|
||||
}
|
||||
|
||||
func (this *Parser) parseCallCore (pos errors.Position, module string) (*entity.Call, error) {
|
||||
err := this.expect(lexer.Ident)
|
||||
if err != nil { return nil, err }
|
||||
call := &entity.Call {
|
||||
Position: pos,
|
||||
Module: module,
|
||||
Name: this.value(),
|
||||
}
|
||||
this.next()
|
||||
|
||||
for {
|
||||
err = this.expectDesc (
|
||||
"argument or end of call",
|
||||
appendCopy(startTokensExpression, lexer.RBracket)...)
|
||||
if err != nil { return nil, err }
|
||||
if this.token.Is(lexer.RBracket) { break }
|
||||
|
||||
argument, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
call.Arguments = append(call.Arguments, argument)
|
||||
}
|
||||
call.Position = call.Position.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return call, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseMethodCallCore (pos errors.Position, source entity.Expression) (*entity.MethodCall, error) {
|
||||
err := this.expect(lexer.LBracket)
|
||||
if err != nil { return nil, err }
|
||||
err = this.expectNext(lexer.Ident)
|
||||
if err != nil { return nil, err }
|
||||
call := &entity.MethodCall {
|
||||
Source: source,
|
||||
Position: pos,
|
||||
Name: this.value(),
|
||||
}
|
||||
this.next()
|
||||
|
||||
for {
|
||||
err = this.expectDesc (
|
||||
"argument or end of method call",
|
||||
appendCopy(startTokensExpression, lexer.RBracket)...)
|
||||
if err != nil { return nil, err }
|
||||
if this.token.Is(lexer.RBracket) { break }
|
||||
|
||||
argument, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
call.Arguments = append(call.Arguments, argument)
|
||||
}
|
||||
call.Position = call.Position.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return call, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseReturnOrBreakCore (pos errors.Position) (entity.Expression, error) {
|
||||
err := this.expectValue(lexer.Ident, "break", "return")
|
||||
if err != nil { return nil, err }
|
||||
name := this.value()
|
||||
|
||||
err = this.expectNextDesc (
|
||||
"expression or end of " + name,
|
||||
appendCopy(startTokensExpression, lexer.RBracket)...)
|
||||
if err != nil { return nil, err }
|
||||
var value entity.Expression
|
||||
if this.token.Is(startTokensExpression...) {
|
||||
// startTokensExpression...: value expression
|
||||
value, err = this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
|
||||
err = this.expectDesc("end of " + name, lexer.RBracket)
|
||||
if err != nil { return nil, err }
|
||||
pos = pos.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
switch name {
|
||||
case "break":
|
||||
return &entity.Break {
|
||||
Position: pos,
|
||||
Value: value,
|
||||
}, nil
|
||||
case "return":
|
||||
return &entity.Return {
|
||||
Position: pos,
|
||||
Value: value,
|
||||
}, nil
|
||||
}
|
||||
panic(this.bug())
|
||||
}
|
||||
|
||||
func (this *Parser) parseSliceCore (pos errors.Position) (*entity.Slice, error) {
|
||||
err := this.expectValue(lexer.Symbol , "\\")
|
||||
if err != nil { return nil, err }
|
||||
slice := &entity.Slice {
|
||||
Position: pos,
|
||||
}
|
||||
|
||||
err = this.expectNextDesc(descriptionExpression, startTokensExpression...)
|
||||
if err != nil { return nil, err }
|
||||
slice.Slice, err = this.parseExpression()
|
||||
|
||||
err = this.expectDesc (
|
||||
"offset expression or '/'",
|
||||
appendCopy(startTokensExpression, lexer.Symbol)...)
|
||||
if err != nil { return nil, err }
|
||||
if this.token.Is(startTokensExpression...) {
|
||||
// startTokensExpression...: starting offset
|
||||
slice.Start, err = this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
|
||||
err = this.expectValue(lexer.Symbol, "/")
|
||||
if err != nil { return nil, err }
|
||||
|
||||
this.expectNextDesc (
|
||||
"offset expression or operation end",
|
||||
appendCopy(startTokensExpression, lexer.RBracket)...)
|
||||
if this.token.Is(startTokensExpression...) {
|
||||
// startTokensExpression...: ending offset
|
||||
slice.End, err = this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
|
||||
err = this.expect(lexer.RBracket)
|
||||
if err != nil { return nil, err }
|
||||
slice.Position = slice.Position.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return slice, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseLengthCore (pos errors.Position) (*entity.Length, error) {
|
||||
err := this.expectValue(lexer.Symbol , "#")
|
||||
if err != nil { return nil, err }
|
||||
length := &entity.Length {
|
||||
Position: pos,
|
||||
}
|
||||
|
||||
this.next()
|
||||
length.Slice, err = this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
err = this.expect(lexer.RBracket)
|
||||
if err != nil { return nil, err }
|
||||
length.Position = length.Position.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return length, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseReferenceCore (pos errors.Position) (*entity.Reference, error) {
|
||||
err := this.expectValue(lexer.Symbol , "@")
|
||||
if err != nil { return nil, err }
|
||||
reference := &entity.Reference {
|
||||
Position: pos,
|
||||
}
|
||||
|
||||
this.next()
|
||||
reference.Value, err = this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
err = this.expect(lexer.RBracket)
|
||||
if err != nil { return nil, err }
|
||||
reference.Position = reference.Position.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return reference, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseValueOrBitCastCore (pos errors.Position) (entity.Expression, error) {
|
||||
err := this.expectValue(lexer.Symbol , "~", "~~")
|
||||
if err != nil { return nil, err }
|
||||
tokValue := this.value()
|
||||
|
||||
this.next()
|
||||
ty, err := this.parseType()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
err = this.expectDesc(descriptionExpression, startTokensExpression...)
|
||||
if err != nil { return nil, err }
|
||||
value, err := this.parseExpression()
|
||||
|
||||
err = this.expect(lexer.RBracket)
|
||||
if err != nil { return nil, err }
|
||||
pos = pos.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
switch tokValue {
|
||||
case "~":
|
||||
return &entity.ValueCast {
|
||||
Position: pos,
|
||||
Ty: ty,
|
||||
Value: value,
|
||||
}, nil
|
||||
case "~~":
|
||||
return &entity.BitCast {
|
||||
Position: pos,
|
||||
Ty: ty,
|
||||
Value: value,
|
||||
}, nil
|
||||
}
|
||||
panic(this.bug())
|
||||
}
|
||||
|
||||
var valuesOperator = []string {
|
||||
"++", "+", "--", "-", "*", "/", "%", "!!", "||", "&&", "^^",
|
||||
"!", "|", "&", "^", "<<", ">>", "<", ">", "<=", ">=", "=",
|
||||
}
|
||||
|
||||
func (this *Parser) parseOperationCore (pos errors.Position) (*entity.Operation, error) {
|
||||
err := this.expectValue(lexer.Symbol, valuesOperator...)
|
||||
if err != nil { return nil, err }
|
||||
operation := &entity.Operation {
|
||||
Position: pos,
|
||||
Operator: entity.OperatorFromString(this.value()),
|
||||
}
|
||||
this.next()
|
||||
|
||||
for {
|
||||
err = this.expectDesc (
|
||||
"expression or operation end",
|
||||
appendCopy(startTokensExpression, lexer.RBracket)...)
|
||||
if err != nil { return nil, err }
|
||||
if this.token.Is(lexer.RBracket) { break }
|
||||
|
||||
argument, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
operation.Arguments = append(operation.Arguments, argument)
|
||||
}
|
||||
operation.Position = operation.Position.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return operation, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseBlock () (*entity.Block, error) {
|
||||
err := this.expectDesc("Block", lexer.LBrace)
|
||||
if err != nil { return nil, err }
|
||||
block := &entity.Block {
|
||||
Position: this.pos(),
|
||||
}
|
||||
this.next()
|
||||
|
||||
for {
|
||||
err := this.expectDesc (
|
||||
descriptionExpression,
|
||||
appendCopy(startTokensExpression, lexer.RBrace)...)
|
||||
if err != nil { return nil, err }
|
||||
if this.kind() == lexer.RBrace{ break }
|
||||
|
||||
step, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
block.Steps = append(block.Steps, step)
|
||||
}
|
||||
block.Position = block.Position.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return block, nil
|
||||
}
|
||||
|
||||
var descriptionDeclaration = "declaration"
|
||||
var startTokensDeclaration = []lexer.TokenKind { lexer.Ident }
|
||||
|
||||
func (this *Parser) parseDeclaration () (*entity.Declaration, error) {
|
||||
err := this.expectDesc(descriptionDeclaration, startTokensDeclaration...)
|
||||
if err != nil { return nil, err }
|
||||
name := this.value()
|
||||
pos := this.pos()
|
||||
this.next()
|
||||
return this.parseDeclarationCore(pos, name)
|
||||
}
|
||||
|
||||
func (this *Parser) parseDeclarationCore (pos errors.Position, name string) (*entity.Declaration, error) {
|
||||
err := this.expect(lexer.Colon)
|
||||
if err != nil { return nil, err }
|
||||
this.next()
|
||||
ty, err := this.parseType()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
return &entity.Declaration {
|
||||
Position: pos,
|
||||
Name: name,
|
||||
Ty: ty,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseIfElse () (*entity.IfElse, error) {
|
||||
err := this.expectValue(lexer.Ident, "if")
|
||||
if err != nil { return nil, err }
|
||||
ifElse := &entity.IfElse {
|
||||
Position: this.pos(),
|
||||
}
|
||||
|
||||
err = this.expectNextDesc("condition", startTokensExpression...)
|
||||
if err != nil { return nil, err }
|
||||
ifElse.Condition, err = this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
err = this.expectValue(lexer.Ident, "then")
|
||||
if err != nil { return nil, err }
|
||||
|
||||
this.expectNextDesc("branch expression", startTokensExpression...)
|
||||
ifElse.True, err = this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if this.token.Is(lexer.Ident) && this.token.ValueIs("else") {
|
||||
// Ident "else": false branch expression
|
||||
this.expectNextDesc("Branch expression", startTokensExpression...)
|
||||
ifElse.False, err = this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
|
||||
return ifElse, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseLoop () (*entity.Loop, error) {
|
||||
err := this.expectValue(lexer.Ident, "loop")
|
||||
if err != nil { return nil, err }
|
||||
pos := this.pos()
|
||||
|
||||
this.next()
|
||||
body, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
return &entity.Loop {
|
||||
Position: pos,
|
||||
Body: body,
|
||||
}, nil
|
||||
}
|
115
parser/literal.go
Normal file
115
parser/literal.go
Normal file
@ -0,0 +1,115 @@
|
||||
package parser
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/fspl/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
func (this *Parser) parseLiteralInt () (*entity.LiteralInt, error) {
|
||||
err := this.expect(lexer.Int)
|
||||
if err != nil { return nil, err }
|
||||
defer this.next()
|
||||
|
||||
value, err := parseInteger(this.value())
|
||||
if err != nil { return nil, errors.Errorf(this.pos(), err.Error()) }
|
||||
|
||||
return &entity.LiteralInt {
|
||||
Position: this.pos(),
|
||||
Value: int(value), // TODO struct should store as int64
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseLiteralFloat () (*entity.LiteralFloat, error) {
|
||||
err := this.expect(lexer.Float)
|
||||
if err != nil { return nil, err }
|
||||
defer this.next()
|
||||
|
||||
value, err := parseFloat(this.value())
|
||||
if err != nil { return nil, errors.Errorf(this.pos(), err.Error()) }
|
||||
|
||||
return &entity.LiteralFloat {
|
||||
Position: this.pos(),
|
||||
Value: value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseLiteralString () (*entity.LiteralString, error) {
|
||||
err := this.expect(lexer.String)
|
||||
if err != nil { return nil, err }
|
||||
defer this.next()
|
||||
|
||||
return &entity.LiteralString {
|
||||
Position: this.pos(),
|
||||
ValueUTF8: this.value(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseLiteralBoolean () (*entity.LiteralBoolean, error) {
|
||||
err := this.expectValueDesc("boolean", lexer.Ident, "true", "false")
|
||||
if err != nil { return nil, err }
|
||||
defer this.next()
|
||||
|
||||
return &entity.LiteralBoolean {
|
||||
Position: this.pos(),
|
||||
Value: this.value() == "true",
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseLiteralNil () (*entity.LiteralNil, error) {
|
||||
err := this.expectValueDesc("Nil", lexer.Ident, "nil")
|
||||
if err != nil { return nil, err }
|
||||
defer this.next()
|
||||
|
||||
return &entity.LiteralNil {
|
||||
Position: this.pos(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseLiteralArrayCore (pos errors.Position) (*entity.LiteralArray, error) {
|
||||
err := this.expect(lexer.Star)
|
||||
if err != nil { return nil, err }
|
||||
literal := &entity.LiteralArray {
|
||||
Position: pos,
|
||||
}
|
||||
this.next()
|
||||
|
||||
for {
|
||||
err := this.expectDesc (
|
||||
"array literal element or end",
|
||||
appendCopy(startTokensExpression, lexer.RParen)...)
|
||||
if err != nil { return nil, err }
|
||||
if this.kind() == lexer.RParen { break }
|
||||
|
||||
element, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
literal.Elements = append(literal.Elements, element)
|
||||
}
|
||||
literal.Position = literal.Position.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return literal, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseLiteralStructCore (pos errors.Position) (*entity.LiteralStruct, error) {
|
||||
err := this.expect(lexer.Dot)
|
||||
if err != nil { return nil, err }
|
||||
literal := &entity.LiteralStruct {
|
||||
Position: pos,
|
||||
}
|
||||
this.next()
|
||||
|
||||
for {
|
||||
err := this.expectDesc (
|
||||
"struct literal member or end",
|
||||
appendCopy(startTokensExpression, lexer.RParen)...)
|
||||
if err != nil { return nil, err }
|
||||
if this.kind() == lexer.RParen { break }
|
||||
|
||||
member, err := this.parseMember()
|
||||
if err != nil { return nil, err }
|
||||
literal.Members = append(literal.Members, member)
|
||||
}
|
||||
literal.Position = literal.Position.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
return literal, nil
|
||||
}
|
91
parser/misc.go
Normal file
91
parser/misc.go
Normal file
@ -0,0 +1,91 @@
|
||||
package parser
|
||||
|
||||
import "strconv"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
var descriptionSignature = "signature"
|
||||
var startTokensSignature = []lexer.TokenKind { lexer.LBracket }
|
||||
|
||||
func (this *Parser) parseSignature () (*entity.Signature, error) {
|
||||
err := this.expectDesc(descriptionSignature, startTokensSignature...)
|
||||
if err != nil { return nil, err }
|
||||
err = this.expectNext(lexer.Ident)
|
||||
if err != nil { return nil, err }
|
||||
signature := &entity.Signature{
|
||||
Position: this.pos(),
|
||||
Name: this.value(),
|
||||
|
||||
}
|
||||
this.next()
|
||||
|
||||
for {
|
||||
err := this.expectDesc (
|
||||
"parameter or signature end",
|
||||
appendCopy(startTokensDeclaration, lexer.RBracket)...)
|
||||
if err != nil { return nil, err }
|
||||
if this.kind() == lexer.RBracket { break }
|
||||
|
||||
parameter, err := this.parseDeclaration()
|
||||
if err != nil { return nil, err }
|
||||
signature.Arguments = append(signature.Arguments, parameter)
|
||||
}
|
||||
signature.Position = signature.Position.Union(this.pos())
|
||||
this.next()
|
||||
|
||||
if this.kind() == lexer.Colon {
|
||||
this.next()
|
||||
ret, err := this.parseType()
|
||||
if err != nil { return nil, err }
|
||||
signature.Return = ret
|
||||
}
|
||||
|
||||
return signature, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseMember () (*entity.Member, error) {
|
||||
err := this.expectDesc("struct member", lexer.Ident)
|
||||
if err != nil { return nil, err }
|
||||
name := this.value()
|
||||
pos := this.pos()
|
||||
|
||||
err = this.expectNext(lexer.Colon)
|
||||
if err != nil { return nil, err }
|
||||
this.next()
|
||||
|
||||
value, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
return &entity.Member {
|
||||
Position: pos,
|
||||
Name: name,
|
||||
Value: value,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseInteger (value string) (int64, error) {
|
||||
base := 10
|
||||
if len(value) > 0 && value[0] == '0' {
|
||||
base = 8
|
||||
if len(value) > 1 {
|
||||
switch value[1] {
|
||||
case 'x':
|
||||
value = value[2:]
|
||||
base = 16
|
||||
case 'b':
|
||||
value = value[2:]
|
||||
base = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
integer, err := strconv.ParseInt(value, base, 64)
|
||||
if err != nil { return 0, err }
|
||||
return integer, nil
|
||||
}
|
||||
|
||||
func parseFloat (value string) (float64, error) {
|
||||
float, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil { return 0, err }
|
||||
return float, nil
|
||||
}
|
190
parser/parser.go
Normal file
190
parser/parser.go
Normal file
@ -0,0 +1,190 @@
|
||||
package parser
|
||||
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
|
||||
// When writing a parsing method on Parser, follow this flow:
|
||||
// - Start with the token already present in Parser.token. Do not get the
|
||||
// token after it.
|
||||
// - Use Parser.expect(), Parser.expectValue(), etc. to test whether the token
|
||||
// is a valid start for the entity
|
||||
// - If starting by calling another parsing method, trust that method to do
|
||||
// this instead.
|
||||
// - When getting new tokens, use Parser.expectNext(),
|
||||
// Parser.expectNextDesc(), etc. Only use Parser.next() when getting a token
|
||||
// *right before* calling another parsing method, or at the *very end* of
|
||||
// the current method.
|
||||
// - To terminate the method, get the next token and do nothing with it.
|
||||
// - If terminating by calling another parsing method, trust that method to do
|
||||
// this instead.
|
||||
//
|
||||
// Remember that parsing methods always start with the current token, and end by
|
||||
// getting a trailing token for the next method to start with. This makes it
|
||||
// possible to reliably switch between parsing methods depending on the type or
|
||||
// value of a token.
|
||||
//
|
||||
// The parser must never backtrack or look ahead, but it may revise previous
|
||||
// data it has output upon receiving a new token that comes directly after the
|
||||
// last token of said previous data. For example:
|
||||
//
|
||||
// X in XYZ may not be converted to A once the parser has seen Z, but
|
||||
// X in XYZ may be converted to A once the parser has seen Y.
|
||||
//
|
||||
// This disallows complex and ambiguous syntax, but should allow things such as
|
||||
// the very occasional infix operator (like . and =)
|
||||
|
||||
// Parser parses tokens from a lexer into syntax entities, which it places into
|
||||
// a tree.
|
||||
type Parser struct {
|
||||
token lexer.Token
|
||||
lexer lexer.Lexer
|
||||
tree *Tree
|
||||
}
|
||||
|
||||
// NewParser creates a new parser that parses the given file.
|
||||
func NewParser (name string, file io.Reader) (*Parser, error) {
|
||||
lx, err := lexer.NewLexer(name, file)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
return &Parser {
|
||||
lexer: lx,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ParseInto parses the parser's file into the given syntax tree.
|
||||
func (this *Parser) ParseInto (tree *Tree) error {
|
||||
this.tree = tree
|
||||
err := this.parse()
|
||||
if err == io.EOF { err = nil }
|
||||
return err
|
||||
}
|
||||
|
||||
// expect checks the current token to see if it matches a list of token kind(s),
|
||||
// else it returns an error describing what it expected.
|
||||
func (this *Parser) expect (allowed ...lexer.TokenKind) error {
|
||||
// fmt.Println("expect", this.token, allowed)
|
||||
if !this.token.Is(allowed...) {
|
||||
return errors.Errorf (
|
||||
this.token.Position, "unexpected %v; expected %s",
|
||||
this.token, commaList(allowed...))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// expectDesc is like expect, but the expected entitie(s) are described
|
||||
// manually. This can be helpful when a large syntactical entity is expected and
|
||||
// the first token(s) of it offer insufficient information.
|
||||
func (this *Parser) expectDesc (description string, allowed ...lexer.TokenKind) error {
|
||||
// fmt.Println("expectDesc", this.token, description, allowed)
|
||||
if !this.token.Is(allowed...) {
|
||||
return errors.Errorf (
|
||||
this.token.Position, "unexpected %v; expected %s",
|
||||
this.token, description)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// expectNext is like expect, but gets the next token first.
|
||||
func (this *Parser) expectNext (allowed ...lexer.TokenKind) error {
|
||||
err := this.next()
|
||||
if err != nil { return err }
|
||||
// fmt.Println("expectNext", this.token, allowed)
|
||||
return this.expect(allowed...)
|
||||
}
|
||||
|
||||
// expectNextDesc is like expectDesc, but gets the next token first.
|
||||
func (this *Parser) expectNextDesc (description string, allowed ...lexer.TokenKind) error {
|
||||
err := this.next()
|
||||
if err != nil { return err }
|
||||
// fmt.Println("expectNextDesc", this.token, description, allowed)
|
||||
return this.expectDesc(description, allowed...)
|
||||
}
|
||||
|
||||
// expectValue returns an error if the current token's value does not match the
|
||||
// allowed values.
|
||||
func (this *Parser) expectValue (kind lexer.TokenKind, allowed ...string) error {
|
||||
// fmt.Println("expectValue", this.token, kind, allowed)
|
||||
if !((this.token.Is(kind) || kind == 0) && this.token.ValueIs(allowed...)) {
|
||||
return errors.Errorf (
|
||||
this.token.Position, "unexpected %v; expected %s",
|
||||
this.token, commaList(allowed))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// expectValueDesc is like expectValue, but the expected value(s) are described
|
||||
// manually.
|
||||
func (this *Parser) expectValueDesc (description string, kind lexer.TokenKind, allowed ...string) error {
|
||||
// fmt.Println("expectValueDesc", this.token, description, kind, allowed)
|
||||
if !this.token.Is(kind) || !this.token.ValueIs(allowed...) {
|
||||
return errors.Errorf (
|
||||
this.token.Position, "unexpected %v; expected %s",
|
||||
this.token, description)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Parser) next () error {
|
||||
token, err := this.lexer.Next()
|
||||
if err != nil { return err }
|
||||
this.token = token
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Parser) bug () string {
|
||||
return fmt.Sprintln (
|
||||
"Bug detected in the compiler!\n" +
|
||||
"The parser has taken an unexpected control path.",
|
||||
"This could be due to an un-implemented feature.\n" +
|
||||
"Please submit a report with this info and stack trace to:",
|
||||
"https://git.tebibyte.media/sashakoshka/fspl/issues\n" +
|
||||
"The token being parsed was:", this.token)
|
||||
}
|
||||
|
||||
func (this *Parser) kind () lexer.TokenKind {
|
||||
return this.token.Kind
|
||||
}
|
||||
|
||||
func (this *Parser) value () string {
|
||||
return this.token.Value
|
||||
}
|
||||
|
||||
func (this *Parser) pos () errors.Position {
|
||||
return this.token.Position
|
||||
}
|
||||
|
||||
func (this *Parser) parse () error {
|
||||
err := this.next()
|
||||
if err != nil { return err }
|
||||
for this.token.Kind != lexer.EOF {
|
||||
err = this.parseTopLevel()
|
||||
if err != nil { return err }
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func commaList[ELEMENT any] (items ...ELEMENT) string {
|
||||
list := ""
|
||||
for index, item := range items {
|
||||
if index > 0 && len(items) > 2 {
|
||||
list += ", "
|
||||
if index == len(items) - 1 {
|
||||
list += " or "
|
||||
}
|
||||
}
|
||||
list += fmt.Sprintf("%v", item)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func prependCopy[ELEMENT any] (item ELEMENT, array []ELEMENT) []ELEMENT {
|
||||
return append([]ELEMENT { item }, array...)
|
||||
}
|
||||
|
||||
func appendCopy[ELEMENT any] (array []ELEMENT, items ...ELEMENT) []ELEMENT {
|
||||
newArray := make([]ELEMENT, len(array) + len(items))
|
||||
copy(newArray[copy(newArray, array):], items)
|
||||
return newArray
|
||||
}
|
@ -5,23 +5,23 @@ import "testing"
|
||||
func TestType (test *testing.T) {
|
||||
testString (test,
|
||||
// correct
|
||||
`BasicInt: Int
|
||||
Structure: (x:Int y:Int)
|
||||
Interface: ([aMethod x:Int]:*U8 [otherMethod arg:5:Int]:*U8)
|
||||
Array: 16:16:16:Int
|
||||
StructArray: 31:24:340920:(x:Int y:Int)
|
||||
String: *:U8`,
|
||||
`- BasicInt: Int
|
||||
- Structure: (. x:Int y:Int)
|
||||
- Interface: (? [aMethod x:Int]:*U8 [otherMethod arg:5:Int]:*U8)
|
||||
- Array: 16:16:16:Int
|
||||
- StructArray: 31:24:340920:(. x:Int y:Int)
|
||||
- String: *:U8`,
|
||||
// input
|
||||
`
|
||||
BasicInt: Int
|
||||
Structure: (
|
||||
Structure: (.
|
||||
x:Int
|
||||
y:Int)
|
||||
Interface: (
|
||||
Interface: (?
|
||||
[aMethod x:Int]:*U8
|
||||
[otherMethod arg:5:Int]:*U8)
|
||||
Array: 16:16:16:Int
|
||||
StructArray: 31:24:340920:(
|
||||
StructArray: 31:24:340920:(.
|
||||
x:Int
|
||||
y:Int)
|
||||
String: *:U8
|
||||
@ -31,19 +31,19 @@ String: *:U8
|
||||
func TestLiteral (test *testing.T) {
|
||||
testString (test,
|
||||
// correct
|
||||
`[int]:Int = 5
|
||||
[float]:F64 = 324.23409
|
||||
[boolean]:Bool = true
|
||||
[boolean2]:Bool = false
|
||||
[struct]:(x:Int y:(w:F64 z:F64)) = (x:1 y:(w:1.2 z:78.5))
|
||||
[string]:String = 'hahah \'sup\005'`,
|
||||
`- [int]:Int = 5
|
||||
- [float]:F64 = 324.23409
|
||||
- [boolean]:Bool = true
|
||||
- [boolean2]:Bool = false
|
||||
- [struct]:(. x:Int y:(. w:F64 z:F64)) = (. x:1 y:(. w:1.2 z:78.5))
|
||||
- [string]:String = 'hahah \'sup\005'`,
|
||||
// input
|
||||
`
|
||||
[int]:Int = 5
|
||||
[float]:F64 = 324.23409
|
||||
[boolean]:Bool = true
|
||||
[boolean2]:Bool = false
|
||||
[struct]:(x:Int y:(w:F64 z:F64)) = (x: 1 y: (w: 1.2 z: 78.5))
|
||||
[struct]:(. x:Int y:(. w:F64 z:F64)) = (. x: 1 y: (. w: 1.2 z: 78.5))
|
||||
[string]:String = 'hahah \'sup\005'
|
||||
`)
|
||||
}
|
||||
@ -51,27 +51,27 @@ testString (test,
|
||||
func TestExpression (test *testing.T) {
|
||||
testString (test,
|
||||
// correct
|
||||
`[var] = sdfdf
|
||||
[memberAccess] = sdfdf.a
|
||||
[methodAccess] = sdfdf.[method 238 9 203]
|
||||
[declaration] = sdfdf:Int
|
||||
[call] = [print 3 1]
|
||||
[emptyBlock] = {}
|
||||
[nestedBlock] = {342 93.34 {3948 32}}
|
||||
[subscript] = [.(* 3 1 2 4) 3]
|
||||
[slice] = [\(* 1 2 3 4 5 6 7 8 9) consumed:]
|
||||
[dereference] = [.ptr]
|
||||
[reference] = [@val]
|
||||
[valueCast] = [~ F32 someValue]
|
||||
[bitCast] = [~~ 4:U8 someValue]
|
||||
[math] = {[+ 3 4 2 [/ 24 3]] [|| 3 [<< 56 4]] [++ 3] [-- 3] [- 3 1]}
|
||||
[ifElse]:Int = if true then 4 else 5
|
||||
[dangleElse]:Int = if true then if false then 3 else 5
|
||||
[loop]:Int = {i:Int=0 if [< i 3] then return 1 loop {[print 3] if [> i 3] then break 0 i=[++ i]}}`,
|
||||
`- [var] = sdfdf
|
||||
- [memberAccess] = [~ T sdfdf].a.x.y.z
|
||||
- [methodAccess] = sdfdf.[method 238 9 203]
|
||||
- [declaration] = sdfdf:Int
|
||||
- [call] = [print 3 1]
|
||||
- [emptyBlock] = {}
|
||||
- [nestedBlock] = {342 93.34 {3948 32}}
|
||||
- [subscript] = [.(* 3 1 2 4) 3]
|
||||
- [slice] = [\(* 1 2 3 4 5 6 7 8 9) consumed/]
|
||||
- [dereference] = [.ptr]
|
||||
- [reference] = [@val]
|
||||
- [valueCast] = [~ F32 someValue]
|
||||
- [bitCast] = [~~ 4:U8 someValue]
|
||||
- [math] = {[+ 3 4 2 [/ 24 3]] [|| 3 [<< 56 4]] [++ 3] [-- 3] [- 3 1]}
|
||||
- [ifElse]:Int = if true then 4 else 5
|
||||
- [dangleElse]:Int = if true then if false then 3 else 5
|
||||
- [loop]:Int = {i:Int=0 if [< i 3] then return 1 loop {[print 3] if [> i 3] then break 0 i=[++ i]}}`,
|
||||
// input
|
||||
`
|
||||
[var] = sdfdf
|
||||
[memberAccess] = sdfdf.a
|
||||
[memberAccess] = [~ T sdfdf].a.x.y.z
|
||||
[methodAccess] = sdfdf.[method 238 9 203]
|
||||
[declaration] = sdfdf:Int
|
||||
[call] = [print 3 1]
|
||||
@ -85,7 +85,7 @@ testString (test,
|
||||
}
|
||||
}
|
||||
[subscript] = [. (* 3 1 2 4) 3]
|
||||
[slice] = [\ (* 1 2 3 4 5 6 7 8 9) consumed:]
|
||||
[slice] = [\ (* 1 2 3 4 5 6 7 8 9) consumed/]
|
||||
[dereference] = [. ptr]
|
||||
[reference] = [@ val]
|
||||
[valueCast] = [~ F32 someValue]
|
||||
@ -119,22 +119,22 @@ testString (test,
|
||||
func TestFunction (test *testing.T) {
|
||||
testString (test,
|
||||
// correct
|
||||
`[arguments x:Int y:Int z:Int] = {}
|
||||
[arrayArguments arg:4:54:23:28:(x:Int y:Int)] = {}`,
|
||||
`- [arguments x:Int y:Int z:Int] = {}
|
||||
- [arrayArguments arg:4:54:23:28:(. x:Int y:Int)] = {}`,
|
||||
//input
|
||||
`
|
||||
[arguments x:Int y:Int z:Int] = { }
|
||||
[arrayArguments arg:4:54:23:28:(x:Int y:Int)] = { }
|
||||
[arrayArguments arg:4:54:23:28:(. x:Int y:Int)] = { }
|
||||
`)
|
||||
}
|
||||
|
||||
func TestMethod (test *testing.T) {
|
||||
testString (test,
|
||||
// correct
|
||||
`BopIt: Int
|
||||
BopIt.[bop force:UInt] = {}
|
||||
BopIt.[twist angle:F64] = {}
|
||||
BopIt.[pull distance:Int] = {}`,
|
||||
`- BopIt: Int
|
||||
- BopIt.[bop force:UInt] = {}
|
||||
- BopIt.[twist angle:F64] = {}
|
||||
- BopIt.[pull distance:Int] = {}`,
|
||||
//input
|
||||
`
|
||||
BopIt: Int
|
||||
@ -148,31 +148,31 @@ func TestAccess (test *testing.T) {
|
||||
testString (test,
|
||||
// correct
|
||||
`+ PublicType: Int
|
||||
# RestrictedType: (x:Int y:Int)
|
||||
~ RestrictedType: (. x:Int y:Int)
|
||||
- PrivateType: String
|
||||
AlsoPrivateType: Byte
|
||||
- AlsoPrivateType: Byte
|
||||
+ [publicFn]:Int = 0
|
||||
# [restrictedFn]:(x:Int y:Int) = (x:0 y:0)
|
||||
~ [restrictedFn]:(. x:Int y:Int) = (. x:0 y:0)
|
||||
- [privateFn]:Rune = 'a'
|
||||
[alsoPrivateFn]:Byte = 0
|
||||
T: Int
|
||||
- [alsoPrivateFn]:Byte = 0
|
||||
- T: Int
|
||||
+ T.[publicFn]:Int = 0
|
||||
# T.[restrictedFn]:(x:Int y:Int) = (x:0 y:0)
|
||||
~ T.[restrictedFn]:(. x:Int y:Int) = (. x:0 y:0)
|
||||
- T.[privateFn]:Rune = 'a'
|
||||
T.[alsoPrivateFn]:Byte = 0`,
|
||||
- T.[alsoPrivateFn]:Byte = 0`,
|
||||
//input
|
||||
`
|
||||
+ PublicType: Int
|
||||
# RestrictedType: (x:Int y:Int)
|
||||
~ RestrictedType: (. x:Int y:Int)
|
||||
- PrivateType: String
|
||||
AlsoPrivateType: Byte
|
||||
+ [publicFn]: Int = 0
|
||||
# [restrictedFn]: (x:Int y:Int) = (x:0 y:0)
|
||||
~ [restrictedFn]: (. x:Int y:Int) = (. x:0 y:0)
|
||||
- [privateFn]: Rune = 'a'
|
||||
[alsoPrivateFn]: Byte = 0
|
||||
T: Int
|
||||
+ T.[publicFn]: Int = 0
|
||||
# T.[restrictedFn]: (x:Int y:Int) = (x:0 y:0)
|
||||
~ T.[restrictedFn]: (. x:Int y:Int) = (. x:0 y:0)
|
||||
- T.[privateFn]: Rune = 'a'
|
||||
T.[alsoPrivateFn]: Byte = 0
|
||||
`)
|
||||
@ -181,9 +181,9 @@ T.[alsoPrivateFn]: Byte = 0
|
||||
func TestLinkName (test *testing.T) {
|
||||
testString (test,
|
||||
// correct
|
||||
`[puts byte:*:Byte]:Index 'puts'
|
||||
File.[write size:Index nmemb:Index]:Index 'fwrite'
|
||||
[main]:Int 'main' = 0`,
|
||||
`- [puts byte:*:Byte]:Index 'puts'
|
||||
- File.[write size:Index nmemb:Index]:Index 'fwrite'
|
||||
- [main]:Int 'main' = 0`,
|
||||
// input
|
||||
`
|
||||
[puts byte:*:Byte]: Index 'puts'
|
||||
@ -195,7 +195,7 @@ File.[write size:Index nmemb:Index]: Index 'fwrite'
|
||||
func TestModuleNamespace (test *testing.T) {
|
||||
testString (test,
|
||||
// correct
|
||||
`[hello out:io::Writer] = ioutil::[writeString out 'hello']`,
|
||||
`- [hello out:io::Writer] = ioutil::[writeString out 'hello']`,
|
||||
// input
|
||||
`
|
||||
[hello out:io::Writer] = ioutil::[writeString out 'hello']
|
||||
|
@ -3,13 +3,20 @@ package parser
|
||||
import "io"
|
||||
import "testing"
|
||||
import "strings"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/testcommon"
|
||||
|
||||
func testString (test *testing.T, correct string, input string) {
|
||||
ast := Tree { }
|
||||
err := ast.Parse("input.fspl", strings.NewReader(input))
|
||||
if err != nil && err != io.EOF{
|
||||
test.Error("parser returned error:", err)
|
||||
if err, ok := err.(*errors.Error); ok {
|
||||
test.Log("parser returned error:")
|
||||
test.Log(err.Format())
|
||||
test.Fail()
|
||||
} else {
|
||||
test.Error("parser returned error:", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
144
parser/toplevel.go
Normal file
144
parser/toplevel.go
Normal file
@ -0,0 +1,144 @@
|
||||
package parser
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/fspl/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
var descriptionTopLevel = "typedef, function, or method"
|
||||
var startTokensTopLevel = []lexer.TokenKind {
|
||||
lexer.Symbol,
|
||||
lexer.LBracket,
|
||||
lexer.TypeIdent,
|
||||
lexer.EOF,
|
||||
}
|
||||
|
||||
func (this *Parser) parseTopLevel () error {
|
||||
err := this.expectDesc (
|
||||
descriptionTopLevel,
|
||||
startTokensTopLevel...)
|
||||
if err != nil { return err }
|
||||
if this.token.EOF() { return nil }
|
||||
|
||||
access := entity.AccessPrivate
|
||||
if this.token.Kind == lexer.Symbol {
|
||||
access, err = this.parseAccess()
|
||||
|
||||
err = this.expectDesc (
|
||||
descriptionTopLevel,
|
||||
lexer.Symbol,
|
||||
lexer.LBracket,
|
||||
lexer.Ident,
|
||||
lexer.TypeIdent)
|
||||
if err != nil { return err }
|
||||
}
|
||||
|
||||
// LBracket: Function
|
||||
if this.token.Is(lexer.LBracket) {
|
||||
function, err := this.parseFunctionCore(access)
|
||||
if err != nil { return err }
|
||||
this.tree.AddDeclaration(function)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TypeIdent: Method, or Typedef
|
||||
typeName := this.token.Value
|
||||
err = this.expectNext(lexer.Dot, lexer.Colon)
|
||||
if err != nil { return err }
|
||||
|
||||
switch this.token.Kind {
|
||||
case lexer.Dot:
|
||||
// Dot: Method
|
||||
err = this.expectNextDesc(descriptionSignature, startTokensSignature...)
|
||||
if err != nil { return err }
|
||||
method, err := this.parseMethodCore(access, typeName)
|
||||
if err != nil { return err }
|
||||
this.tree.AddDeclaration(method)
|
||||
case lexer.Colon:
|
||||
// Colon: Typedef
|
||||
err = this.expectNextDesc(descriptionType, startTokensType...)
|
||||
if err != nil { return err }
|
||||
typedef, err := this.parseTypedefCore(access, typeName)
|
||||
if err != nil { return err }
|
||||
this.tree.AddDeclaration(typedef)
|
||||
default: this.bug()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseAccess () (entity.Access, error) {
|
||||
err := this.expectValueDesc (
|
||||
"Access control specifier",
|
||||
lexer.Symbol, "-", "~", "+")
|
||||
if err != nil { return 0, err }
|
||||
defer this.next()
|
||||
|
||||
switch this.token.Value {
|
||||
case "-": return entity.AccessPrivate, nil
|
||||
case "~": return entity.AccessRestricted, nil
|
||||
case "+": return entity.AccessPublic, nil
|
||||
default: panic(this.bug())
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Parser) parseFunctionCore (access entity.Access) (*entity.Function, error) {
|
||||
signature, err := this.parseSignature()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
function := &entity.Function {
|
||||
Acc: access,
|
||||
Signature: signature,
|
||||
}
|
||||
|
||||
err = this.expectDesc (
|
||||
"function body, link name, or top-level declaration",
|
||||
appendCopy(startTokensTopLevel, lexer.Symbol, lexer.String)...)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if this.token.Is(lexer.String) {
|
||||
// String: Link name
|
||||
function.LinkName = this.token.Value
|
||||
err = this.expectNextDesc (
|
||||
"Function body, " + descriptionTopLevel,
|
||||
appendCopy(startTokensTopLevel, lexer.Symbol)...)
|
||||
if err != nil { return nil, err }
|
||||
}
|
||||
|
||||
if !(this.token.Is(lexer.Symbol) && this.token.ValueIs("=")) {
|
||||
// function has no body and is finished,
|
||||
// move on to next top level declaration or graceful EOF.
|
||||
return function, nil
|
||||
}
|
||||
|
||||
// Symbol '=': Function body
|
||||
err = this.expectValueDesc("function body", lexer.Symbol, "=")
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// Expression
|
||||
err = this.expectNextDesc(descriptionExpression, startTokensExpression...)
|
||||
if err != nil { return nil, err }
|
||||
bodyExpression, err := this.parseExpression()
|
||||
if err != nil { return nil, err }
|
||||
function.Body = bodyExpression
|
||||
return function, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseMethodCore (access entity.Access, typeName string) (*entity.Method, error) {
|
||||
function, err := this.parseFunctionCore(access)
|
||||
if err != nil { return nil, err }
|
||||
return &entity.Method {
|
||||
Acc: function.Acc,
|
||||
TypeName: typeName,
|
||||
Signature: function.Signature,
|
||||
LinkName: function.LinkName,
|
||||
Body: function.Body,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseTypedefCore (access entity.Access, typeName string) (*entity.Typedef, error) {
|
||||
ty, err := this.parseType()
|
||||
if err != nil { return nil, err }
|
||||
return &entity.Typedef {
|
||||
Acc: access,
|
||||
Name: typeName,
|
||||
Type: ty,
|
||||
}, nil
|
||||
}
|
@ -3,7 +3,6 @@ package parser
|
||||
import "io"
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
// Tree represents a parsed abstract syntax tree. It has no constructor and its
|
||||
@ -21,12 +20,6 @@ func (this *Tree) String () string {
|
||||
return out
|
||||
}
|
||||
|
||||
// Parse parses the contents of the given io.Reader into the tree.
|
||||
func (this *Tree) Parse (name string, file io.Reader) error {
|
||||
// TODO
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseFile parses the contents of the given file into the tree.
|
||||
func (this *Tree) ParseFile (name string) error {
|
||||
file, err := os.Open(name)
|
||||
@ -34,3 +27,15 @@ func (this *Tree) ParseFile (name string) error {
|
||||
defer file.Close()
|
||||
return this.Parse(name, file)
|
||||
}
|
||||
|
||||
// Parse parses the contents of the given io.Reader into the tree.
|
||||
func (this *Tree) Parse (name string, file io.Reader) error {
|
||||
parser, err := NewParser(name, file)
|
||||
if err != nil { return err }
|
||||
return parser.ParseInto(this)
|
||||
}
|
||||
|
||||
// AddDeclaration adds a top-level entity to the tree.
|
||||
func (this *Tree) AddDeclaration (topLevel ...entity.TopLevel) {
|
||||
this.Declarations = append(this.Declarations, topLevel...)
|
||||
}
|
||||
|
220
parser/type.go
Normal file
220
parser/type.go
Normal file
@ -0,0 +1,220 @@
|
||||
package parser
|
||||
|
||||
import "strconv"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
var descriptionType = "type"
|
||||
var startTokensType = []lexer.TokenKind {
|
||||
lexer.Ident,
|
||||
lexer.TypeIdent,
|
||||
lexer.Star,
|
||||
lexer.Int,
|
||||
lexer.LParen,
|
||||
}
|
||||
|
||||
func (this *Parser) parseType () (entity.Type, error) {
|
||||
err := this.expectDesc(descriptionType, startTokensType...)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
switch this.kind() {
|
||||
case lexer.Ident:
|
||||
ident := this.token.Value
|
||||
err := this.expectNext(lexer.DoubleColon)
|
||||
if err != nil { return nil, err }
|
||||
err = this.expectNext(lexer.TypeIdent)
|
||||
if err != nil { return nil, err }
|
||||
return this.parseTypeNamedCore(ident)
|
||||
|
||||
case lexer.TypeIdent:
|
||||
if this.value() == "Int" || this.value() == "UInt" {
|
||||
return this.parseTypeWord()
|
||||
}
|
||||
|
||||
if this.token.ValueIs("F16", "F32", "F64", "F128") {
|
||||
return this.parseTypeFloat()
|
||||
}
|
||||
|
||||
if len(this.value()) > 0 {
|
||||
_, err := strconv.Atoi(this.value()[1:])
|
||||
if err == nil && (
|
||||
this.value()[0] == 'U' ||
|
||||
this.value()[0] == 'I') {
|
||||
|
||||
return this.parseTypeInt()
|
||||
}
|
||||
}
|
||||
|
||||
return this.parseTypeNamedCore("")
|
||||
|
||||
case lexer.Star:
|
||||
return this.parseTypePointerOrSlice()
|
||||
|
||||
case lexer.Int:
|
||||
return this.parseTypeArray()
|
||||
|
||||
case lexer.LParen:
|
||||
err := this.expectNext(lexer.Dot, lexer.Symbol)
|
||||
if err != nil { return nil, err }
|
||||
err = this.expectValue(0, ".", "?", "|")
|
||||
if err != nil { return nil, err }
|
||||
|
||||
switch this.value() {
|
||||
case ".": return this.parseTypeStructCore()
|
||||
case "?": return this.parseTypeInterfaceCore()
|
||||
}
|
||||
panic(this.bug())
|
||||
}
|
||||
panic(this.bug())
|
||||
}
|
||||
|
||||
func (this *Parser) parseTypeNamedCore (module string) (entity.Type, error) {
|
||||
err := this.expect(lexer.TypeIdent)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
defer this.next()
|
||||
return &entity.TypeNamed {
|
||||
Position: this.pos(),
|
||||
Name: this.value(),
|
||||
Module: module,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseTypePointerOrSlice () (entity.Type, error) {
|
||||
err := this.expectDesc("pointer type or slice type", lexer.Star)
|
||||
if err != nil { return nil, err }
|
||||
start := this.pos()
|
||||
|
||||
err = this.expectNextDesc (
|
||||
"Colon or Type",
|
||||
appendCopy(startTokensType, lexer.Colon)...)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
if this.token.Is(lexer.Colon) {
|
||||
this.next()
|
||||
element, err := this.parseType()
|
||||
if err != nil { return nil, err }
|
||||
return &entity.TypeSlice {
|
||||
Position: start.Union(this.pos()),
|
||||
Element: element,
|
||||
}, nil
|
||||
} else {
|
||||
referenced, err := this.parseType()
|
||||
if err != nil { return nil, err }
|
||||
return &entity.TypePointer {
|
||||
Position: start.Union(this.pos()),
|
||||
Referenced: referenced,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Parser) parseTypeArray () (entity.Type, error) {
|
||||
err := this.expectDesc("array type", lexer.Int)
|
||||
if err != nil { return nil, err }
|
||||
start := this.pos()
|
||||
|
||||
length, err := strconv.Atoi(this.value())
|
||||
if err != nil { return nil, err }
|
||||
err = this.expectNext(lexer.Colon)
|
||||
if err != nil { return nil, err }
|
||||
err = this.next()
|
||||
if err != nil { return nil, err }
|
||||
element, err := this.parseType()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
return &entity.TypeArray {
|
||||
Position: start.Union(this.pos()),
|
||||
Length: length,
|
||||
Element: element,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseTypeStructCore () (entity.Type, error) {
|
||||
err := this.expect(lexer.Dot)
|
||||
if err != nil { return nil, err }
|
||||
ty := &entity.TypeStruct {
|
||||
Position: this.pos(),
|
||||
}
|
||||
this.next()
|
||||
|
||||
for {
|
||||
err := this.expectDesc (
|
||||
"struct member or end",
|
||||
appendCopy(startTokensDeclaration, lexer.RParen)...)
|
||||
if err != nil { return nil, err }
|
||||
if this.kind() == lexer.RParen { break }
|
||||
|
||||
member, err := this.parseDeclaration()
|
||||
if err != nil { return nil, err }
|
||||
ty.Members = append(ty.Members, member)
|
||||
}
|
||||
ty.Position = ty.Position.Union(this.pos())
|
||||
this.next()
|
||||
return ty, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseTypeInterfaceCore () (entity.Type, error) {
|
||||
err := this.expectValue(lexer.Symbol, "?")
|
||||
if err != nil { return nil, err }
|
||||
ty := &entity.TypeInterface {
|
||||
Position: this.pos(),
|
||||
}
|
||||
this.next()
|
||||
|
||||
for {
|
||||
err := this.expectDesc (
|
||||
"interface behavior or end",
|
||||
appendCopy(startTokensSignature, lexer.RParen)...)
|
||||
if err != nil { return nil, err }
|
||||
if this.kind() == lexer.RParen { break }
|
||||
|
||||
behavior, err := this.parseSignature()
|
||||
if err != nil { return nil, err }
|
||||
ty.Behaviors = append(ty.Behaviors, behavior)
|
||||
}
|
||||
ty.Position = ty.Position.Union(this.pos())
|
||||
this.next()
|
||||
return ty, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseTypeInt () (entity.Type, error) {
|
||||
err := this.expect(lexer.TypeIdent)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
value := this.value()
|
||||
width, err := strconv.Atoi(value[1:])
|
||||
if err != nil || !(value[0] == 'I' || value[0] == 'U') {
|
||||
return nil, errors.Errorf(this.pos(), "malformed Integer type")
|
||||
}
|
||||
|
||||
defer this.next()
|
||||
return &entity.TypeInt {
|
||||
Position: this.pos(),
|
||||
Width: width,
|
||||
Signed: value[0] == 'I',
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseTypeWord () (entity.Type, error) {
|
||||
err := this.expectValue(lexer.TypeIdent, "Int", "UInt")
|
||||
if err != nil { return nil, err }
|
||||
defer this.next()
|
||||
return &entity.TypeWord {
|
||||
Position: this.pos(),
|
||||
Signed: this.value()[0] == 'I',
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseTypeFloat () (entity.Type, error) {
|
||||
err := this.expectValue(lexer.TypeIdent, "F16", "F32", "F64", "F128")
|
||||
if err != nil { return nil, err }
|
||||
|
||||
width, _ := strconv.Atoi(this.value()[1:])
|
||||
|
||||
defer this.next()
|
||||
return &entity.TypeFloat {
|
||||
Position: this.pos(),
|
||||
Width: width,
|
||||
}, nil
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user