Merge pull request 'parser-remove-participle' (#7) from parser-remove-participle into remove-participle

Reviewed-on: sashakoshka/fspl#7
This commit is contained in:
Sasha Koshka 2024-02-08 08:29:23 +00:00
commit 090d52b8f8
22 changed files with 1635 additions and 255 deletions

View File

@ -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)
}

View File

@ -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" }

View File

@ -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 {

View File

@ -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, "'")

View File

@ -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 + ")"
}

View File

@ -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 }

View File

@ -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 {

View File

@ -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
View File

@ -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
View File

@ -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=

View File

@ -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

View File

@ -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`,

View File

@ -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
View 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
View 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
View 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
View 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
}

View File

@ -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']

View File

@ -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
View 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
}

View File

@ -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
View 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
}