Merge pull request 'remove-participle' (#10) from remove-participle into main

Reviewed-on: sashakoshka/fspl#10
This commit is contained in:
Sasha Koshka 2024-02-08 18:34:18 +00:00
commit 4089765633
47 changed files with 2142 additions and 897 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -5,8 +5,8 @@ import "strings"
func TestError (test *testing.T) {
testError (test,
`example.fspl:10:6: some error
10 | lorem ipsum dolor
`example.fspl:11:7: some error
11 | lorem ipsum dolor
^^^^^`,
&Error {
Position: Position {
@ -22,8 +22,8 @@ testError (test,
func TestErrorTab (test *testing.T) {
testError (test,
`example.fspl:10:7: some error
10 | lorem ipsum dolor
`example.fspl:11:8: some error
11 | lorem ipsum dolor
^^^^^`,
&Error {
Position: Position {
@ -39,8 +39,8 @@ testError (test,
func TestErrorTabInBetween (test *testing.T) {
testError (test,
`example.fspl:10:7: some error
10 | lorem ipsum dolor
`example.fspl:11:8: some error
11 | lorem ipsum dolor
^^^^^^^^^`,
&Error {
Position: Position {

View File

@ -6,9 +6,9 @@ import "git.tebibyte.media/sashakoshka/fspl/testcommon"
func testString (test *testing.T, correct string, got string) {
if got != correct {
test.Logf("results do not match")
testcommon.Compare(test, correct, got)
test.Fail()
}
testcommon.Compare(test, correct, got)
}
func testError (test *testing.T, correct string, err *Error) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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/]
}
`)
}

View File

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

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

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

View File

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

View File

@ -8,7 +8,7 @@ testString(test, "", tok(EOF, ""))
func TestLexerErrUnexpectedRune (test *testing.T) {
testStringErr(test,
"unexpected rune \"\"\"", 3, 5,
"unexpected rune '\"'", `ooo " hahsdklhasldk`, 2, 4,
`
valid tokens valid tokens
ooo " hahsdklhasldk
@ -23,7 +23,7 @@ tok(0, ""),
func TestLexerErrUnexpectedEOF (test *testing.T) {
testStringErr(test,
"unexpected EOF", 2, 39,
"unexpected EOF", `'this is a string that does not end...`, 1, 38,
`
'this is a string that does not end...`,
tok(String, "this is a string that does not end..."),
@ -35,7 +35,7 @@ testString(test,
`
normalIdent TypeIdent
[$$abcdEfGhIjKlMnOpQrStUvWxYz]{+}(-) ; this is a comment
~ ! @ # $ % ^ & *- _ = + \ | :** ::, < . > / ?
~ ! @ # $ % ^ & *- _ = + \ | :** ::, < . .. > / ?
!! ++ -- && || 'some \'string' 'nullterm\0' '\110\117\115\105'
1234 -1234 9876540321 (754.2340)
`,
@ -73,7 +73,8 @@ tok(Star, "*"),
tok(DoubleColon, "::"),
tok(Symbol, ","),
tok(Symbol, "<"),
tok(Symbol, "."),
tok(Dot, "."),
tok(DoubleDot, ".."),
tok(Symbol, ">"),
tok(Symbol, "/"),
tok(Symbol, "?"),
@ -98,6 +99,13 @@ tok(RParen, ")"),
tok(EOF, ""),
)}
func TestLexerSingleParen (test *testing.T) {
testString(test,
`)`,
tok(RParen, ")"),
tok(EOF, ""),
)}
func TestLexerNoTrailingEOLInt (test *testing.T) {
testString(test,
`12345`,

View File

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

@ -0,0 +1,115 @@
package parser
import "git.tebibyte.media/sashakoshka/fspl/lexer"
import "git.tebibyte.media/sashakoshka/fspl/errors"
import "git.tebibyte.media/sashakoshka/fspl/entity"
func (this *Parser) parseLiteralInt () (*entity.LiteralInt, error) {
err := this.expect(lexer.Int)
if err != nil { return nil, err }
defer this.next()
value, err := parseInteger(this.value())
if err != nil { return nil, errors.Errorf(this.pos(), err.Error()) }
return &entity.LiteralInt {
Position: this.pos(),
Value: int(value), // TODO struct should store as int64
}, nil
}
func (this *Parser) parseLiteralFloat () (*entity.LiteralFloat, error) {
err := this.expect(lexer.Float)
if err != nil { return nil, err }
defer this.next()
value, err := parseFloat(this.value())
if err != nil { return nil, errors.Errorf(this.pos(), err.Error()) }
return &entity.LiteralFloat {
Position: this.pos(),
Value: value,
}, nil
}
func (this *Parser) parseLiteralString () (*entity.LiteralString, error) {
err := this.expect(lexer.String)
if err != nil { return nil, err }
defer this.next()
return &entity.LiteralString {
Position: this.pos(),
ValueUTF8: this.value(),
}, nil
}
func (this *Parser) parseLiteralBoolean () (*entity.LiteralBoolean, error) {
err := this.expectValueDesc("boolean", lexer.Ident, "true", "false")
if err != nil { return nil, err }
defer this.next()
return &entity.LiteralBoolean {
Position: this.pos(),
Value: this.value() == "true",
}, nil
}
func (this *Parser) parseLiteralNil () (*entity.LiteralNil, error) {
err := this.expectValueDesc("Nil", lexer.Ident, "nil")
if err != nil { return nil, err }
defer this.next()
return &entity.LiteralNil {
Position: this.pos(),
}, nil
}
func (this *Parser) parseLiteralArrayCore (pos errors.Position) (*entity.LiteralArray, error) {
err := this.expect(lexer.Star)
if err != nil { return nil, err }
literal := &entity.LiteralArray {
Position: pos,
}
this.next()
for {
err := this.expectDesc (
"array literal element or end",
appendCopy(startTokensExpression, lexer.RParen)...)
if err != nil { return nil, err }
if this.kind() == lexer.RParen { break }
element, err := this.parseExpression()
if err != nil { return nil, err }
literal.Elements = append(literal.Elements, element)
}
literal.Position = literal.Position.Union(this.pos())
this.next()
return literal, nil
}
func (this *Parser) parseLiteralStructCore (pos errors.Position) (*entity.LiteralStruct, error) {
err := this.expect(lexer.Dot)
if err != nil { return nil, err }
literal := &entity.LiteralStruct {
Position: pos,
}
this.next()
for {
err := this.expectDesc (
"struct literal member or end",
appendCopy(startTokensExpression, lexer.RParen)...)
if err != nil { return nil, err }
if this.kind() == lexer.RParen { break }
member, err := this.parseMember()
if err != nil { return nil, err }
literal.Members = append(literal.Members, member)
}
literal.Position = literal.Position.Union(this.pos())
this.next()
return literal, nil
}

91
parser/misc.go Normal file
View File

@ -0,0 +1,91 @@
package parser
import "strconv"
import "git.tebibyte.media/sashakoshka/fspl/lexer"
import "git.tebibyte.media/sashakoshka/fspl/entity"
var descriptionSignature = "signature"
var startTokensSignature = []lexer.TokenKind { lexer.LBracket }
func (this *Parser) parseSignature () (*entity.Signature, error) {
err := this.expectDesc(descriptionSignature, startTokensSignature...)
if err != nil { return nil, err }
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
View 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
}

View File

@ -5,23 +5,23 @@ import "testing"
func TestType (test *testing.T) {
testString (test,
// correct
`BasicInt: Int
Structure: (x:Int y:Int)
Interface: ([aMethod x:Int]:*U8 [otherMethod arg:5:Int]:*U8)
Array: 16:16:16:Int
StructArray: 31:24:340920:(x:Int y:Int)
String: *:U8`,
`- BasicInt: Int
- Structure: (. x:Int y:Int)
- Interface: (~ [aMethod x:Int]:*U8 [otherMethod arg:5:Int]:*U8)
- Array: 16:16:16:Int
- StructArray: 31:24:340920:(. x:Int y:Int)
- String: *:U8`,
// input
`
BasicInt: Int
Structure: (
Structure: (.
x:Int
y:Int)
Interface: (
Interface: (~
[aMethod x:Int]:*U8
[otherMethod arg:5:Int]:*U8)
Array: 16:16:16:Int
StructArray: 31:24:340920:(
StructArray: 31:24:340920:(.
x:Int
y:Int)
String: *:U8
@ -31,19 +31,19 @@ String: *:U8
func TestLiteral (test *testing.T) {
testString (test,
// correct
`[int]:Int = 5
[float]:F64 = 324.23409
[boolean]:Bool = true
[boolean2]:Bool = false
[struct]:(x:Int y:(w:F64 z:F64)) = (x:1 y:(w:1.2 z:78.5))
[string]:String = 'hahah \'sup\005'`,
`- [int]:Int = 5
- [float]:F64 = 324.23409
- [boolean]:Bool = true
- [boolean2]:Bool = false
- [struct]:(. x:Int y:(. w:F64 z:F64)) = (. x:1 y:(. w:1.2 z:78.5))
- [string]:String = 'hahah \'sup\005'`,
// input
`
[int]:Int = 5
[float]:F64 = 324.23409
[boolean]:Bool = true
[boolean2]:Bool = false
[struct]:(x:Int y:(w:F64 z:F64)) = (x: 1 y: (w: 1.2 z: 78.5))
[struct]:(. x:Int y:(. w:F64 z:F64)) = (. x: 1 y: (. w: 1.2 z: 78.5))
[string]:String = 'hahah \'sup\005'
`)
}
@ -51,27 +51,27 @@ testString (test,
func TestExpression (test *testing.T) {
testString (test,
// correct
`[var] = sdfdf
[memberAccess] = sdfdf.a
[methodAccess] = sdfdf.[method 238 9 203]
[declaration] = sdfdf:Int
[call] = [print 3 1]
[emptyBlock] = {}
[nestedBlock] = {342 93.34 {3948 32}}
[subscript] = [.(* 3 1 2 4) 3]
[slice] = [\(* 1 2 3 4 5 6 7 8 9) consumed:]
[dereference] = [.ptr]
[reference] = [@val]
[valueCast] = [~ F32 someValue]
[bitCast] = [~~ 4:U8 someValue]
[math] = {[+ 3 4 2 [/ 24 3]] [|| 3 [<< 56 4]] [++ 3] [-- 3] [- 3 1]}
[ifElse]:Int = if true then 4 else 5
[dangleElse]:Int = if true then if false then 3 else 5
[loop]:Int = {i:Int=0 if [< i 3] then return 1 loop {[print 3] if [> i 3] then break 0 i=[++ i]}}`,
`- [var] = sdfdf
- [memberAccess] = [~ T sdfdf].a.x.y.z
- [methodAccess] = sdfdf.[method 238 9 203]
- [declaration] = sdfdf:Int
- [call] = [print 3 1]
- [emptyBlock] = {}
- [nestedBlock] = {342 93.34 {3948 32}}
- [subscript] = [.(* 3 1 2 4) 3]
- [slice] = [\(* 1 2 3 4 5 6 7 8 9) consumed/]
- [dereference] = [.ptr]
- [reference] = [@val]
- [valueCast] = [~ F32 someValue]
- [bitCast] = [~~ 4:U8 someValue]
- [math] = {[+ 3 4 2 [/ 24 3]] [|| 3 [<< 56 4]] [++ 3] [-- 3] [- 3 1]}
- [ifElse]:Int = if true then 4 else 5
- [dangleElse]:Int = if true then if false then 3 else 5
- [loop]:Int = {i:Int=0 if [< i 3] then return 1 loop {[print 3] if [> i 3] then break 0 i=[++ i]}}`,
// input
`
[var] = sdfdf
[memberAccess] = sdfdf.a
[memberAccess] = [~ T sdfdf].a.x.y.z
[methodAccess] = sdfdf.[method 238 9 203]
[declaration] = sdfdf:Int
[call] = [print 3 1]
@ -85,7 +85,7 @@ testString (test,
}
}
[subscript] = [. (* 3 1 2 4) 3]
[slice] = [\ (* 1 2 3 4 5 6 7 8 9) consumed:]
[slice] = [\ (* 1 2 3 4 5 6 7 8 9) consumed/]
[dereference] = [. ptr]
[reference] = [@ val]
[valueCast] = [~ F32 someValue]
@ -119,22 +119,22 @@ testString (test,
func TestFunction (test *testing.T) {
testString (test,
// correct
`[arguments x:Int y:Int z:Int] = {}
[arrayArguments arg:4:54:23:28:(x:Int y:Int)] = {}`,
`- [arguments x:Int y:Int z:Int] = {}
- [arrayArguments arg:4:54:23:28:(. x:Int y:Int)] = {}`,
//input
`
[arguments x:Int y:Int z:Int] = { }
[arrayArguments arg:4:54:23:28:(x:Int y:Int)] = { }
[arrayArguments arg:4:54:23:28:(. x:Int y:Int)] = { }
`)
}
func TestMethod (test *testing.T) {
testString (test,
// correct
`BopIt: Int
BopIt.[bop force:UInt] = {}
BopIt.[twist angle:F64] = {}
BopIt.[pull distance:Int] = {}`,
`- BopIt: Int
- BopIt.[bop force:UInt] = {}
- BopIt.[twist angle:F64] = {}
- BopIt.[pull distance:Int] = {}`,
//input
`
BopIt: Int
@ -148,31 +148,31 @@ func TestAccess (test *testing.T) {
testString (test,
// correct
`+ PublicType: Int
# RestrictedType: (x:Int y:Int)
~ RestrictedType: (. x:Int y:Int)
- PrivateType: String
AlsoPrivateType: Byte
- AlsoPrivateType: Byte
+ [publicFn]:Int = 0
# [restrictedFn]:(x:Int y:Int) = (x:0 y:0)
~ [restrictedFn]:(. x:Int y:Int) = (. x:0 y:0)
- [privateFn]:Rune = 'a'
[alsoPrivateFn]:Byte = 0
T: Int
- [alsoPrivateFn]:Byte = 0
- T: Int
+ T.[publicFn]:Int = 0
# T.[restrictedFn]:(x:Int y:Int) = (x:0 y:0)
~ T.[restrictedFn]:(. x:Int y:Int) = (. x:0 y:0)
- T.[privateFn]:Rune = 'a'
T.[alsoPrivateFn]:Byte = 0`,
- T.[alsoPrivateFn]:Byte = 0`,
//input
`
+ PublicType: Int
# RestrictedType: (x:Int y:Int)
~ RestrictedType: (. x:Int y:Int)
- PrivateType: String
AlsoPrivateType: Byte
+ [publicFn]: Int = 0
# [restrictedFn]: (x:Int y:Int) = (x:0 y:0)
~ [restrictedFn]: (. x:Int y:Int) = (. x:0 y:0)
- [privateFn]: Rune = 'a'
[alsoPrivateFn]: Byte = 0
T: Int
+ T.[publicFn]: Int = 0
# T.[restrictedFn]: (x:Int y:Int) = (x:0 y:0)
~ T.[restrictedFn]: (. x:Int y:Int) = (. x:0 y:0)
- T.[privateFn]: Rune = 'a'
T.[alsoPrivateFn]: Byte = 0
`)
@ -181,9 +181,9 @@ T.[alsoPrivateFn]: Byte = 0
func TestLinkName (test *testing.T) {
testString (test,
// correct
`[puts byte:*:Byte]:Index 'puts'
File.[write size:Index nmemb:Index]:Index 'fwrite'
[main]:Int 'main' = 0`,
`- [puts byte:*:Byte]:Index 'puts'
- File.[write size:Index nmemb:Index]:Index 'fwrite'
- [main]:Int 'main' = 0`,
// input
`
[puts byte:*:Byte]: Index 'puts'
@ -195,7 +195,7 @@ File.[write size:Index nmemb:Index]: Index 'fwrite'
func TestModuleNamespace (test *testing.T) {
testString (test,
// correct
`[hello out:io::Writer] = ioutil::[writeString out 'hello']`,
`- [hello out:io::Writer] = ioutil::[writeString out 'hello']`,
// input
`
[hello out:io::Writer] = ioutil::[writeString out 'hello']

View File

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

View File

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

@ -0,0 +1,220 @@
package parser
import "strconv"
import "git.tebibyte.media/sashakoshka/fspl/lexer"
import "git.tebibyte.media/sashakoshka/fspl/errors"
import "git.tebibyte.media/sashakoshka/fspl/entity"
var descriptionType = "type"
var startTokensType = []lexer.TokenKind {
lexer.Ident,
lexer.TypeIdent,
lexer.Star,
lexer.Int,
lexer.LParen,
}
func (this *Parser) parseType () (entity.Type, error) {
err := this.expectDesc(descriptionType, startTokensType...)
if err != nil { return nil, err }
switch this.kind() {
case lexer.Ident:
ident := this.token.Value
err := this.expectNext(lexer.DoubleColon)
if err != nil { return nil, err }
err = this.expectNext(lexer.TypeIdent)
if err != nil { return nil, err }
return this.parseTypeNamedCore(ident)
case lexer.TypeIdent:
if this.value() == "Int" || this.value() == "UInt" {
return this.parseTypeWord()
}
if this.token.ValueIs("F16", "F32", "F64", "F128") {
return this.parseTypeFloat()
}
if len(this.value()) > 0 {
_, err := strconv.Atoi(this.value()[1:])
if err == nil && (
this.value()[0] == 'U' ||
this.value()[0] == 'I') {
return this.parseTypeInt()
}
}
return this.parseTypeNamedCore("")
case lexer.Star:
return this.parseTypePointerOrSlice()
case lexer.Int:
return this.parseTypeArray()
case lexer.LParen:
err := this.expectNext(lexer.Dot, lexer.Symbol)
if err != nil { return nil, err }
err = this.expectValue(0, ".", "~", "|")
if err != nil { return nil, err }
switch this.value() {
case ".": return this.parseTypeStructCore()
case "~": return this.parseTypeInterfaceCore()
}
panic(this.bug())
}
panic(this.bug())
}
func (this *Parser) parseTypeNamedCore (module string) (entity.Type, error) {
err := this.expect(lexer.TypeIdent)
if err != nil { return nil, err }
defer this.next()
return &entity.TypeNamed {
Position: this.pos(),
Name: this.value(),
Module: module,
}, nil
}
func (this *Parser) parseTypePointerOrSlice () (entity.Type, error) {
err := this.expectDesc("pointer type or slice type", lexer.Star)
if err != nil { return nil, err }
start := this.pos()
err = this.expectNextDesc (
"Colon or Type",
appendCopy(startTokensType, lexer.Colon)...)
if err != nil { return nil, err }
if this.token.Is(lexer.Colon) {
this.next()
element, err := this.parseType()
if err != nil { return nil, err }
return &entity.TypeSlice {
Position: start.Union(this.pos()),
Element: element,
}, nil
} else {
referenced, err := this.parseType()
if err != nil { return nil, err }
return &entity.TypePointer {
Position: start.Union(this.pos()),
Referenced: referenced,
}, nil
}
}
func (this *Parser) parseTypeArray () (entity.Type, error) {
err := this.expectDesc("array type", lexer.Int)
if err != nil { return nil, err }
start := this.pos()
length, err := strconv.Atoi(this.value())
if err != nil { return nil, err }
err = this.expectNext(lexer.Colon)
if err != nil { return nil, err }
err = this.next()
if err != nil { return nil, err }
element, err := this.parseType()
if err != nil { return nil, err }
return &entity.TypeArray {
Position: start.Union(this.pos()),
Length: length,
Element: element,
}, nil
}
func (this *Parser) parseTypeStructCore () (entity.Type, error) {
err := this.expect(lexer.Dot)
if err != nil { return nil, err }
ty := &entity.TypeStruct {
Position: this.pos(),
}
this.next()
for {
err := this.expectDesc (
"struct member or end",
appendCopy(startTokensDeclaration, lexer.RParen)...)
if err != nil { return nil, err }
if this.kind() == lexer.RParen { break }
member, err := this.parseDeclaration()
if err != nil { return nil, err }
ty.Members = append(ty.Members, member)
}
ty.Position = ty.Position.Union(this.pos())
this.next()
return ty, nil
}
func (this *Parser) parseTypeInterfaceCore () (entity.Type, error) {
err := this.expectValue(lexer.Symbol, "~")
if err != nil { return nil, err }
ty := &entity.TypeInterface {
Position: this.pos(),
}
this.next()
for {
err := this.expectDesc (
"interface behavior or end",
appendCopy(startTokensSignature, lexer.RParen)...)
if err != nil { return nil, err }
if this.kind() == lexer.RParen { break }
behavior, err := this.parseSignature()
if err != nil { return nil, err }
ty.Behaviors = append(ty.Behaviors, behavior)
}
ty.Position = ty.Position.Union(this.pos())
this.next()
return ty, nil
}
func (this *Parser) parseTypeInt () (entity.Type, error) {
err := this.expect(lexer.TypeIdent)
if err != nil { return nil, err }
value := this.value()
width, err := strconv.Atoi(value[1:])
if err != nil || !(value[0] == 'I' || value[0] == 'U') {
return nil, errors.Errorf(this.pos(), "malformed Integer type")
}
defer this.next()
return &entity.TypeInt {
Position: this.pos(),
Width: width,
Signed: value[0] == 'I',
}, nil
}
func (this *Parser) parseTypeWord () (entity.Type, error) {
err := this.expectValue(lexer.TypeIdent, "Int", "UInt")
if err != nil { return nil, err }
defer this.next()
return &entity.TypeWord {
Position: this.pos(),
Signed: this.value()[0] == 'I',
}, nil
}
func (this *Parser) parseTypeFloat () (entity.Type, error) {
err := this.expectValue(lexer.TypeIdent, "F16", "F32", "F64", "F128")
if err != nil { return nil, err }
width, _ := strconv.Atoi(this.value()[1:])
defer this.next()
return &entity.TypeFloat {
Position: this.pos(),
Width: width,
}, nil
}