Merge branch 'main' into generator-multi-unit-tests

This commit is contained in:
Sasha Koshka 2024-02-28 19:42:11 -05:00
commit 4cfa431919
6 changed files with 156 additions and 46 deletions

View File

@ -89,3 +89,34 @@ IntDerived: Int
}
`)
}
func TestBitCastPointer (test *testing.T) {
testString (test,
`
Struct: (. x:Int y:Int)
Array: 4:Int
[main] = {
ptr:*Byte
stc:Struct
arr:Array
str:String
idx:Index
; struct
ptr = [~~*Byte [~~Struct ptr]]
stc = [~~Struct [~~*Byte stc]]
; array
ptr = [~~*Byte [~~Array ptr]]
arr = [~~Array [~~*Byte arr]]
; slice
ptr = [~~*Byte [~~String ptr]]
str = [~~String [~~*Byte str]]
; int
ptr = [~~*Byte [~~Index ptr]]
idx = [~~Index [~~*Byte idx]]
; arithmetic
ptr = [~~*Byte [+ 1 [~~Index ptr]]]
}
`)
}

View File

@ -417,6 +417,14 @@ func (this *Tree) analyzeOperation (
err := this.typeRestricted(operation.Position, into)
if err != nil { return nil, err }
intoUntrustworthy := into == nil || mode == force || mode == coerce
undefined := func (ty entity.Type) (entity.Expression, error) {
return nil, errors.Errorf (
operation.Position, "operator %v undefined for %v",
operation.Operator, entity.FormatType(ty))
}
wrongInto := func () (entity.Expression, error) {
return nil, errors.Errorf (
operation.Position, "expected %v",
@ -428,6 +436,28 @@ func (this *Tree) analyzeOperation (
operation.Position, "wrong argument count for %v",
operation.Operator)
}
determineArgumentType := func () (entity.Type, int, error) {
// find the first argument that has explicit type information.
boss := -1
var argumentType entity.Type
for index, argument := range operation.Arguments {
if argument.HasExplicitType() {
argument, err := this.analyzeExpression(nil, strict, argument)
if err != nil { return nil, -1, err }
boss = index
operation.Arguments[index] = argument
argumentType = argument.Type()
break
}
}
if argumentType == nil {
return nil, -1, errors.Errorf (
operation.Position,
"operation arguments have ambiguous type")
}
return argumentType, boss, err
}
nSameType := func (n int, constraint func (entity.Type) bool) (entity.Expression, error) {
if n > 0 {
@ -436,17 +466,29 @@ func (this *Tree) analyzeOperation (
if len(operation.Arguments) < 1 { return wrongArgCount() }
}
n = len(operation.Arguments)
if !constraint(into) { return wrongInto() }
left, err := this.analyzeExpression(into, mode, operation.Arguments[0])
if err != nil { return nil, err }
operation.Arguments[0] = left
operation.Ty = left.Type()
boss := -1
var argumentType entity.Type
if intoUntrustworthy {
// determine the type of all arguments (and the return
// type) using determineArgumentType
var err error
argumentType, boss, err = determineArgumentType()
if err != nil { return nil, err }
if !constraint(argumentType) { return undefined(argumentType) }
} else {
// into is trustworthy, so we don't need to determine
// anything
argumentType = into
}
if !constraint(argumentType) { return undefined(argumentType) }
operation.Ty = argumentType
for index := 1; index < n; index ++ {
for index, argument := range operation.Arguments {
if index == boss { continue }
argument, err := this.analyzeExpression (
left.Type(), strict,
operation.Arguments[index])
operation.Ty, strict,
argument)
if err != nil { return nil, err }
operation.Arguments[index] = argument
}
@ -455,41 +497,21 @@ func (this *Tree) analyzeOperation (
}
comparison := func (argConstraint func (entity.Type) bool) (entity.Expression, error) {
if len(operation.Arguments) < 2 { return wrongArgCount() }
if !isBoolean(into) { return wrongInto() }
operation.Ty = builtinType("Bool")
if len(operation.Arguments) < 2 { return wrongArgCount() }
if !isBoolean(into) && !intoUntrustworthy { return wrongInto() }
operation.Ty = builtinType("Bool")
// find the first argument that has explicit type information.
// TODO: possibly make a method of expressions to check this
// without analyzing anything
boss := -1
var argumentType entity.Type
for index, argument := range operation.Arguments {
argument, err := this.analyzeExpression(nil, strict, argument)
if err == nil {
boss = index
operation.Arguments[index] = argument
argumentType = argument.Type()
break
}
}
if argumentType == nil {
return nil, errors.Errorf (
operation.Position,
"operation arguments have ambiguous type")
}
// determine argument type
argumentType, boss, err := determineArgumentType()
if err != nil { return nil, err }
if !argConstraint(argumentType) { return undefined(argumentType) }
// analyze all remaining arguments
for index, argument := range operation.Arguments {
if index == boss { continue }
argument, err := this.analyzeExpression (
argumentType, strict, argument)
if err != nil {
boss = index
operation.Arguments[index] = argument
break
}
if err != nil { return nil, err }
operation.Arguments[index] = argument
}

View File

@ -31,12 +31,12 @@ order) until it is found.
There are standard paths that the compiler will search for units in. These are,
in order of preference:
- `$HOME/.local/include/fsplc`
- `$HOME/.local/src/fsplc`
- `/usr/local/include/fsplc`
- `/usr/local/src/fsplc`
- `/usr/include/fsplc`
- `/usr/src/fsplc`
- `$HOME/.local/src/fspl`
- `$HOME/.local/include/fspl`
- `/usr/local/src/fspl`
- `/usr/local/include/fspl`
- `/usr/src/fspl`
- `/usr/include/fspl`
Files in `include` directories should *not* include program code, and should
only define types and external functions and methods, similar to header files in
@ -110,7 +110,7 @@ designed to make use of the same parsing and lexing infrastructure used to parse
and tokenize source files. A sample metadata file might look like:
```
'5a8353f8cad84604be6029a2575996bc'
'5a8353f8-cad8-4604-be60-29a2575996bc'
+ 'io'
+ '../io' customIo
```
@ -143,8 +143,8 @@ module metadata file. If a nickname is listed, then that is used as the unit
name. Otherwise, the unit name is the basename of the address, which is
normalized and formatted into a valid identifier by the the following rules:
- If the name contains at least one dot, the last dot and everything after it is
removed
- If the name contains at least one dot, the last dot and everything after it
are removed
- All non-alphabetical and non-numeric characters are removed, and any
alphabetical characters that were directly after them are converted to
uppercase
@ -168,7 +168,7 @@ refuse to process the module and it is up to the user to either nickname the
unit, or change the unit's basename to something workable.
The compiler will also refuse to process the module if one or more units end up
with the same unit name. However, a function or a variable may have the
with the same unit name. However, a function or a variable may have the same
name as a unit because units are only ever used within the context of their own
special syntax (`::`).

View File

@ -7,6 +7,7 @@ import "git.tebibyte.media/fspl/fspl/errors"
// Expression is any construct that can be evaluated.
type Expression interface {
Type () Type
HasExplicitType () bool
expression ()
}
@ -24,6 +25,7 @@ type Variable struct {
}
func (*Variable) expression(){}
func (this *Variable) Type () Type { return this.Declaration.Type() }
func (this *Variable) HasExplicitType () bool { return true }
func (this *Variable) String () string {
return this.Name
}
@ -40,6 +42,7 @@ type Declaration struct {
}
func (*Declaration) expression(){}
func (this *Declaration) Type () Type { return this.Ty }
func (this *Declaration) HasExplicitType () bool { return true }
func (this *Declaration) String () string {
return fmt.Sprint(this.Name, ":", this.Ty)
}
@ -63,6 +66,7 @@ type Call struct {
}
func (*Call) expression(){}
func (this *Call) Type () Type { return this.Function.Signature.Return }
func (this *Call) HasExplicitType () bool { return true }
func (this *Call) String () string {
out := ""
if this.UnitNickname != "" {
@ -101,6 +105,7 @@ func (this *MethodCall) Type () Type {
return this.Behavior.Return
}
}
func (this *MethodCall) HasExplicitType () bool { return true }
func (this *MethodCall) String () string {
out := fmt.Sprint(this.Source, ".[", this.Name)
for _, argument := range this.Arguments {
@ -125,6 +130,7 @@ type Subscript struct {
}
func (*Subscript) expression(){}
func (this *Subscript) Type () Type { return this.Ty }
func (this *Subscript) HasExplicitType () bool { return true }
func (this *Subscript) String () string {
return fmt.Sprint("[.", this.Slice, " ", this.Offset, "]")
}
@ -142,6 +148,7 @@ type Slice struct {
}
func (*Slice) expression(){}
func (this *Slice) Type () Type { return this.Slice.Type() }
func (this *Slice) HasExplicitType () bool { return true }
func (this *Slice) String () string {
out := fmt.Sprint("[\\", this.Slice, " ")
if this.Start != nil {
@ -166,6 +173,7 @@ type Length struct {
}
func (*Length) expression(){}
func (this *Length) Type () Type { return this.Ty }
func (this *Length) HasExplicitType () bool { return true }
func (this *Length) String () string {
return fmt.Sprint("[#", this.Slice, "]")
}
@ -185,6 +193,7 @@ type Dereference struct {
}
func (*Dereference) expression(){}
func (this *Dereference) Type () Type { return this.Ty }
func (this *Dereference) HasExplicitType () bool { return true }
func (this *Dereference) String () string {
return fmt.Sprint("[.", this.Pointer, "]")
}
@ -206,6 +215,7 @@ type Reference struct {
}
func (*Reference) expression(){}
func (this *Reference) Type () Type { return this.Ty }
func (this *Reference) HasExplicitType () bool { return true }
func (this *Reference) String () string {
return fmt.Sprint("[@", this.Value, "]")
}
@ -220,6 +230,7 @@ type ValueCast struct {
}
func (*ValueCast) expression(){}
func (this *ValueCast) Type () Type { return this.Ty }
func (this *ValueCast) HasExplicitType () bool { return true }
func (this *ValueCast) String () string {
return fmt.Sprint("[~ ", this.Ty, this.Value, "]")
}
@ -235,6 +246,7 @@ type BitCast struct {
}
func (*BitCast) expression(){}
func (this *BitCast) Type () Type { return this.Ty }
func (this *BitCast) HasExplicitType () bool { return true }
func (this *BitCast) String () string {
return fmt.Sprint("[~~ ", this.Ty, this.Value, "]")
}
@ -254,6 +266,9 @@ type Operation struct {
}
func (*Operation) expression(){}
func (this *Operation) Type () Type { return this.Ty }
func (this *Operation) HasExplicitType () bool {
return this.Operator.ResultsInBoolean()
}
func (this *Operation) String () string {
out := fmt.Sprint("[", this.Operator)
for _, argument := range this.Arguments {
@ -278,6 +293,10 @@ type Block struct {
}
func (*Block) expression(){}
func (this *Block) Type () Type { return this.Ty }
func (this *Block) HasExplicitType () bool {
if len(this.Steps) == 0 { return false }
return this.Steps[len(this.Steps) - 1].HasExplicitType()
}
func (this *Block) String () string {
out := "{"
for index, step := range this.Steps {
@ -304,6 +323,7 @@ type MemberAccess struct {
}
func (*MemberAccess) expression(){}
func (this *MemberAccess) Type () Type { return this.Ty }
func (this *MemberAccess) HasExplicitType () bool { return true }
func (this *MemberAccess) String () string {
return fmt.Sprint(this.Source, ".", this.Member)
}
@ -325,6 +345,9 @@ type IfElse struct {
}
func (*IfElse) expression(){}
func (this *IfElse) Type () Type { return this.Ty }
func (this *IfElse) HasExplicitType () bool {
return this.True.HasExplicitType()
}
func (this *IfElse) String () string {
out := fmt.Sprint("if ", this.Condition, " then ", this.True)
if this.False != nil {
@ -350,6 +373,11 @@ type Loop struct {
}
func (*Loop) expression(){}
func (this *Loop) Type () Type { return this.Ty }
func (this *Loop) HasExplicitType () bool {
// this is as accurate as possible without doing a full analysis of the
// loop
return false
}
func (this *Loop) String () string {
return fmt.Sprint("loop ", this.Body)
}
@ -366,6 +394,7 @@ type Break struct {
}
func (*Break) expression(){}
func (this *Break) Type () Type { return nil }
func (this *Break) HasExplicitType () bool { return false }
func (this *Break) String () string {
if this.Value == nil {
return "[break]"
@ -388,6 +417,7 @@ type Return struct {
}
func (*Return) expression(){}
func (this *Return) Type () Type { return nil }
func (this *Return) HasExplicitType () bool { return false }
func (this *Return) String () string {
if this.Value == nil {
return "[return]"
@ -407,6 +437,7 @@ type Assignment struct {
}
func (*Assignment) expression(){}
func (this *Assignment) Type () Type { return nil }
func (this *Assignment) HasExplicitType () bool { return false }
func (this *Assignment) String () string {
return fmt.Sprint(this.Location, "=", this.Value)
}

View File

@ -18,6 +18,7 @@ type LiteralInt struct {
}
func (*LiteralInt) expression(){}
func (this *LiteralInt) Type () Type { return this.Ty }
func (this *LiteralInt) HasExplicitType () bool { return false }
func (this *LiteralInt) String () string {
return fmt.Sprint(this.Value)
}
@ -36,6 +37,7 @@ type LiteralFloat struct {
}
func (*LiteralFloat) expression(){}
func (this *LiteralFloat) Type () Type { return this.Ty }
func (this *LiteralFloat) HasExplicitType () bool { return false }
func (this *LiteralFloat) String () string {
return fmt.Sprint(this.Value)
}
@ -67,6 +69,7 @@ type LiteralString struct {
}
func (*LiteralString) expression(){}
func (this *LiteralString) Type () Type { return this.Ty }
func (this *LiteralString) HasExplicitType () bool { return false }
func (this *LiteralString) String () string {
return Quote(this.ValueUTF8)
}
@ -88,6 +91,7 @@ type LiteralArray struct {
}
func (*LiteralArray) expression(){}
func (this *LiteralArray) Type () Type { return this.Ty }
func (this *LiteralArray) HasExplicitType () bool { return false }
func (this *LiteralArray) String () string {
out := "(*"
for _, element := range this.Elements {
@ -115,6 +119,7 @@ type LiteralStruct struct {
}
func (*LiteralStruct) expression(){}
func (this *LiteralStruct) Type () Type { return this.Ty }
func (this *LiteralStruct) HasExplicitType () bool { return false }
func (this *LiteralStruct) String () string {
out := "(."
for _, member := range this.Members {
@ -137,6 +142,7 @@ type LiteralBoolean struct {
}
func (*LiteralBoolean) expression(){}
func (this *LiteralBoolean) Type () Type { return this.Ty }
func (this *LiteralBoolean) HasExplicitType () bool { return false }
func (this *LiteralBoolean) String () string {
if this.Value {
return "true"
@ -154,4 +160,5 @@ type LiteralNil struct {
}
func (*LiteralNil) expression(){}
func (this *LiteralNil) Type () Type { return this.Ty }
func (this *LiteralNil) HasExplicitType () bool { return false }
func (this *LiteralNil) String () string { return "nil" }

View File

@ -95,6 +95,25 @@ type Operator int; const (
OperatorEqual
)
// ResultsInBoolean determines whether or not this operation will always result
// in a boolean value.
func (operator Operator) ResultsInBoolean () bool {
switch operator {
case OperatorLogicalNot,
OperatorLogicalOr,
OperatorLogicalAnd,
OperatorLogicalXor,
OperatorLess,
OperatorGreater,
OperatorLessEqual,
OperatorGreaterEqual,
OperatorEqual:
return true
default:
return false
}
}
var operatorToString = []string {
// Math
"+",