Merge pull request 'remove-participle' (#10) from remove-participle into main
Reviewed-on: sashakoshka/fspl#10
This commit is contained in:
commit
4089765633
@ -2,8 +2,7 @@ package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "math"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/integer"
|
||||
|
||||
@ -24,7 +23,7 @@ type strictness int; const (
|
||||
// canAssign takes in an analyzed destination type and an analyzed source type,
|
||||
// and determines whether source can be assigned to destination.
|
||||
func (this *Tree) canAssign (
|
||||
pos lexer.Position,
|
||||
pos errors.Position,
|
||||
// assuming both are analyzed already
|
||||
destination entity.Type,
|
||||
mode strictness,
|
||||
@ -33,13 +32,13 @@ func (this *Tree) canAssign (
|
||||
fail := func () error {
|
||||
switch mode {
|
||||
case strict:
|
||||
return participle.Errorf(pos, "expected %v", destination)
|
||||
return errors.Errorf(pos, "expected %v", destination)
|
||||
case weak:
|
||||
return participle.Errorf (
|
||||
return errors.Errorf (
|
||||
pos, "cannot use %v as %v",
|
||||
source, destination)
|
||||
case structural:
|
||||
return participle.Errorf (
|
||||
return errors.Errorf (
|
||||
pos, "cannot convert from %v to %v",
|
||||
source, destination)
|
||||
default:
|
||||
@ -142,12 +141,12 @@ func (this *Tree) canAssign (
|
||||
// canAssignCoerce determines if data of an analyzed source type can be
|
||||
// converted into data of an analyzed destination type.
|
||||
func (this *Tree) canAssignCoerce (
|
||||
pos lexer.Position,
|
||||
pos errors.Position,
|
||||
destination entity.Type,
|
||||
source entity.Type,
|
||||
) error {
|
||||
fail := func () error {
|
||||
return participle.Errorf (
|
||||
return errors.Errorf (
|
||||
pos, "cannot convert from %v to %v",
|
||||
source, destination)
|
||||
}
|
||||
@ -213,13 +212,13 @@ func (this *Tree) canAssignCoerce (
|
||||
// canAssignSliceArray takes in an analyzed slice type and an analyzed array
|
||||
// type, and determines whether the array can be assigned to the slice.
|
||||
func (this *Tree) canAssignSliceArray (
|
||||
pos lexer.Position,
|
||||
pos errors.Position,
|
||||
destination *entity.TypeSlice,
|
||||
source *entity.TypeArray,
|
||||
) error {
|
||||
err := this.canAssign(pos, destination.Element, strict, source.Element)
|
||||
if err != nil {
|
||||
return participle.Errorf(pos, "expected %v", destination)
|
||||
return errors.Errorf(pos, "expected %v", destination)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
@ -229,7 +228,7 @@ func (this *Tree) canAssignSliceArray (
|
||||
// analyzed source type, and determines whether source can be assigned to
|
||||
// destination.
|
||||
func (this *Tree) canAssignInterface (
|
||||
pos lexer.Position,
|
||||
pos errors.Position,
|
||||
destination *entity.TypeInterface,
|
||||
source entity.Type,
|
||||
) error {
|
||||
@ -254,7 +253,7 @@ func (this *Tree) canAssignInterface (
|
||||
|
||||
// check equivalence
|
||||
if !signature.Equals(behavior) {
|
||||
return participle.Errorf (
|
||||
return errors.Errorf (
|
||||
pos, "%v has wrong signature for method %v",
|
||||
source, name)
|
||||
}
|
||||
@ -326,60 +325,60 @@ func (this *Tree) isLocationExpression (expression entity.Expression) error {
|
||||
case *entity.Declaration:
|
||||
return nil
|
||||
case *entity.Call:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to function call")
|
||||
case *entity.MethodCall:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to method call")
|
||||
case *entity.Subscript:
|
||||
return this.isLocationExpression(expression.Slice)
|
||||
case *entity.Slice:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to slice operation")
|
||||
case *entity.Dereference:
|
||||
return this.isLocationExpression(expression.Pointer)
|
||||
case *entity.Reference:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to reference operation")
|
||||
case *entity.ValueCast:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to value cast")
|
||||
case *entity.BitCast:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to bit cast")
|
||||
case *entity.Operation:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to %v operation",
|
||||
expression.Operator)
|
||||
case *entity.Block:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to block")
|
||||
case *entity.MemberAccess:
|
||||
return this.isLocationExpression (
|
||||
expression.Source)
|
||||
case *entity.IfElse:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to if/else")
|
||||
case *entity.Loop:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to loop")
|
||||
case *entity.Break:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to break statement")
|
||||
case *entity.Return:
|
||||
return participle.Errorf (
|
||||
expression.Pos,
|
||||
return errors.Errorf (
|
||||
expression.Position,
|
||||
"cannot assign to return statement")
|
||||
default:
|
||||
panic(fmt.Sprint (
|
||||
|
@ -27,7 +27,7 @@ testStringErr (test,
|
||||
"cannot use struct literal as Int", 3, 10,
|
||||
`
|
||||
[main] = {
|
||||
x:Int = (x: 5)
|
||||
x:Int = (. x: 5)
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -64,7 +64,7 @@ testStringErr (test,
|
||||
|
||||
func TestAssignmentLiteralErrArrayWrongLength (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"expected 3 elements", 3, 12,
|
||||
"expected 3 elements or less", 3, 12,
|
||||
`
|
||||
[main] = {
|
||||
x:3:Int = (* 1 2 3 4)
|
||||
@ -94,19 +94,19 @@ testStringErr (test,
|
||||
|
||||
func TestAssignmentLiteralErrStructWrongType (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Int", 3, 24,
|
||||
"cannot use float literal as Int", 3, 28,
|
||||
`
|
||||
[main] = {
|
||||
x:(x:Int y:Int) = (x: 5.5 y: 3)
|
||||
x:(. x:Int y:Int) = (. x: 5.5 y: 3)
|
||||
}
|
||||
`)
|
||||
}
|
||||
func TestAssignmentLiteralErrStructInteger (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use integer literal as (x:Int y:Int)", 3, 20,
|
||||
"cannot use integer literal as (. x:Int y:Int)", 3, 22,
|
||||
`
|
||||
[main] = {
|
||||
x:(x:Int y:Int) = 5
|
||||
x:(. x:Int y:Int) = 5
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -115,7 +115,7 @@ func TestAssignmentLiteralErrInterfaceInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use integer literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5
|
||||
}
|
||||
@ -126,7 +126,7 @@ func TestAssignmentLiteralErrInterfaceFloat (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use float literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = 5.5
|
||||
}
|
||||
@ -137,7 +137,7 @@ func TestAssignmentLiteralErrInterfaceArray (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use array literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = (* 1 2 3 4)
|
||||
}
|
||||
@ -148,9 +148,9 @@ func TestAssignmentLiteralErrInterfaceStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot use struct literal as Bird", 4, 11,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
[main] = {
|
||||
b:Bird = (x: 5 y: 6)
|
||||
b:Bird = (. x: 5 y: 6)
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -183,7 +183,7 @@ testString (test,
|
||||
(* 5 6)
|
||||
(* 7 8))
|
||||
slice:*:Int = (* 3 1 2 3)
|
||||
struct:(x:Int y:Int) = (
|
||||
struct:(. x:Int y:Int) = (.
|
||||
x: 9
|
||||
y: 10)
|
||||
|
||||
@ -197,7 +197,7 @@ func TestAssignmentInterfaceErrBadSignature (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"BlueJay has wrong signature for method fly", 7, 11,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly] = { }
|
||||
BlueJay.[land] = { }
|
||||
@ -211,7 +211,7 @@ func TestAssignmentInterfaceErrMissingMethod (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method named fly defined on this type", 6, 11,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[land] = { }
|
||||
[main] = {
|
||||
@ -224,7 +224,7 @@ func TestAssignmentInterfaceErrBadLayer (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method named fly defined on this type", 7, 11,
|
||||
`
|
||||
Bird: ([fly distance:F64])
|
||||
Bird: (~ [fly distance:F64])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly distance:F64] = { }
|
||||
BlueJayRef: *BlueJay
|
||||
@ -237,7 +237,7 @@ BlueJayRef: *BlueJay
|
||||
func TestAssignmentInterface (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly distance:F64] = { }
|
||||
BlueJay.[land] = { }
|
||||
@ -267,15 +267,15 @@ testString (test,
|
||||
[f]:Byte = 5
|
||||
A:Int
|
||||
A.[g]:Int = 2
|
||||
B:([g]:Int)
|
||||
B:(~ [g]:Int)
|
||||
[main] = {
|
||||
a:Int
|
||||
b:Int = a
|
||||
c:Int = d:Int
|
||||
d = { a:F64 b }
|
||||
e:Byte = [f]
|
||||
g:(x:Int y:(w:F64 z:F64)) = (x: 1 y: (w: 1.2 z: 78.5))
|
||||
gx:(w:F64 z:F64) = g.y
|
||||
g:(. x:Int y:(. w:F64 z:F64)) = (. x: 1 y: (. w: 1.2 z: 78.5))
|
||||
gx:(. w:F64 z:F64) = g.y
|
||||
h:F64 = gx.z
|
||||
i:A
|
||||
j:Int = i.[g]
|
||||
|
@ -15,9 +15,9 @@ testStringErr (test,
|
||||
|
||||
func TestCastErrIntStruct (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from (x:Int y:Int) to Int", 2, 21,
|
||||
"cannot convert from (. x:Int y:Int) to Int", 2, 21,
|
||||
`
|
||||
[main]:Int = [~ Int a:(x:Int y:Int)]
|
||||
[main]:Int = [~ Int a:(. x:Int y:Int)]
|
||||
`)
|
||||
}
|
||||
|
||||
@ -47,9 +47,9 @@ testStringErr (test,
|
||||
|
||||
func TestCastErrStructInt (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"cannot convert from Int to (x:Int y:Int)", 2, 41,
|
||||
"cannot convert from Int to (. x:Int y:Int)", 2, 45,
|
||||
`
|
||||
[main]:(x:Int y:Int) = [~ (x:Int y:Int) a:Int]
|
||||
[main]:(. x:Int y:Int) = [~ (. x:Int y:Int) a:Int]
|
||||
`)
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ testStringErr (test,
|
||||
func TestCast (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly distance:F64] [land])
|
||||
Bird: (~ [fly distance:F64] [land])
|
||||
BlueJay: Int
|
||||
BlueJay.[fly distance:F64] = { }
|
||||
BlueJay.[land] = { }
|
||||
@ -81,8 +81,8 @@ IntDerived: Int
|
||||
a:IntDerived = 5
|
||||
b:Int [~ Int [~ F64 [~ Byte a]]]
|
||||
c:Int [~~ Int [~~ F64 [~~ Byte a]]]
|
||||
d:(x:Int y:Int) = (x: 1 y: 2)
|
||||
e:(z:Int a:Int) = [~~ (z:Int a:Int) d]
|
||||
d:(. x:Int y:Int) = (. x: 1 y: 2)
|
||||
e:(. z:Int a:Int) = [~~ (. z:Int a:Int) d]
|
||||
f:Bird = [~~ BlueJay 0]
|
||||
g:String = 'hello'
|
||||
h:*:Byte = [~ *:Byte g]
|
||||
|
@ -12,6 +12,8 @@ func (this *Tree) analyzeExpression (
|
||||
error,
|
||||
) {
|
||||
switch expression := expression.(type) {
|
||||
case *entity.Assignment:
|
||||
return this.analyzeAssignment(expression)
|
||||
case *entity.Variable:
|
||||
return this.analyzeVariable(into, mode, expression)
|
||||
case *entity.Declaration:
|
||||
@ -69,22 +71,3 @@ func (this *Tree) analyzeExpression (
|
||||
expression, expression))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeStatement (
|
||||
statement entity.Statement,
|
||||
) (
|
||||
entity.Statement,
|
||||
error,
|
||||
) {
|
||||
if assignment, ok := statement.(*entity.Assignment); ok {
|
||||
return this.analyzeAssignment(assignment)
|
||||
} else if expression, ok := statement.(entity.Expression); ok {
|
||||
expression, err := this.analyzeExpression(nil, strict, expression)
|
||||
if err != nil { return nil, err }
|
||||
return expression.(entity.Statement), nil
|
||||
} else {
|
||||
panic(fmt.Sprintf (
|
||||
"BUG: analyzer doesnt know about statement %v, ty: %T",
|
||||
statement, statement))
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
// All expression analysis routines must take in the type they are being
|
||||
@ -9,6 +9,29 @@ import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
// is nil, the expression must ignore it unless it can do upwards type
|
||||
// inference.
|
||||
|
||||
func (this *Tree) analyzeAssignment (
|
||||
assignment *entity.Assignment,
|
||||
) (
|
||||
entity.Expression,
|
||||
error,
|
||||
) {
|
||||
// analyze location
|
||||
location, err := this.analyzeExpression(nil, strict, assignment.Location)
|
||||
if err != nil { return nil, err }
|
||||
assignment.Location = location
|
||||
|
||||
// ensure location is location expression
|
||||
err = this.isLocationExpression(location)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// analyze value
|
||||
value, err := this.analyzeExpression(location.Type(), strict, assignment.Value)
|
||||
if err != nil { return nil, err }
|
||||
assignment.Value = value
|
||||
|
||||
return assignment, nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeVariable (
|
||||
into entity.Type,
|
||||
mode strictness,
|
||||
@ -19,12 +42,12 @@ func (this *Tree) analyzeVariable (
|
||||
) {
|
||||
declaration := this.variable(variable.Name)
|
||||
if declaration == nil {
|
||||
return nil, participle.Errorf (
|
||||
variable.Pos, "no variable named %s",
|
||||
return nil, errors.Errorf (
|
||||
variable.Position, "no variable named %s",
|
||||
variable.Name)
|
||||
}
|
||||
|
||||
err := this.canAssign(variable.Pos, into, mode, declaration.Type())
|
||||
err := this.canAssign(variable.Position, into, mode, declaration.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
variable.Declaration = declaration
|
||||
@ -42,17 +65,17 @@ func (this *Tree) analyzeDeclaration (
|
||||
scope, _ := this.topScope()
|
||||
existing := scope.Variable(declaration.Name)
|
||||
if existing != nil {
|
||||
return nil, participle.Errorf (
|
||||
declaration.Pos,
|
||||
return nil, errors.Errorf (
|
||||
declaration.Position,
|
||||
"%s already declared in block at %v",
|
||||
declaration.Name, existing.Pos)
|
||||
declaration.Name, existing.Position)
|
||||
}
|
||||
|
||||
ty, err := this.analyzeType(declaration.Ty, false)
|
||||
declaration.Ty = ty
|
||||
if err != nil { return nil, err }
|
||||
|
||||
err = this.canAssign(declaration.Pos, into, mode, declaration.Type())
|
||||
err = this.canAssign(declaration.Position, into, mode, declaration.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
this.addVariable(declaration)
|
||||
@ -68,22 +91,22 @@ func (this *Tree) analyzeCall (
|
||||
error,
|
||||
) {
|
||||
// get function
|
||||
function, err := this.analyzeFunction(call.Pos, call.Name)
|
||||
function, err := this.analyzeFunction(call.Position, call.Name)
|
||||
call.Function = function
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// check return result
|
||||
err = this.canAssign(call.Pos, into, mode, function.Signature.Return)
|
||||
err = this.canAssign(call.Position, into, mode, function.Signature.Return)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// check arg count
|
||||
if len(call.Arguments) > len(function.Signature.Arguments) {
|
||||
return nil, participle.Errorf (
|
||||
call.Pos, "too many arguments in call to %s",
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too many arguments in call to %s",
|
||||
call.Name)
|
||||
} else if len(call.Arguments) < len(function.Signature.Arguments) {
|
||||
return nil, participle.Errorf (
|
||||
call.Pos, "too few arguments in call to %s",
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too few arguments in call to %s",
|
||||
call.Name)
|
||||
}
|
||||
|
||||
@ -108,11 +131,11 @@ func (this *Tree) analyzeMethodCall (
|
||||
error,
|
||||
) {
|
||||
// get method
|
||||
sourceExpr, err := this.analyzeVariable(nil, strict, call.Source)
|
||||
sourceExpr, err := this.analyzeExpression(nil, strict, call.Source)
|
||||
source := sourceExpr.(*entity.Variable)
|
||||
if err != nil { return nil, err }
|
||||
method, err := this.analyzeMethodOrBehavior (
|
||||
call.Pos, source.Declaration.Type(), call.Name)
|
||||
call.Position, source.Declaration.Type(), call.Name)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// extract signature
|
||||
@ -131,17 +154,17 @@ func (this *Tree) analyzeMethodCall (
|
||||
}
|
||||
|
||||
// check return result
|
||||
err = this.canAssign(call.Pos, into, mode, signature.Return)
|
||||
err = this.canAssign(call.Position, into, mode, signature.Return)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// check arg count
|
||||
if len(call.Arguments) > len(signature.Arguments) {
|
||||
return nil, participle.Errorf (
|
||||
call.Pos, "too many arguments in call to %s",
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too many arguments in call to %s",
|
||||
call.Name)
|
||||
} else if len(call.Arguments) < len(signature.Arguments) {
|
||||
return nil, participle.Errorf (
|
||||
call.Pos, "too few arguments in call to %s",
|
||||
return nil, errors.Errorf (
|
||||
call.Position, "too few arguments in call to %s",
|
||||
call.Name)
|
||||
}
|
||||
|
||||
@ -166,8 +189,8 @@ func (this *Tree) analyzeSubscript (
|
||||
) {
|
||||
slice, err := this.analyzeExpression (
|
||||
&entity.TypeSlice {
|
||||
Pos: subscript.Pos,
|
||||
Element: into,
|
||||
Position: subscript.Position,
|
||||
Element: into,
|
||||
}, weak,
|
||||
subscript.Slice)
|
||||
if err != nil { return nil, err }
|
||||
@ -254,7 +277,7 @@ func (this *Tree) analyzeDereference (
|
||||
) {
|
||||
pointer, err := this.analyzeExpression (
|
||||
&entity.TypePointer {
|
||||
Pos: dereference.Pos,
|
||||
Position: dereference.Position,
|
||||
Referenced: into,
|
||||
}, weak,
|
||||
dereference.Pointer)
|
||||
@ -288,7 +311,7 @@ func (this *Tree) analyzeReference (
|
||||
) {
|
||||
referenced, ok := into.(*entity.TypePointer)
|
||||
if !ok {
|
||||
return nil, participle.Errorf(reference.Pos, "expected %v", into)
|
||||
return nil, errors.Errorf(reference.Position, "expected %v", into)
|
||||
}
|
||||
|
||||
value, err := this.analyzeExpression (
|
||||
@ -315,7 +338,7 @@ func (this *Tree) analyzeValueCast (
|
||||
if err != nil { return nil, err }
|
||||
cast.Ty = ty
|
||||
|
||||
err = this.canAssign(cast.Pos, into, mode, cast.Type())
|
||||
err = this.canAssign(cast.Position, into, mode, cast.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
value, err := this.analyzeExpression(cast.Ty, coerce, cast.Value)
|
||||
@ -337,7 +360,7 @@ func (this *Tree) analyzeBitCast (
|
||||
if err != nil { return nil, err }
|
||||
cast.Ty = ty
|
||||
|
||||
err = this.canAssign(cast.Pos, into, mode, cast.Type())
|
||||
err = this.canAssign(cast.Position, into, mode, cast.Type())
|
||||
if err != nil { return nil, err }
|
||||
|
||||
value, err := this.analyzeExpression(cast.Ty, force, cast.Value)
|
||||
@ -356,12 +379,12 @@ func (this *Tree) analyzeOperation (
|
||||
error,
|
||||
) {
|
||||
wrongInto := func () (entity.Expression, error) {
|
||||
return nil, participle.Errorf(operation.Pos, "expected %v", into)
|
||||
return nil, errors.Errorf(operation.Position, "expected %v", into)
|
||||
}
|
||||
|
||||
wrongArgCount := func () (entity.Expression, error) {
|
||||
return nil, participle.Errorf (
|
||||
operation.Pos, "wrong argument count for %v",
|
||||
return nil, errors.Errorf (
|
||||
operation.Position, "wrong argument count for %v",
|
||||
operation.Operator)
|
||||
}
|
||||
|
||||
@ -413,8 +436,8 @@ func (this *Tree) analyzeOperation (
|
||||
}
|
||||
}
|
||||
if argumentType == nil {
|
||||
return nil, participle.Errorf (
|
||||
operation.Pos,
|
||||
return nil, errors.Errorf (
|
||||
operation.Position,
|
||||
"operation arguments have ambiguous type")
|
||||
}
|
||||
|
||||
@ -510,8 +533,8 @@ func (this *Tree) analyzeBlock (
|
||||
defer this.popScope()
|
||||
|
||||
if len(block.Steps) == 0 && into != nil {
|
||||
return nil, participle.Errorf (
|
||||
block.Pos, "block must have at least one statement")
|
||||
return nil, errors.Errorf (
|
||||
block.Position, "block must have at least one statement")
|
||||
}
|
||||
|
||||
final := len(block.Steps) - 1
|
||||
@ -519,8 +542,8 @@ func (this *Tree) analyzeBlock (
|
||||
if index == final && into != nil {
|
||||
expression, ok := step.(entity.Expression)
|
||||
if !ok {
|
||||
return nil, participle.Errorf (
|
||||
block.Pos, "expected expression")
|
||||
return nil, errors.Errorf (
|
||||
block.Position, "expected expression")
|
||||
}
|
||||
|
||||
step, err := this.analyzeExpression(into, strict, expression)
|
||||
@ -528,7 +551,7 @@ func (this *Tree) analyzeBlock (
|
||||
block.Steps[index] = step
|
||||
block.Ty = step.Type()
|
||||
} else {
|
||||
step, err := this.analyzeStatement(step)
|
||||
step, err := this.analyzeExpression(nil, strict, step)
|
||||
if err != nil { return nil, err }
|
||||
block.Steps[index] = step
|
||||
}
|
||||
@ -555,22 +578,22 @@ func (this *Tree) analyzeMemberAccess (
|
||||
case *entity.TypePointer:
|
||||
referenced, ok := ReduceToBase(sourceTypeAny.Referenced).(*entity.TypeStruct)
|
||||
if !ok {
|
||||
return nil, participle.Errorf (
|
||||
access.Pos, "cannot access members of %v", source)
|
||||
return nil, errors.Errorf (
|
||||
access.Position, "cannot access members of %v", source)
|
||||
}
|
||||
sourceType = referenced
|
||||
default:
|
||||
return nil, participle.Errorf (
|
||||
access.Pos, "cannot access members of %v", source)
|
||||
return nil, errors.Errorf (
|
||||
access.Position, "cannot access members of %v", source)
|
||||
}
|
||||
|
||||
member, ok := sourceType.MemberMap[access.Member]
|
||||
if !ok {
|
||||
return nil, participle.Errorf (
|
||||
access.Pos, "no member %v", access)
|
||||
return nil, errors.Errorf (
|
||||
access.Position, "no member %v", access)
|
||||
}
|
||||
|
||||
err = this.canAssign(access.Pos, into, mode, member.Type())
|
||||
err = this.canAssign(access.Position, into, mode, member.Type())
|
||||
if err != nil { return nil, err }
|
||||
access.Ty = member.Type()
|
||||
|
||||
@ -600,8 +623,8 @@ func (this *Tree) analyzeIfElse (
|
||||
|
||||
if ifelse.False == nil {
|
||||
if into != nil {
|
||||
return nil, participle.Errorf (
|
||||
ifelse.Pos,
|
||||
return nil, errors.Errorf (
|
||||
ifelse.Position,
|
||||
"else case required when using value of if ")
|
||||
}
|
||||
} else {
|
||||
@ -643,20 +666,20 @@ func (this *Tree) analyzeBreak (
|
||||
error,
|
||||
) {
|
||||
if into != nil {
|
||||
return nil, participle.Errorf(brk.Pos, "expected %v", into)
|
||||
return nil, errors.Errorf(brk.Position, "expected %v", into)
|
||||
}
|
||||
|
||||
loop, ok := this.topLoop()
|
||||
if !ok {
|
||||
return nil, participle.Errorf (
|
||||
brk.Pos,
|
||||
return nil, errors.Errorf (
|
||||
brk.Position,
|
||||
"break statement must be within loop")
|
||||
}
|
||||
brk.Loop = loop
|
||||
|
||||
if loop.Type() != nil && brk.Value == nil {
|
||||
return nil, participle.Errorf (
|
||||
brk.Pos,
|
||||
return nil, errors.Errorf (
|
||||
brk.Position,
|
||||
"break statement must have value")
|
||||
}
|
||||
|
||||
@ -678,7 +701,7 @@ func (this *Tree) analyzeReturn (
|
||||
error,
|
||||
) {
|
||||
if into != nil {
|
||||
return nil, participle.Errorf(ret.Pos, "expected %v", into)
|
||||
return nil, errors.Errorf(ret.Position, "expected %v", into)
|
||||
}
|
||||
|
||||
ret.Declaration, _ = this.topDeclaration()
|
||||
@ -691,8 +714,8 @@ func (this *Tree) analyzeReturn (
|
||||
}
|
||||
|
||||
if ty != nil && ret.Value == nil {
|
||||
return nil, participle.Errorf (
|
||||
ret.Pos,
|
||||
return nil, errors.Errorf (
|
||||
ret.Position,
|
||||
"break statement must have value")
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,10 @@
|
||||
package analyzer
|
||||
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeFunction (
|
||||
pos lexer.Position,
|
||||
pos errors.Position,
|
||||
name string,
|
||||
) (
|
||||
*entity.Function,
|
||||
@ -21,7 +20,7 @@ func (this *Tree) analyzeFunction (
|
||||
// error if function is missing
|
||||
function, exists := this.rawFunctions[name]
|
||||
if !exists {
|
||||
return nil, participle.Errorf(pos, "no function named %s", name)
|
||||
return nil, errors.Errorf(pos, "no function named %s", name)
|
||||
}
|
||||
|
||||
// create a new scope context for this function
|
||||
|
@ -1,7 +1,7 @@
|
||||
package analyzer
|
||||
|
||||
import "unicode/utf16"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeLiteralInt (
|
||||
@ -13,14 +13,14 @@ func (this *Tree) analyzeLiteralInt (
|
||||
error,
|
||||
) {
|
||||
if !isNumeric(into) {
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos, "cannot use integer literal as %v",
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use integer literal as %v",
|
||||
into)
|
||||
}
|
||||
|
||||
if isInteger(into) && !inRange(into, int64(literal.Value)) {
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos, "integer literal out of range for type %v",
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "integer literal out of range for type %v",
|
||||
into)
|
||||
}
|
||||
|
||||
@ -37,8 +37,8 @@ func (this *Tree) analyzeLiteralFloat (
|
||||
error,
|
||||
) {
|
||||
if !isFloat(into) {
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos, "cannot use float literal as %v",
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use float literal as %v",
|
||||
into)
|
||||
}
|
||||
|
||||
@ -57,8 +57,8 @@ func (this *Tree) analyzeLiteralString (
|
||||
base := ReduceToBase(into)
|
||||
|
||||
errCantUse := func () error {
|
||||
return participle.Errorf (
|
||||
literal.Pos, "cannot use string literal as %v",
|
||||
return errors.Errorf (
|
||||
literal.Position, "cannot use string literal as %v",
|
||||
into)
|
||||
}
|
||||
|
||||
@ -82,14 +82,14 @@ func (this *Tree) analyzeLiteralString (
|
||||
length = len(literal.ValueUTF8)
|
||||
}
|
||||
if length > 1 {
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos,
|
||||
return nil, errors.Errorf (
|
||||
literal.Position,
|
||||
"cannot fit string literal of length " +
|
||||
"%v into %v",
|
||||
length, into)
|
||||
} else if length < 1 {
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos,
|
||||
return nil, errors.Errorf (
|
||||
literal.Position,
|
||||
"string literal must have data when " +
|
||||
"assigning to %v",
|
||||
into)
|
||||
@ -108,8 +108,8 @@ func (this *Tree) analyzeLiteralString (
|
||||
length = len(literal.ValueUTF8)
|
||||
}
|
||||
if length > base.Length {
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos,
|
||||
return nil, errors.Errorf (
|
||||
literal.Position,
|
||||
"cannot fit string literal of length " +
|
||||
"%v into array of length %v",
|
||||
length, base.Length)
|
||||
@ -160,8 +160,8 @@ func (this *Tree) analyzeLiteralArray (
|
||||
case *entity.TypeArray:
|
||||
base := base.(*entity.TypeArray)
|
||||
if base.Length < len(literal.Elements) {
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos, "expected %v elements or less",
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "expected %v elements or less",
|
||||
base.Length)
|
||||
}
|
||||
elementType = base.Element
|
||||
@ -174,8 +174,8 @@ func (this *Tree) analyzeLiteralArray (
|
||||
base := base.(*entity.TypePointer)
|
||||
elementType = base.Referenced
|
||||
default:
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos, "cannot use array literal as %v",
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use array literal as %v",
|
||||
into)
|
||||
}
|
||||
|
||||
@ -199,8 +199,8 @@ func (this *Tree) analyzeLiteralStruct (
|
||||
) {
|
||||
base, ok := ReduceToBase(into).(*entity.TypeStruct)
|
||||
if !ok {
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos, "cannot use struct literal as %v",
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use struct literal as %v",
|
||||
into)
|
||||
}
|
||||
|
||||
@ -210,8 +210,8 @@ func (this *Tree) analyzeLiteralStruct (
|
||||
for name, member := range literal.MemberMap {
|
||||
correct, ok := base.MemberMap[name]
|
||||
if !ok {
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos, "no member %v.%s",
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "no member %v.%s",
|
||||
into, name)
|
||||
}
|
||||
|
||||
@ -233,8 +233,8 @@ func (this *Tree) analyzeLiteralBoolean (
|
||||
error,
|
||||
) {
|
||||
if !isBoolean(into) {
|
||||
return nil, participle.Errorf (
|
||||
literal.Pos, "cannot use boolean literal as %v",
|
||||
return nil, errors.Errorf (
|
||||
literal.Position, "cannot use boolean literal as %v",
|
||||
into)
|
||||
}
|
||||
|
||||
@ -264,9 +264,9 @@ func (this *Tree) assembleStructLiteralMap (
|
||||
literal.MemberOrder = make([]string, len(literal.Members))
|
||||
for index, member := range literal.Members {
|
||||
if previous, exists := literal.MemberMap[member.Name]; exists {
|
||||
return literal, participle.Errorf (
|
||||
member.Pos, "%s already listed in struct literal at %v",
|
||||
member.Name, previous.Pos)
|
||||
return literal, errors.Errorf (
|
||||
member.Position, "%s already listed in struct literal at %v",
|
||||
member.Name, previous.Position)
|
||||
}
|
||||
literal.MemberMap [member.Name] = member
|
||||
literal.MemberOrder[index] = member.Name
|
||||
|
@ -6,9 +6,9 @@ func TestLiteralStructMemberUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already listed in struct literal at stream0.fspl:5:3", 7, 3,
|
||||
`
|
||||
Point: (x:Int y:Int z:Int)
|
||||
Point: (. x:Int y:Int z:Int)
|
||||
[main] = {
|
||||
point:Point = (
|
||||
point:Point = (.
|
||||
x: 5
|
||||
y: 0
|
||||
x: 32)
|
||||
@ -19,9 +19,9 @@ Point: (x:Int y:Int z:Int)
|
||||
func TestLiteralStructMemberUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Point: (x:Int y:Int z:Int)
|
||||
Point: (. x:Int y:Int z:Int)
|
||||
[main] = {
|
||||
point:Point = (
|
||||
point:Point = (.
|
||||
x: 5
|
||||
y: 0
|
||||
z: 32)
|
||||
@ -33,9 +33,9 @@ func TestLiteralStructNested (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
[main] = {
|
||||
g:(x:Int y:(w:F64 z:F64)) = (
|
||||
g:(. x:Int y:(. w:F64 z:F64)) = (.
|
||||
x: 1
|
||||
y: (
|
||||
y: (.
|
||||
w: 1.2
|
||||
z: 78.5))
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
// analyzeMethod analyzes a method that is directly owned by the specified type.
|
||||
func (this *Tree) analyzeMethod (
|
||||
pos lexer.Position,
|
||||
pos errors.Position,
|
||||
typeName string,
|
||||
name string,
|
||||
) (
|
||||
@ -26,7 +25,7 @@ func (this *Tree) analyzeMethod (
|
||||
// error if method is missing
|
||||
method, exists := this.rawMethods[typeName + "." + name]
|
||||
if !exists {
|
||||
return nil, participle.Errorf (
|
||||
return nil, errors.Errorf (
|
||||
pos, "no method named %s.%s",
|
||||
typeName, name)
|
||||
}
|
||||
@ -52,12 +51,12 @@ func (this *Tree) analyzeMethod (
|
||||
|
||||
// add owner to method
|
||||
method.This = &entity.Declaration {
|
||||
Pos: method.Pos,
|
||||
Name: "this",
|
||||
Ty: &entity.TypePointer {
|
||||
Pos: method.Pos,
|
||||
Position: method.Position,
|
||||
Name: "this",
|
||||
Ty: &entity.TypePointer {
|
||||
Position: method.Position,
|
||||
Referenced: &entity.TypeNamed {
|
||||
Pos: method.Pos,
|
||||
Position: method.Position,
|
||||
Name: typeName,
|
||||
Type: owner.Type,
|
||||
},
|
||||
@ -88,7 +87,7 @@ func (this *Tree) methodExists (typeName, name string) bool {
|
||||
// analyzeMethodOrBehavior returns *entity.Signature if it found an interface
|
||||
// behavior, and *entity.Method if it found a method.
|
||||
func (this *Tree) analyzeMethodOrBehavior (
|
||||
pos lexer.Position,
|
||||
pos errors.Position,
|
||||
ty entity.Type,
|
||||
name string,
|
||||
) (
|
||||
@ -99,7 +98,7 @@ func (this *Tree) analyzeMethodOrBehavior (
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeMethodOrBehaviorInternal (
|
||||
pos lexer.Position,
|
||||
pos errors.Position,
|
||||
ty entity.Type,
|
||||
name string,
|
||||
pierceReference bool,
|
||||
@ -124,7 +123,7 @@ func (this *Tree) analyzeMethodOrBehaviorInternal (
|
||||
if behavior, ok := ty.BehaviorMap[name]; ok {
|
||||
return behavior, nil
|
||||
} else {
|
||||
return nil, participle.Errorf (
|
||||
return nil, errors.Errorf (
|
||||
pos, "no behavior or method named %s",
|
||||
name)
|
||||
}
|
||||
@ -135,7 +134,7 @@ func (this *Tree) analyzeMethodOrBehaviorInternal (
|
||||
return this.analyzeMethodOrBehaviorInternal (
|
||||
pos, ty.Referenced, name, false)
|
||||
} else {
|
||||
return nil, participle.Errorf (
|
||||
return nil, errors.Errorf (
|
||||
pos, "no method named %s defined on this type",
|
||||
name)
|
||||
}
|
||||
@ -147,7 +146,7 @@ func (this *Tree) analyzeMethodOrBehaviorInternal (
|
||||
*entity.TypeFloat,
|
||||
*entity.TypeWord:
|
||||
|
||||
return nil, participle.Errorf (
|
||||
return nil, errors.Errorf (
|
||||
pos, "no method named %s defined on this type",
|
||||
name)
|
||||
|
||||
|
@ -47,7 +47,7 @@ testString (test,
|
||||
`
|
||||
Number: Int
|
||||
Number.[add x:Number]:Number = [++[.this] x]
|
||||
StringHolder: (string:String)
|
||||
StringHolder: (. string:String)
|
||||
StringHolder.[setString string:String] = {
|
||||
this.string = string
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package analyzer
|
||||
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
func (this *Tree) assembleSignatureMap (signature *entity.Signature) (*entity.Signature, error) {
|
||||
@ -8,9 +8,9 @@ func (this *Tree) assembleSignatureMap (signature *entity.Signature) (*entity.Si
|
||||
signature.ArgumentOrder = make([]string, len(signature.Arguments))
|
||||
for index, member := range signature.Arguments {
|
||||
if previous, exists := signature.ArgumentMap[member.Name]; exists {
|
||||
return signature, participle.Errorf (
|
||||
member.Pos, "%s already listed as argument at %v",
|
||||
member.Name, previous.Pos)
|
||||
return signature, errors.Errorf (
|
||||
member.Position, "%s already listed as argument at %v",
|
||||
member.Name, previous.Position)
|
||||
}
|
||||
signature.ArgumentMap [member.Name] = member
|
||||
signature.ArgumentOrder[index] = member.Name
|
||||
|
@ -4,7 +4,7 @@ import "testing"
|
||||
|
||||
func TestMethodNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method named world defined on this type", 6, 2,
|
||||
"no method named world defined on this type", 6, 10,
|
||||
`
|
||||
Type: Int
|
||||
Type.[something] = { }
|
||||
@ -17,7 +17,7 @@ Type.[something] = { }
|
||||
|
||||
func TestMethodNameErrBadLayer (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no method named something defined on this type", 7, 2,
|
||||
"no method named something defined on this type", 7, 10,
|
||||
`
|
||||
Type: Int
|
||||
Type.[something] = { }
|
||||
@ -121,7 +121,6 @@ testString (test,
|
||||
example = 3
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -155,9 +154,9 @@ testString (test,
|
||||
|
||||
func TestInterfaceBehaviorNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no behavior or method named swim", 8, 2,
|
||||
"no behavior or method named swim", 8, 6,
|
||||
`
|
||||
Bird: ([fly] [land])
|
||||
Bird: (~ [fly] [land])
|
||||
BirdImpl: Int
|
||||
BirdImpl.[fly] = { }
|
||||
BirdImpl.[land] = { }
|
||||
@ -171,7 +170,7 @@ BirdImpl.[land] = { }
|
||||
func TestInterfaceBehaviorName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly] [land])
|
||||
Bird: (~ [fly] [land])
|
||||
BirdImpl: Int
|
||||
BirdImpl.[fly] = { }
|
||||
BirdImpl.[land] = { }
|
||||
@ -184,9 +183,9 @@ BirdImpl.[land] = { }
|
||||
|
||||
func TestStructMemberNameErrMissing (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"no member instance.world", 5, 2,
|
||||
"no member instance.world", 5, 10,
|
||||
`
|
||||
Type: (something:Int)
|
||||
Type: (. something:Int)
|
||||
[main] = {
|
||||
instance:Type
|
||||
instance.world = 5
|
||||
@ -197,7 +196,7 @@ Type: (something:Int)
|
||||
func TestStructMemberName (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Type: (world:Int)
|
||||
Type: (. world:Int)
|
||||
[main] = {
|
||||
instance:Type
|
||||
instance.world = 5
|
||||
|
@ -1,28 +0,0 @@
|
||||
package analyzer
|
||||
|
||||
// import "fmt"
|
||||
//import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeAssignment (
|
||||
assignment *entity.Assignment,
|
||||
) (
|
||||
entity.Statement,
|
||||
error,
|
||||
) {
|
||||
// analyze location
|
||||
location, err := this.analyzeExpression(nil, strict, assignment.Location)
|
||||
if err != nil { return nil, err }
|
||||
assignment.Location = location
|
||||
|
||||
// ensure location is location expression
|
||||
err = this.isLocationExpression(location)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
// analyze value
|
||||
value, err := this.analyzeExpression(location.Type(), strict, assignment.Value)
|
||||
if err != nil { return nil, err }
|
||||
assignment.Value = value
|
||||
|
||||
return assignment, nil
|
||||
}
|
@ -4,7 +4,7 @@ import "io"
|
||||
import "fmt"
|
||||
import "testing"
|
||||
import "strings"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/parser"
|
||||
|
||||
func testStringErr (
|
||||
@ -23,8 +23,8 @@ func testStringErr (
|
||||
func testReaderErr (
|
||||
test *testing.T,
|
||||
errMessage string,
|
||||
errLine int,
|
||||
errColumn int,
|
||||
errRow int,
|
||||
errStart int,
|
||||
inputs ...io.Reader,
|
||||
) {
|
||||
ast := parser.Tree { }
|
||||
@ -42,20 +42,20 @@ func testReaderErr (
|
||||
test.Error("analyzer did not return error")
|
||||
return
|
||||
}
|
||||
got := err.(participle.Error)
|
||||
gotMessage := got.Message()
|
||||
gotLine := got.Position().Line
|
||||
gotColumn := got.Position().Column
|
||||
|
||||
got := err.(*errors.Error)
|
||||
gotMessage := got.Message
|
||||
gotRow := got.Position.Row + 1
|
||||
gotStart := got.Position.Start + 1
|
||||
correct :=
|
||||
gotMessage == errMessage &&
|
||||
gotLine == errLine &&
|
||||
gotColumn == errColumn
|
||||
gotRow == errRow &&
|
||||
gotStart == errStart
|
||||
if !correct {
|
||||
test.Log("errors do not match")
|
||||
test.Logf("got:\n%v:%v: %v", gotLine, gotColumn, gotMessage)
|
||||
test.Logf("correct:\n%v:%v: %v", errLine, errColumn, errMessage)
|
||||
test.Log("got:\n" + got.Format())
|
||||
test.Logf("correct:\n%v:%v: %v", errRow, errStart, errMessage)
|
||||
test.Fail()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package analyzer
|
||||
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/parser"
|
||||
|
||||
@ -36,21 +35,21 @@ func (this *Tree) assembleRawMaps () error {
|
||||
switch declaration.(type) {
|
||||
case *entity.Typedef:
|
||||
ty := declaration.(*entity.Typedef)
|
||||
err := this.topLevelNameAvailable(ty.Pos, ty.Name)
|
||||
err := this.topLevelNameAvailable(ty.Position, ty.Name)
|
||||
if err != nil { return err }
|
||||
ty.Methods = make(map[string] *entity.Method)
|
||||
this.rawTypes[ty.Name] = ty
|
||||
case *entity.Function:
|
||||
function := declaration.(*entity.Function)
|
||||
err := this.topLevelNameAvailable (
|
||||
function.Pos,
|
||||
function.Position,
|
||||
function.Signature.Name)
|
||||
if err != nil { return err }
|
||||
this.rawFunctions[function.Signature.Name] = function
|
||||
case *entity.Method:
|
||||
method := declaration.(*entity.Method)
|
||||
name := method.TypeName + "." + method.Signature.Name
|
||||
err := this.topLevelNameAvailable(method.Pos, name)
|
||||
err := this.topLevelNameAvailable(method.Position, name)
|
||||
if err != nil { return err }
|
||||
this.rawMethods[name] = method
|
||||
}
|
||||
@ -58,48 +57,48 @@ func (this *Tree) assembleRawMaps () error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) topLevelNameAvailable (currentPos lexer.Position, name string) error {
|
||||
func (this *Tree) topLevelNameAvailable (currentPos errors.Position, name string) error {
|
||||
if _, isPrimitive := primitiveTypes[name]; isPrimitive {
|
||||
return participle.Errorf (
|
||||
return errors.Errorf (
|
||||
currentPos, "cannot shadow primitive %s",
|
||||
name)
|
||||
}
|
||||
if _, isBuiltin := builtinTypes[name]; isBuiltin {
|
||||
return participle.Errorf (
|
||||
return errors.Errorf (
|
||||
currentPos, "cannot shadow builtin %s",
|
||||
name)
|
||||
}
|
||||
if ty, isType := this.rawTypes[name]; isType {
|
||||
return participle.Errorf (
|
||||
return errors.Errorf (
|
||||
currentPos, "%s already declared at %v",
|
||||
name, ty.Pos)
|
||||
name, ty.Position)
|
||||
}
|
||||
if function, isFunction := this.rawFunctions[name]; isFunction {
|
||||
return participle.Errorf (
|
||||
return errors.Errorf (
|
||||
currentPos, "%s already declared at %v",
|
||||
name, function.Pos)
|
||||
name, function.Position)
|
||||
}
|
||||
if method, isMethod := this.rawMethods[name]; isMethod {
|
||||
return participle.Errorf (
|
||||
return errors.Errorf (
|
||||
currentPos, "%s already declared at %v",
|
||||
name, method.Pos)
|
||||
name, method.Position)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *Tree) analyzeDeclarations () error {
|
||||
for name, rawType := range this.rawTypes {
|
||||
ty, err := this.analyzeTypedef(rawType.Pos, name, false)
|
||||
ty, err := this.analyzeTypedef(rawType.Position, name, false)
|
||||
if err != nil { return err }
|
||||
this.Types[name] = ty
|
||||
}
|
||||
for name, rawFunction := range this.rawFunctions {
|
||||
_, err := this.analyzeFunction(rawFunction.Pos, name)
|
||||
_, err := this.analyzeFunction(rawFunction.Position, name)
|
||||
if err != nil { return err }
|
||||
}
|
||||
for _, rawMethod := range this.rawMethods {
|
||||
_, err := this.analyzeMethod (
|
||||
rawMethod.Pos,
|
||||
rawMethod.Position,
|
||||
rawMethod.TypeName,
|
||||
rawMethod.Signature.Name)
|
||||
if err != nil { return err }
|
||||
@ -117,9 +116,8 @@ func (this *Tree) ensure () {
|
||||
this.Functions = make(map[string] *entity.Function)
|
||||
|
||||
for name, ty := range builtinTypes {
|
||||
access := entity.AccessPublic
|
||||
this.Types[name] = &entity.Typedef {
|
||||
Acc: &access,
|
||||
Acc: entity.AccessPublic,
|
||||
Name: name,
|
||||
Type: ty,
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
package analyzer
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
|
||||
func (this *Tree) analyzeTypedef (
|
||||
pos lexer.Position,
|
||||
pos errors.Position,
|
||||
name string,
|
||||
acceptIncomplete bool,
|
||||
) (
|
||||
@ -23,7 +22,7 @@ func (this *Tree) analyzeTypedef (
|
||||
if acceptIncomplete {
|
||||
return definition, nil
|
||||
} else {
|
||||
return nil, participle.Errorf (
|
||||
return nil, errors.Errorf (
|
||||
pos, "type %s cannot be used in this context",
|
||||
name)
|
||||
}
|
||||
@ -32,7 +31,7 @@ func (this *Tree) analyzeTypedef (
|
||||
// error if type is missing
|
||||
definition, exists := this.rawTypes[name]
|
||||
if !exists {
|
||||
return nil, participle.Errorf(pos, "no type named %s", name)
|
||||
return nil, errors.Errorf(pos, "no type named %s", name)
|
||||
}
|
||||
|
||||
var err error
|
||||
@ -83,7 +82,7 @@ func (this *Tree) analyzeTypeInternal (
|
||||
return primitive, nil
|
||||
}
|
||||
var def *entity.Typedef
|
||||
def, err = this.analyzeTypedef(ty.Pos, ty.Name, acceptIncomplete)
|
||||
def, err = this.analyzeTypedef(ty.Position, ty.Name, acceptIncomplete)
|
||||
if err == nil {
|
||||
ty.Type = def.Type
|
||||
}
|
||||
@ -108,8 +107,8 @@ func (this *Tree) analyzeTypeInternal (
|
||||
ty := ty.(*entity.TypeArray)
|
||||
updateIncompleteInfo()
|
||||
if ty.Length < 1 {
|
||||
return ty, participle.Errorf (
|
||||
ty.Pos, "array length must be > 0")
|
||||
return ty, errors.Errorf (
|
||||
ty.Position, "array length must be > 0")
|
||||
}
|
||||
ty.Element, err = this.analyzeType(ty.Element, false)
|
||||
return ty, err
|
||||
@ -144,8 +143,8 @@ func (this *Tree) analyzeTypeInternal (
|
||||
ty := ty.(*entity.TypeInt)
|
||||
updateIncompleteInfo()
|
||||
if ty.Width < 1 {
|
||||
return ty, participle.Errorf (
|
||||
ty.Pos, "integer width must be > 0")
|
||||
return ty, errors.Errorf (
|
||||
ty.Position, "integer width must be > 0")
|
||||
}
|
||||
return ty, nil
|
||||
|
||||
@ -162,9 +161,9 @@ func (this *Tree) assembleStructMap (ty *entity.TypeStruct) (*entity.TypeStruct,
|
||||
ty.MemberOrder = make([]string, len(ty.Members))
|
||||
for index, member := range ty.Members {
|
||||
if previous, exists := ty.MemberMap[member.Name]; exists {
|
||||
return ty, participle.Errorf (
|
||||
member.Pos, "%s already listed in struct at %v",
|
||||
member.Name, previous.Pos)
|
||||
return ty, errors.Errorf (
|
||||
member.Position, "%s already listed in struct at %v",
|
||||
member.Name, previous.Position)
|
||||
}
|
||||
ty.MemberMap [member.Name] = member
|
||||
ty.MemberOrder[index] = member.Name
|
||||
@ -178,9 +177,9 @@ func (this *Tree) assembleInterfaceMap (ty *entity.TypeInterface) (*entity.TypeI
|
||||
ty.BehaviorOrder = make([]string, len(ty.Behaviors))
|
||||
for index, method := range ty.Behaviors {
|
||||
if previous, exists := ty.BehaviorMap[method.Name]; exists {
|
||||
return ty, participle.Errorf (
|
||||
method.Pos, "%s already listed in interface at %v",
|
||||
method.Name, previous.Pos)
|
||||
return ty, errors.Errorf (
|
||||
method.Position, "%s already listed in interface at %v",
|
||||
method.Name, previous.Position)
|
||||
}
|
||||
ty.BehaviorMap [method.Name] = method
|
||||
ty.BehaviorOrder[index] = method.Name
|
||||
|
@ -7,7 +7,7 @@ testStringErr (test,
|
||||
"Hello already declared at stream0.fspl:2:1", 3, 1,
|
||||
`
|
||||
Hello: *Int
|
||||
Hello: (x:Int y:Int)
|
||||
Hello: (. x:Int y:Int)
|
||||
`)
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ func TestTypedefUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Hello: *Int
|
||||
World: (x:Int y:Int)
|
||||
World: (. x:Int y:Int)
|
||||
`)
|
||||
}
|
||||
|
||||
@ -39,7 +39,7 @@ func TestTypedefRecursiveErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"type List cannot be used in this context", 4, 9,
|
||||
`
|
||||
List: (
|
||||
List: (.
|
||||
value: Int
|
||||
next: List)
|
||||
`)
|
||||
@ -48,7 +48,7 @@ List: (
|
||||
func TestTypedefRecursive (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
List: (
|
||||
List: (.
|
||||
value: Int
|
||||
next: *List)
|
||||
`)
|
||||
@ -66,7 +66,7 @@ func TestTypeNamed (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Example: Int
|
||||
AllBuiltin: (
|
||||
AllBuiltin: (.
|
||||
int: Int
|
||||
uint: UInt
|
||||
byte: Byte
|
||||
@ -129,7 +129,7 @@ func TestTypeStructMemberUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"x already listed in struct at stream0.fspl:3:2", 6, 2,
|
||||
`
|
||||
Bird: (
|
||||
Bird: (.
|
||||
x:Int
|
||||
y:Int
|
||||
z:Int
|
||||
@ -140,7 +140,7 @@ Bird: (
|
||||
func TestTypeStructMemberUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: (
|
||||
Bird: (.
|
||||
x:Int
|
||||
y:Int
|
||||
z:Int
|
||||
@ -150,15 +150,15 @@ Bird: (
|
||||
|
||||
func TestTypeInterfaceBehaviorUniqueErr (test *testing.T) {
|
||||
testStringErr (test,
|
||||
"fly already listed in interface at stream0.fspl:2:8", 2, 14,
|
||||
"fly already listed in interface at stream0.fspl:2:10", 2, 16,
|
||||
`
|
||||
Bird: ([fly] [fly])
|
||||
Bird: (~ [fly] [fly])
|
||||
`)
|
||||
}
|
||||
|
||||
func TestTypeInterfaceBehaviorUnique (test *testing.T) {
|
||||
testString (test,
|
||||
`
|
||||
Bird: ([fly] [land])
|
||||
Bird: (~ [fly] [land])
|
||||
`)
|
||||
}
|
||||
|
@ -1,35 +1,27 @@
|
||||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
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
|
||||
// location expression.
|
||||
type Variable struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Name string `parser:" @Ident "`
|
||||
Position errors.Position
|
||||
Name string
|
||||
|
||||
// Semantics
|
||||
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
|
||||
@ -41,12 +33,11 @@ func (this *Variable) String () string {
|
||||
// assigned to an interface. A declaration is always a valid location
|
||||
// expression.
|
||||
type Declaration struct {
|
||||
Pos lexer.Position
|
||||
Name string `parser:" @Ident "`
|
||||
Ty Type `parser:" ':' @@ "`
|
||||
Position errors.Position
|
||||
Name string
|
||||
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)
|
||||
@ -60,16 +51,15 @@ func (this *Declaration) String () string {
|
||||
// expression.
|
||||
type Call struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Module string `parser:" (@Ident '::')? "`
|
||||
Name string `parser:" '[' @Ident "`
|
||||
Arguments []Expression `parser:" @@* ']' "`
|
||||
Position errors.Position
|
||||
Module string
|
||||
Name string
|
||||
Arguments []Expression
|
||||
|
||||
// Semantics
|
||||
Function *Function
|
||||
}
|
||||
func (*Call) expression(){}
|
||||
func (*Call) statement(){}
|
||||
func (this *Call) Type () Type { return this.Function.Signature.Return }
|
||||
func (this *Call) String () string {
|
||||
out := ""
|
||||
@ -91,10 +81,10 @@ func (this *Call) String () string {
|
||||
// A method call is never a valid location expression.
|
||||
type MethodCall struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Source *Variable `parser:" @@ '.' "`
|
||||
Name string `parser:" '[' @Ident "`
|
||||
Arguments []Expression `parser:" @@* ']' "`
|
||||
Position errors.Position
|
||||
Source Expression
|
||||
Name string
|
||||
Arguments []Expression
|
||||
|
||||
// Semantics
|
||||
Method *Method
|
||||
@ -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
|
||||
@ -125,15 +114,14 @@ func (this *MethodCall) String () string {
|
||||
// valid location expression only if the array being subscripted is.
|
||||
type Subscript struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Slice Expression `parser:" '[' '.' @@ "`
|
||||
Offset Expression `parser:" @@ ']' "`
|
||||
Position errors.Position
|
||||
Slice Expression
|
||||
Offset Expression
|
||||
|
||||
// Semantics
|
||||
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, "]")
|
||||
@ -145,20 +133,19 @@ func (this *Subscript) String () string {
|
||||
// is operating on. A slice is never a valid location expression.
|
||||
type Slice struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Slice Expression `parser:" '[' '\\\\' @@ "`
|
||||
Start Expression `parser:" @@? "`
|
||||
End Expression `parser:" ':' @@? ']' "`
|
||||
Position errors.Position
|
||||
Slice Expression
|
||||
Start Expression
|
||||
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 `parser:" '[' '#' @@ ']' "`
|
||||
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, "]")
|
||||
@ -188,14 +175,13 @@ func (this *Length) String () string {
|
||||
// if the pointer being dereferenced is.
|
||||
type Dereference struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Pointer Expression `parser:" '[' '.' @@ ']' "`
|
||||
Position errors.Position
|
||||
Pointer Expression
|
||||
|
||||
// Semantics
|
||||
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, "]")
|
||||
@ -210,14 +196,13 @@ func (this *Dereference) String () string {
|
||||
// expression.
|
||||
type Reference struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Value Expression `parser:" '[' '@' @@ ']' "`
|
||||
Position errors.Position
|
||||
Value Expression
|
||||
|
||||
// Semantics
|
||||
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, "]")
|
||||
@ -227,12 +212,11 @@ func (this *Reference) String () string {
|
||||
// contains inherent type information, it may be directly assigned to an
|
||||
// interface. A value cast is never a valid location expression.
|
||||
type ValueCast struct {
|
||||
Pos lexer.Position
|
||||
Ty Type `parser:" '[' '~' @@ "`
|
||||
Value Expression `parser:" @@ ']' "`
|
||||
Position errors.Position
|
||||
Ty Type
|
||||
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, "]")
|
||||
@ -243,12 +227,11 @@ func (this *ValueCast) String () string {
|
||||
// it may be directly assigned to an interface. A bit cast is never a valid
|
||||
// location expression.
|
||||
type BitCast struct {
|
||||
Pos lexer.Position
|
||||
Ty Type `parser:" '[' '~~' @@ "`
|
||||
Value Expression `parser:" @@ ']' "`
|
||||
Position errors.Position
|
||||
Ty Type
|
||||
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, "]")
|
||||
@ -260,15 +243,14 @@ func (this *BitCast) String () string {
|
||||
// be assigned to interfaces. An operation is never a valid location expression.
|
||||
type Operation struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Operator Operator `parser:" '[' @('++' | '+' | '--' | '-' | '*' | '/' | '%' | '!!' | '||' | '&&' | '^^' | '!' | '|' | '&' | '^' | '<<' | '>>' | '<' | '>' | '<=' | '>=' | '=') "`
|
||||
Arguments []Expression `parser:" @@+ ']' "`
|
||||
Position errors.Position
|
||||
Operator Operator
|
||||
Arguments []Expression
|
||||
|
||||
// Semantics
|
||||
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)
|
||||
@ -285,15 +267,14 @@ func (this *Operation) String () string {
|
||||
// expression.
|
||||
type Block struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Steps []Statement `parser:" '{' @@* '}' "`
|
||||
Position errors.Position
|
||||
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 := "{"
|
||||
@ -312,15 +293,14 @@ func (this *Block) String () string {
|
||||
// struct being accessed is.
|
||||
type MemberAccess struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Source *Variable `parser:" @@ "`
|
||||
Member string `parser:" '.' @Ident "`
|
||||
Position errors.Position
|
||||
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)
|
||||
@ -333,16 +313,15 @@ func (this *MemberAccess) String () string {
|
||||
// expressions. An If/else is never a valid location expression.
|
||||
type IfElse struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Condition Expression `parser:" 'if' @@ "`
|
||||
True Expression `parser:" 'then' @@ "`
|
||||
False Expression `parser:" ('else' @@)? "`
|
||||
Position errors.Position
|
||||
Condition Expression
|
||||
True Expression
|
||||
False Expression
|
||||
|
||||
// Semantics
|
||||
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)
|
||||
@ -361,14 +340,13 @@ func (this *IfElse) String () string {
|
||||
// loop's expression is never used. A loop is never a valid location expression.
|
||||
type Loop struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Body Expression `parser:" 'loop' @@ "`
|
||||
Position errors.Position
|
||||
Body Expression
|
||||
|
||||
// Semantics
|
||||
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)
|
||||
@ -378,14 +356,13 @@ func (this *Loop) String () string {
|
||||
// to anything. It is never a valid location expression.
|
||||
type Break struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Value Expression `parser:" '[' 'break' @@? ']' "`
|
||||
Position errors.Position
|
||||
Value Expression
|
||||
|
||||
// Semantics
|
||||
Loop *Loop
|
||||
}
|
||||
func (*Break) expression(){}
|
||||
func (*Break) statement(){}
|
||||
func (this *Break) Type () Type { return nil }
|
||||
func (this *Break) String () string {
|
||||
if this.Value == nil {
|
||||
@ -401,14 +378,13 @@ func (this *Break) String () string {
|
||||
// to anything. A return statement is never a valid location expression.
|
||||
type Return struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Value Expression `parser:" '[' 'return' @@? ']' "`
|
||||
Position errors.Position
|
||||
Value Expression
|
||||
|
||||
// Semantics
|
||||
Declaration TopLevel
|
||||
}
|
||||
func (*Return) expression(){}
|
||||
func (*Return) statement(){}
|
||||
func (this *Return) Type () Type { return nil }
|
||||
func (this *Return) String () string {
|
||||
if this.Value == nil {
|
||||
@ -423,11 +399,12 @@ func (this *Return) String () string {
|
||||
// not be assigned to anything. An assignment statement is never a valid
|
||||
// location expression.
|
||||
type Assignment struct {
|
||||
Pos lexer.Position
|
||||
Location Expression `parser:" @@ "`
|
||||
Value Expression `parser:" '=' @@ "`
|
||||
Position errors.Position
|
||||
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)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ package entity
|
||||
|
||||
import "fmt"
|
||||
import "unicode"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
|
||||
// LiteralInt specifies an integer value. It can be assigned to any type that is
|
||||
// derived from an integer or a float, as long as the value fo the literal can
|
||||
@ -11,14 +11,13 @@ import "github.com/alecthomas/participle/v2/lexer"
|
||||
// be used for this purpose.
|
||||
type LiteralInt struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Value int `parser:" @Int "`
|
||||
Position errors.Position
|
||||
Value int
|
||||
|
||||
// Semantics
|
||||
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)
|
||||
@ -30,14 +29,13 @@ func (this *LiteralInt) String () string {
|
||||
// for this purpose.
|
||||
type LiteralFloat struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Value float64 `parser:" @Float "`
|
||||
Position errors.Position
|
||||
Value float64
|
||||
|
||||
// Semantics
|
||||
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)
|
||||
@ -60,8 +58,8 @@ func (this *LiteralFloat) String () string {
|
||||
// purpose.
|
||||
type LiteralString struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
ValueUTF8 string `parser:" @String "`
|
||||
Position errors.Position
|
||||
ValueUTF8 string
|
||||
ValueUTF16 []uint16
|
||||
ValueUTF32 []rune
|
||||
|
||||
@ -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 := "'"
|
||||
@ -94,14 +91,13 @@ func (this *LiteralString) String () string {
|
||||
// inherent type information. A value cast may be used for this purpose.
|
||||
type LiteralArray struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Elements []Expression `parser:" '(' '*' @@* ')' "`
|
||||
Position errors.Position
|
||||
Elements []Expression
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
}
|
||||
func (*LiteralArray) expression(){}
|
||||
func (*LiteralArray) statement(){}
|
||||
func (this *LiteralArray) Type () Type { return this.Ty }
|
||||
func (this *LiteralArray) String () string {
|
||||
out := "(*"
|
||||
@ -120,8 +116,8 @@ func (this *LiteralArray) String () string {
|
||||
// inherent type information. A value cast may be used for this purpose.
|
||||
type LiteralStruct struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Members []*Member `parser:" '(' @@* ')' "`
|
||||
Position errors.Position
|
||||
Members []*Member
|
||||
|
||||
// Semantics
|
||||
Ty Type
|
||||
@ -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 + ")"
|
||||
}
|
||||
@ -146,39 +140,29 @@ func (this *LiteralStruct) String () string {
|
||||
// value cast may be used for this purpose.
|
||||
type LiteralBoolean struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Value *Boolean `parser:" @('true' | 'false') "`
|
||||
Position errors.Position
|
||||
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
|
||||
Pos lexer.Position
|
||||
Value string `parser:" @'nil' "`
|
||||
Position errors.Position
|
||||
|
||||
// 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,18 +1,17 @@
|
||||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "strings"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
|
||||
// Signature is a function or method signature that is used in functions,
|
||||
// methods, and specifying interface behaviors. It defines the type of a
|
||||
// function.
|
||||
type Signature struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Name string `parser:" '[' @Ident "`
|
||||
Arguments []*Declaration `parser:" @@* ']' "`
|
||||
Return Type `parser:" ( ':' @@ )? "`
|
||||
Position errors.Position
|
||||
Name string
|
||||
Arguments []*Declaration
|
||||
Return Type
|
||||
|
||||
// Semantics
|
||||
ArgumentOrder []string
|
||||
@ -48,9 +47,9 @@ func (this *Signature) Equals (ty Type) bool {
|
||||
// Member is a syntactical construct that is used to list members in struct
|
||||
// literals.
|
||||
type Member struct {
|
||||
Pos lexer.Position
|
||||
Name string `parser:" @Ident "`
|
||||
Value Expression `parser:" ':' @@ "`
|
||||
Position errors.Position
|
||||
Name string
|
||||
Value Expression
|
||||
}
|
||||
func (this *Member) String () string {
|
||||
return fmt.Sprint(this.Name, ":", this.Value)
|
||||
@ -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 {
|
||||
|
@ -1,7 +1,7 @@
|
||||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
|
||||
// TopLevel is any construct that is placed at the root of a file.
|
||||
type TopLevel interface {
|
||||
@ -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,22 +24,13 @@ 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
|
||||
Pos lexer.Position
|
||||
Acc *Access `parser:" @('-' | '~' | '+')? "`
|
||||
Name string `parser:" @TypeIdent "`
|
||||
Type Type `parser:" ':' @@ "`
|
||||
Position errors.Position
|
||||
Acc Access
|
||||
Name string
|
||||
Type Type
|
||||
|
||||
// Semantics
|
||||
Methods map[string] *Method
|
||||
@ -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 {
|
||||
@ -66,11 +55,11 @@ func (this *Typedef) String () string {
|
||||
// these are typed.
|
||||
type Function struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Acc *Access `parser:" @('-' | '~' | '+')? "`
|
||||
Signature *Signature `parser:" @@ "`
|
||||
LinkName string `parser:" @String? "`
|
||||
Body Expression `parser:" ( '=' @@ )? "`
|
||||
Position errors.Position
|
||||
Acc Access
|
||||
Signature *Signature
|
||||
LinkName string
|
||||
Body Expression
|
||||
|
||||
// Semantics
|
||||
Scope
|
||||
@ -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, "'")
|
||||
@ -97,12 +84,12 @@ func (this *Function) String () string {
|
||||
// unique, but are unique within the type they are defined on.
|
||||
type Method struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Acc *Access `parser:" @('-' | '~' | '+')? "`
|
||||
TypeName string `parser:" @TypeIdent "`
|
||||
Signature *Signature `parser:" '.' @@ "`
|
||||
LinkName string `parser:" @String? "`
|
||||
Body Expression `parser:" ( '=' @@ )? "`
|
||||
Position errors.Position
|
||||
Acc Access
|
||||
TypeName string
|
||||
Signature *Signature
|
||||
LinkName string
|
||||
Body Expression
|
||||
|
||||
// Semantics
|
||||
Scope
|
||||
@ -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, "'")
|
||||
|
@ -1,7 +1,7 @@
|
||||
package entity
|
||||
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
|
||||
// Type is any type notation.
|
||||
type Type interface {
|
||||
@ -15,10 +15,10 @@ type Type interface {
|
||||
|
||||
// TypeNamed refers to a user-defined or built in named type.
|
||||
type TypeNamed struct {
|
||||
Pos lexer.Position
|
||||
Module string `parser:" (@Ident '::')? "`
|
||||
Name string `parser:" @TypeIdent "`
|
||||
Type Type
|
||||
Position errors.Position
|
||||
Module string
|
||||
Name string
|
||||
Type Type
|
||||
}
|
||||
func (*TypeNamed) ty(){}
|
||||
func (this *TypeNamed) String () string {
|
||||
@ -35,8 +35,8 @@ func (this *TypeNamed) Equals (ty Type) bool {
|
||||
|
||||
// TypePointer is a pointer to another type.
|
||||
type TypePointer struct {
|
||||
Pos lexer.Position
|
||||
Referenced Type `parser:" '*' @@ "`
|
||||
Position errors.Position
|
||||
Referenced Type
|
||||
}
|
||||
func (*TypePointer) ty(){}
|
||||
func (this *TypePointer) String () string {
|
||||
@ -51,8 +51,8 @@ func (this *TypePointer) Equals (ty Type) bool {
|
||||
// eachother. Its length is not built into its type and can be changed at
|
||||
// runtime.
|
||||
type TypeSlice struct {
|
||||
Pos lexer.Position
|
||||
Element Type `parser:" '*' ':' @@ "`
|
||||
Position errors.Position
|
||||
Element Type
|
||||
}
|
||||
func (*TypeSlice) ty(){}
|
||||
func (this *TypeSlice) String () string {
|
||||
@ -67,9 +67,9 @@ func (this *TypeSlice) Equals (ty Type) bool {
|
||||
// length of an array is fixed and is part of its type. Arrays are passed by
|
||||
// value unless a pointer is used.
|
||||
type TypeArray struct {
|
||||
Pos lexer.Position
|
||||
Length int `parser:" @Int "`
|
||||
Element Type `parser:" ':' @@ "`
|
||||
Position errors.Position
|
||||
Length int
|
||||
Element Type
|
||||
}
|
||||
func (*TypeArray) ty(){}
|
||||
func (this *TypeArray) String () string {
|
||||
@ -87,8 +87,8 @@ func (this *TypeArray) Equals (ty Type) bool {
|
||||
// are specified in. Structs are passed by value unless a pointer is used.
|
||||
type TypeStruct struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Members []*Declaration `parser:" '(' @@+ ')' "`
|
||||
Position errors.Position
|
||||
Members []*Declaration
|
||||
|
||||
// Semantics
|
||||
MemberOrder []string
|
||||
@ -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 + ")"
|
||||
}
|
||||
@ -122,8 +121,8 @@ func (this *TypeStruct) Equals (ty Type) bool {
|
||||
// pointer to an interface, the pointer's reference will be used instead.
|
||||
type TypeInterface struct {
|
||||
// Syntax
|
||||
Pos lexer.Position
|
||||
Behaviors []*Signature `parser:" '(' @@+ ')' "`
|
||||
Position errors.Position
|
||||
Behaviors []*Signature
|
||||
|
||||
// Semantics
|
||||
BehaviorOrder []string
|
||||
@ -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 + ")"
|
||||
}
|
||||
@ -152,9 +150,9 @@ func (this *TypeInterface) Equals (ty Type) bool {
|
||||
|
||||
// TypeInt represents any signed or unsigned integer type.
|
||||
type TypeInt struct {
|
||||
Pos lexer.Position
|
||||
Width int
|
||||
Signed bool
|
||||
Position errors.Position
|
||||
Width int
|
||||
Signed bool
|
||||
}
|
||||
func (*TypeInt) ty(){}
|
||||
func (this *TypeInt) String () string {
|
||||
@ -171,8 +169,8 @@ func (this *TypeInt) Equals (ty Type) bool {
|
||||
|
||||
// TypeFloat represents any floating point type.
|
||||
type TypeFloat struct {
|
||||
Pos lexer.Position
|
||||
Width int
|
||||
Position errors.Position
|
||||
Width int
|
||||
}
|
||||
func (*TypeFloat) ty(){}
|
||||
func (this *TypeFloat) String () string {
|
||||
@ -187,8 +185,8 @@ func (this *TypeFloat) Equals (ty Type) bool {
|
||||
// is chosen based on the machine word size (32 on 32 bit systems, 64 on 64 bit
|
||||
// systems, etc)
|
||||
type TypeWord struct {
|
||||
Pos lexer.Position
|
||||
Signed bool
|
||||
Position errors.Position
|
||||
Signed bool
|
||||
}
|
||||
func (*TypeWord) ty(){}
|
||||
func (this *TypeWord) String () string {
|
||||
|
@ -16,16 +16,19 @@ type Position struct {
|
||||
}
|
||||
|
||||
func (pos Position) String () string {
|
||||
return fmt.Sprintf("%s:%d:%d", pos.File, pos.Row, pos.Start)
|
||||
return fmt.Sprintf("%s:%d:%d", pos.File, pos.Row + 1, pos.Start + 1)
|
||||
}
|
||||
|
||||
// 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 }
|
||||
@ -102,3 +105,11 @@ func (err *Error) String () string {
|
||||
func (err *Error) Format () string {
|
||||
return fmt.Sprintf("%v\n%v", err, err.Position.Format())
|
||||
}
|
||||
|
||||
// Errorf produces an error with a fmt'd message.
|
||||
func Errorf (position Position, format string, variables ...any) error {
|
||||
return &Error {
|
||||
Position: position,
|
||||
Message: fmt.Sprintf(format, variables...),
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -97,6 +97,9 @@ func (this *generator) generateExpressionAny (expression entity.Expression) (reg
|
||||
return this.generateLoop(expression, ResultModeAny)
|
||||
|
||||
// we get nothing from these
|
||||
case *entity.Assignment:
|
||||
_, err := this.generateAssignment(expression)
|
||||
return nil, false, err
|
||||
case *entity.Break:
|
||||
_, err := this.generateBreak(expression)
|
||||
return nil, false, err
|
||||
@ -163,6 +166,8 @@ func (this *generator) generateExpressionVal (expression entity.Expression) (llv
|
||||
return this.generateLiteralNil(expression)
|
||||
|
||||
// we get nothing from these
|
||||
case *entity.Assignment:
|
||||
return this.generateAssignment(expression)
|
||||
case *entity.Break:
|
||||
return this.generateBreak(expression)
|
||||
case *entity.Return:
|
||||
@ -228,6 +233,8 @@ func (this *generator) generateExpressionLoc (expression entity.Expression) (llv
|
||||
return this.generateLiteralStructLoc(expression, nil)
|
||||
|
||||
// we get nothing from these
|
||||
case *entity.Assignment:
|
||||
return this.generateAssignment(expression)
|
||||
case *entity.Break:
|
||||
return this.generateBreak(expression)
|
||||
case *entity.Return:
|
||||
@ -238,66 +245,3 @@ func (this *generator) generateExpressionLoc (expression entity.Expression) (llv
|
||||
expression, expression))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateStatement (
|
||||
statement entity.Statement,
|
||||
mode ResultMode,
|
||||
) (
|
||||
register llvm.Value,
|
||||
location bool,
|
||||
err error,
|
||||
) {
|
||||
switch mode {
|
||||
case ResultModeAny:
|
||||
return this.generateStatementAny(statement)
|
||||
case ResultModeVal:
|
||||
val, err := this.generateStatementVal(statement)
|
||||
return val, false, err
|
||||
case ResultModeLoc:
|
||||
loc, err := this.generateStatementLoc(statement)
|
||||
return loc, true, err
|
||||
default: panic("unknown ResultMode")
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateStatementAny (statement entity.Statement) (register llvm.Value, location bool, err error) {
|
||||
switch statement := statement.(type) {
|
||||
case *entity.Assignment:
|
||||
_, err := this.generateAssignment(statement)
|
||||
return nil, false, err
|
||||
case entity.Expression:
|
||||
return this.generateExpressionAny(statement)
|
||||
default:
|
||||
panic(fmt.Sprintf (
|
||||
"BUG: generator doesnt know about statement %v, ty: %T",
|
||||
statement, statement))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateStatementVal (statement entity.Statement) (llvm.Value, error) {
|
||||
switch statement := statement.(type) {
|
||||
case *entity.Assignment:
|
||||
_, err := this.generateAssignment(statement)
|
||||
return nil, err
|
||||
case entity.Expression:
|
||||
return this.generateExpressionVal(statement)
|
||||
default:
|
||||
panic(fmt.Sprintf (
|
||||
"BUG: generator doesnt know about value statement %v, ty: %T",
|
||||
statement, statement))
|
||||
}
|
||||
}
|
||||
|
||||
func (this *generator) generateStatementLoc (statement entity.Statement) (llvm.Value, error) {
|
||||
switch statement := statement.(type) {
|
||||
case *entity.Assignment:
|
||||
_, err := this.generateAssignment(statement)
|
||||
return nil, err
|
||||
case entity.Expression:
|
||||
return this.generateExpressionLoc(statement)
|
||||
default:
|
||||
panic(fmt.Sprintf (
|
||||
"BUG: generator doesnt know about location statement %v, ty: %T",
|
||||
statement, statement))
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,11 @@ func (this *generator) generateBlock (block *entity.Block, mode ResultMode) (llv
|
||||
|
||||
lastIndex := len(block.Steps) - 1
|
||||
for _, step := range block.Steps[:lastIndex] {
|
||||
_, _, err := this.generateStatementAny(step)
|
||||
_, _, err := this.generateExpressionAny(step)
|
||||
if err != nil { return nil, false, err }
|
||||
}
|
||||
|
||||
return this.generateStatement(block.Steps[lastIndex], mode)
|
||||
return this.generateExpression(block.Steps[lastIndex], mode)
|
||||
}
|
||||
|
||||
func (this *generator) generateBreak (brk *entity.Break) (llvm.Value, error) {
|
||||
|
@ -151,11 +151,11 @@ declare %Index @puts(ptr %string)
|
||||
`
|
||||
[puts string: *Byte]: Index
|
||||
|
||||
Greeter: (message: String)
|
||||
Greeter: (. message: String)
|
||||
Greeter.[greet] = [puts [@[.this.message 0]]]
|
||||
|
||||
[main] = {
|
||||
greeter: Greeter = (
|
||||
greeter: Greeter = (.
|
||||
message: 'hello\0'
|
||||
)
|
||||
greeter.[greet]
|
||||
|
@ -29,13 +29,13 @@ define void @T.do(ptr %this) {
|
||||
}
|
||||
`,
|
||||
`
|
||||
Doer: ([do])
|
||||
Doer: (~ [do])
|
||||
T: Int
|
||||
T.[do] = { }
|
||||
|
||||
[main] = {
|
||||
if:Doer = x:T
|
||||
if.[do]
|
||||
ifa:Doer = x:T
|
||||
ifa.[do]
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -70,14 +70,14 @@ define i64 @Number.number(ptr %this) {
|
||||
}
|
||||
`,
|
||||
`
|
||||
Numbered: ([number]: Int)
|
||||
Numbered: (~ [number]: Int)
|
||||
Number: Int
|
||||
Number.[number]: Int = [.this]
|
||||
|
||||
[main]: Int = {
|
||||
num:Number = 5
|
||||
if:Numbered = num
|
||||
if.[number]
|
||||
ifa:Numbered = num
|
||||
ifa.[number]
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -173,7 +173,7 @@ declare %Index @write(%File %fd, ptr %buffer, %Index %count)
|
||||
`
|
||||
[write fd:File buffer:*Byte count:Index]: Index
|
||||
|
||||
Writer: ([write buffer:*:Byte]: Index)
|
||||
Writer: (~ [write buffer:*:Byte]: Index)
|
||||
File: I32
|
||||
File.[write buffer:*:Byte]:Index = [write [.this] [~*Byte buffer] [#buffer]]
|
||||
|
||||
@ -208,13 +208,13 @@ define void @main() {
|
||||
declare %Index @File.write(ptr %this, { ptr, %Index } %buffer)
|
||||
`,
|
||||
`
|
||||
Writer: ([write buffer:*:Byte]: Index)
|
||||
A: (output: Writer)
|
||||
Writer: (~ [write buffer:*:Byte]: Index)
|
||||
A: (. output: Writer)
|
||||
File: I32
|
||||
File.[write buffer:*:Byte]:Index
|
||||
|
||||
[main] 'main' = {
|
||||
a:A = (output: [~File 0])
|
||||
a:A = (. output: [~File 0])
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -241,7 +241,7 @@ define i64 @Impl.x(ptr %this) {
|
||||
}
|
||||
`,
|
||||
`
|
||||
Face: ([x]:Int)
|
||||
Face: (~ [x]:Int)
|
||||
Impl: Int
|
||||
Impl.[x]:Int = 5
|
||||
[main] 'main' = {
|
||||
|
@ -269,7 +269,7 @@ func (this *generator) generateLiteralStructLoc (literal *entity.LiteralStruct,
|
||||
}
|
||||
|
||||
func (this *generator) generateLiteralBoolean (literal *entity.LiteralBoolean) (llvm.Value, error) {
|
||||
return llvm.NewConstBool(bool(*literal.Value)), nil
|
||||
return llvm.NewConstBool(bool(literal.Value)), nil
|
||||
}
|
||||
|
||||
func (this *generator) generateLiteralNil (literal *entity.LiteralNil) (llvm.Value, error) {
|
||||
|
@ -284,7 +284,7 @@ testString (test,
|
||||
`,
|
||||
`
|
||||
[main] 'main' = {
|
||||
ta:(x:Int y:(z:Int a:Int)) = (x: 1 y: (z: 2 a: 3))
|
||||
ta:(.x:Int y:(.z:Int a:Int)) = (.x: 1 y: (.z: 2 a: 3))
|
||||
}
|
||||
`)
|
||||
}
|
||||
@ -301,9 +301,9 @@ define void @main() {
|
||||
}
|
||||
`,
|
||||
`
|
||||
A: (x:Int)
|
||||
A: (.x:Int)
|
||||
[main] 'main' = {
|
||||
a:A = (x: 5)
|
||||
a:A = (.x: 5)
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ declare %Index @write(i32 %file, ptr %buffer, %Index %count)
|
||||
[print string: String] = loop {
|
||||
if [< [#string] 1] then [break]
|
||||
[write 1 [~*Byte string] 1]
|
||||
string = [\string 1:]
|
||||
string = [\string 1/]
|
||||
}
|
||||
`)
|
||||
}
|
||||
|
@ -20,16 +20,16 @@ testString (test,
|
||||
declare %AllTypes @x()
|
||||
`,
|
||||
`
|
||||
Point: (x:Int y:Int)
|
||||
Pegasus: (
|
||||
Point: (.x:Int y:Int)
|
||||
Pegasus: (~
|
||||
[clear clouds:*:Point]
|
||||
[fly rings:*:Point]
|
||||
[fall distance:Int]
|
||||
[complete]:Bool)
|
||||
Rectangle: (
|
||||
Rectangle: (.
|
||||
min:Point
|
||||
max:Point)
|
||||
AllInts: (
|
||||
AllInts: (.
|
||||
bool:Bool
|
||||
byte:Byte
|
||||
index:Index
|
||||
@ -44,12 +44,12 @@ AllInts: (
|
||||
u16:U16
|
||||
u32:U32
|
||||
u64:U64)
|
||||
AllFloats: (
|
||||
AllFloats: (.
|
||||
f32:F32
|
||||
f64:F64)
|
||||
Path: *:Point
|
||||
Quadrangle: 4:Point
|
||||
AllTypes: (
|
||||
AllTypes: (.
|
||||
string:String
|
||||
pegasus:Pegasus
|
||||
rectangle:Rectangle
|
||||
@ -121,7 +121,7 @@ define void @main() {
|
||||
}
|
||||
`,
|
||||
`
|
||||
A: (x:Int)
|
||||
A: (.x:Int)
|
||||
B: A
|
||||
[main] = {
|
||||
b:B
|
||||
|
17
go.mod
17
go.mod
@ -2,19 +2,4 @@ module git.tebibyte.media/sashakoshka/fspl
|
||||
|
||||
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
|
||||
)
|
||||
require github.com/mewmew/float v0.0.0-20201204173432-505706aa38fa
|
||||
|
50
go.sum
50
go.sum
@ -1,52 +1,2 @@
|
||||
github.com/alecthomas/assert/v2 v2.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0=
|
||||
github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
|
||||
github.com/alecthomas/participle/v2 v2.1.0 h1:z7dElHRrOEEq45F2TG5cbQihMtNTv8vwldytDj7Wrz4=
|
||||
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=
|
||||
|
217
lexer/lexer.go
217
lexer/lexer.go
@ -1,13 +1,13 @@
|
||||
package lexer
|
||||
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "bufio"
|
||||
import "unicode"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
|
||||
const (
|
||||
EOF lexer.TokenType = -(iota + 1)
|
||||
type TokenKind int; const (
|
||||
EOF TokenKind = -(iota + 1)
|
||||
|
||||
// Name Rough regex-ish description
|
||||
Ident // [a-z][a-zA-Z0-9]*
|
||||
@ -16,7 +16,7 @@ const (
|
||||
Float // [0-9]*\.[0-9]+
|
||||
String // \'.*\'
|
||||
|
||||
Symbol // [~!@#$%^&*-_=+\\|;:,<.>/?]+
|
||||
Symbol // [~!@#$%^&*-_=+\\|;,<>/?]+
|
||||
LParen // \(
|
||||
LBrace // \{
|
||||
LBracket // \[
|
||||
@ -25,18 +25,37 @@ const (
|
||||
RBracket // \]
|
||||
Colon // :
|
||||
DoubleColon // ::
|
||||
Dot // .
|
||||
DoubleDot // ..
|
||||
Star // \*
|
||||
)
|
||||
|
||||
type definition struct { }
|
||||
|
||||
// NewDefinition returns a lexer definition.
|
||||
func NewDefinition () lexer.Definition {
|
||||
return definition { }
|
||||
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 (definition) Symbols () map[string] lexer.TokenType {
|
||||
return map[string] lexer.TokenType {
|
||||
func Symbols () map[string] TokenKind {
|
||||
return map[string] TokenKind {
|
||||
"EOF": EOF,
|
||||
"Ident": Ident,
|
||||
"TypeIdent": TypeIdent,
|
||||
@ -52,45 +71,81 @@ func (definition) Symbols () map[string] lexer.TokenType {
|
||||
"RBracket": RBracket,
|
||||
"Colon": Colon,
|
||||
"DoubleColon": DoubleColon,
|
||||
"Dot": Dot,
|
||||
"DoubleDot": DoubleDot,
|
||||
"Star": Star,
|
||||
}
|
||||
}
|
||||
|
||||
func (definition) Lex (filename string, reader io.Reader) (lexer.Lexer, error) {
|
||||
type Token struct {
|
||||
Kind TokenKind
|
||||
Value string
|
||||
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),
|
||||
line: 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
|
||||
line int
|
||||
row int
|
||||
column int
|
||||
|
||||
eof bool
|
||||
}
|
||||
|
||||
func (this *fsplLexer) Next () (lexer.Token, error) {
|
||||
func (this *fsplLexer) Next () (Token, error) {
|
||||
token, err := this.nextInternal()
|
||||
if err == io.EOF { err = this.errUnexpectedEOF() }
|
||||
return token, err
|
||||
}
|
||||
|
||||
func (this *fsplLexer) nextInternal () (token lexer.Token, err error) {
|
||||
func (this *fsplLexer) nextInternal () (token Token, err error) {
|
||||
err = this.skipWhitespace()
|
||||
|
||||
token.Pos = this.pos()
|
||||
|
||||
token.Position = this.pos()
|
||||
if this.eof {
|
||||
token.Type = EOF
|
||||
token.Kind = EOF
|
||||
err = nil
|
||||
return
|
||||
}
|
||||
@ -102,14 +157,14 @@ func (this *fsplLexer) nextInternal () (token lexer.Token, err error) {
|
||||
}
|
||||
|
||||
doNumber := func () {
|
||||
token.Type = Int
|
||||
token.Kind = Int
|
||||
for isDigit(this.rune) {
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
if err != nil { return }
|
||||
}
|
||||
if this.rune == '.' {
|
||||
token.Type = Float
|
||||
token.Kind = Float
|
||||
appendRune()
|
||||
for isDigit(this.rune) {
|
||||
appendRune()
|
||||
@ -119,17 +174,23 @@ func (this *fsplLexer) nextInternal () (token lexer.Token, err error) {
|
||||
}
|
||||
|
||||
doSymbol := func () {
|
||||
token.Type = Symbol
|
||||
token.Kind = Symbol
|
||||
for isSymbol(this.rune) {
|
||||
appendRune()
|
||||
if err != nil { return }
|
||||
}
|
||||
}
|
||||
|
||||
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):
|
||||
token.Type = Ident
|
||||
token.Kind = Ident
|
||||
for unicode.IsLetter(this.rune) || isDigit(this.rune) {
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
@ -137,7 +198,7 @@ func (this *fsplLexer) nextInternal () (token lexer.Token, err error) {
|
||||
}
|
||||
// TypeIdent
|
||||
case unicode.IsUpper(this.rune):
|
||||
token.Type = TypeIdent
|
||||
token.Kind = TypeIdent
|
||||
for unicode.IsLetter(this.rune) || isDigit(this.rune) {
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
@ -146,9 +207,10 @@ func (this *fsplLexer) nextInternal () (token lexer.Token, err error) {
|
||||
// Int, Float
|
||||
case isDigit(this.rune):
|
||||
doNumber()
|
||||
if this.eof { err = nil; return }
|
||||
// String
|
||||
case this.rune == '\'':
|
||||
token.Type = String
|
||||
token.Kind = String
|
||||
err = this.nextRune()
|
||||
if err != nil { return }
|
||||
|
||||
@ -169,7 +231,7 @@ func (this *fsplLexer) nextInternal () (token lexer.Token, err error) {
|
||||
if err != nil { return }
|
||||
// Symbol, Int, Float
|
||||
case this.rune == '-':
|
||||
token.Type = Symbol
|
||||
token.Kind = Symbol
|
||||
appendRune()
|
||||
if err != nil { return }
|
||||
if isDigit(this.rune) {
|
||||
@ -184,41 +246,58 @@ func (this *fsplLexer) nextInternal () (token lexer.Token, err error) {
|
||||
doSymbol()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == '(':
|
||||
token.Type = LParen
|
||||
token.Kind = LParen
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == '{':
|
||||
token.Type = LBrace
|
||||
token.Kind = LBrace
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == '[':
|
||||
token.Type = LBracket
|
||||
token.Kind = LBracket
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == ')':
|
||||
token.Type = RParen
|
||||
token.Kind = RParen
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == '}':
|
||||
token.Type = RBrace
|
||||
token.Kind = RBrace
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case this.rune == ']':
|
||||
token.Type = RBracket
|
||||
token.Kind = RBracket
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
// Colon, DoubleColon
|
||||
case this.rune == ':':
|
||||
token.Type = Colon
|
||||
token.Kind = Colon
|
||||
appendRune()
|
||||
if this.rune == ':' {
|
||||
token.Type = DoubleColon
|
||||
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.Type = Star
|
||||
token.Kind = Star
|
||||
appendRune()
|
||||
if this.eof { err = nil; return }
|
||||
case unicode.IsPrint(this.rune):
|
||||
err = participle.Errorf (
|
||||
this.pos(), "unexpected rune \"%c\"",
|
||||
err = errors.Errorf (
|
||||
this.pos(), "unexpected rune '%c'",
|
||||
this.rune)
|
||||
default:
|
||||
err = participle.Errorf (
|
||||
err = errors.Errorf (
|
||||
this.pos(), "unexpected rune %U",
|
||||
this.rune)
|
||||
}
|
||||
@ -227,21 +306,28 @@ func (this *fsplLexer) nextInternal () (token lexer.Token, err error) {
|
||||
}
|
||||
|
||||
func (this *fsplLexer) nextRune () error {
|
||||
char, _, err := this.reader.ReadRune()
|
||||
this.rune = char
|
||||
|
||||
this.offset ++
|
||||
if char == '\n' {
|
||||
this.line ++
|
||||
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) {
|
||||
@ -299,21 +385,22 @@ func (this *fsplLexer) skipComment () error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *fsplLexer) pos () lexer.Position {
|
||||
return lexer.Position {
|
||||
Filename: this.filename,
|
||||
Offset: this.offset,
|
||||
Line: this.line,
|
||||
Column: this.column,
|
||||
func (this *fsplLexer) pos () errors.Position {
|
||||
return errors.Position {
|
||||
File: this.filename,
|
||||
Line: this.lineScanner.Text(),
|
||||
Row: this.row - 1,
|
||||
Start: this.column - 1,
|
||||
End: this.column,
|
||||
}
|
||||
}
|
||||
|
||||
func (this *fsplLexer) errUnexpectedEOF () error {
|
||||
return participle.Errorf(this.pos(), "unexpected EOF")
|
||||
return errors.Errorf(this.pos(), "unexpected EOF")
|
||||
}
|
||||
|
||||
func (this *fsplLexer) errBadEscapeSequence () error {
|
||||
return participle.Errorf(this.pos(), "bad escape sequence")
|
||||
return errors.Errorf(this.pos(), "bad escape sequence")
|
||||
}
|
||||
|
||||
func isWhitespace (char rune) bool {
|
||||
@ -327,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`,
|
||||
|
@ -3,12 +3,11 @@ package lexer
|
||||
import "fmt"
|
||||
import "testing"
|
||||
import "strings"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "github.com/alecthomas/participle/v2/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
|
||||
func tok (ty lexer.TokenType, value string) lexer.Token {
|
||||
return lexer.Token {
|
||||
Type: ty,
|
||||
func tok (kind TokenKind, value string) Token {
|
||||
return Token {
|
||||
Kind: kind,
|
||||
Value: value,
|
||||
}
|
||||
}
|
||||
@ -16,57 +15,78 @@ func tok (ty lexer.TokenType, value string) lexer.Token {
|
||||
func testString (
|
||||
test *testing.T,
|
||||
input string,
|
||||
correct ...lexer.Token,
|
||||
correct ...Token,
|
||||
) {
|
||||
testStringErr(test, "", 0, 0, input, correct...)
|
||||
testStringErr(test, "", "", 0, 0, input, correct...)
|
||||
}
|
||||
|
||||
func testStringErr (
|
||||
test *testing.T,
|
||||
errMessage string,
|
||||
errLine int,
|
||||
errColumn int,
|
||||
errLine string,
|
||||
errRow int,
|
||||
errStart int,
|
||||
input string,
|
||||
correct ...lexer.Token,
|
||||
correct ...Token,
|
||||
) {
|
||||
testError := func (err error) bool {
|
||||
got := err.(participle.Error)
|
||||
gotMessage := got.Message()
|
||||
gotLine := got.Position().Line
|
||||
gotColumn := got.Position().Column
|
||||
got := err.(*errors.Error)
|
||||
gotMessage := got.Message
|
||||
gotLine := got.Line
|
||||
gotRow := got.Position.Row
|
||||
gotStart := got.Position.Start
|
||||
correct :=
|
||||
gotMessage == errMessage &&
|
||||
gotLine == errLine &&
|
||||
gotColumn == errColumn
|
||||
gotRow == errRow &&
|
||||
gotStart == errStart
|
||||
if !correct {
|
||||
test.Log("errors do not match")
|
||||
test.Logf("got:\n%v:%v: %v", gotLine, gotColumn, gotMessage)
|
||||
test.Logf("correct:\n%v:%v: %v", errLine, errColumn, 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 := NewDefinition().Lex("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 }
|
||||
}
|
||||
}
|
||||
|
||||
var tokens []lexer.Token
|
||||
var tokens []Token
|
||||
for {
|
||||
got, err := lx.Next()
|
||||
tokens = append(tokens, got)
|
||||
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 }
|
||||
}
|
||||
@ -75,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 lexer.Token) string {
|
||||
return fmt.Sprintf("%4v: \"%s\"", token.Type, token.Value)
|
||||
dumpToken := func (token Token) string {
|
||||
return fmt.Sprintf("%12v: \"%s\"", token.Kind, token.Value)
|
||||
}
|
||||
|
||||
compareTokens := func () {
|
||||
@ -113,7 +133,7 @@ func testStringErr (
|
||||
|
||||
for index, token := range correct {
|
||||
gotToken := tokens[index]
|
||||
if token.Type != gotToken.Type || token.Value != gotToken.Value {
|
||||
if token.Kind != gotToken.Kind || token.Value != gotToken.Value {
|
||||
test.Logf("correct and got do not match at %v", index)
|
||||
compareTokens()
|
||||
test.Fail()
|
||||
|
620
parser/expression.go
Normal file
620
parser/expression.go
Normal file
@ -0,0 +1,620 @@
|
||||
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
|
||||
// | | +Star =Operation
|
||||
// | |
|
||||
// | | +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:
|
||||
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.Star, 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.Star: return this.parseOperationCore(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) {
|
||||
// TODO: maybe come up with a more elegant way of writing this?
|
||||
// possibly make a verion of expectValue that matches a list of kinds
|
||||
// and a list of values. could also make a version that accepts any kind
|
||||
// of token, and always supplement that with a normal expect.
|
||||
if this.token.Is(lexer.Star) {
|
||||
err := this.expectValue(lexer.Star, valuesOperator...)
|
||||
if err != nil { return nil, err }
|
||||
} else {
|
||||
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 }
|
||||
pos := this.pos()
|
||||
err = this.expectNext(lexer.Ident)
|
||||
if err != nil { return nil, err }
|
||||
signature := &entity.Signature{
|
||||
Position: 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
|
||||
}
|
196
parser/parser.go
Normal file
196
parser/parser.go
Normal file
@ -0,0 +1,196 @@
|
||||
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 := ""
|
||||
|
||||
switch {
|
||||
case len(items) == 1: list = fmt.Sprint(items[0])
|
||||
case len(items) == 2: list = fmt.Sprint(items[0], " or ", items[1])
|
||||
default:
|
||||
for index, item := range items {
|
||||
if index > 0 {
|
||||
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']
|
||||
|
@ -1,30 +1,31 @@
|
||||
package parser
|
||||
|
||||
import "io"
|
||||
import "fmt"
|
||||
import "testing"
|
||||
import "strings"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/testcommon"
|
||||
|
||||
func testString (test *testing.T, correct, input string) {
|
||||
testReader(test, correct, strings.NewReader(input))
|
||||
}
|
||||
|
||||
func testReader (test *testing.T, correct string, inputs ...io.Reader) {
|
||||
tree := Tree { }
|
||||
for index, stream := range inputs {
|
||||
err := tree.Parse(fmt.Sprintf("stream%d.fspl", index), stream)
|
||||
if err != nil {
|
||||
test.Error("parser returned error: ", err)
|
||||
return
|
||||
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{
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
got := tree.String()
|
||||
if got != correct {
|
||||
test.Log("strings do not match")
|
||||
test.Log("tree.String():\n" + got)
|
||||
test.Log("correct:\n" + correct)
|
||||
test.Fail()
|
||||
return
|
||||
}
|
||||
|
||||
got := ast.String()
|
||||
if got != correct {
|
||||
test.Logf("results do not match")
|
||||
testcommon.Compare(test, correct, got)
|
||||
test.Log("SOURCE FSPL CODE:")
|
||||
test.Log("\033[32m" + input + "\033[0m")
|
||||
test.Fail()
|
||||
}
|
||||
}
|
||||
|
150
parser/toplevel.go
Normal file
150
parser/toplevel.go
Normal file
@ -0,0 +1,150 @@
|
||||
package parser
|
||||
|
||||
import "git.tebibyte.media/sashakoshka/fspl/lexer"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/errors"
|
||||
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 }
|
||||
pos := this.pos()
|
||||
|
||||
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(pos, 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(pos, 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(pos, 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 (pos errors.Position, access entity.Access) (*entity.Function, error) {
|
||||
signature, err := this.parseSignature()
|
||||
if err != nil { return nil, err }
|
||||
|
||||
function := &entity.Function {
|
||||
Position: pos.Union(signature.Position),
|
||||
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 (pos errors.Position, access entity.Access, typeName string) (*entity.Method, error) {
|
||||
function, err := this.parseFunctionCore(pos, access)
|
||||
if err != nil { return nil, err }
|
||||
return &entity.Method {
|
||||
Position: function.Position,
|
||||
Acc: function.Acc,
|
||||
TypeName: typeName,
|
||||
Signature: function.Signature,
|
||||
LinkName: function.LinkName,
|
||||
Body: function.Body,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *Parser) parseTypedefCore (pos errors.Position, access entity.Access, typeName string) (*entity.Typedef, error) {
|
||||
pos = pos.Union(this.pos())
|
||||
ty, err := this.parseType()
|
||||
if err != nil { return nil, err }
|
||||
return &entity.Typedef {
|
||||
Position: pos,
|
||||
Acc: access,
|
||||
Name: typeName,
|
||||
Type: ty,
|
||||
}, nil
|
||||
}
|
@ -3,81 +3,12 @@ package parser
|
||||
import "io"
|
||||
import "os"
|
||||
import "fmt"
|
||||
import "github.com/alecthomas/participle/v2"
|
||||
import "git.tebibyte.media/sashakoshka/fspl/entity"
|
||||
import flexer "git.tebibyte.media/sashakoshka/fspl/lexer"
|
||||
|
||||
var parser = participle.MustBuild[Tree] (
|
||||
participle.Union[entity.TopLevel] (
|
||||
new(entity.Function),
|
||||
new(entity.Typedef),
|
||||
new(entity.Method)),
|
||||
participle.Union[entity.Type] (
|
||||
new(entity.TypeNamed),
|
||||
new(entity.TypePointer),
|
||||
new(entity.TypeInterface),
|
||||
new(entity.TypeStruct),
|
||||
new(entity.TypeArray),
|
||||
new(entity.TypeSlice)),
|
||||
participle.Union[entity.Expression] (
|
||||
new(entity.LiteralInt),
|
||||
new(entity.LiteralFloat),
|
||||
new(entity.LiteralString),
|
||||
new(entity.LiteralArray),
|
||||
new(entity.LiteralStruct),
|
||||
new(entity.LiteralBoolean),
|
||||
new(entity.LiteralNil),
|
||||
new(entity.Break),
|
||||
new(entity.Return),
|
||||
new(entity.IfElse),
|
||||
new(entity.Loop),
|
||||
new(entity.MethodCall),
|
||||
new(entity.MemberAccess),
|
||||
new(entity.Declaration),
|
||||
new(entity.Subscript),
|
||||
new(entity.Slice),
|
||||
new(entity.Length),
|
||||
new(entity.Dereference),
|
||||
new(entity.Reference),
|
||||
new(entity.ValueCast),
|
||||
new(entity.BitCast),
|
||||
new(entity.Operation),
|
||||
new(entity.Call),
|
||||
new(entity.Variable),
|
||||
new(entity.Block)),
|
||||
participle.Union[entity.Statement] (
|
||||
new(entity.LiteralInt),
|
||||
new(entity.LiteralFloat),
|
||||
new(entity.LiteralArray),
|
||||
new(entity.LiteralStruct),
|
||||
new(entity.LiteralBoolean),
|
||||
new(entity.LiteralNil),
|
||||
new(entity.Break),
|
||||
new(entity.Return),
|
||||
new(entity.Assignment),
|
||||
new(entity.IfElse),
|
||||
new(entity.Loop),
|
||||
new(entity.MethodCall),
|
||||
new(entity.MemberAccess),
|
||||
new(entity.Declaration),
|
||||
new(entity.Subscript),
|
||||
new(entity.Slice),
|
||||
new(entity.Length),
|
||||
new(entity.Dereference),
|
||||
new(entity.Reference),
|
||||
new(entity.ValueCast),
|
||||
new(entity.BitCast),
|
||||
new(entity.Operation),
|
||||
new(entity.Call),
|
||||
new(entity.Variable),
|
||||
new(entity.Block)),
|
||||
participle.UseLookahead(participle.MaxLookahead),
|
||||
participle.Lexer(flexer.NewDefinition()))
|
||||
|
||||
// Tree represents a parsed abstract syntax tree. It has no constructor and its
|
||||
// zero value can be used safely.
|
||||
type Tree struct {
|
||||
Declarations []entity.TopLevel `parser:" @@* "`
|
||||
Declarations []entity.TopLevel
|
||||
}
|
||||
|
||||
func (this *Tree) String () string {
|
||||
@ -89,15 +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 {
|
||||
parsed, err := parser.Parse(name, file)
|
||||
if parsed != nil {
|
||||
this.Declarations = append(this.Declarations, parsed.Declarations...)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ParseFile parses the contents of the given file into the tree.
|
||||
func (this *Tree) ParseFile (name string) error {
|
||||
file, err := os.Open(name)
|
||||
@ -105,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…
Reference in New Issue
Block a user