Implemented parser

This commit is contained in:
2023-09-18 00:13:55 -04:00
commit 6df77d35fc
9 changed files with 597 additions and 0 deletions

5
go.mod Normal file
View File

@@ -0,0 +1,5 @@
module git.tebibyte.media/sashakoshka/fspl
go 1.21.0
require github.com/alecthomas/participle/v2 v2.1.0

8
go.sum Normal file
View File

@@ -0,0 +1,8 @@
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/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=

5
main.go Normal file
View File

@@ -0,0 +1,5 @@
package main
func main () {
}

270
parser/expression.go Normal file
View File

@@ -0,0 +1,270 @@
package parser
import "fmt"
import "github.com/alecthomas/participle/v2/lexer"
type Expression interface {
expression ()
}
// 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 {
Pos lexer.Position
Name string `parser:" @Ident "`
}
func (*Variable) expression(){}
func (this *Variable) String () string {
return this.Name
}
// Declaration binds a local identifier to a typed variable, but also acts as a
// variable expression allowing the variable to be used the moment it is
// defined. Since it contains inherent type information, it may be directly
// assigned to an interface. A declaration is always a valid location
// expression.
type Declaration struct {
Pos lexer.Position
Name string `parser:" @Ident "`
Type Type `parser:" ':' @@ "`
}
func (*Declaration) expression(){}
func (this *Declaration) String () string {
return fmt.Sprint(this.Name, ":", this.Type)
}
// Call calls upon the function specified by the first argument, and passes the
// rest of that argument to the function. The first argument must be a function
// type, usually the name of a function. The result of a call may be assigned to
// any type matching the function's return type. Since it contains inherent type
// information, it may be directly assigned to an interface. A call is never a
// valid location expression.
type Call struct {
Pos lexer.Position
Function Expression `parser:" '[' @@ "`
Arguments []Expression `parser:" @@* ']' "`
}
func (*Call) expression(){}
func (this *Call) String () string {
out := fmt.Sprint("[", this.Function)
for _, argument := range this.Arguments {
out += fmt.Sprint(" ", argument)
}
return out + "]"
}
// Array subscripting allows referring to a specific element of an array. It
// accepts any array, and any offset of type Size. It may be assigned to any
// type matching the array's element type. Since it contains inherent type
// information, it may be directly assigned to an interface. A subscript is a
// valid location expression only if the array being subscripted is.
type Subscript struct {
Pos lexer.Position
Array Expression `parser:" '[' '.' @@ "`
Offset Expression `parser:" @@ ']' "`
}
func (*Subscript) expression(){}
func (this *Subscript) String () string {
return fmt.Sprint("[.", this.Array, this.Offset, "]")
}
// Pointer dereferencing allows retrieving the value of a pointer. It accepts
// any pointer. It may be assigned to any type matching the pointer's pointed
// type. Since it contains inherent type information, it may be directly
// assigned to an interface. A dereference is a valid location expression only
// if the pointer being dereferenced is.
type Dereference struct {
Pos lexer.Position
Pointer Expression `parser:" '[' '.' @@ ']' "`
}
func (*Dereference) expression(){}
func (this *Dereference) String () string {
return fmt.Sprint("[.", this.Pointer, "]")
}
// Value referencing allows retrieving the location of a value in memory. It
// accepts any location expression, and can be assigned to any type that is a
// pointer to the location expression's type. Since it contains inherent type
// information, it can be directly assigned to an interface, although it doesn't
// make a whole lot of sense to do so because assigning a value to an interface
// automatically references it anyway. A reference is never a valid location
// expression.
type Reference struct {
Pos lexer.Position
Value Expression `parser:" '[' '@' @@ ']' "`
}
func (*Reference) expression(){}
func (this *Reference) String () string {
return fmt.Sprint("[@", this.Value, "]")
}
// Vaue casting converts a value of a certain type to another type. Since it
// 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
Type Expression `parser:" '[' 'cast' @@ "`
Value Expression `parser:" @@ ']' "`
}
func (*ValueCast) expression(){}
func (this *ValueCast) String () string {
return fmt.Sprint("[cast", this.Type, this.Value, "]")
}
// Bit casting takes the raw data in memory of a certain value and re-interprets
// it as a value of another type. Since it contains inherent type information,
// it may be directly assigned to an interface. A bit cast is never a valid
// location expression.
type BitCast struct {
Pos lexer.Position
Type Expression `parser:" '[' 'bitcast' @@ "`
Value Expression `parser:" @@ ']' "`
}
func (*BitCast) expression(){}
func (this *BitCast) String () string {
return fmt.Sprint("[bitcast", this.Type, this.Value, "]")
}
// Operations perform math, logic, or bit manipulation on values. They accept
// values of the same type as the type they are being assigned to, except in
// special cases. Since they contain no inherent type information, they may not
// be assigned to interfaces. An operation is never a valid location expression.
type Operation struct {
Pos lexer.Position
Operator string `parser:" '[' @('+' | '++' | '-' | '--' | '*' | '/' |
'%' | '!!' | '||' | '&&' | '^^' | '!' |
'|' | '&' | '^' | '<<' | '>>' | '<' |
'>' | '<=' | '>=' | '=') "`
Arguments []Expression `parser:" @@+ ']' "`
}
func (*Operation) expression(){}
func (this *Operation) String () string {
out := "[" + this.Operator
for _, argument := range this.Arguments {
out += fmt.Sprint(" ", argument)
}
return out + "]"
}
// Block is an ordered collection of expressions that are evaluated
// sequentially. It has its own scope. The last expression in the block
// specifies the block's value, and any assignment rules of the block are
// equivalent to those of its last expression. A block is never a valid location
// expression.
type Block struct {
Pos lexer.Position
Steps []Expression `parser:" '{' @@* '}' "`
}
func (*Block) expression(){}
func (this *Block) String () string {
out := "{"
for _, step := range this.Steps {
out += fmt.Sprint(" ", step)
}
return out + "}"
}
// Member access allows referring to a specific member of a value with a struct
// type. It accepts any struct type that contains the specified member name, and
// may be assigned to any type that matches the type of the selected member.
// Since it contains inherent type information, it may be directly assigned to
// an interface. A member access is a valid location expression only when the
// struct being accessed is.
type MemberAccess struct {
Pos lexer.Position
Source Expression `parser:" @@ "`
Member string `parser:" '.' @@ "`
}
func (*MemberAccess) expression(){}
func (this *MemberAccess) String () string {
return fmt.Sprint(this.Source, ".", this.Member)
}
// Method access allows referring to a specific method of a type, or a behavior
// of an interface. It can only be assigned to the first argument of a call. A
// method access is never a valid location expression.
type MethodAccess struct {
Pos lexer.Position
Source Expression `parser:" @@ "`
Member string `parser:" '::' @@ "`
}
func (*MethodAccess) expression(){}
func (this *MethodAccess) String () string {
return fmt.Sprint(this.Source, "::", this.Member)
}
// If/else is a control flow branching expression that executes one of two
// expressions depending on a boolean value. If the value of the if/else is
// unused, the else expression need not be specified. It may be assigned to any
// type that satisfies the assignment rules of both the true and false
// expressions. An If/else is never a valid location expression.
type IfElse struct {
Pos lexer.Position
Condition Expression `parser:" 'if' @@ "`
True Expression `parser:" 'then' @@ "`
False Expression `parser:" ('else' @@)? "`
}
func (*IfElse) expression(){}
func (this *IfElse) String () string {
out := fmt.Sprint("if", this.Condition, "then", this.True)
if this.False != nil {
out += fmt.Sprint(" ", this.False)
}
return out
}
// Loop is a control flow expression that repeats an expression until a break
// statement is called from within it. The break statement must be given a value
// if the value of the loop is used. Otherwise, it need not even have a break
// statement. The result of the loop may be assigned to any type that satisfies
// the assignment rules of all of its break statements. Loops may be nested, and
// break statements only apply to the closest containing loop. The value of the
// loop's expression is never used. A loop is never a valid location expression.
type Loop struct {
Pos lexer.Position
Body Expression `parser:" 'loop' @@ "`
}
func (*Loop) expression(){}
func (this *Loop) String () string {
return fmt.Sprint("loop", this.Body)
}
// Break allows breaking out of loops. It has no value and may not be assigned
// to anything. It is never a valid location expression.
type Break struct {
Pos lexer.Position
Value Expression `parser:" '[' 'break' @@? " ']'`
}
func (*Break) expression(){}
func (this *Break) String () string {
return fmt.Sprint("break", this.Value)
}
// Return allows terminating functions before they have reached their end. It
// accepts values that may be assigned to the function's return type. If a
// function does not return anything, the return statement does not accept a
// value. In all cases, return statements have no value and may not be assigned
// to anything. A return statement is never a valid location expression.
type Return struct {
Pos lexer.Position
Value Expression `parser:" '[' 'return' @@? " ']'`
}
func (*Return) expression(){}
func (this *Return) String () string {
return fmt.Sprint("return", this.Value)
}
// Assignment allows assigning the result of one expression to one or more
// location expressions. The assignment statement itself has no value and may
// 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:" '=' @@ "`
}
func (*Assignment) expression(){}
func (this *Assignment) String () string {
return fmt.Sprint(this.Location, "=", this.Value)
}

71
parser/literal.go Normal file
View File

@@ -0,0 +1,71 @@
package parser
import "fmt"
import "github.com/alecthomas/participle/v2/lexer"
// LiteralInt specifies an integer value. It can be assigned to any type that is
// derived from an integer, or a float. It cannot be directly assigned to an
// interface because it contains no inherent type information. A value cast may
// be used for this purpose.
type LiteralInt struct {
Pos lexer.Position
Value int `parser:" @Int "`
}
func (*LiteralInt) expression(){}
func (this *LiteralInt) String () string {
return fmt.Sprint(this.Value)
}
// LiteralFloat specifies a floating point value. It can be assigned to any type
// that is derived from a float. It cannot be directly assigned to an interface
// because it contains no inherent type information. A value cast may be used
// for this purpose.
type LiteralFloat struct {
Pos lexer.Position
Value float64 `parser:" @Float "`
}
func (*LiteralFloat) expression(){}
func (this *LiteralFloat) String () string {
return fmt.Sprint(this.Value)
}
// Array is a composite array literal. It can contain any number of values. It
// can be assigned to any array type that:
// 1. has an identical length, and
// 2. who's element type can be assigned to by all the element values in the
// literal.
// It cannot be directly assigned to an interface because it contains no
// inherent type information. A value cast may be used for this purpose.
type LiteralArray struct {
Pos lexer.Position
Elements []Expression `parser:" '(' '*' @@* ')' "`
}
func (*LiteralArray) expression(){}
func (this *LiteralArray) String () string {
out := "(*"
for _, element := range this.Elements {
out += fmt.Sprint(" ", element)
}
return out + ")"
}
// Struct is a composite structure literal. It can contain any number of
// name:value pairs. It can be assigned to any struct type that:
// 1. has at least the members specified in the literal
// 2. who's member types can be assigned to by the corresponding member
// values in the literal.
// It cannot be directly assigned to an interface because it contains no
// inherent type information. A value cast may be used for this purpose.
type LiteralStruct struct {
Pos lexer.Position
Members []Member `parser:" '(' @@* ')' "`
}
func (*LiteralStruct) expression(){}
func (this *LiteralStruct) String () string {
out := "("
for index, member := range this.Members {
if index > 1 { out += " " }
out += fmt.Sprint(member)
}
return out + ")"
}

37
parser/misc.go Normal file
View File

@@ -0,0 +1,37 @@
package parser
import "fmt"
import "github.com/alecthomas/participle/v2/lexer"
// 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 {
Pos lexer.Position
Name string `parser:" '[' @Ident "`
Arguments []Declaration `parser:" @@* ']' "`
Return Type `parser:" ( ':' @@ )? "`
}
func (*Signature) ty(){}
func (this *Signature) String () string {
out := "[" + this.Name
for _, argument := range this.Arguments {
out += fmt.Sprint(" ", argument)
}
out += "]"
if this.Return != nil {
out += fmt.Sprint(": ", this.Return)
}
return out
}
// 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:" ':' @@ "`
}
func (this *Member) String () string {
return fmt.Sprint(this.Name, ":", this.Value)
}

76
parser/toplevel.go Normal file
View File

@@ -0,0 +1,76 @@
package parser
import "fmt"
import "github.com/alecthomas/participle/v2/lexer"
// TopLevel is any construct that is placed at the root of a file.
type TopLevel interface {
topLevel ()
}
// Typedef binds a type to a global identifier.
type Typedef struct {
// Syntax
Pos lexer.Position
Public bool `parser: " @'+'? "`
Name string `parser: " @Ident "`
Type Type `parser: " ':' @@ "`
// Semantics
Methods map[string] Method
}
func (*Typedef) topLevel(){}
func (this *Typedef) String () string {
out := fmt.Sprint(this.Name, " : ", this.Type)
if this.Methods != nil {
for _, method := range this.Methods {
out += fmt.Sprint("\n", method)
}
}
return out
}
// Function binds a global identifier and argument list to an expression which
// is evaluated each time the function is called. If no expression is specified,
// the function is marked as external. Functions have an argument list, where
// each argument is passed as a separate variable. They return one value. All of
// these are typed.
type Function struct {
// Syntax
Pos lexer.Position
Public bool `parser: " @'+'? "`
Signature `parser: " @@ "`
Body Expression `parser: " ( '=' @@ )? "`
}
func (*Function) topLevel(){}
func (this *Function) String () string {
if this.Body == nil {
return fmt.Sprint(this.Signature)
} else {
return fmt.Sprint(this.Signature, " = ", this.Body)
}
}
// Method is like a function, except localized to a defined type. Methods are
// called on an instance of that type, and receive a pointer to that instance
// via the "this" variable. Method names are not globally unique, bur are unique
// within the type they are defined on.
type Method struct {
// Syntax
Pos lexer.Position
Public bool `parser: " @'+'? "`
TypeName string `parser: " @Ident "`
Signature `parser: " '.' @@ "`
Body Expression `parser: " ( '=' @@ )? "`
}
func (*Method) topLevel(){}
func (this *Method) String () string {
if this.Body == nil {
return fmt.Sprint(this.TypeName, ".", this.Signature)
} else {
return fmt.Sprint (
this.TypeName, ".",
this.Signature, " = ",
this.Body)
}
}

51
parser/tree.go Normal file
View File

@@ -0,0 +1,51 @@
package parser
import "io"
import "github.com/alecthomas/participle/v2"
var parser = participle.MustBuild[Tree] (
participle.Union[TopLevel] (
&Function { },
&Typedef { },
&Method { }),
participle.Union[Type] (
&TypePointer { },
&TypeInterface { },
&TypeStruct { },
&TypeArray { }),
participle.Union[Expression] (
&LiteralInt { },
&LiteralFloat { },
&LiteralArray { },
&LiteralStruct { },
&Variable { },
&Declaration { },
&Call { },
&Subscript { },
&Dereference { },
&Reference { },
&ValueCast { },
&BitCast { },
&Operation { },
&Block { },
&MemberAccess { },
&MethodAccess { },
&IfElse { },
&Loop { },
&Break { },
&Return { },
&Assignment { }),
participle.UseLookahead(participle.MaxLookahead))
// Tree represents a parsed abstract syntax tree. It has no constructor and its
// zero value can be used safely.
type Tree struct {
Declarations []TopLevel `parser:" @@* "`
}
// 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)
this.Declarations = append(this.Declarations, parsed.Declarations...)
return err
}

74
parser/type.go Normal file
View File

@@ -0,0 +1,74 @@
package parser
import "fmt"
import "github.com/alecthomas/participle/v2/lexer"
// Type is any type notation.
type Type interface {
ty ()
}
// TypeNamed refers to a user-defined or built in named type.
type TypeNamed struct {
Pos lexer.Position
Name string `parser:" @Ident "`
}
func (*TypeNamed) ty(){}
func (this *TypeNamed) String () string { return this.Name }
// TypePointer is a pointer to another type.
type TypePointer struct {
Pos lexer.Position
Referenced Type `parser:" '*' @@ "`
}
func (*TypePointer) ty(){}
func (this *TypePointer) String () string {
return fmt.Sprint("*", this.Referenced)
}
// TypeArray is a group of values of a given type stored next to eachother. The
// 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 'x' "`
Element Type `parser:" @@ "`
}
func (*TypeArray) ty(){}
func (this *TypeArray) String () string {
return fmt.Sprint(this.Length, "x", this.Element)
}
// Struct is a composite type that stores keyed values. The positions of the
// values within the struct are decided at compile time, based on the order they
// are specified in. Structs are passed by value unless a pointer is used.
type TypeStruct struct {
Pos lexer.Position
Members []*Declaration `parser:" '(' @@+ ')' `
}
func (*TypeStruct) ty(){}
func (this *TypeStruct) String () string {
out := "("
for _, member := range this.Members {
out += fmt.Sprint(member)
}
return out + ")"
}
// Interface is a polymorphic pointer that allows any value of any type through,
// except it must have at least the methods defined within the interface.
// Interfaces are always passed by reference. When assigning a value to an
// interface, it will be referenced automatically. When assigning a pointer to
// an interface, the pointer's reference will be used instead.
type TypeInterface struct {
Pos lexer.Position
Behaviors []*Signature `parser:" '(' @@+ ')' "`
}
func (*TypeInterface) ty(){}
func (this *TypeInterface) String () string {
out := "("
for _, behavior := range this.Behaviors {
out += fmt.Sprint(behavior)
}
return out + ")"
}