From 5c286cf955dfaf6c92dc64139313d3f2d01efab8 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Tue, 4 Oct 2022 16:19:26 -0400 Subject: [PATCH] Added some useful type checking thigns to literals --- analyzer/argument.go | 1 + analyzer/literals.go | 94 ++++++++++++++++++++++++++++++++++++++++++ analyzer/primitives.go | 2 + analyzer/type.go | 87 ++++++++++++++++++++++++++++++++++---- 4 files changed, 177 insertions(+), 7 deletions(-) diff --git a/analyzer/argument.go b/analyzer/argument.go index 15553f6..86e9311 100644 --- a/analyzer/argument.go +++ b/analyzer/argument.go @@ -19,6 +19,7 @@ type Argument interface { // RuneLiteral ToString (indent int) (output string) + canBePassedAs (what Type) (allowed bool) } // analyzeArgument analyzes an argument diff --git a/analyzer/literals.go b/analyzer/literals.go index c9a13aa..0c56755 100644 --- a/analyzer/literals.go +++ b/analyzer/literals.go @@ -14,24 +14,118 @@ func (literal IntLiteral) ToString (indent int) (output string) { return } +// canBePassedAs returns true if this literal can be implicitly cast to the +// specified type, and false if it can't. +func (literal IntLiteral) canBePassedAs (what Type) (allowed bool) { + // must be a singlular value + if what.length != 1 { return } + + // can be passed to types that are signed numbers at a primitive level. + primitive := what.underlyingPrimitive() + switch primitive { + case + &PrimitiveF64, + &PrimitiveF32, + &PrimitiveI64, + &PrimitiveI32, + &PrimitiveI16, + &PrimitiveI8, + &PrimitiveInt: + + allowed = true + } + return +} + // ToString outputs the data in the argument as a string. func (literal UIntLiteral) ToString (indent int) (output string) { output += doIndent(indent, fmt.Sprint("arg uint ", literal, "\n")) return } +// canBePassedAs returns true if this literal can be implicitly cast to the +// specified type, and false if it can't. +func (literal UIntLiteral) canBePassedAs (what Type) (allowed bool) { + // must be a singlular value + if what.length != 1 { return } + + // can be passed to types that are numbers at a primitive level. + primitive := what.underlyingPrimitive() + switch primitive { + case + &PrimitiveF64, + &PrimitiveF32, + &PrimitiveI64, + &PrimitiveI32, + &PrimitiveI16, + &PrimitiveI8, + &PrimitiveInt, + &PrimitiveU64, + &PrimitiveU32, + &PrimitiveU16, + &PrimitiveU8, + &PrimitiveUInt: + + allowed = true + } + return +} + // ToString outputs the data in the argument as a string. func (literal FloatLiteral) ToString (indent int) (output string) { output += doIndent(indent, fmt.Sprint("arg float ", literal, "\n")) return } +// canBePassedAs returns true if this literal can be implicitly cast to the +// specified type, and false if it can't. +func (literal FloatLiteral) canBePassedAs (what Type) (allowed bool) { + // must be a singlular value + if what.length != 1 { return } + + // can be passed to types that are floats at a primitive level. + primitive := what.underlyingPrimitive() + switch primitive { + case + &PrimitiveF64, + &PrimitiveF32: + + allowed = true + } + return +} + // ToString outputs the data in the argument as a string. func (literal StringLiteral) ToString (indent int) (output string) { output += doIndent(indent, fmt.Sprint("arg string \"", literal, "\"\n")) return } +// canBePassedAs returns true if this literal can be implicitly cast to the +// specified type, and false if it can't. +func (literal StringLiteral) canBePassedAs (what Type) (allowed bool) { + // can be passed to types that are numbers at a primitive level. + primitive := what.underlyingPrimitive() + switch primitive { + case + &PrimitiveF64, + &PrimitiveF32, + &PrimitiveI64, + &PrimitiveI32, + &PrimitiveI16, + &PrimitiveI8, + &PrimitiveInt, + &PrimitiveU64, + &PrimitiveU32, + &PrimitiveU16, + &PrimitiveU8, + &PrimitiveUInt: + + allowed = true + } + return +} + // ToString outputs the data in the argument as a string. func (literal RuneLiteral) ToString (indent int) (output string) { output += doIndent(indent, fmt.Sprint("arg rune '", literal, "'\n")) diff --git a/analyzer/primitives.go b/analyzer/primitives.go index 2b10afb..ee42038 100644 --- a/analyzer/primitives.go +++ b/analyzer/primitives.go @@ -2,6 +2,8 @@ package analyzer // This is a global, cannonical list of primitive and built-in types. +var PrimitiveF32 = createPrimitive("Int", Type {}) +var PrimitiveF64 = createPrimitive("Int", Type {}) var PrimitiveInt = createPrimitive("Int", Type {}) var PrimitiveUInt = createPrimitive("UInt", Type {}) var PrimitiveI8 = createPrimitive("I8", Type {}) diff --git a/analyzer/type.go b/analyzer/type.go index 1ad90b6..be73c4e 100644 --- a/analyzer/type.go +++ b/analyzer/type.go @@ -16,21 +16,20 @@ const ( // TypeKindVariableArray means it's an array of variable length. TypeKindVariableArray - - // TypeKindObject means it's a structured type with members. - TypeKindObject ) // Type represents a description of a type. It must eventually point to a // TypeSection. type Type struct { // one of these must be nil. - actual Section + actual *TypeSection points *Type mutable bool kind TypeKind + primitiveCache *TypeSection + // if this is greater than 1, it means that this is a fixed-length array // of whatever the type is. even if the type is a variable length array. // because literally why not. @@ -52,8 +51,6 @@ func (what Type) ToString (indent int) (output string) { output += " pointer" case TypeKindVariableArray: output += " variableArray" - case TypeKindObject: - output += " object" } if what.points != nil { @@ -70,6 +67,78 @@ func (what Type) ToString (indent int) (output string) { return } +// underlyingPrimitive returns the primitive that this type eventually inherits +// from. +func (what Type) underlyingPrimitive () (underlying *TypeSection) { + // if we have already done this operation, return the cahced result. + if what.primitiveCache != nil { + underlying = what.primitiveCache + return + } + + if what.kind != TypeKindBasic { + // if we point to something, return nil because there is no void + // pointer bullshit in this language + return + } + + actual := what.actual + switch actual { + case + &PrimitiveF32, + &PrimitiveF64, + &PrimitiveFunc, + &PrimitiveFace, + &PrimitiveObj, + &PrimitiveU64, + &PrimitiveU32, + &PrimitiveU16, + &PrimitiveU8, + &PrimitiveI64, + &PrimitiveI32, + &PrimitiveI16, + &PrimitiveI8, + &PrimitiveUInt, + &PrimitiveInt: + + underlying = actual + return + + case nil: + panic("invalid state: Type.actual is nil") + + default: + // if none of the primitives matched, recurse. + underlying = actual.what.underlyingPrimitive() + return + } +} + +// reduce ascends up the inheritence chain and gets the first type it finds that +// isn't basic. If the type has a clear path of inheritence to a simple +// primitive, there will be no non-basic types in the chain and this method will +// return false for reducible. If the type this method is called on is not +// basic, it itself is returned. +func (what Type) reduce () (reduced Type, reducible bool) { + reducible = true + + // returns itself if it is not basic (cannot be reduced further) + if what.kind != TypeKindBasic { + reduced = what + return + } + + // if we can't recurse, return false for reducible + if what.actual == nil { + reducible = false + return + } + + // otherwise, recurse + reduced, reducible = what.actual.what.reduce() + return +} + // analyzeType analyzes a type specifier. func (analyzer AnalysisOperation) analyzeType ( inputType parser.Type, @@ -93,9 +162,13 @@ func (analyzer AnalysisOperation) analyzeType ( outputType.points = &points } else { var bitten parser.Identifier - outputType.actual, + var actual Section + actual, bitten, err = analyzer.fetchSectionFromIdentifier(inputType.Name()) + + outputType.actual = actual.(*TypeSection) + // TODO: produce an error if this doesnt work if bitten.Length() > 0 { err = bitten.NewError(