From 0200f8d4862359e64e3d5d2d4b7914989d07caaa Mon Sep 17 00:00:00 2001 From: monstermichl Date: Sat, 6 Sep 2025 18:51:19 +0200 Subject: [PATCH 01/22] Add struct declaration parsing (https://github.com/monstermichl/TypeShell/issues/51) --- lexer/lexer.go | 2 + parser/parser.go | 120 +++++++++++++++++++++++++++++++++++++++++++---- parser/struct.go | 26 ++++++++++ parser/types.go | 2 + 4 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 parser/struct.go diff --git a/lexer/lexer.go b/lexer/lexer.go index 8812193..3094630 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -56,6 +56,7 @@ const ( // Keywords. IMPORT TYPE_DECLARATION + STRUCT CONST_DEFINITION VAR_DEFINITION FUNCTION_DEFINITION @@ -174,6 +175,7 @@ var keywords = map[string]TokenType{ // Common keywords. "import": IMPORT, "type": TYPE_DECLARATION, + "struct": STRUCT, "const": CONST_DEFINITION, "var": VAR_DEFINITION, "func": FUNCTION_DEFINITION, diff --git a/parser/parser.go b/parser/parser.go index 03eab0e..b0646d6 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -51,7 +51,8 @@ type foundTypeDefinition struct { type context struct { imports map[string]string // Maps import aliases to file hashes. - types map[string]typeDefinition // Stores the defined types. + types map[string]typeDefinition // Stores the declared types. + structs map[string]StructDeclaration // Stores the declared structs. namedValues map[string][]NamedValue // Stores the variable/constant name to variable/constant relation. functions map[string]FunctionDefinition // Stores the function name to function relation. scopeStack []scope // Stores the current scopes. @@ -63,6 +64,7 @@ func newContext() context { c := context{ imports: map[string]string{}, types: map[string]typeDefinition{}, + structs: map[string]StructDeclaration{}, namedValues: map[string][]NamedValue{}, functions: map[string]FunctionDefinition{}, layer: -1, // Init at -1 since program increases it right away. @@ -145,7 +147,7 @@ func (c context) addType(typeName string, valueType ValueType, isAlias bool, isE _, exists := c.findType(typeName, false) if exists { - return fmt.Errorf("%s has already been defined", typeName) + return fmt.Errorf("type %s has already been defined", typeName) } if valueType.IsSlice() { // TODO: Add support. @@ -159,6 +161,16 @@ func (c context) addType(typeName string, valueType ValueType, isAlias bool, isE return nil } +func (c context) addStruct(structName string, structDeclaration StructDeclaration) error { + _, exists := c.findStruct(structName) + + if exists { + return fmt.Errorf("struct %s has already been defined", structName) + } + c.structs[structName] = structDeclaration + return nil +} + func (c context) addNamedValues(prefix string, global bool, namedValues ...NamedValue) error { for _, namedValue := range namedValues { prefixedName, err := c.buildPrefixedName(namedValue.Name(), prefix, global, false) @@ -214,6 +226,11 @@ func (c context) findType(typeName string, searchUntilElementary bool) (foundTyp return foundDefinition, exists } +func (c context) findStruct(name string) (StructDeclaration, bool) { + structDeclaration, exists := c.structs[name] + return structDeclaration, exists +} + func (c context) findNamedValue(name string, prefix string, global bool) (NamedValue, bool) { prefixedName, err := c.buildPrefixedName(name, prefix, global, true) @@ -246,7 +263,8 @@ func (c context) clone() context { return context{ imports: maps.Clone(c.imports), types: maps.Clone(c.types), - namedValues: maps.Clone(c.namedValues), + structs: maps.Clone(c.structs), + namedValues: maps.Clone(c.namedValues), // TODO: Make sure this is appropriate cloning because each entry contains a slice. functions: maps.Clone(c.functions), scopeStack: slices.Clone(c.scopeStack), layer: c.layer, @@ -361,8 +379,8 @@ func allowedBinaryOperators(t ValueType) []BinaryOperator { case DATA_TYPE_STRING: operators = []BinaryOperator{BINARY_OPERATOR_ADDITION} default: - // For other types no operations are permitted. } + // For other types no operations are permitted. } return operators } @@ -859,7 +877,7 @@ func (p *Parser) evaluateImports(ctx context) ([]Statement, error) { return nil, err } regexp := regexp.MustCompile(`package \w+`) - bodyBytes =regexp.ReplaceAll(bodyBytes, []byte("")) // Remove package statemnt. + bodyBytes = regexp.ReplaceAll(bodyBytes, []byte("")) // Remove package statemnt. err = os.WriteFile(absPath, bodyBytes, 0400) @@ -1209,6 +1227,66 @@ func (p *Parser) evaluateValueType(ctx context) (ValueType, error) { return evaluatedType, nil } +func (p *Parser) evaluateStructDeclaration(ctx context) (Statement, error) { + structToken := p.eat() + + if structToken.Type() != lexer.STRUCT { + return nil, p.expectedKeywordError("struct", structToken) + } + nextToken := p.eat() + + if nextToken.Type() != lexer.OPENING_CURLY_BRACKET { + return nil, p.expectedError(`"{"`, nextToken) + } + nextToken = p.eat() + + if nextToken.Type() != lexer.NEWLINE { + return nil, p.expectedNewlineError(nextToken) + } + fields := []StructField{} + + // Evaluate fields. + for { + nameTokens, err := p.evaluateNames() + + if err != nil { + return nil, err + } + valueTypeToken := p.peek() + valueType, err := p.evaluateValueType(ctx) + + if err != nil { + return nil, err + } + + // Don't allow nested structs for now. + if valueType.DataType() == DATA_TYPE_STRUCT { + return nil, p.atError("nested structs are not allowed", valueTypeToken) + } + + for _, nameToken := range nameTokens { + fields = append(fields, StructField{ + name: nameToken.Value(), + valueType: valueType, + }) + } + nextToken = p.eat() + + if nextToken.Type() != lexer.NEWLINE { + return nil, p.expectedNewlineError(nextToken) + } + nextToken = p.peek() + + if nextToken.Type() == lexer.CLOSING_CURLY_BRACKET { + p.eat() // Eat closing curly bracket and break. + break + } + } + return StructDeclaration{ + fields, + }, nil +} + func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { typeToken := p.eat() @@ -1221,20 +1299,42 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { return nil, p.expectedIdentifierError(nameToken) } isAlias := false + assignToken := p.peek() // If a type is assigned to the new type with an assign-operator, // then it's just an alias. - if p.peek().Type() == lexer.ASSIGN_OPERATOR { + if assignToken.Type() == lexer.ASSIGN_OPERATOR { isAlias = true p.eat() } + name := nameToken.Value() valueTypeToken := p.peek() - valueType, err := p.evaluateValueType(ctx) - if err != nil { - return nil, err + var valueType ValueType + var err error + + if valueTypeToken.Type() == lexer.STRUCT { + if isAlias { + return nil, p.atError("struct alias is not supported", assignToken) + } + structDeclaration, err := p.evaluateStructDeclaration(ctx) + + if err != nil { + return nil, err + } + err = ctx.addStruct(name, structDeclaration.(StructDeclaration)) + + if err != nil { + return nil, p.atError(err.Error(), nameToken) + } + valueType = NewValueType(DATA_TYPE_STRUCT, false) + } else { + valueType, err = p.evaluateValueType(ctx) + + if err != nil { + return nil, err + } } - name := nameToken.Value() err = ctx.addType(name, valueType, isAlias, false) if err != nil { diff --git a/parser/struct.go b/parser/struct.go new file mode 100644 index 0000000..15f46b7 --- /dev/null +++ b/parser/struct.go @@ -0,0 +1,26 @@ +package parser + +type StructField struct { + name string + valueType ValueType +} + +func (f StructField) Name() string { + return f.name +} + +func (f StructField) ValueType() ValueType { + return f.valueType +} + +type StructDeclaration struct { + fields []StructField +} + +func (d StructDeclaration) StatementType() StatementType { + return STATEMENT_TYPE_STRUCT_DECLARATION +} + +func (d StructDeclaration) Fields() []StructField { + return d.fields +} diff --git a/parser/types.go b/parser/types.go index f9754d4..a47387c 100644 --- a/parser/types.go +++ b/parser/types.go @@ -65,6 +65,7 @@ const ( STATEMENT_TYPE_PROGRAM StatementType = "program" STATEMENT_TYPE_TYPE_DECLARATION StatementType = "type declaration" STATEMENT_TYPE_TYPE_DEFINITION StatementType = "type definition" + STATEMENT_TYPE_STRUCT_DECLARATION StatementType = "struct declaration" STATEMENT_TYPE_BOOL_LITERAL StatementType = "boolean" STATEMENT_TYPE_INT_LITERAL StatementType = "integer" STATEMENT_TYPE_STRING_LITERAL StatementType = "string" @@ -121,6 +122,7 @@ const ( DATA_TYPE_INTEGER DataType = "int" DATA_TYPE_STRING DataType = "string" DATA_TYPE_ERROR DataType = "error" + DATA_TYPE_STRUCT DataType = "struct" ) const ( From b70c2b615e756ad3b605ff3d45f21b1eb056f5ae Mon Sep 17 00:00:00 2001 From: monstermichl Date: Sat, 6 Sep 2025 18:55:36 +0200 Subject: [PATCH 02/22] Fix test --- tests/type_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/type_test.go b/tests/type_test.go index 5662ab8..73c6065 100644 --- a/tests/type_test.go +++ b/tests/type_test.go @@ -124,7 +124,7 @@ func testDeclareTypeTwiceFail(t *testing.T, transpilerFunc transpilerFunc) { type myType int type myType string `, func(output string, err error) { - require.EqualError(t, shortenError(err), "myType has already been defined") + require.EqualError(t, shortenError(err), "type myType has already been defined") }) } From 82d5386f4e0d22ad8c490ef24d5b120e7cb61a0a Mon Sep 17 00:00:00 2001 From: monstermichl Date: Sat, 6 Sep 2025 19:20:27 +0200 Subject: [PATCH 03/22] Add struct declaration struct and default value for structs (https://github.com/monstermichl/TypeShell/issues/51) --- parser/parser.go | 27 ++++++++++++++++++++++----- parser/struct.go | 17 +++++++++++++++++ parser/types.go | 1 + 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index b0646d6..1cfc63b 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -231,6 +231,11 @@ func (c context) findStruct(name string) (StructDeclaration, bool) { return structDeclaration, exists } +func (c context) isStruct(dataType DataType) bool { + _, exists := c.findStruct(dataType) + return exists +} + func (c context) findNamedValue(name string, prefix string, global bool) (NamedValue, bool) { prefixedName, err := c.buildPrefixedName(name, prefix, global, true) @@ -404,22 +409,32 @@ func allowedCompareOperators(t ValueType) []CompareOperator { } func defaultVarValue(valueType ValueType, ctx context) (Expression, error) { - foundType, exists := ctx.findType(valueType.DataType(), true) + dataType := valueType.DataType() + elementaryType, exists := ctx.findType(dataType, true) if exists { - dataType := foundType.valueType.dataType + elementaryDataType := elementaryType.valueType.DataType() if !valueType.IsSlice() { - switch dataType { + switch elementaryDataType { case DATA_TYPE_BOOLEAN: return BooleanLiteral{}, nil case DATA_TYPE_INTEGER: return IntegerLiteral{}, nil case DATA_TYPE_STRING: return StringLiteral{}, nil + case DATA_TYPE_STRUCT: + structDeclaration, exists := ctx.findStruct(dataType) + + if exists { + return StructDefinition{ + valueType: valueType, + fields: slices.Clone(structDeclaration.Fields()), + }, nil + } } } else { - return SliceInstantiation{dataType: dataType}, nil + return SliceInstantiation{dataType: elementaryDataType}, nil } } return nil, fmt.Errorf("no default value found for type %s", valueType.String()) @@ -1309,6 +1324,7 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { } name := nameToken.Value() valueTypeToken := p.peek() + isElementary := false var valueType ValueType var err error @@ -1328,6 +1344,7 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { return nil, p.atError(err.Error(), nameToken) } valueType = NewValueType(DATA_TYPE_STRUCT, false) + isElementary = true } else { valueType, err = p.evaluateValueType(ctx) @@ -1335,7 +1352,7 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { return nil, err } } - err = ctx.addType(name, valueType, isAlias, false) + err = ctx.addType(name, valueType, isAlias, isElementary) if err != nil { return nil, p.atError(err.Error(), valueTypeToken) diff --git a/parser/struct.go b/parser/struct.go index 15f46b7..34e1ec6 100644 --- a/parser/struct.go +++ b/parser/struct.go @@ -24,3 +24,20 @@ func (d StructDeclaration) StatementType() StatementType { func (d StructDeclaration) Fields() []StructField { return d.fields } + +type StructDefinition struct { + valueType ValueType + fields []StructField +} + +func (d StructDefinition) StatementType() StatementType { + return STATEMENT_TYPE_STRUCT_DEFINITION +} + +func (d StructDefinition) ValueType() ValueType { + return d.valueType +} + +func (d StructDefinition) IsConstant() bool { + return false +} diff --git a/parser/types.go b/parser/types.go index a47387c..c691af3 100644 --- a/parser/types.go +++ b/parser/types.go @@ -66,6 +66,7 @@ const ( STATEMENT_TYPE_TYPE_DECLARATION StatementType = "type declaration" STATEMENT_TYPE_TYPE_DEFINITION StatementType = "type definition" STATEMENT_TYPE_STRUCT_DECLARATION StatementType = "struct declaration" + STATEMENT_TYPE_STRUCT_DEFINITION StatementType = "struct definition" STATEMENT_TYPE_BOOL_LITERAL StatementType = "boolean" STATEMENT_TYPE_INT_LITERAL StatementType = "integer" STATEMENT_TYPE_STRING_LITERAL StatementType = "string" From efa6d1fed4cb97fed29b120fb721af8fde1b322b Mon Sep 17 00:00:00 2001 From: monstermichl Date: Sun, 7 Sep 2025 14:43:51 +0200 Subject: [PATCH 04/22] Add first draft of struct definition (https://github.com/monstermichl/TypeShell/issues/51) --- converters/bash/converter.go | 25 ++++++++++++++++++++++--- converters/batch/converter.go | 26 ++++++++++++++++++++++---- parser/parser.go | 15 ++++++++++++++- parser/struct.go | 23 ++++++++++++++++++++++- transpiler/converter.go | 14 ++++++++++++++ transpiler/transpiler.go | 24 ++++++++++++++++++++++++ 6 files changed, 118 insertions(+), 9 deletions(-) diff --git a/converters/bash/converter.go b/converters/bash/converter.go index c388c71..14cde54 100644 --- a/converters/bash/converter.go +++ b/converters/bash/converter.go @@ -376,9 +376,7 @@ func (c *converter) VarEvaluation(name string, valueUsed bool, global bool) (str } func (c *converter) SliceInstantiation(values []string, valueUsed bool) (string, error) { - c.addLine(fmt.Sprintf(`_dvc=$((%s+1))`, c.varEvaluationString("_dvc", true))) // Dynamic variable counter. - helper := c.nextHelperVar() - c.VarAssignment(helper, fmt.Sprintf(`_dv%s`, c.varEvaluationString("_dvc", true)), false) + helper := c.nextDynamicHelperVar() if len(values) > 0 { vals := "" @@ -408,6 +406,15 @@ func (c *converter) SliceLen(name string, valueUsed bool) (string, error) { return c.VarEvaluation(helper, valueUsed, false) } +func (c *converter) StructDefinition(values []transpiler.StructValue, valueUsed bool) (string, error) { + helper := c.nextDynamicHelperVar() + + for _, value := range values { + c.addLine(c.structAssignmentString(c.varEvaluationString(helper, false), value.Name(), value.Value(), false)) + } + return c.varEvaluationString(helper, false), nil +} + func (c *converter) StringSubscript(value string, startIndex string, endIndex string, valueUsed bool) (string, error) { helper := c.nextHelperVar() @@ -575,6 +582,10 @@ func (c *converter) sliceAssignmentString(name string, index string, value strin return fmt.Sprintf(`eval "%s[%s]=\"%s\""`, name, index, value) } +func (c *converter) structAssignmentString(name string, field string, value string, global bool) string { + return fmt.Sprintf(`eval "%s_%s=\"%s\""`, name, field, value) +} + func (c *converter) sliceEvaluationString(name string, index string) string { return fmt.Sprintf(`$(eval "echo \${%s[%s]}")`, name, index) } @@ -616,3 +627,11 @@ func (c *converter) nextHelperVar() string { return helperVar } + +func (c *converter) nextDynamicHelperVar() string { + c.addLine(fmt.Sprintf(`_dvc=$((%s+1))`, c.varEvaluationString("_dvc", true))) // Dynamic variable counter. + helper := c.nextHelperVar() + c.VarAssignment(helper, fmt.Sprintf(`_dv%s`, c.varEvaluationString("_dvc", true)), false) + + return helper +} diff --git a/converters/batch/converter.go b/converters/batch/converter.go index 2d11ebc..00e05ad 100644 --- a/converters/batch/converter.go +++ b/converters/batch/converter.go @@ -579,9 +579,7 @@ func (c *converter) VarEvaluation(name string, valueUsed bool, global bool) (str } func (c *converter) SliceInstantiation(values []string, valueUsed bool) (string, error) { - c.addLine(`set /A "_dvc=!_dvc!+1"`) // Dynamic variable counter. - helper := c.nextHelperVar() - c.VarAssignment(helper, "_dv!_dvc!", false) + helper := c.nextDynamicHelperVar() c.sliceAssignmentHelperRequired = true c.callFunc(sliceLenSetHelper, []string{}, c.varEvaluationString(helper, false), strconv.Itoa(len(values))) @@ -629,6 +627,15 @@ func (c *converter) SliceLen(name string, valueUsed bool) (string, error) { return c.VarEvaluation(helper, valueUsed, false) } +func (c *converter) StructDefinition(values []transpiler.StructValue, valueUsed bool) (string, error) { + helper := c.nextDynamicHelperVar() + + for _, value := range values { + c.addLine(c.structAssignmentString(c.varEvaluationString(helper, false), value.Name(), value.Value(), false)) + } + return c.varEvaluationString(helper, false), nil +} + func (c *converter) StringSubscript(value string, startIndex string, endIndex string, valueUsed bool) (string, error) { helper := c.nextHelperVar() c.stringSubscriptHelperRequired = true @@ -891,11 +898,22 @@ func (c *converter) nextHelperVar() string { return helperVar } +func (c *converter) nextDynamicHelperVar() string { + c.addLine(`set /A "_dvc=!_dvc!+1"`) // Dynamic variable counter. + helper := c.nextHelperVar() + c.VarAssignment(helper, "_dv!_dvc!", false) + + return helper +} + func (c *converter) sliceAssignmentString(name string, index string, value string, global bool) string { - // TODO: Is global flag even required here? Because value is already passed to function. return fmt.Sprintf(`set "%s_%s=%s"`, name, index, value) } +func (c *converter) structAssignmentString(name string, field string, value string, global bool) string { + return fmt.Sprintf(`set "%s_%s=%s"`, name, field, value) +} + func (c *converter) addLf() { if !c.lfSet { c.addStartLine("(set LF=^") // https://stackoverflow.com/a/60389149 diff --git a/parser/parser.go b/parser/parser.go index 1cfc63b..d87752f 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -427,9 +427,22 @@ func defaultVarValue(valueType ValueType, ctx context) (Expression, error) { structDeclaration, exists := ctx.findStruct(dataType) if exists { + structValues := []StructValue{} + + for _, field := range structDeclaration.Fields() { + defaultValue, err := defaultVarValue(field.ValueType(), ctx) + + if err != nil { + return nil, err + } + structValues = append(structValues, StructValue{ + name: field.Name(), + value: defaultValue, + }) + } return StructDefinition{ valueType: valueType, - fields: slices.Clone(structDeclaration.Fields()), + values: structValues, }, nil } } diff --git a/parser/struct.go b/parser/struct.go index 34e1ec6..0bb9db0 100644 --- a/parser/struct.go +++ b/parser/struct.go @@ -25,9 +25,26 @@ func (d StructDeclaration) Fields() []StructField { return d.fields } +type StructValue struct { + name string + value Expression +} + +func (v StructValue) Name() string { + return v.name +} + +func (v StructValue) ValueType() ValueType { + return v.Value().ValueType() +} + +func (v StructValue) Value() Expression { + return v.value +} + type StructDefinition struct { valueType ValueType - fields []StructField + values []StructValue } func (d StructDefinition) StatementType() StatementType { @@ -41,3 +58,7 @@ func (d StructDefinition) ValueType() ValueType { func (d StructDefinition) IsConstant() bool { return false } + +func (d StructDefinition) Values() []StructValue { + return d.values +} diff --git a/transpiler/converter.go b/transpiler/converter.go index c0561c4..a2320c5 100644 --- a/transpiler/converter.go +++ b/transpiler/converter.go @@ -46,6 +46,19 @@ func (rv ReturnValue) ValueType() parser.ValueType { return rv.valueType } +type StructValue struct { + name string + value string +} + +func (sv StructValue) Name() string { + return sv.name +} + +func (sv StructValue) Value() string { + return sv.value +} + type Converter interface { // Common methods StringToString(value string) string @@ -88,6 +101,7 @@ type Converter interface { SliceInstantiation(values []string, valueUsed bool) (string, error) SliceEvaluation(name string, index string, valueUsed bool) (string, error) SliceLen(name string, valueUsed bool) (string, error) + StructDefinition(values []StructValue, valueUsed bool) (string, error) StringSubscript(value string, startIndex string, endIndex string, valueUsed bool) (string, error) StringLen(value string, valueUsed bool) (string, error) Group(value string, valueUsed bool) (string, error) diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index b674bc9..8e1fc9b 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -693,6 +693,28 @@ func (t *transpiler) evaluateSliceInstantiation(instantiation parser.SliceInstan return newExpressionResult(s), nil } +func (t *transpiler) evaluateStructDefinition(definition parser.StructDefinition, valueUsed bool) (expressionResult, error) { + values := []StructValue{} + + for _, value := range definition.Values() { + result, err := t.evaluateExpression(value.Value(), true) + + if err != nil { + return expressionResult{}, err + } + values = append(values, StructValue{ + name: value.Name(), + value: result.firstValue(), + }) + } + s, err := t.converter.StructDefinition(values, valueUsed) + + if err != nil { + return expressionResult{}, err + } + return newExpressionResult(s), nil +} + func (t *transpiler) evaluateInput(input parser.Input, valueUsed bool) (expressionResult, error) { promptString := "" prompt := input.Prompt() @@ -886,6 +908,8 @@ func (t *transpiler) evaluateExpression(expression parser.Expression, valueUsed return t.evaluateAppCall(expression.(parser.AppCall), valueUsed) case parser.STATEMENT_TYPE_SLICE_INSTANTIATION: return t.evaluateSliceInstantiation(expression.(parser.SliceInstantiation), valueUsed) + case parser.STATEMENT_TYPE_STRUCT_DEFINITION: + return t.evaluateStructDefinition(expression.(parser.StructDefinition), valueUsed) case parser.STATEMENT_TYPE_INPUT: return t.evaluateInput(expression.(parser.Input), valueUsed) case parser.STATEMENT_TYPE_COPY: From 28aad3d0820db79cf33e370338a779c0c22cba72 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Sun, 7 Sep 2025 19:28:10 +0200 Subject: [PATCH 05/22] Start struct assignment implementation (https://github.com/monstermichl/TypeShell/issues/51) --- converters/bash/converter.go | 61 +++++++++++++++++------ converters/batch/converter.go | 90 +++++++++++++++++++-------------- parser/parser.go | 94 ++++++++++++++++++++++++++++++++--- parser/struct.go | 34 +++++++++---- parser/types.go | 1 + transpiler/converter.go | 1 + transpiler/transpiler.go | 16 ++++++ 7 files changed, 229 insertions(+), 68 deletions(-) diff --git a/converters/bash/converter.go b/converters/bash/converter.go index 14cde54..4379cc6 100644 --- a/converters/bash/converter.go +++ b/converters/bash/converter.go @@ -9,21 +9,31 @@ import ( "github.com/monstermichl/typeshell/transpiler" ) +type helperName = string + +const ( + sliceAssignmentHelper helperName = "_sah" // Slice assignment + sliceCopyHelper helperName = "_sch" // Slice copy + structAssignmentHelper helperName = "_stah" // Struct assignment + stringSubscriptHelper helperName = "_stsh" // String subscript +) + type funcInfo struct { name string } type converter struct { - interpreter string - startCode []string - code []string - varCounter int - forCounter int - funcs []funcInfo - funcCounter int - sliceAssignmentHelperRequired bool - sliceCopyHelperRequired bool - stringSubscriptHelperRequired bool + interpreter string + startCode []string + code []string + varCounter int + forCounter int + funcs []funcInfo + funcCounter int + sliceAssignmentHelperRequired bool + structAssignmentHelperRequired bool + sliceCopyHelperRequired bool + stringSubscriptHelperRequired bool } func New() *converter { @@ -62,7 +72,7 @@ func (c *converter) ProgramEnd() error { // $2: Assignment index // $3: Assignment value // $4: Default value - c.addHelper("slice assignment", "_sah", + c.addHelper("slice assignment", sliceAssignmentHelper, "local _i=${2}", fmt.Sprintf(`local _l=%s`, c.sliceLenString("${1}")), `for ((_c=${_l};_c<${_i};_c++)); do`, @@ -73,7 +83,7 @@ func (c *converter) ProgramEnd() error { } if c.sliceCopyHelperRequired { - c.addHelper("slice copy", "_sch", + c.addHelper("slice copy", sliceCopyHelper, "local _i=0", fmt.Sprintf(`local _l=%s`, c.sliceLenString("${2}")), "local _n=$(eval \"echo \\${${1}}\")", @@ -85,8 +95,17 @@ func (c *converter) ProgramEnd() error { ) } + if c.structAssignmentHelperRequired { + // $1: Slice name + // $2: Assignment field + // $3: Assignment value + c.addHelper("struct assignment", structAssignmentHelper, + c.sliceAssignmentString("${1}", "${2}", "${3}", false), + ) + } + if c.stringSubscriptHelperRequired { - c.addHelper("substring", "_ssh", + c.addHelper("substring", stringSubscriptHelper, `_ls=$((${2}))`, `_ll=$(((${3}-${2})+1))`, `_ret="${1:${_ls}:${_ll}}"`, @@ -106,7 +125,13 @@ func (c *converter) VarAssignment(name string, value string, global bool) error func (c *converter) SliceAssignment(name string, index string, value string, defaultValue string, global bool) error { c.sliceAssignmentHelperRequired = true - c.addLine(fmt.Sprintf(`_sah %s %s "%s" "%s"`, c.varEvaluationString(name, global), index, value, defaultValue)) + c.callFunc(sliceAssignmentHelper, c.varEvaluationString(name, global), index, value, defaultValue) + return nil +} + +func (c *converter) StructAssignment(name string, field string, value string, global bool) error { + c.structAssignmentHelperRequired = true + c.callFunc(structAssignmentHelper, c.varEvaluationString(name, global), field, value) return nil } @@ -418,7 +443,7 @@ func (c *converter) StructDefinition(values []transpiler.StructValue, valueUsed func (c *converter) StringSubscript(value string, startIndex string, endIndex string, valueUsed bool) (string, error) { helper := c.nextHelperVar() - c.addLine(fmt.Sprintf(`_ssh "%s" %s %s`, value, startIndex, endIndex)) + c.callFunc(stringSubscriptHelper, value, startIndex, endIndex) c.VarAssignment(helper, c.varEvaluationString("_ret", true), false) // https://www.baeldung.com/linux/bash-substring#1-using-thecut-command c.stringSubscriptHelperRequired = true @@ -518,7 +543,7 @@ func (c *converter) Input(prompt string, valueUsed bool) (string, error) { func (c *converter) Copy(destination string, source string, valueUsed bool, global bool) (string, error) { destination = c.varName(destination, global) - c.addLine(fmt.Sprintf("_sch %s %s", destination, source)) + c.callFunc(sliceCopyHelper, destination, source) c.sliceAssignmentHelperRequired = true c.sliceCopyHelperRequired = true @@ -548,6 +573,10 @@ func (c *converter) ReadFile(path string, valueUsed bool) (string, error) { return c.VarEvaluation(helper, valueUsed, false) } +func (c *converter) callFunc(name string, args ...string) { + c.addLine(fmt.Sprintf(`%s "%s"`, name, strings.Join(args, `" "`))) +} + func (c *converter) mustCurrentForVar() string { return fmt.Sprintf("_fv%d", c.forCounter) } diff --git a/converters/batch/converter.go b/converters/batch/converter.go index 00e05ad..ed72ae0 100644 --- a/converters/batch/converter.go +++ b/converters/batch/converter.go @@ -13,17 +13,18 @@ import ( type helperName = string const ( - appCallHelper helperName = "_ach" // App call - fileReadHelper helperName = "_frh" // File write - fileWriteHelper helperName = "_fwh" // File read - sliceLenSetHelper helperName = "_sls" // Slice length set - sliceLenGetHelper helperName = "_slg" // Slice length get - sliceAssignmentHelper helperName = "_sah" // Slice assignment - sliceCopyHelper helperName = "_sch" // Slice copy - stringSubscriptHelper helperName = "_stsh" // String subscript - stringLengthHelper helperName = "_stlh" // String length - stringEscapeHelper helperName = "_seh" // String escape - echoHelper helperName = "_ech" // Echo + appCallHelper helperName = "_ach" // App call + fileReadHelper helperName = "_frh" // File write + fileWriteHelper helperName = "_fwh" // File read + sliceLenSetHelper helperName = "_sls" // Slice length set + sliceLenGetHelper helperName = "_slg" // Slice length get + sliceAssignmentHelper helperName = "_sah" // Slice assignment + sliceCopyHelper helperName = "_sch" // Slice copy + structAssignmentHelper helperName = "_stah" // Struct assignment + stringSubscriptHelper helperName = "_stsh" // String subscript + stringLengthHelper helperName = "_stlh" // String length + stringEscapeHelper helperName = "_seh" // String escape + echoHelper helperName = "_ech" // Echo ) type funcInfo struct { @@ -39,31 +40,32 @@ type ifInfo struct { } type converter struct { - startCode []string - helperCode []string - globalCode []string - previousFunctionName string - functionsCode [][]string - endCode []string - varCounter int - ifCounter int - forCounter int - endLabels []string - funcs []funcInfo - funcCounter int - fors []forInfo - ifs []ifInfo - lfSet bool - appCallHelperRequired bool - readHelperRequired bool - sliceAssignmentHelperRequired bool - sliceCopyHelperRequired bool - sliceLenSetHelperRequired bool - sliceLenGetHelperRequired bool - stringSubscriptHelperRequired bool - stringLenHelperRequired bool - fileWriteHelperRequired bool - echoHelperRequired bool + startCode []string + helperCode []string + globalCode []string + previousFunctionName string + functionsCode [][]string + endCode []string + varCounter int + ifCounter int + forCounter int + endLabels []string + funcs []funcInfo + funcCounter int + fors []forInfo + ifs []ifInfo + lfSet bool + appCallHelperRequired bool + readHelperRequired bool + sliceAssignmentHelperRequired bool + structAssignmentHelperRequired bool + sliceCopyHelperRequired bool + sliceLenSetHelperRequired bool + sliceLenGetHelperRequired bool + stringSubscriptHelperRequired bool + stringLenHelperRequired bool + fileWriteHelperRequired bool + echoHelperRequired bool } func New() *converter { @@ -184,7 +186,6 @@ func (c *converter) ProgramEnd() error { if c.sliceAssignmentHelperRequired { c.sliceLenGetHelperRequired = true c.sliceLenSetHelperRequired = true - c.sliceAssignmentHelperRequired = true // %1: Slice name // %2: Assigned index @@ -218,6 +219,15 @@ func (c *converter) ProgramEnd() error { ) } + if c.structAssignmentHelperRequired { + // %1: Slice name + // %2: Assigned field + // arg0: Assigned value + c.addHelper("struct assignment", structAssignmentHelper, + c.sliceAssignmentString("!%1!", "%2", fmt.Sprintf("!%s!", funcArgVar(0)), false), + ) + } + if c.stringSubscriptHelperRequired { c.addHelper("string subscript", stringSubscriptHelper, `set /A "_sh=(%2-%1)+1"`, @@ -263,6 +273,12 @@ func (c *converter) SliceAssignment(name string, index string, value string, def return nil } +func (c *converter) StructAssignment(name string, field string, value string, global bool) error { + c.structAssignmentHelperRequired = true + c.callFunc(structAssignmentHelper, []string{value}, c.varName(name, global), field) + return nil +} + func (c *converter) FuncStart(name string, params []string, returnTypes []parser.ValueType) error { c.funcCounter++ c.funcs = append(c.funcs, funcInfo{ diff --git a/parser/parser.go b/parser/parser.go index d87752f..0a0e3ac 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -436,7 +436,10 @@ func defaultVarValue(valueType ValueType, ctx context) (Expression, error) { return nil, err } structValues = append(structValues, StructValue{ - name: field.Name(), + StructField: StructField{ + name: field.Name(), + valueType: field.ValueType(), + }, value: defaultValue, }) } @@ -2843,7 +2846,7 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) { } else { // If token is identifier it could be a slice assignment, an increment or a decrement. if token.Type() == lexer.IDENTIFIER { - switch p.peekAt(1).Type() { + switch nextTokenType := p.peekAt(1).Type(); nextTokenType { case lexer.INCREMENT_OPERATOR, lexer.DECREMENT_OPERATOR: stmt, err = p.evaluateIncrementDecrement(ctx) case lexer.COMPOUND_ASSIGN_OPERATOR: @@ -2851,12 +2854,18 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) { case lexer.ASSIGN_OPERATOR, lexer.COMMA: stmt, err = p.evaluateVarAssignment(ctx) default: - // Handle slice assignment. variable, exists := ctx.findNamedValue(token.Value(), p.prefix, ctx.global()) - // If variable has been defined and is a slice, handles slice assignment. - if exists && variable.ValueType().IsSlice() { - stmt, err = p.evaluateSliceAssignment(ctx) + switch nextTokenType { + case lexer.DOT: + // Could be a library variable or a struct assignment. + // TODO: Handle library stuff as well, but for now handle struct assignment. + stmt, err = p.evaluateStructAssignment(ctx) + default: + // If variable has been defined and is a slice, handles slice assignment. + if exists && variable.ValueType().IsSlice() { + stmt, err = p.evaluateSliceAssignment(ctx) + } } } } @@ -3397,6 +3406,79 @@ func (p *Parser) evaluateSliceAssignment(ctx context) (Statement, error) { }, nil } +func (p *Parser) evaluateStructAssignment(ctx context) (Statement, error) { + nameToken := p.eat() + + if nameToken.Type() != lexer.IDENTIFIER { + return nil, p.expectedError("struct variable", nameToken) + } + name := nameToken.Value() + namedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) + + if !exists { + return nil, p.variableNotDefinedError(name, nameToken) + } else if namedValue.IsConstant() { + return nil, p.constantError(name, nameToken) + } + namedValueValueType := namedValue.ValueType() + baseTypeDefinition, exists := ctx.findType(namedValueValueType.DataType(), false) + + if !exists { + return nil, p.atError(fmt.Sprintf(`type of %s could not be found`, name), nameToken) + } + baseValueType := baseTypeDefinition.valueType + + if baseValueType.IsSlice() { + return nil, p.expectedError("struct but got slice", nameToken) + } else if baseValueType.DataType() != DATA_TYPE_STRUCT { + return nil, p.expectedError(fmt.Sprintf("struct but variable is of type %s", baseValueType.String()), nameToken) + } + structDeclaration, exists := ctx.findStruct(baseTypeDefinition.name) + + if !exists { + return nil, p.atError(fmt.Sprintf(`declaration of struct %s could not be found`, name), nameToken) + } + nextToken := p.eat() + + if nextToken.Type() != lexer.DOT { + return nil, p.expectedError(`"."`, nextToken) + } + nextToken = p.eat() + + if nextToken.Type() != lexer.IDENTIFIER { + return nil, p.expectedError(`struct field`, nextToken) + } + structField, err := structDeclaration.FindField(nextToken.Value()) + + if err != nil { + return nil, p.atError(err.Error(), nextToken) + } + nextToken = p.eat() + + if nextToken.Type() != lexer.ASSIGN_OPERATOR { + return nil, p.expectedError(`"="`, nameToken) + } + valueToken := p.peek() + value, err := p.evaluateExpression(ctx) + + if err != nil { + return nil, err + } + variableValueType := structField.ValueType() + assignedValueType := value.ValueType() + + if !variableValueType.Equals(assignedValueType) { + return nil, p.expectedError(fmt.Sprintf("%s value but got %s", variableValueType.String(), assignedValueType.String()), valueToken) + } + return StructAssignment{ + Variable: namedValue.(Variable), + value: StructValue{ + StructField: structField, + value: value, + }, + }, nil +} + func (p *Parser) evaluateIncrementDecrement(ctx context) (Statement, error) { identifierToken := p.eat() diff --git a/parser/struct.go b/parser/struct.go index 0bb9db0..9b241ab 100644 --- a/parser/struct.go +++ b/parser/struct.go @@ -1,5 +1,7 @@ package parser +import "fmt" + type StructField struct { name string valueType ValueType @@ -25,17 +27,18 @@ func (d StructDeclaration) Fields() []StructField { return d.fields } -type StructValue struct { - name string - value Expression +func (d StructDeclaration) FindField(name string) (StructField, error) { + for _, field := range d.Fields() { + if field.Name() == name { + return field, nil + } + } + return StructField{}, fmt.Errorf(`struct field %s could not be found`, name) } -func (v StructValue) Name() string { - return v.name -} - -func (v StructValue) ValueType() ValueType { - return v.Value().ValueType() +type StructValue struct { + StructField + value Expression } func (v StructValue) Value() Expression { @@ -62,3 +65,16 @@ func (d StructDefinition) IsConstant() bool { func (d StructDefinition) Values() []StructValue { return d.values } + +type StructAssignment struct { + Variable + value StructValue +} + +func (a StructAssignment) StatementType() StatementType { + return STATEMENT_TYPE_STRUCT_ASSIGNMENT +} + +func (a StructAssignment) Value() StructValue { + return a.value +} diff --git a/parser/types.go b/parser/types.go index c691af3..74cf81b 100644 --- a/parser/types.go +++ b/parser/types.go @@ -67,6 +67,7 @@ const ( STATEMENT_TYPE_TYPE_DEFINITION StatementType = "type definition" STATEMENT_TYPE_STRUCT_DECLARATION StatementType = "struct declaration" STATEMENT_TYPE_STRUCT_DEFINITION StatementType = "struct definition" + STATEMENT_TYPE_STRUCT_ASSIGNMENT StatementType = "struct assignment" STATEMENT_TYPE_BOOL_LITERAL StatementType = "boolean" STATEMENT_TYPE_INT_LITERAL StatementType = "integer" STATEMENT_TYPE_STRING_LITERAL StatementType = "string" diff --git a/transpiler/converter.go b/transpiler/converter.go index a2320c5..66bc5d5 100644 --- a/transpiler/converter.go +++ b/transpiler/converter.go @@ -71,6 +71,7 @@ type Converter interface { VarDefinition(name string, value string, global bool) error VarAssignment(name string, value string, global bool) error SliceAssignment(name string, index string, value string, defaultValue string, global bool) error + StructAssignment(name string, field string, value string, dglobal bool) error FuncStart(name string, params []string, returnTypes []parser.ValueType) error FuncEnd() error Return(values []ReturnValue) error diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 8e1fc9b..fa36412 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -493,6 +493,20 @@ func (t *transpiler) evaluateSliceAssignment(assignment parser.SliceAssignment) return t.converter.SliceAssignment(assignment.LayerName(), indexResult.firstValue(), valueResult.firstValue(), defaultValue, assignment.Global()) } +func (t *transpiler) evaluateStructAssignment(assignment parser.StructAssignment) error { + value := assignment.Value() + valueResult, err := t.evaluateExpression(value.Value(), true) + + if err != nil { + return err + } + + if err != nil { + return err + } + return t.converter.StructAssignment(assignment.LayerName(), value.Name(), valueResult.firstValue(), assignment.Global()) +} + func (t *transpiler) evaluateConstEvaluation(evaluation parser.ConstEvaluation, valueUsed bool) (expressionResult, error) { // Map const evaluation to var evaluation since constant evaluation works the same. varEvaluation := parser.NewVariableEvaluation(evaluation.LayerName(), evaluation.ValueType(), evaluation.Layer(), evaluation.Public()) @@ -843,6 +857,8 @@ func (t *transpiler) evaluate(statement parser.Statement) error { return t.evaluateVarAssignmentCallAssignment(statement.(parser.VariableAssignmentCallAssignment)) case parser.STATEMENT_TYPE_SLICE_ASSIGNMENT: return t.evaluateSliceAssignment(statement.(parser.SliceAssignment)) + case parser.STATEMENT_TYPE_STRUCT_ASSIGNMENT: + return t.evaluateStructAssignment(statement.(parser.StructAssignment)) case parser.STATEMENT_TYPE_FUNCTION_DEFINITION: return t.evaluateFunctionDefinition(statement.(parser.FunctionDefinition)) case parser.STATEMENT_TYPE_RETURN: From ca6d97bd3d50b56c9145d0bc06304925ee4e8287 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Tue, 9 Sep 2025 18:12:00 +0200 Subject: [PATCH 06/22] Get struct evaluation working in Batch (https://github.com/monstermichl/TypeShell/issues/51) --- converters/bash/converter.go | 14 +++++ converters/batch/converter.go | 13 +++++ parser/parser.go | 96 ++++++++++++++++++++++++++++------- parser/struct.go | 13 +++++ parser/types.go | 1 + transpiler/converter.go | 1 + transpiler/transpiler.go | 11 ++++ 7 files changed, 130 insertions(+), 19 deletions(-) diff --git a/converters/bash/converter.go b/converters/bash/converter.go index 4379cc6..2feb073 100644 --- a/converters/bash/converter.go +++ b/converters/bash/converter.go @@ -440,6 +440,16 @@ func (c *converter) StructDefinition(values []transpiler.StructValue, valueUsed return c.varEvaluationString(helper, false), nil } +func (c *converter) StructEvaluation(name string, field string, valueUsed bool) (string, error) { + helper := c.nextHelperVar() + c.VarAssignment( + helper, + c.structEvaluationString(name, field), + false, + ) + return c.VarEvaluation(helper, valueUsed, false) +} + func (c *converter) StringSubscript(value string, startIndex string, endIndex string, valueUsed bool) (string, error) { helper := c.nextHelperVar() @@ -623,6 +633,10 @@ func (c *converter) sliceLenString(name string) string { return fmt.Sprintf(`$(eval "echo \${#%s[@]}")`, name) } +func (c *converter) structEvaluationString(name string, field string) string { + return fmt.Sprintf(`$(eval "echo \${%s_%s}")`, c.varEvaluationString(name, false), field) +} + func (c *converter) ifStart(condition string, startWord string) error { c.addLine(fmt.Sprintf("%s [ %s -eq %s ]; then", startWord, condition, transpiler.BoolToString(true))) return nil diff --git a/converters/batch/converter.go b/converters/batch/converter.go index ed72ae0..266f89a 100644 --- a/converters/batch/converter.go +++ b/converters/batch/converter.go @@ -652,6 +652,19 @@ func (c *converter) StructDefinition(values []transpiler.StructValue, valueUsed return c.varEvaluationString(helper, false), nil } +func (c *converter) StructEvaluation(name string, field string, valueUsed bool) (string, error) { + helper := c.nextHelperVar() + + c.addLine( + fmt.Sprintf(`for /f "delims=" %%%%i in ("%s_%s") do set "%s=!%%%%i!"`, + c.varEvaluationString(name, false), + field, + c.varName(helper, false), + ), + ) + return c.VarEvaluation(helper, valueUsed, false) +} + func (c *converter) StringSubscript(value string, startIndex string, endIndex string, valueUsed bool) (string, error) { helper := c.nextHelperVar() c.stringSubscriptHelperRequired = true diff --git a/parser/parser.go b/parser/parser.go index 0a0e3ac..1410ac7 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -2596,6 +2596,48 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { }, nil } +func (p *Parser) evaluateStructEvaluation(importAlias string, ctx context) (Expression, error) { + identifierToken := p.eat() // Eat identifier token. + + if identifierToken.Type() != lexer.IDENTIFIER { + return nil, p.expectedIdentifierError(identifierToken) + } + name := identifierToken.Value() + namedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) + + if !exists { + return nil, p.variableNotDefinedError(name, identifierToken) + } + dotToken := p.eat() + + if dotToken.Type() != lexer.DOT { + return nil, p.expectedError(`"."`, dotToken) + } + fieldToken := p.eat() + + if fieldToken.Type() != lexer.IDENTIFIER { + return nil, p.expectedError("field name", fieldToken) + } + structType := namedValue.ValueType().DataType() + structDeclararion, exists := ctx.findStruct(structType) + + if !exists { + return nil, p.atError(fmt.Sprintf("no struct declaration found for %s", structType), identifierToken) + } + + // Check field. + fieldName := fieldToken.Value() + foundField, err := structDeclararion.FindField(fieldName) + + if err != nil { + return nil, p.atError(err.Error(), fieldToken) + } + return StructEvaluation{ + Variable: namedValue.(Variable), + field: foundField, + }, nil +} + func (p *Parser) evaluateNamedValueEvaluation(ctx context) (Expression, error) { identifierToken := p.eat() // Eat identifier token. @@ -2619,6 +2661,27 @@ func (p *Parser) evaluateNamedValueEvaluation(ctx context) (Expression, error) { }, nil } +func (p *Parser) evaluateImportAlias(ctx context) (string, lexer.Token, error) { + nextToken := p.peek() + + // If next token is an identifier, try to find an import for it. + if nextToken.Type() == lexer.IDENTIFIER { + alias := nextToken.Value() + _, exists := ctx.findImport(alias) + + if exists { + p.eat() + nextToken = p.eat() // Eat dot token. + + if nextToken.Type() != lexer.DOT { + return "", nextToken, p.expectedError(`"."`, nextToken) + } + return alias, p.peek(), nil + } + } + return "", nextToken, nil +} + func (p *Parser) evaluateSingleExpression(ctx context) (Expression, error) { var err error var expr Expression @@ -2715,24 +2778,28 @@ func (p *Parser) evaluateSingleExpression(ctx context) (Expression, error) { // Handle identifiers. case lexer.IDENTIFIER: + importAlias, _, errTemp := p.evaluateImportAlias(ctx) + err = errTemp + + if err != nil { + return nil, err + } nextToken := p.peekAt(1) - // If the current token is an identifier and the next is an opening - // round bracket or a dot, it's a function call if the next is an - // opening square bracket, it's a slice evaluation, otherwise it's - // a variable evaluation. switch nextToken.Type() { - case lexer.OPENING_ROUND_BRACKET, lexer.DOT: + case lexer.OPENING_ROUND_BRACKET: // If a type exists with the provided name, it's a type-cast/-instantiation. _, exists := ctx.findType(value, false) if exists { expr, err = p.evaluateTypeDefinition(ctx) } else { - expr, err = p.evaluateFunctionCall(ctx) + expr, err = p.evaluateFunctionCall(importAlias, ctx) } case lexer.OPENING_SQUARE_BRACKET: expr, err = p.evaluateSubscript(ctx) + case lexer.DOT: + expr, err = p.evaluateStructEvaluation(importAlias, ctx) default: expr, err = p.evaluateNamedValueEvaluation(ctx) } @@ -3076,17 +3143,8 @@ func (p *Parser) evaluateArguments(typeName string, name string, params []Variab return args, nil } -func (p *Parser) evaluateFunctionCall(ctx context) (Call, error) { +func (p *Parser) evaluateFunctionCall(importAlias string, ctx context) (Call, error) { nextToken := p.eat() - dotToken := p.peek() - alias := "" - - // If next token is a dot, it's an include-function call. - if dotToken.Type() == lexer.DOT { - p.eat() - alias = nextToken.Value() - nextToken = p.eat() - } if nextToken.Type() != lexer.IDENTIFIER { return nil, p.expectedError("function identifier", nextToken) @@ -3096,9 +3154,9 @@ func (p *Parser) evaluateFunctionCall(ctx context) (Call, error) { dotedName := name // If it's an include-function call, use provided alias. - if len(alias) > 0 { - prefix = alias - dotedName = fmt.Sprintf("%s.%s", alias, name) + if len(importAlias) > 0 { + prefix = importAlias + dotedName = fmt.Sprintf("%s.%s", importAlias, name) } // Make sure function has been defined. diff --git a/parser/struct.go b/parser/struct.go index 9b241ab..40eec9b 100644 --- a/parser/struct.go +++ b/parser/struct.go @@ -78,3 +78,16 @@ func (a StructAssignment) StatementType() StatementType { func (a StructAssignment) Value() StructValue { return a.value } + +type StructEvaluation struct { + Variable + field StructField +} + +func (e StructEvaluation) StatementType() StatementType { + return STATEMENT_TYPE_STRUCT_EVALUATION +} + +func (e StructEvaluation) Field() StructField { + return e.field +} diff --git a/parser/types.go b/parser/types.go index 74cf81b..6f78eeb 100644 --- a/parser/types.go +++ b/parser/types.go @@ -68,6 +68,7 @@ const ( STATEMENT_TYPE_STRUCT_DECLARATION StatementType = "struct declaration" STATEMENT_TYPE_STRUCT_DEFINITION StatementType = "struct definition" STATEMENT_TYPE_STRUCT_ASSIGNMENT StatementType = "struct assignment" + STATEMENT_TYPE_STRUCT_EVALUATION StatementType = "struct evaluation" STATEMENT_TYPE_BOOL_LITERAL StatementType = "boolean" STATEMENT_TYPE_INT_LITERAL StatementType = "integer" STATEMENT_TYPE_STRING_LITERAL StatementType = "string" diff --git a/transpiler/converter.go b/transpiler/converter.go index 66bc5d5..56685b1 100644 --- a/transpiler/converter.go +++ b/transpiler/converter.go @@ -103,6 +103,7 @@ type Converter interface { SliceEvaluation(name string, index string, valueUsed bool) (string, error) SliceLen(name string, valueUsed bool) (string, error) StructDefinition(values []StructValue, valueUsed bool) (string, error) + StructEvaluation(name string, field string, valueUsed bool) (string, error) StringSubscript(value string, startIndex string, endIndex string, valueUsed bool) (string, error) StringLen(value string, valueUsed bool) (string, error) Group(value string, valueUsed bool) (string, error) diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index fa36412..92be89d 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -544,6 +544,15 @@ func (t *transpiler) evaluateSliceEvaluation(evaluation parser.SliceEvaluation, return newExpressionResult(s), err } +func (t *transpiler) evaluateStructEvaluation(evaluation parser.StructEvaluation, valueUsed bool) (expressionResult, error) { + s, err := t.converter.StructEvaluation(evaluation.LayerName(), evaluation.Field().Name(), valueUsed) + + if err != nil { + return expressionResult{}, err + } + return newExpressionResult(s), err +} + func (t *transpiler) evaluateStringSubscript(subscript parser.StringSubscript, valueUsed bool) (expressionResult, error) { startIndexResult, err := t.evaluateIndex(subscript.StartIndex(), true) @@ -914,6 +923,8 @@ func (t *transpiler) evaluateExpression(expression parser.Expression, valueUsed return t.evaluateVarEvaluation(expression.(parser.VariableEvaluation), valueUsed) case parser.STATEMENT_TYPE_SLICE_EVALUATION: return t.evaluateSliceEvaluation(expression.(parser.SliceEvaluation), valueUsed) + case parser.STATEMENT_TYPE_STRUCT_EVALUATION: + return t.evaluateStructEvaluation(expression.(parser.StructEvaluation), valueUsed) case parser.STATEMENT_TYPE_STRING_SUBSCRIPT: return t.evaluateStringSubscript(expression.(parser.StringSubscript), valueUsed) case parser.STATEMENT_TYPE_GROUP: From 6dbf416c8882a2d64d95730adc05528f63cbb217 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Tue, 9 Sep 2025 18:49:15 +0200 Subject: [PATCH 07/22] Get struct evaluation working in Bash (https://github.com/monstermichl/TypeShell/issues/51) --- converters/bash/converter.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/converters/bash/converter.go b/converters/bash/converter.go index 2feb073..a13a54b 100644 --- a/converters/bash/converter.go +++ b/converters/bash/converter.go @@ -96,11 +96,11 @@ func (c *converter) ProgramEnd() error { } if c.structAssignmentHelperRequired { - // $1: Slice name + // $1: Struct name // $2: Assignment field // $3: Assignment value c.addHelper("struct assignment", structAssignmentHelper, - c.sliceAssignmentString("${1}", "${2}", "${3}", false), + c.structAssignmentString("${1}", "${2}", "${3}", false), ) } @@ -442,6 +442,7 @@ func (c *converter) StructDefinition(values []transpiler.StructValue, valueUsed func (c *converter) StructEvaluation(name string, field string, valueUsed bool) (string, error) { helper := c.nextHelperVar() + c.VarAssignment( helper, c.structEvaluationString(name, field), From 5ca240c806ed0b1c5ee88dcf44c05ba5636e36cc Mon Sep 17 00:00:00 2001 From: monstermichl Date: Tue, 9 Sep 2025 20:21:17 +0200 Subject: [PATCH 08/22] Fix nested struct check (https://github.com/monstermichl/TypeShell/issues/51) --- parser/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/parser.go b/parser/parser.go index 1410ac7..c31c28c 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1291,7 +1291,7 @@ func (p *Parser) evaluateStructDeclaration(ctx context) (Statement, error) { } // Don't allow nested structs for now. - if valueType.DataType() == DATA_TYPE_STRUCT { + if ctx.isStruct(valueType.DataType()) { return nil, p.atError("nested structs are not allowed", valueTypeToken) } From b93fabc136ba5607d67b554451c4176614ea7d73 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Tue, 9 Sep 2025 20:21:41 +0200 Subject: [PATCH 09/22] Improve error messages (https://github.com/monstermichl/TypeShell/issues/51) --- parser/parser.go | 4 ++-- parser/struct.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index c31c28c..e9e4622 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -3482,7 +3482,7 @@ func (p *Parser) evaluateStructAssignment(ctx context) (Statement, error) { baseTypeDefinition, exists := ctx.findType(namedValueValueType.DataType(), false) if !exists { - return nil, p.atError(fmt.Sprintf(`type of %s could not be found`, name), nameToken) + return nil, p.atError(fmt.Sprintf(`type of %s not be found`, name), nameToken) } baseValueType := baseTypeDefinition.valueType @@ -3494,7 +3494,7 @@ func (p *Parser) evaluateStructAssignment(ctx context) (Statement, error) { structDeclaration, exists := ctx.findStruct(baseTypeDefinition.name) if !exists { - return nil, p.atError(fmt.Sprintf(`declaration of struct %s could not be found`, name), nameToken) + return nil, p.atError(fmt.Sprintf(`declaration of struct %s not be found`, name), nameToken) } nextToken := p.eat() diff --git a/parser/struct.go b/parser/struct.go index 40eec9b..b073311 100644 --- a/parser/struct.go +++ b/parser/struct.go @@ -33,7 +33,7 @@ func (d StructDeclaration) FindField(name string) (StructField, error) { return field, nil } } - return StructField{}, fmt.Errorf(`struct field %s could not be found`, name) + return StructField{}, fmt.Errorf(`struct field %s doesn't exist`, name) } type StructValue struct { From 77e119491eb42a99281812cbc55000f3484a309a Mon Sep 17 00:00:00 2001 From: monstermichl Date: Tue, 9 Sep 2025 20:27:58 +0200 Subject: [PATCH 10/22] Improve error message (https://github.com/monstermichl/TypeShell/issues/51) --- parser/parser.go | 4 ++-- tests/type_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index e9e4622..6bb8647 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -2634,7 +2634,7 @@ func (p *Parser) evaluateStructEvaluation(importAlias string, ctx context) (Expr } return StructEvaluation{ Variable: namedValue.(Variable), - field: foundField, + field: foundField, }, nil } @@ -3108,7 +3108,7 @@ func (p *Parser) evaluateArguments(typeName string, name string, params []Variab lastArgType := expr.ValueType() if !lastParamType.Equals(lastArgType) { - return nil, p.expectedError(fmt.Sprintf("parameter of type %s (%s) but got %s", lastParamType.String(), param.Name(), lastArgType.String()), argToken) + return nil, p.expectedError(fmt.Sprintf("type of parameter %s is %s but got %s", param.Name(), lastParamType.String(), lastArgType.String()), argToken) } } nextToken = p.peek() diff --git a/tests/type_test.go b/tests/type_test.go index 73c6065..b75e396 100644 --- a/tests/type_test.go +++ b/tests/type_test.go @@ -151,7 +151,7 @@ func testPassBaseTypeToFunctionFail(t *testing.T, transpilerFunc transpilerFunc) } test("test") `, func(output string, err error) { - require.EqualError(t, shortenError(err), "expected parameter of type myType (param) but got string") + require.EqualError(t, shortenError(err), "expected type of parameter param is myType but got string") }) } From 371b7e0c09e4495692b639df6848dfdd19f9c545 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 11 Sep 2025 17:18:11 +0200 Subject: [PATCH 11/22] Completely redesign type handling (https://github.com/monstermichl/TypeShell/issues/51 + https://github.com/monstermichl/TypeShell/issues/45) --- converters/bash/converter.go | 14 +- converters/batch/converter.go | 14 +- parser/appcall.go | 8 +- parser/comparison.go | 2 +- parser/copy.go | 2 +- parser/exists.go | 2 +- parser/function.go | 2 +- parser/input.go | 2 +- parser/iota.go | 2 +- parser/itoa.go | 2 +- parser/len.go | 2 +- parser/literals.go | 6 +- parser/logicaloperation.go | 2 +- parser/parser.go | 262 +++++++++++++--------------------- parser/read.go | 2 +- parser/slice.go | 14 +- parser/string.go | 2 +- parser/struct.go | 50 ++++++- parser/types.go | 191 +++++++++++++++++++++---- parser/write.go | 2 +- transpiler/transpiler.go | 8 +- 21 files changed, 362 insertions(+), 229 deletions(-) diff --git a/converters/bash/converter.go b/converters/bash/converter.go index a13a54b..38ad749 100644 --- a/converters/bash/converter.go +++ b/converters/bash/converter.go @@ -284,8 +284,8 @@ func (c *converter) BinaryOperation(left string, operator parser.BinaryOperator, return notAllowedError() } - switch valueType.DataType() { - case parser.DATA_TYPE_INTEGER: + switch valueType.Type().ElementaryType().Kind() { + case parser.TypeKindInt: switch operator { case parser.BINARY_OPERATOR_MULTIPLICATION, parser.BINARY_OPERATOR_DIVISION, @@ -297,7 +297,7 @@ func (c *converter) BinaryOperation(left string, operator parser.BinaryOperator, return notAllowedError() } c.VarAssignment(helper, fmt.Sprintf("$((%s%s%s))", left, operator, right), false) // Backslash is required for * operator to prevent pattern expansion (https://www.shell-tips.com/bash/math-arithmetic-calculation/#using-the-expr-command-line). - case parser.DATA_TYPE_STRING: + case parser.TypeKindString: switch operator { case parser.BINARY_OPERATOR_ADDITION: c.VarAssignment(helper, fmt.Sprintf("\"%s%s\"", left, right), false) @@ -314,15 +314,15 @@ func (c *converter) Comparison(left string, operator parser.CompareOperator, rig var operatorString string if !valueType.IsSlice() { - switch valueType.DataType() { - case parser.DATA_TYPE_BOOLEAN: + switch valueType.Type().ElementaryType().Kind() { + case parser.TypeKindBool: switch operator { case parser.COMPARE_OPERATOR_EQUAL: operatorString = "-eq" case parser.COMPARE_OPERATOR_NOT_EQUAL: operatorString = "-ne" } - case parser.DATA_TYPE_INTEGER: + case parser.TypeKindInt: switch operator { case parser.COMPARE_OPERATOR_EQUAL: operatorString = "-eq" @@ -337,7 +337,7 @@ func (c *converter) Comparison(left string, operator parser.CompareOperator, rig case parser.COMPARE_OPERATOR_LESS_OR_EQUAL: operatorString = "-le" } - case parser.DATA_TYPE_STRING: + case parser.TypeKindString: switch operator { case parser.COMPARE_OPERATOR_EQUAL: operatorString = "==" diff --git a/converters/batch/converter.go b/converters/batch/converter.go index 266f89a..07782fb 100644 --- a/converters/batch/converter.go +++ b/converters/batch/converter.go @@ -464,8 +464,8 @@ func (c *converter) BinaryOperation(left string, operator parser.BinaryOperator, return notAllowedError() } - switch valueType.DataType() { - case parser.DATA_TYPE_INTEGER: + switch valueType.Type().ElementaryType().Kind() { + case parser.TypeKindInt: switch operator { case parser.BINARY_OPERATOR_MULTIPLICATION, parser.BINARY_OPERATOR_DIVISION, @@ -478,7 +478,7 @@ func (c *converter) BinaryOperation(left string, operator parser.BinaryOperator, return notAllowedError() } c.addLine(fmt.Sprintf(`set /A "%s=%s%s%s"`, c.varName(helper, false), left, operator, right)) - case parser.DATA_TYPE_STRING: + case parser.TypeKindString: switch operator { case parser.BINARY_OPERATOR_ADDITION: c.VarAssignment(helper, fmt.Sprintf("%s%s", left, right), false) @@ -499,15 +499,15 @@ func (c *converter) Comparison(left string, operator parser.CompareOperator, rig quote := "" if !valueType.IsSlice() { - switch valueType.DataType() { - case parser.DATA_TYPE_BOOLEAN: + switch valueType.Type().ElementaryType().Kind() { + case parser.TypeKindBool: switch operator { case parser.COMPARE_OPERATOR_EQUAL: operatorString = EQUAL_OPERATOR case parser.COMPARE_OPERATOR_NOT_EQUAL: operatorString = NOT_EQUAL_OPERATOR } - case parser.DATA_TYPE_INTEGER: + case parser.TypeKindInt: switch operator { case parser.COMPARE_OPERATOR_EQUAL: operatorString = EQUAL_OPERATOR @@ -522,7 +522,7 @@ func (c *converter) Comparison(left string, operator parser.CompareOperator, rig case parser.COMPARE_OPERATOR_LESS_OR_EQUAL: operatorString = "leq" } - case parser.DATA_TYPE_STRING: + case parser.TypeKindString: switch operator { case parser.COMPARE_OPERATOR_EQUAL: operatorString = EQUAL_OPERATOR diff --git a/parser/appcall.go b/parser/appcall.go index 1880573..44357f5 100644 --- a/parser/appcall.go +++ b/parser/appcall.go @@ -11,7 +11,7 @@ func (a AppCall) StatementType() StatementType { } func (a AppCall) ValueType() ValueType { - return NewValueType(DATA_TYPE_MULTIPLE, false) + return NewValueType(TypeMultiple{}, false) } func (a AppCall) IsConstant() bool { @@ -32,8 +32,8 @@ func (a AppCall) Next() *AppCall { func (a AppCall) ReturnTypes() []ValueType { return []ValueType{ - NewValueType(DATA_TYPE_STRING, false), // stdout - NewValueType(DATA_TYPE_STRING, false), // stderr - NewValueType(DATA_TYPE_INTEGER, false), // error code + NewValueType(TypeString{}, false), // stdout + NewValueType(TypeString{}, false), // stderr + NewValueType(TypeInt{}, false), // error code } } diff --git a/parser/comparison.go b/parser/comparison.go index 24c02a7..678ab5e 100644 --- a/parser/comparison.go +++ b/parser/comparison.go @@ -19,7 +19,7 @@ func (c Comparison) StatementType() StatementType { } func (c Comparison) ValueType() ValueType { - return ValueType{dataType: DATA_TYPE_BOOLEAN} + return NewValueType(TypeBool{}, false) } func (c Comparison) IsConstant() bool { diff --git a/parser/copy.go b/parser/copy.go index bf13050..66fe847 100644 --- a/parser/copy.go +++ b/parser/copy.go @@ -10,7 +10,7 @@ func (c Copy) StatementType() StatementType { } func (c Copy) ValueType() ValueType { - return NewValueType(DATA_TYPE_INTEGER, false) + return NewValueType(TypeInt{}, false) } func (c Copy) IsConstant() bool { diff --git a/parser/exists.go b/parser/exists.go index 2c1402a..ff4573d 100644 --- a/parser/exists.go +++ b/parser/exists.go @@ -9,7 +9,7 @@ func (e Exists) StatementType() StatementType { } func (e Exists) ValueType() ValueType { - return NewValueType(DATA_TYPE_BOOLEAN, false) + return NewValueType(TypeBool{}, false) } func (e Exists) IsConstant() bool { diff --git a/parser/function.go b/parser/function.go index ed46f3f..b3ddd3d 100644 --- a/parser/function.go +++ b/parser/function.go @@ -74,7 +74,7 @@ func functionValueType(returnTypes []ValueType) ValueType { var valueType ValueType if len(returnTypes) > 1 { - valueType = NewValueType(DATA_TYPE_MULTIPLE, false) + valueType = NewValueType(TypeMultiple{}, false) } else { valueType = returnTypes[0] } diff --git a/parser/input.go b/parser/input.go index 77be324..73dd0ac 100644 --- a/parser/input.go +++ b/parser/input.go @@ -9,7 +9,7 @@ func (i Input) StatementType() StatementType { } func (i Input) ValueType() ValueType { - return ValueType{dataType: DATA_TYPE_STRING} + return NewValueType(TypeString{}, false) } func (i Input) IsConstant() bool { diff --git a/parser/iota.go b/parser/iota.go index 78d5829..da5f354 100644 --- a/parser/iota.go +++ b/parser/iota.go @@ -8,7 +8,7 @@ func (i Iota) StatementType() StatementType { } func (i Iota) ValueType() ValueType { - return NewValueType(DATA_TYPE_INTEGER, false) + return NewValueType(TypeInt{}, false) } func (i Iota) IsConstant() bool { diff --git a/parser/itoa.go b/parser/itoa.go index 89a30ed..cfe7163 100644 --- a/parser/itoa.go +++ b/parser/itoa.go @@ -9,7 +9,7 @@ func (e Itoa) StatementType() StatementType { } func (e Itoa) ValueType() ValueType { - return NewValueType(DATA_TYPE_STRING, false) + return NewValueType(TypeString{}, false) } func (o Itoa) IsConstant() bool { diff --git a/parser/len.go b/parser/len.go index 067ec1e..3e2d39f 100644 --- a/parser/len.go +++ b/parser/len.go @@ -9,7 +9,7 @@ func (l Len) StatementType() StatementType { } func (l Len) ValueType() ValueType { - return NewValueType(DATA_TYPE_INTEGER, false) + return NewValueType(TypeInt{}, false) } func (l Len) IsConstant() bool { diff --git a/parser/literals.go b/parser/literals.go index 16f6eb8..2129c9a 100644 --- a/parser/literals.go +++ b/parser/literals.go @@ -9,7 +9,7 @@ func (l BooleanLiteral) StatementType() StatementType { } func (l BooleanLiteral) ValueType() ValueType { - return ValueType{dataType: DATA_TYPE_BOOLEAN} + return NewValueType(TypeBool{}, false) } func (l BooleanLiteral) IsConstant() bool { @@ -29,7 +29,7 @@ func (l IntegerLiteral) StatementType() StatementType { } func (l IntegerLiteral) ValueType() ValueType { - return ValueType{dataType: DATA_TYPE_INTEGER} + return NewValueType(TypeInt{}, false) } func (l IntegerLiteral) IsConstant() bool { @@ -49,7 +49,7 @@ func (l StringLiteral) StatementType() StatementType { } func (l StringLiteral) ValueType() ValueType { - return ValueType{dataType: DATA_TYPE_STRING} + return NewValueType(TypeString{}, false) } func (l StringLiteral) IsConstant() bool { diff --git a/parser/logicaloperation.go b/parser/logicaloperation.go index b1f7357..a28d7e8 100644 --- a/parser/logicaloperation.go +++ b/parser/logicaloperation.go @@ -11,7 +11,7 @@ func (l LogicalOperation) StatementType() StatementType { } func (l LogicalOperation) ValueType() ValueType { - return ValueType{dataType: DATA_TYPE_BOOLEAN} + return NewValueType(TypeBool{}, false) } func (l LogicalOperation) IsConstant() bool { diff --git a/parser/parser.go b/parser/parser.go index 6bb8647..6ba6eda 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -51,8 +51,7 @@ type foundTypeDefinition struct { type context struct { imports map[string]string // Maps import aliases to file hashes. - types map[string]typeDefinition // Stores the declared types. - structs map[string]StructDeclaration // Stores the declared structs. + types map[string]Type // Stores the declared types. namedValues map[string][]NamedValue // Stores the variable/constant name to variable/constant relation. functions map[string]FunctionDefinition // Stores the function name to function relation. scopeStack []scope // Stores the current scopes. @@ -63,21 +62,18 @@ type context struct { func newContext() context { c := context{ imports: map[string]string{}, - types: map[string]typeDefinition{}, - structs: map[string]StructDeclaration{}, + types: map[string]Type{}, namedValues: map[string][]NamedValue{}, functions: map[string]FunctionDefinition{}, layer: -1, // Init at -1 since program increases it right away. iotaCounter: 0, } - // Define elementary types. - c.addType(DATA_TYPE_BOOLEAN, NewValueType(DATA_TYPE_BOOLEAN, false), false, true) - c.addType(DATA_TYPE_INTEGER, NewValueType(DATA_TYPE_INTEGER, false), false, true) - c.addType(DATA_TYPE_STRING, NewValueType(DATA_TYPE_STRING, false), false, true) - - // Define error alias. - c.addType(DATA_TYPE_ERROR, NewValueType(DATA_TYPE_STRING, false), true, false) + // Add elementary types. + c.addType(NewValueType(TypeBool{}, false)) + c.addType(NewValueType(TypeInt{}, false)) + c.addType(NewValueType(TypeString{}, false)) + c.addType(NewValueType(TypeError{}, false)) return c } @@ -143,31 +139,19 @@ func (c context) addImport(alias string, hash string) error { return nil } -func (c context) addType(typeName string, valueType ValueType, isAlias bool, isElementary bool) error { - _, exists := c.findType(typeName, false) +func (c context) addType(valueType ValueType) error { + t := valueType.Type() + name := t.Name() + _, exists := c.findType(name) if exists { - return fmt.Errorf("type %s has already been defined", typeName) + return fmt.Errorf("type %s has already been defined", name) } if valueType.IsSlice() { // TODO: Add support. - return errors.New("slices are not allowed yet in type definitions") - } - c.types[typeName] = typeDefinition{ - valueType, - isAlias, - isElementary, + return errors.New("slices are not allowed yet in type declarations") } - return nil -} - -func (c context) addStruct(structName string, structDeclaration StructDeclaration) error { - _, exists := c.findStruct(structName) - - if exists { - return fmt.Errorf("struct %s has already been defined", structName) - } - c.structs[structName] = structDeclaration + c.types[name] = t return nil } @@ -205,35 +189,9 @@ func (c context) findImport(alias string) (string, bool) { return hash, exists } -func (c context) findType(typeName string, searchUntilElementary bool) (foundTypeDefinition, bool) { - var foundDefinition foundTypeDefinition - typeDefinition, exists := c.types[typeName] - - if exists { - foundDefinitionTemp := foundTypeDefinition{ - typeDefinition: typeDefinition, - name: typeName, - } - - // If the defined type is an alias, trace it down to the root. - if typeDefinition.isAlias || (searchUntilElementary && !typeDefinition.isElementary) { - foundDefinitionTemp, exists = c.findType(typeDefinition.valueType.DataType(), searchUntilElementary) - } - if exists { - foundDefinition = foundDefinitionTemp - } - } - return foundDefinition, exists -} - -func (c context) findStruct(name string) (StructDeclaration, bool) { - structDeclaration, exists := c.structs[name] - return structDeclaration, exists -} - -func (c context) isStruct(dataType DataType) bool { - _, exists := c.findStruct(dataType) - return exists +func (c context) findType(typeName string) (Type, bool) { + t, exists := c.types[typeName] + return t, exists } func (c context) findNamedValue(name string, prefix string, global bool) (NamedValue, bool) { @@ -268,7 +226,6 @@ func (c context) clone() context { return context{ imports: maps.Clone(c.imports), types: maps.Clone(c.types), - structs: maps.Clone(c.structs), namedValues: maps.Clone(c.namedValues), // TODO: Make sure this is appropriate cloning because each entry contains a slice. functions: maps.Clone(c.functions), scopeStack: slices.Clone(c.scopeStack), @@ -378,10 +335,10 @@ func allowedBinaryOperators(t ValueType) []BinaryOperator { operators := []BinaryOperator{} if !t.IsSlice() { - switch t.DataType() { - case DATA_TYPE_INTEGER: + switch t.Type().ElementaryType().Kind() { + case TypeKindInt: operators = []BinaryOperator{BINARY_OPERATOR_MULTIPLICATION, BINARY_OPERATOR_DIVISION, BINARY_OPERATOR_MODULO, BINARY_OPERATOR_ADDITION, BINARY_OPERATOR_SUBTRACTION} - case DATA_TYPE_STRING: + case TypeKindString: operators = []BinaryOperator{BINARY_OPERATOR_ADDITION} default: } @@ -394,12 +351,12 @@ func allowedCompareOperators(t ValueType) []CompareOperator { operators := []CompareOperator{} if !t.IsSlice() { - switch t.DataType() { - case DATA_TYPE_BOOLEAN: + switch t.Type().ElementaryType().Kind() { + case TypeKindBool: operators = []CompareOperator{COMPARE_OPERATOR_EQUAL, COMPARE_OPERATOR_NOT_EQUAL} - case DATA_TYPE_INTEGER: + case TypeKindInt: operators = []CompareOperator{COMPARE_OPERATOR_EQUAL, COMPARE_OPERATOR_NOT_EQUAL, COMPARE_OPERATOR_LESS, COMPARE_OPERATOR_LESS_OR_EQUAL, COMPARE_OPERATOR_GREATER, COMPARE_OPERATOR_GREATER_OR_EQUAL} - case DATA_TYPE_STRING: + case TypeKindString: operators = []CompareOperator{COMPARE_OPERATOR_EQUAL, COMPARE_OPERATOR_NOT_EQUAL, COMPARE_OPERATOR_LESS, COMPARE_OPERATOR_LESS_OR_EQUAL, COMPARE_OPERATOR_GREATER, COMPARE_OPERATOR_GREATER_OR_EQUAL} default: // For other types no operations are permitted. @@ -409,22 +366,21 @@ func allowedCompareOperators(t ValueType) []CompareOperator { } func defaultVarValue(valueType ValueType, ctx context) (Expression, error) { - dataType := valueType.DataType() - elementaryType, exists := ctx.findType(dataType, true) + foundType, exists := ctx.findType(valueType.Type().Name()) if exists { - elementaryDataType := elementaryType.valueType.DataType() + elementaryDataType := foundType.ElementaryType() if !valueType.IsSlice() { - switch elementaryDataType { - case DATA_TYPE_BOOLEAN: + switch elementaryDataType.Kind() { + case TypeKindBool: return BooleanLiteral{}, nil - case DATA_TYPE_INTEGER: + case TypeKindInt: return IntegerLiteral{}, nil - case DATA_TYPE_STRING: + case TypeKindString: return StringLiteral{}, nil - case DATA_TYPE_STRUCT: - structDeclaration, exists := ctx.findStruct(dataType) + case TypeKindStruct: + structDeclaration, exists := elementaryDataType.(StructDeclaration) if exists { structValues := []StructValue{} @@ -450,7 +406,7 @@ func defaultVarValue(valueType ValueType, ctx context) (Expression, error) { } } } else { - return SliceInstantiation{dataType: elementaryDataType}, nil + return SliceInstantiation{t: elementaryDataType}, nil } } return nil, fmt.Errorf("no default value found for type %s", valueType.String()) @@ -554,6 +510,10 @@ func (p *Parser) variableNotDefinedError(variable string, token lexer.Token) err return p.notDefinedError("variable", variable, token) } +func (p *Parser) typeNotDefinedError(t string, token lexer.Token) error { + return p.notDefinedError("type", t, token) +} + func (p Parser) peek() lexer.Token { return p.peekAt(0) } @@ -1230,7 +1190,7 @@ func (p *Parser) evaluateBlock(callback blockCallback, ctx context, scope scope) func (p *Parser) evaluateValueType(ctx context) (ValueType, error) { nextToken := p.peek() - evaluatedType := NewValueType(DATA_TYPE_UNKNOWN, false) + evaluatedType := NewValueType(TypeUnknown{}, false) // Evaluate if value type is a slice type. if nextToken.Type() == lexer.OPENING_SQUARE_BRACKET { @@ -1249,30 +1209,30 @@ func (p *Parser) evaluateValueType(ctx context) (ValueType, error) { return evaluatedType, p.expectedError("data type", nextToken) } p.eat() // Eat data type token. - foundDefinition, exists := ctx.findType(nextToken.Value(), false) + foundDefinition, exists := ctx.findType(nextToken.Value()) if !exists { return evaluatedType, p.expectedError("valid data type", nextToken) } - evaluatedType.dataType = foundDefinition.name + evaluatedType.t = foundDefinition return evaluatedType, nil } -func (p *Parser) evaluateStructDeclaration(ctx context) (Statement, error) { +func (p *Parser) evaluateStructDeclaration(name string, ctx context) (StructDeclaration, error) { structToken := p.eat() if structToken.Type() != lexer.STRUCT { - return nil, p.expectedKeywordError("struct", structToken) + return StructDeclaration{}, p.expectedKeywordError("struct", structToken) } nextToken := p.eat() if nextToken.Type() != lexer.OPENING_CURLY_BRACKET { - return nil, p.expectedError(`"{"`, nextToken) + return StructDeclaration{}, p.expectedError(`"{"`, nextToken) } nextToken = p.eat() if nextToken.Type() != lexer.NEWLINE { - return nil, p.expectedNewlineError(nextToken) + return StructDeclaration{}, p.expectedNewlineError(nextToken) } fields := []StructField{} @@ -1281,18 +1241,18 @@ func (p *Parser) evaluateStructDeclaration(ctx context) (Statement, error) { nameTokens, err := p.evaluateNames() if err != nil { - return nil, err + return StructDeclaration{}, err } valueTypeToken := p.peek() valueType, err := p.evaluateValueType(ctx) if err != nil { - return nil, err + return StructDeclaration{}, err } // Don't allow nested structs for now. - if ctx.isStruct(valueType.DataType()) { - return nil, p.atError("nested structs are not allowed", valueTypeToken) + if valueType.Type().Kind() == TypeKindStruct { + return StructDeclaration{}, p.atError("nested structs are not allowed", valueTypeToken) } for _, nameToken := range nameTokens { @@ -1304,7 +1264,7 @@ func (p *Parser) evaluateStructDeclaration(ctx context) (Statement, error) { nextToken = p.eat() if nextToken.Type() != lexer.NEWLINE { - return nil, p.expectedNewlineError(nextToken) + return StructDeclaration{}, p.expectedNewlineError(nextToken) } nextToken = p.peek() @@ -1313,9 +1273,7 @@ func (p *Parser) evaluateStructDeclaration(ctx context) (Statement, error) { break } } - return StructDeclaration{ - fields, - }, nil + return NewStructDeclaration(name, fields), nil } func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { @@ -1340,7 +1298,6 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { } name := nameToken.Value() valueTypeToken := p.peek() - isElementary := false var valueType ValueType var err error @@ -1349,18 +1306,12 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { if isAlias { return nil, p.atError("struct alias is not supported", assignToken) } - structDeclaration, err := p.evaluateStructDeclaration(ctx) + structDeclaration, err := p.evaluateStructDeclaration(name, ctx) if err != nil { return nil, err } - err = ctx.addStruct(name, structDeclaration.(StructDeclaration)) - - if err != nil { - return nil, p.atError(err.Error(), nameToken) - } - valueType = NewValueType(DATA_TYPE_STRUCT, false) - isElementary = true + valueType = NewValueType(structDeclaration, false) } else { valueType, err = p.evaluateValueType(ctx) @@ -1368,10 +1319,11 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { return nil, err } } - err = ctx.addType(name, valueType, isAlias, isElementary) + customType := NewValueType(NewTypeCustom(name, isAlias, valueType.Type()), valueType.IsSlice()) + err = ctx.addType(customType) if err != nil { - return nil, p.atError(err.Error(), valueTypeToken) + return nil, p.atError(err.Error(), nameToken) } return TypeDeclaration{name}, nil } @@ -1446,7 +1398,7 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat return nil, err } } - specifiedType := NewValueType(DATA_TYPE_UNKNOWN, false) + specifiedType := NewValueType(TypeUnknown{}, false) namedValues := []NamedValue{} reuseIota := false @@ -1470,10 +1422,10 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat nextToken = p.peek() } nextTokenType := nextToken.Type() - dataType := specifiedType.DataType() + dataType := specifiedType.Type() // If no data type has been specified and no value is being assigned, return an error. - if dataType == DATA_TYPE_UNKNOWN && nextTokenType != lexer.ASSIGN_OPERATOR { + if dataType.Kind() == TypeKindUnknown && nextTokenType != lexer.ASSIGN_OPERATOR { // If iota has already been used in constant definition and only one value // needs to be assigned, the iota value gets used automatically. if evalConst && useIota && nameTokensLength == 1 { @@ -1505,7 +1457,7 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat variableValueType := namedValue.ValueType() // If the variable already exists, make sure it has the same type as the specified type. - if exists && specifiedType.DataType() != DATA_TYPE_UNKNOWN && !specifiedType.Equals(variableValueType) { + if exists && specifiedType.Type().Kind() != TypeKindUnknown && !specifiedType.Equals(variableValueType) { return nil, p.atError(fmt.Sprintf(`%s %s already exists but has type %s`, noun, name, variableValueType.String()), nextToken) } storedName := name @@ -1587,7 +1539,7 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat } // If a type has been specified, make sure the returned types fit this type. - if specifiedType.DataType() != DATA_TYPE_UNKNOWN { + if specifiedType.Type().Kind() != TypeKindUnknown { for _, valueType := range valuesTypes { if !valueType.Equals(specifiedType) { return nil, p.expectedError(fmt.Sprintf("%s but got %s", specifiedType.String(), valueType.String()), nextToken) @@ -1600,7 +1552,7 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat valueValueType := valuesTypes[i] variableValueType := namedValue.ValueType() - if variableValueType.DataType() == DATA_TYPE_UNKNOWN { + if variableValueType.Type().Kind() == TypeKindUnknown { var updatedNamedValue NamedValue name, layer, public := namedValue.Name(), namedValue.Layer(), namedValue.Public() @@ -1845,7 +1797,7 @@ func (p *Parser) evaluateVarAssignment(ctx context) (Statement, error) { valueType := valuesTypes[i] expectedValueType := namedValue.ValueType() - if valueType != expectedValueType { + if !valueType.Equals(expectedValueType) { return nil, p.expectedError(fmt.Sprintf("%s but got %s", expectedValueType.String(), valueType.String()), valuesToken) } variables = append(variables, NewVariable(name, valueType, namedValue.Layer(), isPublic(name))) @@ -2361,14 +2313,14 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { } iterableValueType := iterableExpression.ValueType() layer := ctx.layer + 1 - indexVar := NewVariable(indexVarName, NewValueType(DATA_TYPE_INTEGER, false), layer, false) + indexVar := NewVariable(indexVarName, NewValueType(TypeInt{}, false), layer, false) var iterableEvaluation Expression if iterableValueType.IsSlice() { iterableEvaluation = SliceEvaluation{ - value: iterableExpression, - index: VariableEvaluation{indexVar}, - dataType: iterableValueType.DataType(), + value: iterableExpression, + index: VariableEvaluation{indexVar}, + t: iterableValueType.Type(), } } else if iterableValueType.IsString() { iterableEvaluation = StringSubscript{ @@ -2558,10 +2510,10 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { return nil, p.expectedIdentifierError(identifierToken) } typeName := identifierToken.Value() - foundElementaryDefinition, exists := ctx.findType(typeName, true) + definitionTypeDeclaration, exists := ctx.findType(typeName) if !exists { - return nil, p.notDefinedError("type", typeName, identifierToken) + return nil, p.typeNotDefinedError(typeName, identifierToken) } nextToken := p.eat() @@ -2575,14 +2527,15 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { return nil, err } exprValueType := expr.ValueType() - exprBaseTypeDefinition, _ := ctx.findType(exprValueType.DataType(), true) - exprBaseValueType := exprBaseTypeDefinition.valueType - foundElementaryDefinitionValueType := foundElementaryDefinition.valueType - baseDefinition, _ := ctx.findType(typeName, false) - baseTypeName := baseDefinition.name - if !foundElementaryDefinitionValueType.Equals(exprBaseValueType) { - return nil, p.atError(fmt.Sprintf(`%s cannot be converted into %s`, exprValueType.String(), baseTypeName), nextToken) + if !exists { + return nil, p.typeNotDefinedError(typeName, identifierToken) + } + definitionAliasType := definitionTypeDeclaration.ElementaryType() + exprAliasType := exprValueType.Type().ElementaryType() + + if !definitionAliasType.Equals(exprAliasType) { + return nil, p.atError(fmt.Sprintf(`%s cannot be converted into %s`, exprValueType.String(), definitionTypeDeclaration.Name()), nextToken) } nextToken = p.eat() @@ -2592,7 +2545,7 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { return TypeDefinition{ value: expr, - valueType: NewValueType(baseTypeName, exprValueType.IsSlice()), + valueType: NewValueType(definitionTypeDeclaration, exprValueType.IsSlice()), }, nil } @@ -2618,16 +2571,17 @@ func (p *Parser) evaluateStructEvaluation(importAlias string, ctx context) (Expr if fieldToken.Type() != lexer.IDENTIFIER { return nil, p.expectedError("field name", fieldToken) } - structType := namedValue.ValueType().DataType() - structDeclararion, exists := ctx.findStruct(structType) + typeDeclaration := namedValue.ValueType().Type() + typeDeclarationKind := typeDeclaration.Kind() - if !exists { - return nil, p.atError(fmt.Sprintf("no struct declaration found for %s", structType), identifierToken) + if typeDeclarationKind != TypeKindStruct { + return nil, p.expectedError(fmt.Sprintf("%s but got %s", TypeKindStruct, typeDeclarationKind), identifierToken) } + structDeclaration := typeDeclaration.(StructDeclaration) // Check field. fieldName := fieldToken.Value() - foundField, err := structDeclararion.FindField(fieldName) + foundField, err := structDeclaration.FindField(fieldName) if err != nil { return nil, p.atError(err.Error(), fieldToken) @@ -2789,7 +2743,7 @@ func (p *Parser) evaluateSingleExpression(ctx context) (Expression, error) { switch nextToken.Type() { case lexer.OPENING_ROUND_BRACKET: // If a type exists with the provided name, it's a type-cast/-instantiation. - _, exists := ctx.findType(value, false) + _, exists := ctx.findType(value) if exists { expr, err = p.evaluateTypeDefinition(ctx) @@ -3017,7 +2971,7 @@ func (p *Parser) evaluateComparison(ctx context) (Expression, error) { rightType := rightExpression.ValueType() if !leftType.Equals(rightType) { - return nil, p.expectedError(fmt.Sprintf("same comparison types but got %s and %s", leftType.DataType(), rightType.String()), operatorToken) + return nil, p.expectedError(fmt.Sprintf("same comparison types but got %s and %s", leftType.String(), rightType.String()), operatorToken) } allowedOperators := allowedCompareOperators(leftType) @@ -3279,8 +3233,8 @@ func (p *Parser) evaluateSliceInstantiation(ctx context) (Expression, error) { return nil, p.expectedError(`"}"`, nextToken) } return SliceInstantiation{ - dataType: sliceValueType.DataType(), - values: values, + t: sliceValueType.Type(), + values: values, }, nil } @@ -3305,7 +3259,7 @@ func (p *Parser) evaluateSubscript(ctx context) (Expression, error) { valueType := value.ValueType() isSlice := valueType.IsSlice() - if !isSlice && valueType.DataType() != DATA_TYPE_STRING { + if !isSlice && valueType.Type().Kind() != TypeKindString { return nil, p.expectedError("slice or string", valueToken) } nextToken := p.eat() @@ -3332,7 +3286,7 @@ func (p *Parser) evaluateSubscript(ctx context) (Expression, error) { startIndexValueType := startIndex.ValueType() if !startIndexValueType.IsInt() { - return nil, p.expectedError(fmt.Sprintf("%s as start-index but got %s", DATA_TYPE_INTEGER, startIndexValueType.String()), startToken) + return nil, p.expectedError(fmt.Sprintf("%s as start-index but got %s", TypeKindInt, startIndexValueType.String()), startToken) } nextToken = p.peek() @@ -3383,7 +3337,7 @@ func (p *Parser) evaluateSubscript(ctx context) (Expression, error) { endIndexValueType := endIndex.ValueType() if !endIndexValueType.IsInt() { - return nil, p.expectedError(fmt.Sprintf("%s as stop-index but got %s", DATA_TYPE_INTEGER, endIndexValueType.String()), endToken) + return nil, p.expectedError(fmt.Sprintf("%s as stop-index but got %s", TypeKindInt, endIndexValueType.String()), endToken) } if !isSlice { @@ -3394,9 +3348,9 @@ func (p *Parser) evaluateSubscript(ctx context) (Expression, error) { }, nil } return SliceEvaluation{ - value: value, - index: startIndex, - dataType: valueType.DataType(), + value: value, + index: startIndex, + t: valueType.Type(), }, nil } @@ -3433,7 +3387,7 @@ func (p *Parser) evaluateSliceAssignment(ctx context) (Statement, error) { indexValueType := index.ValueType() if !indexValueType.IsInt() { - return nil, p.expectedError(fmt.Sprintf("%s as index but got %s", DATA_TYPE_INTEGER, indexValueType.String()), nextToken) + return nil, p.expectedError(fmt.Sprintf("%s as index but got %s", TypeKindInt, indexValueType.String()), nextToken) } nextToken = p.eat() @@ -3451,11 +3405,11 @@ func (p *Parser) evaluateSliceAssignment(ctx context) (Statement, error) { if err != nil { return nil, err } - variableDataType := variableValueType.DataType() - assignedDataType := value.ValueType().DataType() + variableType := variableValueType.Type() + assignedType := value.ValueType().Type() - if variableDataType != assignedDataType { - return nil, p.expectedError(fmt.Sprintf("%s value but got %s", variableDataType, assignedDataType), valueToken) + if !variableType.Equals(assignedType) { + return nil, p.expectedError(fmt.Sprintf("%s value but got %s", variableType.Name(), assignedType.Name()), valueToken) } return SliceAssignment{ Variable: namedValue.(Variable), @@ -3479,23 +3433,13 @@ func (p *Parser) evaluateStructAssignment(ctx context) (Statement, error) { return nil, p.constantError(name, nameToken) } namedValueValueType := namedValue.ValueType() - baseTypeDefinition, exists := ctx.findType(namedValueValueType.DataType(), false) - - if !exists { - return nil, p.atError(fmt.Sprintf(`type of %s not be found`, name), nameToken) - } - baseValueType := baseTypeDefinition.valueType - if baseValueType.IsSlice() { + if namedValueValueType.IsSlice() { return nil, p.expectedError("struct but got slice", nameToken) - } else if baseValueType.DataType() != DATA_TYPE_STRUCT { - return nil, p.expectedError(fmt.Sprintf("struct but variable is of type %s", baseValueType.String()), nameToken) - } - structDeclaration, exists := ctx.findStruct(baseTypeDefinition.name) - - if !exists { - return nil, p.atError(fmt.Sprintf(`declaration of struct %s not be found`, name), nameToken) + } else if namedValueValueType.Type().Kind() != TypeKindStruct { + return nil, p.expectedError(fmt.Sprintf("struct but variable is of type %s", namedValueValueType.String()), nameToken) } + structDeclaration := namedValueValueType.Type().(StructDeclaration) nextToken := p.eat() if nextToken.Type() != lexer.DOT { @@ -3555,7 +3499,7 @@ func (p *Parser) evaluateIncrementDecrement(ctx context) (Statement, error) { valueType := variable.ValueType() if !valueType.IsInt() { - return nil, p.expectedError(fmt.Sprintf("%s but got %s", NewValueType(DATA_TYPE_INTEGER, false).String(), valueType.String()), identifierToken) + return nil, p.expectedError(fmt.Sprintf("%s but got %s", NewValueType(TypeInt{}, false).String(), valueType.String()), identifierToken) } operationToken := p.eat() increment := true diff --git a/parser/read.go b/parser/read.go index 7e65730..4162a43 100644 --- a/parser/read.go +++ b/parser/read.go @@ -9,7 +9,7 @@ func (r Read) StatementType() StatementType { } func (r Read) ValueType() ValueType { - return NewValueType(DATA_TYPE_STRING, false) + return NewValueType(TypeString{}, false) } func (r Read) IsConstant() bool { diff --git a/parser/slice.go b/parser/slice.go index 199ce9f..69cf930 100644 --- a/parser/slice.go +++ b/parser/slice.go @@ -1,8 +1,8 @@ package parser type SliceInstantiation struct { - dataType DataType - values []Expression + t Type + values []Expression } func (s SliceInstantiation) StatementType() StatementType { @@ -10,7 +10,7 @@ func (s SliceInstantiation) StatementType() StatementType { } func (s SliceInstantiation) ValueType() ValueType { - return ValueType{dataType: s.dataType, isSlice: true} + return ValueType{t: s.t, isSlice: true} } func (s SliceInstantiation) IsConstant() bool { @@ -22,9 +22,9 @@ func (s SliceInstantiation) Values() []Expression { } type SliceEvaluation struct { - value Expression - index Expression - dataType DataType + value Expression + index Expression + t Type } func (s SliceEvaluation) StatementType() StatementType { @@ -40,7 +40,7 @@ func (s SliceEvaluation) Index() Expression { } func (s SliceEvaluation) ValueType() ValueType { - return ValueType{dataType: s.dataType} + return ValueType{t: s.t} } func (s SliceEvaluation) IsConstant() bool { diff --git a/parser/string.go b/parser/string.go index ef49e8c..7ceabfc 100644 --- a/parser/string.go +++ b/parser/string.go @@ -11,7 +11,7 @@ func (s StringSubscript) StatementType() StatementType { } func (s StringSubscript) ValueType() ValueType { - return NewValueType(DATA_TYPE_STRING, false) + return NewValueType(TypeString{}, false) } func (s StringSubscript) IsConstant() bool { diff --git a/parser/struct.go b/parser/struct.go index b073311..11b57a1 100644 --- a/parser/struct.go +++ b/parser/struct.go @@ -16,17 +16,63 @@ func (f StructField) ValueType() ValueType { } type StructDeclaration struct { + name string fields []StructField } -func (d StructDeclaration) StatementType() StatementType { - return STATEMENT_TYPE_STRUCT_DECLARATION +func NewStructDeclaration(name string, fields []StructField) StructDeclaration { + return StructDeclaration{ + name: name, + fields: fields, + } } func (d StructDeclaration) Fields() []StructField { return d.fields } +func (d StructDeclaration) Name() string { + return d.name +} + +func (d StructDeclaration) IsAlias() bool { + return false +} + +func (d StructDeclaration) Kind() TypeKind { + return TypeKindStruct +} + +func (d StructDeclaration) Base() Type { + return nil +} + +func (d StructDeclaration) Equals(c Type) bool { + compareType, isDeclaration := c.(StructDeclaration) + + if !isDeclaration { + return false + } + fieldsD1 := d.Fields() + fieldsD2 := compareType.Fields() + + if len(fieldsD1) != len(fieldsD2) { + return false + } + + for i, fieldD1 := range fieldsD1 { + fieldD2 := fieldsD2[i] + + if fieldD1.Name() != fieldD2.Name() || !fieldD1.ValueType().Equals(fieldD2.ValueType()) { + return false + } + } + return true +} + +func (t StructDeclaration) ElementaryType() Type { return elementaryType(t) } +func (t StructDeclaration) AliasedType() Type { return aliasedType(t) } + func (d StructDeclaration) FindField(name string) (StructField, error) { for _, field := range d.Fields() { if field.Name() == name { diff --git a/parser/types.go b/parser/types.go index 6f78eeb..dbee197 100644 --- a/parser/types.go +++ b/parser/types.go @@ -6,26 +6,178 @@ import ( type StatementType string type AssignmentType string -type DataType = string +type TypeKind string type CompareOperator = string type UnaryOperator = string type BinaryOperator = string type LogicalOperator = string +const ( + TypeKindUnknown TypeKind = "unknown" + TypeKindBool TypeKind = "bool" + TypeKindInt TypeKind = "int" + TypeKindString TypeKind = "string" + TypeKindError TypeKind = "error" + TypeKindCustom TypeKind = "custom" + TypeKindStruct TypeKind = "struct" + TypeKindMultiple TypeKind = "multiple" +) + +type Type interface { + Name() string + IsAlias() bool + Kind() TypeKind + Base() Type + Equals(compareType Type) bool + ElementaryType() Type + AliasedType() Type +} + +func equals(t1 Type, t2 Type) bool { + base1 := t1.AliasedType() + base2 := t2.AliasedType() + + return base1.Name() == base2.Name() && base1.Kind() == base2.Kind() +} + +func elementaryType(t Type) Type { + base := t.Base() + + if base != nil { + return base.ElementaryType() + } + return t +} + +func aliasedType(t Type) Type { + base := t.Base() + + if t.IsAlias() && base != nil { + return base.AliasedType() + } + return t +} + +type TypeUnknown struct{} + +func (t TypeUnknown) Name() string { return "unknown" } +func (t TypeUnknown) IsAlias() bool { return false } +func (t TypeUnknown) Kind() TypeKind { return TypeKindUnknown } +func (t TypeUnknown) Base() Type { return nil } +func (t TypeUnknown) Equals(compareType Type) bool { return equals(t, compareType) } +func (t TypeUnknown) ElementaryType() Type { return elementaryType(t) } +func (t TypeUnknown) AliasedType() Type { return aliasedType(t) } + +type TypeCustom struct { + name string + isAlias bool + base Type +} + +func NewTypeCustom(name string, isAlias bool, base Type) TypeCustom { + return TypeCustom{ + name, + isAlias, + base, + } +} + +func (t TypeCustom) Name() string { return t.name } +func (t TypeCustom) IsAlias() bool { return t.isAlias } +func (t TypeCustom) Kind() TypeKind { return TypeKindCustom } +func (t TypeCustom) Base() Type { return t.base } +func (t TypeCustom) Equals(compareType Type) bool { return equals(t, compareType) } +func (t TypeCustom) ElementaryType() Type { return elementaryType(t) } +func (t TypeCustom) AliasedType() Type { return aliasedType(t) } + +type TypeBool struct{} + +func (t TypeBool) Name() string { return "bool" } +func (t TypeBool) IsAlias() bool { return false } +func (t TypeBool) Kind() TypeKind { return TypeKindBool } +func (t TypeBool) Base() Type { return nil } +func (t TypeBool) Equals(compareType Type) bool { return equals(t, compareType) } +func (t TypeBool) ElementaryType() Type { return elementaryType(t) } +func (t TypeBool) AliasedType() Type { return aliasedType(t) } + +type TypeInt struct{} + +func (t TypeInt) Name() string { return "int" } +func (t TypeInt) IsAlias() bool { return false } +func (t TypeInt) Kind() TypeKind { return TypeKindInt } +func (t TypeInt) Base() Type { return nil } +func (t TypeInt) Equals(compareType Type) bool { return equals(t, compareType) } +func (t TypeInt) ElementaryType() Type { return elementaryType(t) } +func (t TypeInt) AliasedType() Type { return aliasedType(t) } + +type TypeString struct{} + +func (t TypeString) Name() string { return "string" } +func (t TypeString) IsAlias() bool { return false } +func (t TypeString) Kind() TypeKind { return TypeKindString } +func (t TypeString) Base() Type { return nil } +func (t TypeString) Equals(compareType Type) bool { return equals(t, compareType) } +func (t TypeString) ElementaryType() Type { return elementaryType(t) } +func (t TypeString) AliasedType() Type { return aliasedType(t) } + +type TypeError struct{} + +func (t TypeError) Name() string { return "error" } +func (t TypeError) IsAlias() bool { return true } +func (t TypeError) Kind() TypeKind { return TypeKindError } +func (t TypeError) Base() Type { return TypeString{} } +func (t TypeError) Equals(compareType Type) bool { return equals(t, compareType) } +func (t TypeError) ElementaryType() Type { return elementaryType(t) } +func (t TypeError) AliasedType() Type { return aliasedType(t) } + +type TypeMultiple struct { + types []Type +} + +func (t TypeMultiple) Name() string { return "multiple" } +func (t TypeMultiple) IsAlias() bool { return false } +func (t TypeMultiple) Kind() TypeKind { return TypeKindMultiple } +func (t TypeMultiple) Base() Type { return nil } +func (t TypeMultiple) Equals(c Type) bool { + ct, isType := c.(TypeMultiple) + + if isType { + typesT1 := t.Types() + typesT2 := ct.Types() + + if len(typesT1) != len(typesT2) { + return false + } + + for i, t1 := range typesT1 { + if !t1.Equals(typesT2[i]) { + return false + } + } + } + return false +} +func (t TypeMultiple) ElementaryType() Type { return elementaryType(t) } +func (t TypeMultiple) AliasedType() Type { return aliasedType(t) } + +func (t TypeMultiple) Types() []Type { + return t.types +} + type ValueType struct { - dataType DataType - isSlice bool + t Type + isSlice bool } -func NewValueType(dataType DataType, isSlice bool) ValueType { +func NewValueType(t Type, isSlice bool) ValueType { return ValueType{ - dataType, + t, isSlice, } } -func (vt ValueType) DataType() DataType { - return vt.dataType +func (vt ValueType) Type() Type { + return vt.t } func (vt ValueType) IsSlice() bool { @@ -33,7 +185,7 @@ func (vt ValueType) IsSlice() bool { } func (vt ValueType) String() string { - s := string(vt.dataType) + s := string(vt.Type().Name()) if vt.isSlice { s = fmt.Sprintf("[]%s", s) @@ -42,26 +194,27 @@ func (vt ValueType) String() string { } func (vt ValueType) Equals(valueType ValueType) bool { - return vt.DataType() == valueType.DataType() && vt.IsSlice() == valueType.IsSlice() + return vt.Type().Equals(valueType.Type()) && vt.IsSlice() == valueType.IsSlice() } func (vt ValueType) IsBool() bool { - return vt.isNonSliceType(DATA_TYPE_BOOLEAN) + return vt.isNonSliceType(TypeBool{}) } func (vt ValueType) IsInt() bool { - return vt.isNonSliceType(DATA_TYPE_INTEGER) + return vt.isNonSliceType(TypeInt{}) } func (vt ValueType) IsString() bool { - return vt.isNonSliceType(DATA_TYPE_STRING) + return vt.isNonSliceType(TypeString{}) } -func (vt ValueType) isNonSliceType(dataType DataType) bool { - return vt.DataType() == dataType && !vt.IsSlice() +func (vt ValueType) isNonSliceType(t Type) bool { + return vt.Type().Equals(t) && !vt.IsSlice() } const ( + STATEMENT_TYPE_NOP StatementType = "nop" STATEMENT_TYPE_PROGRAM StatementType = "program" STATEMENT_TYPE_TYPE_DECLARATION StatementType = "type declaration" STATEMENT_TYPE_TYPE_DEFINITION StatementType = "type definition" @@ -118,16 +271,6 @@ const ( ASSIGNMENT_TYPE_CALL AssignmentType = "call" ) -const ( - DATA_TYPE_UNKNOWN DataType = "unknown" - DATA_TYPE_MULTIPLE DataType = "multiple" - DATA_TYPE_BOOLEAN DataType = "bool" - DATA_TYPE_INTEGER DataType = "int" - DATA_TYPE_STRING DataType = "string" - DATA_TYPE_ERROR DataType = "error" - DATA_TYPE_STRUCT DataType = "struct" -) - const ( COMPARE_OPERATOR_EQUAL CompareOperator = "==" COMPARE_OPERATOR_NOT_EQUAL CompareOperator = "!=" diff --git a/parser/write.go b/parser/write.go index d5fc30a..94de16b 100644 --- a/parser/write.go +++ b/parser/write.go @@ -11,7 +11,7 @@ func (w Write) StatementType() StatementType { } func (w Write) ValueType() ValueType { - return NewValueType(DATA_TYPE_ERROR, false) + return NewValueType(TypeError{}, false) } func (w Write) IsConstant() bool { diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 92be89d..23d5482 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -71,12 +71,12 @@ func (t *transpiler) evaluateValueTypeDefaultValue(valueType parser.ValueType) ( var defaultValue string conv := t.converter - switch valueType.DataType() { - case parser.DATA_TYPE_BOOLEAN: + switch valueType.Type().Kind() { + case parser.TypeKindBool: defaultValue = BoolToString(false) - case parser.DATA_TYPE_INTEGER: + case parser.TypeKindInt: defaultValue = IntToString(0) - case parser.DATA_TYPE_STRING: + case parser.TypeKindString: defaultValue = conv.StringToString("") default: return "", fmt.Errorf(`no default value defined for %s`, valueType.String()) From 6d681131f7270c7a254dad75dfeaa6724f03adf0 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 11 Sep 2025 19:22:44 +0200 Subject: [PATCH 12/22] Improve type declaration and fix struct assignment (https://github.com/monstermichl/TypeShell/issues/51) --- parser/parser.go | 14 +++++++++----- parser/struct.go | 4 ++-- parser/types.go | 7 ++++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index 6ba6eda..2d8a451 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1318,9 +1318,12 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { if err != nil { return nil, err } + t := valueType.Type() + + // Create a wrapper with the same type but different name. + valueType = NewValueType(NewTypeCustom(name, isAlias, t.Kind(), t), valueType.IsSlice()) } - customType := NewValueType(NewTypeCustom(name, isAlias, valueType.Type()), valueType.IsSlice()) - err = ctx.addType(customType) + err = ctx.addType(valueType) if err != nil { return nil, p.atError(err.Error(), nameToken) @@ -3433,13 +3436,14 @@ func (p *Parser) evaluateStructAssignment(ctx context) (Statement, error) { return nil, p.constantError(name, nameToken) } namedValueValueType := namedValue.ValueType() + namedValueBaseType := namedValueValueType.Type() if namedValueValueType.IsSlice() { return nil, p.expectedError("struct but got slice", nameToken) - } else if namedValueValueType.Type().Kind() != TypeKindStruct { - return nil, p.expectedError(fmt.Sprintf("struct but variable is of type %s", namedValueValueType.String()), nameToken) + } else if namedValueBaseType.Kind() != TypeKindStruct { + return nil, p.expectedError(fmt.Sprintf("struct but variable is of type %s", namedValueBaseType.Kind()), nameToken) } - structDeclaration := namedValueValueType.Type().(StructDeclaration) + structDeclaration := namedValueBaseType.(StructDeclaration) nextToken := p.eat() if nextToken.Type() != lexer.DOT { diff --git a/parser/struct.go b/parser/struct.go index 11b57a1..c2ccae5 100644 --- a/parser/struct.go +++ b/parser/struct.go @@ -22,8 +22,8 @@ type StructDeclaration struct { func NewStructDeclaration(name string, fields []StructField) StructDeclaration { return StructDeclaration{ - name: name, - fields: fields, + name, + fields, } } diff --git a/parser/types.go b/parser/types.go index dbee197..293d6bb 100644 --- a/parser/types.go +++ b/parser/types.go @@ -18,7 +18,6 @@ const ( TypeKindInt TypeKind = "int" TypeKindString TypeKind = "string" TypeKindError TypeKind = "error" - TypeKindCustom TypeKind = "custom" TypeKindStruct TypeKind = "struct" TypeKindMultiple TypeKind = "multiple" ) @@ -71,20 +70,22 @@ func (t TypeUnknown) AliasedType() Type { return aliasedType(t) } type TypeCustom struct { name string isAlias bool + kind TypeKind base Type } -func NewTypeCustom(name string, isAlias bool, base Type) TypeCustom { +func NewTypeCustom(name string, isAlias bool, kind TypeKind, base Type) TypeCustom { return TypeCustom{ name, isAlias, + kind, base, } } func (t TypeCustom) Name() string { return t.name } func (t TypeCustom) IsAlias() bool { return t.isAlias } -func (t TypeCustom) Kind() TypeKind { return TypeKindCustom } +func (t TypeCustom) Kind() TypeKind { return t.kind } func (t TypeCustom) Base() Type { return t.base } func (t TypeCustom) Equals(compareType Type) bool { return equals(t, compareType) } func (t TypeCustom) ElementaryType() Type { return elementaryType(t) } From 077c57da85c03dcc22acc5b25de12a0afbe7d74c Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 11 Sep 2025 20:14:00 +0200 Subject: [PATCH 13/22] Get struct default values in slices working (https://github.com/monstermichl/TypeShell/issues/51) --- parser/literals.go | 4 ++++ parser/parser.go | 5 +---- parser/struct.go | 23 ++++++++++++++++++++--- transpiler/transpiler.go | 27 +++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 7 deletions(-) diff --git a/parser/literals.go b/parser/literals.go index 2129c9a..4b920cb 100644 --- a/parser/literals.go +++ b/parser/literals.go @@ -44,6 +44,10 @@ type StringLiteral struct { value string } +func NewStringLiteral(value string) StringLiteral { + return StringLiteral{value} +} + func (l StringLiteral) StatementType() StatementType { return STATEMENT_TYPE_STRING_LITERAL } diff --git a/parser/parser.go b/parser/parser.go index 2d8a451..bee0ac0 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -399,10 +399,7 @@ func defaultVarValue(valueType ValueType, ctx context) (Expression, error) { value: defaultValue, }) } - return StructDefinition{ - valueType: valueType, - values: structValues, - }, nil + return NewStructDefinition(valueType.Type(), structValues...), nil } } } else { diff --git a/parser/struct.go b/parser/struct.go index c2ccae5..b749087 100644 --- a/parser/struct.go +++ b/parser/struct.go @@ -87,13 +87,30 @@ type StructValue struct { value Expression } +func NewStructValue(name string, valueType ValueType, value Expression) StructValue { + return StructValue{ + StructField: StructField{ + name, + valueType, + }, + value: value, + } +} + func (v StructValue) Value() Expression { return v.value } type StructDefinition struct { - valueType ValueType - values []StructValue + t Type + values []StructValue +} + +func NewStructDefinition(t Type, values ...StructValue) StructDefinition { + return StructDefinition{ + t, + values, + } } func (d StructDefinition) StatementType() StatementType { @@ -101,7 +118,7 @@ func (d StructDefinition) StatementType() StatementType { } func (d StructDefinition) ValueType() ValueType { - return d.valueType + return NewValueType(d.t, false) } func (d StructDefinition) IsConstant() bool { diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 23d5482..89c0e50 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -1,6 +1,7 @@ package transpiler import ( + "errors" "fmt" "strconv" @@ -78,6 +79,32 @@ func (t *transpiler) evaluateValueTypeDefaultValue(valueType parser.ValueType) ( defaultValue = IntToString(0) case parser.TypeKindString: defaultValue = conv.StringToString("") + case parser.TypeKindStruct: + structDeclaration, valid := valueType.Type().(parser.StructDeclaration) + + if !valid { + return "", errors.New("struct declaration could not be evaluated") + } + values := []parser.StructValue{} + + for _, field := range structDeclaration.Fields() { + fieldValueType := field.ValueType() + defaultValueTemp, err := t.evaluateValueTypeDefaultValue(fieldValueType) + + if err != nil { + return "", err + } + values = append(values, parser.NewStructValue(field.Name(), fieldValueType, parser.NewStringLiteral(defaultValueTemp))) + } + + // Create helper struct definition. + structDefinition := parser.NewStructDefinition(structDeclaration, values...) + result, err := t.evaluateExpression(structDefinition, true) + + if err != nil { + return "", err + } + defaultValue = result.firstValue() default: return "", fmt.Errorf(`no default value defined for %s`, valueType.String()) } From 425301bcbe857d620d3b304ca44899d7e4c7570d Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 12 Sep 2025 09:23:12 +0200 Subject: [PATCH 14/22] Implement first draft of struct initialization with values (https://github.com/monstermichl/TypeShell/issues/51) --- parser/parser.go | 186 ++++++++++++++++++++++++++++------- tests/struct.go | 70 +++++++++++++ tests/struct_windows_test.go | 17 ++++ 3 files changed, 238 insertions(+), 35 deletions(-) create mode 100644 tests/struct.go create mode 100644 tests/struct_windows_test.go diff --git a/parser/parser.go b/parser/parser.go index bee0ac0..beb7ccb 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -49,6 +49,13 @@ type foundTypeDefinition struct { name string } +type initValue struct { + nameToken lexer.Token + name string + valueToken lexer.Token + value Expression +} + type context struct { imports map[string]string // Maps import aliases to file hashes. types map[string]Type // Stores the declared types. @@ -526,6 +533,12 @@ func (p Parser) peekAt(add uint) lexer.Token { return token } +func (p *Parser) skipNewlines() { + for p.peek().Type() == lexer.NEWLINE { + p.eat() + } +} + func (p Parser) findAllowed(searchTokenType lexer.TokenType, allowed ...lexer.TokenType) (lexer.Token, error) { tokens := p.tokens @@ -787,16 +800,10 @@ func (p *Parser) evaluateImports(ctx context) ([]Statement, error) { statementsTemp := []Statement{} for { - var nextToken lexer.Token + // Skip newlines. + p.skipNewlines() - // Skip empty characters. - for { - nextToken = p.peek() - if !slices.Contains([]lexer.TokenType{lexer.NEWLINE}, nextToken.Type()) { - break - } - p.eat() - } + nextToken := p.peek() if nextToken.Type() == lexer.IMPORT { p.eat() @@ -2696,7 +2703,7 @@ func (p *Parser) evaluateSingleExpression(ctx context) (Expression, error) { // Handle slice instantiation. case lexer.OPENING_SQUARE_BRACKET: - expr, err = p.evaluateSliceInstantiation(ctx) + expr, err = p.evaluateSliceInitialization(ctx) // Handle iota. case lexer.IOTA: @@ -2752,9 +2759,18 @@ func (p *Parser) evaluateSingleExpression(ctx context) (Expression, error) { } case lexer.OPENING_SQUARE_BRACKET: expr, err = p.evaluateSubscript(ctx) + case lexer.OPENING_CURLY_BRACKET: + _, valid := ctx.findType(value) + + if valid { + expr, err = p.evaluateStructInitialization(ctx) + } case lexer.DOT: expr, err = p.evaluateStructEvaluation(importAlias, ctx) - default: + } + + // If nothing has been set yet, try to evaluate named value. + if expr == nil && err == nil { expr, err = p.evaluateNamedValueEvaluation(ctx) } @@ -3180,50 +3196,67 @@ func (p *Parser) evaluateAppCall(ctx context) (Call, error) { return call, nil } -func (p *Parser) evaluateSliceInstantiation(ctx context) (Expression, error) { - nextToken := p.peek() - sliceValueType, err := p.evaluateValueType(ctx) - - if err != nil { - return nil, err - } - if !sliceValueType.IsSlice() { - return nil, p.expectedError(fmt.Sprintf("slice type but got %s", sliceValueType.String()), nextToken) - } - nextToken = p.eat() +func (p *Parser) evaluateInitializationValues(checkCallout func(initValue initValue) error, ctx context) ([]initValue, error) { + nextToken := p.eat() if nextToken.Type() != lexer.OPENING_CURLY_BRACKET { return nil, p.expectedError(`"{"`, nextToken) } nextToken = p.peek() - values := []Expression{} + names := []string{} + initValues := []initValue{} + rowInit := p.peek().Type() == lexer.NEWLINE // If there's a newline after the bracket, it's a row initialization. - // Evaluate slice initialization values. + // Evaluate initialization values. if nextToken.Type() != lexer.CLOSING_CURLY_BRACKET { for { + // Get rid of consecutive newlines. + p.skipNewlines() + + nameToken := p.peek() + initValue := initValue{nameToken: nameToken} + + // Check if a name is defined. + if nameToken.Type() == lexer.IDENTIFIER && p.peekAt(1).Type() == lexer.COLON { + p.eat() // Eat name token. + p.eat() // Eat colon token. + + name := nameToken.Value() + + if slices.Contains(names, name) { + return nil, p.atError(fmt.Sprintf("a value has already been assigned to %s", name), nameToken) + } + initValue.name = name + } valueToken := p.peek() expr, err := p.evaluateExpression(ctx) if err != nil { return nil, err } - valueDataType := expr.ValueType() - sliceElementValueType := sliceValueType - sliceElementValueType.isSlice = false + initValue.valueToken = valueToken + initValue.value = expr - if !valueDataType.Equals(sliceElementValueType) { - return nil, p.atError(fmt.Sprintf("%s cannot not be added to %s", valueDataType.String(), sliceElementValueType.String()), valueToken) + err = checkCallout(initValue) + + if err != nil { + return nil, err } - values = append(values, expr) + initValues = append(initValues, initValue) nextToken = p.peek() nextTokenType := nextToken.Type() + if rowInit && nextTokenType != lexer.COMMA { + return nil, p.expectedError(`","`, nextToken) + } + if nextTokenType == lexer.COMMA { p.eat() - } else if nextTokenType == lexer.CLOSING_CURLY_BRACKET { + } + p.skipNewlines() + + if p.peek().Type() == lexer.CLOSING_CURLY_BRACKET { break - } else { - return nil, p.expectedError(`"," or "}"`, nextToken) } } } @@ -3232,10 +3265,93 @@ func (p *Parser) evaluateSliceInstantiation(ctx context) (Expression, error) { if nextToken.Type() != lexer.CLOSING_CURLY_BRACKET { return nil, p.expectedError(`"}"`, nextToken) } - return SliceInstantiation{ + return initValues, nil +} + +func (p *Parser) evaluateSliceInitialization(ctx context) (Expression, error) { + nextToken := p.peek() + sliceValueType, err := p.evaluateValueType(ctx) + + if err != nil { + return nil, err + } + + if !sliceValueType.IsSlice() { + return nil, p.expectedError(fmt.Sprintf("slice type but got %s", sliceValueType.String()), nextToken) + } + initValues, err := p.evaluateInitializationValues(func(initValue initValue) error { + if len(initValue.name) > 0 { + return p.atError("unexpected name", initValue.nameToken) + } + valueDataType := initValue.value.ValueType() + sliceElementValueType := sliceValueType + sliceElementValueType.isSlice = false + + if !valueDataType.Equals(sliceElementValueType) { + return p.atError(fmt.Sprintf("%s cannot not be added to %s", valueDataType.String(), sliceElementValueType.String()), initValue.valueToken) + } + return nil + }, ctx) + + if err != nil { + return nil, err + } + values := []Expression{} + + for _, initValue := range initValues { + values = append(values, initValue.value) + } + initialization := SliceInstantiation{ t: sliceValueType.Type(), values: values, - }, nil + } + return initialization, nil +} + +func (p *Parser) evaluateStructInitialization(ctx context) (Expression, error) { + nextToken := p.peek() + structValueType, err := p.evaluateValueType(ctx) + + if err != nil { + return nil, err + } + intialization, err := defaultVarValue(structValueType, ctx) + + if err != nil { + return nil, err + } + structInitialization := intialization.(StructDefinition) + structDeclaration, valid := structValueType.Type().(StructDeclaration) + + if !valid || structValueType.IsSlice() { + return nil, p.expectedError(fmt.Sprintf("struct type but got %s", structValueType.String()), nextToken) + } + _, err = p.evaluateInitializationValues(func(initValue initValue) error { + fieldName := initValue.name + + if len(fieldName) == 0 { + return p.expectedError("field name", initValue.nameToken) + } + structField, err := structDeclaration.FindField(fieldName) + + if err != nil { + return err + } + value := initValue.value + valueDataType := value.ValueType() + structFieldValueType := structField.ValueType() + + if !valueDataType.Equals(structFieldValueType) { + return p.atError(fmt.Sprintf("%s cannot not be assigned to %s", valueDataType.String(), structFieldValueType.String()), initValue.valueToken) + } + structInitialization.values = append(structInitialization.values, NewStructValue(fieldName, structFieldValueType, value)) + return nil + }, ctx) + + if err != nil { + return nil, err + } + return structInitialization, nil } func (p *Parser) evaluateSubscript(ctx context) (Expression, error) { diff --git a/tests/struct.go b/tests/struct.go new file mode 100644 index 0000000..d671c95 --- /dev/null +++ b/tests/struct.go @@ -0,0 +1,70 @@ +package tests + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func testDeclareAndDefineStructSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + c bool + d int + } + var s myStruct + + s.a = "Hello" + s.b = "World" + + print(s.a, s.b, s.c, s.d) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "Hello World 0 0", output) + }) +} + +func testDeclareAndDefineStructWithValuesSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + c bool + d int + } + s := myStruct{ + a: "Hello", + b: "World", + } + + print(s.a, s.b, s.c, s.d) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "Hello World 0 0", output) + }) +} + +func testDeclareAndDefineStructSliceSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + c bool + d int + } + var s myStruct + var sl []myStruct + + s.a = "Hello" + s.b = "World" + s.d = 2 + + sl[1] = s + + for i, val := range sl { + print(val.a, val.b, val.c, val.d) + } + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "0 0\nHello World 0 2", output) + }) +} diff --git a/tests/struct_windows_test.go b/tests/struct_windows_test.go new file mode 100644 index 0000000..c9d938c --- /dev/null +++ b/tests/struct_windows_test.go @@ -0,0 +1,17 @@ +package tests + +import ( + "testing" +) + +func TestDeclareAndDefineStructSuccess(t *testing.T) { + testDeclareAndDefineStructSuccess(t, transpileBatch) +} + +func TestDeclareAndDefineStructWithValuesSuccess(t *testing.T) { + testDeclareAndDefineStructWithValuesSuccess(t, transpileBatch) +} + +func TestDeclareAndDefineStructSliceSuccess(t *testing.T) { + testDeclareAndDefineStructSliceSuccess(t, transpileBatch) +} From 3fe936acf29ecf2cef504d1d4c22832f229bcf32 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 12 Sep 2025 09:48:25 +0200 Subject: [PATCH 15/22] Fix double name check (https://github.com/monstermichl/TypeShell/issues/51) --- parser/parser.go | 1 + 1 file changed, 1 insertion(+) diff --git a/parser/parser.go b/parser/parser.go index beb7ccb..7af64b6 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -3226,6 +3226,7 @@ func (p *Parser) evaluateInitializationValues(checkCallout func(initValue initVa if slices.Contains(names, name) { return nil, p.atError(fmt.Sprintf("a value has already been assigned to %s", name), nameToken) } + names = append(names, name) initValue.name = name } valueToken := p.peek() From da1c7e19c17e74530cd57f69aaf33f97c7776c13 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 12 Sep 2025 09:48:57 +0200 Subject: [PATCH 16/22] Add tests (https://github.com/monstermichl/TypeShell/issues/51) --- parser/parser.go | 2 +- tests/slices.go | 14 ++++++ tests/slices_linux_test.go | 4 ++ tests/slices_windows_test.go | 4 ++ tests/struct.go | 85 ++++++++++++++++++++++++++++++++++++ tests/struct_linux_test.go | 41 +++++++++++++++++ tests/struct_windows_test.go | 24 ++++++++++ 7 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 tests/struct_linux_test.go diff --git a/parser/parser.go b/parser/parser.go index 7af64b6..39c2222 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -3343,7 +3343,7 @@ func (p *Parser) evaluateStructInitialization(ctx context) (Expression, error) { structFieldValueType := structField.ValueType() if !valueDataType.Equals(structFieldValueType) { - return p.atError(fmt.Sprintf("%s cannot not be assigned to %s", valueDataType.String(), structFieldValueType.String()), initValue.valueToken) + return p.expectedError(fmt.Sprintf("%s value but got %s", structFieldValueType.String(), valueDataType.String()), initValue.valueToken) } structInitialization.values = append(structInitialization.values, NewStructValue(fieldName, structFieldValueType, value)) return nil diff --git a/tests/slices.go b/tests/slices.go index b2b5d16..79fc9ee 100644 --- a/tests/slices.go +++ b/tests/slices.go @@ -17,6 +17,20 @@ func testDefineSliceSuccess(t *testing.T, transpilerFunc transpilerFunc) { }) } +func testDefineSliceRowValuesSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + var a = []int{ + 1, + 2, + } + + print(a[0], a[1]) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "1 2", output) + }) +} + func testSliceAssignValuesSuccess(t *testing.T, transpilerFunc transpilerFunc) { transpilerFunc(t, ` var a = []int{1, 2} diff --git a/tests/slices_linux_test.go b/tests/slices_linux_test.go index 2365b33..fc9f9d3 100644 --- a/tests/slices_linux_test.go +++ b/tests/slices_linux_test.go @@ -8,6 +8,10 @@ func TestDefineSliceSuccess(t *testing.T) { testDefineSliceSuccess(t, transpileBash) } +func TestDefineSliceRowValuesSuccess(t *testing.T) { + testDefineSliceRowValuesSuccess(t, transpileBash) +} + func TestSliceAssignValuesSuccess(t *testing.T) { testSliceAssignValuesSuccess(t, transpileBash) } diff --git a/tests/slices_windows_test.go b/tests/slices_windows_test.go index 4e371e2..ce34f65 100644 --- a/tests/slices_windows_test.go +++ b/tests/slices_windows_test.go @@ -8,6 +8,10 @@ func TestDefineSliceSuccess(t *testing.T) { testDefineSliceSuccess(t, transpileBatch) } +func TestDefineSliceRowValuesSuccess(t *testing.T) { + testDefineSliceRowValuesSuccess(t, transpileBatch) +} + func TestSliceAssignValuesSuccess(t *testing.T) { testSliceAssignValuesSuccess(t, transpileBatch) } diff --git a/tests/struct.go b/tests/struct.go index d671c95..f7a407c 100644 --- a/tests/struct.go +++ b/tests/struct.go @@ -44,6 +44,22 @@ func testDeclareAndDefineStructWithValuesSuccess(t *testing.T, transpilerFunc tr }) } +func testDeclareAndDefineStructWithValuesOneLineSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + c bool + d int + } + s := myStruct{a: "Hello", b: "World"} + + print(s.a, s.b, s.c, s.d) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "Hello World 0 0", output) + }) +} + func testDeclareAndDefineStructSliceSuccess(t *testing.T, transpilerFunc transpilerFunc) { transpilerFunc(t, ` type myStruct struct { @@ -68,3 +84,72 @@ func testDeclareAndDefineStructSliceSuccess(t *testing.T, transpilerFunc transpi require.Equal(t, "0 0\nHello World 0 2", output) }) } + +func testStructFieldAssignedTwiceInInitializationFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + c bool + d int + } + s := myStruct{a: "Hello", a: "World"} + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "a value has already been assigned to a") + }) +} + +func testStructUnknownFieldInInitializationFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + c bool + d int + } + s := myStruct{a: "Hello", x: "World"} + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "struct field x doesn't exist") + }) +} + +func testStructFieldWrongTypeAssignmentInInitializationFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + c bool + d int + } + s := myStruct{a: "Hello", b: 1} + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "expected string value but got int") + }) +} + +func testStructUnknownFieldInAssignmentFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + c bool + d int + } + var s myStruct + + s.x = "Test" + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "struct field x doesn't exist") + }) +} + +func testStructFieldWrongTypeAssignmentInAssignmentFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + c bool + d int + } + var s myStruct + + s.b = 1 + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "expected string value but got int") + }) +} diff --git a/tests/struct_linux_test.go b/tests/struct_linux_test.go new file mode 100644 index 0000000..17becaa --- /dev/null +++ b/tests/struct_linux_test.go @@ -0,0 +1,41 @@ +package tests + +import ( + "testing" +) + +func TestDeclareAndDefineStructSuccess(t *testing.T) { + testDeclareAndDefineStructSuccess(t, transpileBash) +} + +func TestDeclareAndDefineStructWithValuesSuccess(t *testing.T) { + testDeclareAndDefineStructWithValuesSuccess(t, transpileBash) +} + +func TestDeclareAndDefineStructWithValuesOneLineSuccess(t *testing.T) { + testDeclareAndDefineStructWithValuesOneLineSuccess(t, transpileBash) +} + +func TestDeclareAndDefineStructSliceSuccess(t *testing.T) { + testDeclareAndDefineStructSliceSuccess(t, transpileBash) +} + +func TestStructFieldAssignedTwiceInInitializationFail(t *testing.T) { + testStructFieldAssignedTwiceInInitializationFail(t, transpileBash) +} + +func TestStructUnknownFieldInInitializationFail(t *testing.T) { + testStructUnknownFieldInInitializationFail(t, transpileBash) +} + +func TestStructFieldWrongTypeAssignmentInInitializationFail(t *testing.T) { + testStructFieldWrongTypeAssignmentInInitializationFail(t, transpileBash) +} + +func TestStructUnknownFieldInAssignmentFail(t *testing.T) { + testStructUnknownFieldInAssignmentFail(t, transpileBash) +} + +func TestStructFieldWrongTypeAssignmentInAssignmentFail(t *testing.T) { + testStructFieldWrongTypeAssignmentInAssignmentFail(t, transpileBash) +} diff --git a/tests/struct_windows_test.go b/tests/struct_windows_test.go index c9d938c..36641c5 100644 --- a/tests/struct_windows_test.go +++ b/tests/struct_windows_test.go @@ -12,6 +12,30 @@ func TestDeclareAndDefineStructWithValuesSuccess(t *testing.T) { testDeclareAndDefineStructWithValuesSuccess(t, transpileBatch) } +func TestDeclareAndDefineStructWithValuesOneLineSuccess(t *testing.T) { + testDeclareAndDefineStructWithValuesOneLineSuccess(t, transpileBatch) +} + func TestDeclareAndDefineStructSliceSuccess(t *testing.T) { testDeclareAndDefineStructSliceSuccess(t, transpileBatch) } + +func TestStructFieldAssignedTwiceInInitializationFail(t *testing.T) { + testStructFieldAssignedTwiceInInitializationFail(t, transpileBatch) +} + +func TestStructUnknownFieldInInitializationFail(t *testing.T) { + testStructUnknownFieldInInitializationFail(t, transpileBatch) +} + +func TestStructFieldWrongTypeAssignmentInInitializationFail(t *testing.T) { + testStructFieldWrongTypeAssignmentInInitializationFail(t, transpileBatch) +} + +func TestStructUnknownFieldInAssignmentFail(t *testing.T) { + testStructUnknownFieldInAssignmentFail(t, transpileBatch) +} + +func TestStructFieldWrongTypeAssignmentInAssignmentFail(t *testing.T) { + testStructFieldWrongTypeAssignmentInAssignmentFail(t, transpileBatch) +} From b33d488c3ba7160a7c7c8e84599c79635cfef111 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 12 Sep 2025 09:55:02 +0200 Subject: [PATCH 17/22] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 538724c..0ccf885 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ examples/* !examples/*.tsh ext +tests/std From b4e83240187e411ac51b0fffe7a8d311a6285dd1 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 12 Sep 2025 19:04:15 +0200 Subject: [PATCH 18/22] Copy struct values to param before calling a function (probably needs to be improved, because it adds a lot of code to the output file) (https://github.com/monstermichl/TypeShell/issues/51) --- parser/function.go | 5 +++++ parser/parser.go | 1 + transpiler/transpiler.go | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/parser/function.go b/parser/function.go index b3ddd3d..0c884c7 100644 --- a/parser/function.go +++ b/parser/function.go @@ -43,6 +43,7 @@ func (e FunctionDefinition) Public() bool { type FunctionCall struct { name string returnTypes []ValueType + params []Variable arguments []Expression } @@ -66,6 +67,10 @@ func (e FunctionCall) ReturnTypes() []ValueType { return e.returnTypes } +func (e FunctionCall) Params() []Variable { + return e.params +} + func (e FunctionCall) Args() []Expression { return e.arguments } diff --git a/parser/parser.go b/parser/parser.go index 39c2222..b353f9c 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -3154,6 +3154,7 @@ func (p *Parser) evaluateFunctionCall(importAlias string, ctx context) (Call, er return FunctionCall{ name: name, arguments: args, + params: definedFunction.Params(), returnTypes: definedFunction.ReturnTypes(), }, nil } diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 89c0e50..1b81a37 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -669,15 +669,43 @@ func (t *transpiler) evaluateFunctionDefinition(functionDefinition parser.Functi func (t *transpiler) evaluateFunctionCall(functionCall parser.FunctionCall, valueUsed bool) (expressionResult, error) { name := functionCall.Name() + params := functionCall.Params() args := []string{} - for _, arg := range functionCall.Args() { + for i, arg := range functionCall.Args() { result, err := t.evaluateExpression(arg, true) + value := result.firstValue() if err != nil { return expressionResult{}, err } - args = append(args, result.firstValue()) + param := params[i] + + switch evaluationType := arg.ValueType().Type().(type) { + case parser.StructDeclaration: + // If passed argument is a struct, the values need to be copied to avoid manipulation of the original. + for _, field := range evaluationType.Fields() { + fieldName := field.Name() + fieldValue, err := t.converter.StructEvaluation(value, fieldName, true) + paramName := param.LayerName() + + if err != nil { + return expressionResult{}, nil + } + err = t.converter.StructAssignment(paramName, fieldName, fieldValue, false) + + if err != nil { + return expressionResult{}, err + } + evaluatedValue, err := t.converter.VarEvaluation(paramName, true, false) + + if err != nil { + return expressionResult{}, err + } + value = evaluatedValue + } + } + args = append(args, value) } returnTypes := functionCall.ReturnTypes() values, err := t.converter.FuncCall(name, args, returnTypes, valueUsed) From 68c35da4417ac7dbecb505e094cab460ca195b57 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 12 Sep 2025 19:04:53 +0200 Subject: [PATCH 19/22] Only evaluate variables if they are not already evaluated (https://github.com/monstermichl/TypeShell/issues/51) --- converters/bash/converter.go | 6 +++++- converters/batch/converter.go | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/converters/bash/converter.go b/converters/bash/converter.go index 38ad749..5e35cd9 100644 --- a/converters/bash/converter.go +++ b/converters/bash/converter.go @@ -614,7 +614,11 @@ func (c *converter) varAssignmentString(name string, value string, global bool) } func (c *converter) varEvaluationString(name string, global bool) string { - return fmt.Sprintf("${%s}", c.varName(name, global)) + // Only evaluate if it's not already evaluated. + if !strings.HasPrefix(name, "${") && !strings.HasSuffix(name, "}") { + return fmt.Sprintf("${%s}", c.varName(name, global)) + } + return name } func (c *converter) sliceAssignmentString(name string, index string, value string, global bool) string { diff --git a/converters/batch/converter.go b/converters/batch/converter.go index 07782fb..85e2e80 100644 --- a/converters/batch/converter.go +++ b/converters/batch/converter.go @@ -816,7 +816,11 @@ func (c *converter) varAssignmentString(name string, value string, global bool) } func (c *converter) varEvaluationString(name string, global bool) string { - return fmt.Sprintf("!%s!", c.varName(name, global)) + // Only evaluate if it's not already evaluated. + if !strings.HasPrefix(name, "!") && !strings.HasSuffix(name, "!") { + return fmt.Sprintf("!%s!", c.varName(name, global)) + } + return name } func (c *converter) ifStart(condition string, startAddition string) error { From 28e975c544370fddc0533c070c0267e19b38b78f Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 12 Sep 2025 19:24:59 +0200 Subject: [PATCH 20/22] Fix struct field copying (https://github.com/monstermichl/TypeShell/issues/51) --- transpiler/transpiler.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 1b81a37..efba90d 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -684,10 +684,11 @@ func (t *transpiler) evaluateFunctionCall(functionCall parser.FunctionCall, valu switch evaluationType := arg.ValueType().Type().(type) { case parser.StructDeclaration: // If passed argument is a struct, the values need to be copied to avoid manipulation of the original. + paramName := param.LayerName() + for _, field := range evaluationType.Fields() { fieldName := field.Name() fieldValue, err := t.converter.StructEvaluation(value, fieldName, true) - paramName := param.LayerName() if err != nil { return expressionResult{}, nil @@ -697,13 +698,13 @@ func (t *transpiler) evaluateFunctionCall(functionCall parser.FunctionCall, valu if err != nil { return expressionResult{}, err } - evaluatedValue, err := t.converter.VarEvaluation(paramName, true, false) + } + evaluatedValue, err := t.converter.VarEvaluation(paramName, true, false) - if err != nil { - return expressionResult{}, err - } - value = evaluatedValue + if err != nil { + return expressionResult{}, err } + value = evaluatedValue } args = append(args, value) } From be8bb3ea7ef2b2a18fdaa2a75ab924ed07719c38 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 12 Sep 2025 19:29:17 +0200 Subject: [PATCH 21/22] Add tests (https://github.com/monstermichl/TypeShell/issues/51) --- tests/struct.go | 56 ++++++++++++++++++++++++++++++++++++ tests/struct_linux_test.go | 8 ++++++ tests/struct_windows_test.go | 8 ++++++ 3 files changed, 72 insertions(+) diff --git a/tests/struct.go b/tests/struct.go index f7a407c..90f2ff5 100644 --- a/tests/struct.go +++ b/tests/struct.go @@ -85,6 +85,62 @@ func testDeclareAndDefineStructSliceSuccess(t *testing.T, transpilerFunc transpi }) } +func testPassStructToFunctionSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + } + + func test(x myStruct) { + print(x.a, x.b) + + x.a = "Bye" + x.b = "Mars" + + print(x.a, x.b) + } + var s myStruct + + s.a = "Hello" + s.b = "World" + + print(s.a, s.b) + test(s) + print(s.a, s.b) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "Hello World\nHello World\nBye Mars\nHello World", output) + }) +} + +func testReturnDifferentStructsSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myStruct struct { + a, b string + c bool + d int + } + count := 0 + + func test() myStruct { + s := myStruct{} + s.a = itoa(count) + + return s + } + + s1 := test() + print(s1.a) + count++ + s2 := test() + print(s1.a) + print(s2.a) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "0\n0\n1", output) + }) +} + func testStructFieldAssignedTwiceInInitializationFail(t *testing.T, transpilerFunc transpilerFunc) { transpilerFunc(t, ` type myStruct struct { diff --git a/tests/struct_linux_test.go b/tests/struct_linux_test.go index 17becaa..19e0d83 100644 --- a/tests/struct_linux_test.go +++ b/tests/struct_linux_test.go @@ -20,6 +20,14 @@ func TestDeclareAndDefineStructSliceSuccess(t *testing.T) { testDeclareAndDefineStructSliceSuccess(t, transpileBash) } +func TestPassStructToFunctionSuccess(t *testing.T) { + testPassStructToFunctionSuccess(t, transpileBash) +} + +func TestReturnDifferentStructsSuccess(t *testing.T) { + testReturnDifferentStructsSuccess(t, transpileBash) +} + func TestStructFieldAssignedTwiceInInitializationFail(t *testing.T) { testStructFieldAssignedTwiceInInitializationFail(t, transpileBash) } diff --git a/tests/struct_windows_test.go b/tests/struct_windows_test.go index 36641c5..4d57a09 100644 --- a/tests/struct_windows_test.go +++ b/tests/struct_windows_test.go @@ -20,6 +20,14 @@ func TestDeclareAndDefineStructSliceSuccess(t *testing.T) { testDeclareAndDefineStructSliceSuccess(t, transpileBatch) } +func TestPassStructToFunctionSuccess(t *testing.T) { + testPassStructToFunctionSuccess(t, transpileBatch) +} + +func TestReturnDifferentStructsSuccess(t *testing.T) { + testReturnDifferentStructsSuccess(t, transpileBatch) +} + func TestStructFieldAssignedTwiceInInitializationFail(t *testing.T) { testStructFieldAssignedTwiceInInitializationFail(t, transpileBatch) } From b48e58498e516bfe9bf4dbd4262e7fcafbbea9f2 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 12 Sep 2025 19:38:57 +0200 Subject: [PATCH 22/22] Do some renaming (https://github.com/monstermichl/TypeShell/issues/51) --- converters/bash/converter.go | 2 +- converters/batch/converter.go | 2 +- parser/parser.go | 42 +++++++++++++++++------------------ parser/struct.go | 40 ++++++++++++++++----------------- transpiler/converter.go | 4 ++-- transpiler/transpiler.go | 16 ++++++------- 6 files changed, 53 insertions(+), 53 deletions(-) diff --git a/converters/bash/converter.go b/converters/bash/converter.go index 5e35cd9..ea477af 100644 --- a/converters/bash/converter.go +++ b/converters/bash/converter.go @@ -431,7 +431,7 @@ func (c *converter) SliceLen(name string, valueUsed bool) (string, error) { return c.VarEvaluation(helper, valueUsed, false) } -func (c *converter) StructDefinition(values []transpiler.StructValue, valueUsed bool) (string, error) { +func (c *converter) StructInitialization(values []transpiler.StructValue, valueUsed bool) (string, error) { helper := c.nextDynamicHelperVar() for _, value := range values { diff --git a/converters/batch/converter.go b/converters/batch/converter.go index 85e2e80..3abc82a 100644 --- a/converters/batch/converter.go +++ b/converters/batch/converter.go @@ -643,7 +643,7 @@ func (c *converter) SliceLen(name string, valueUsed bool) (string, error) { return c.VarEvaluation(helper, valueUsed, false) } -func (c *converter) StructDefinition(values []transpiler.StructValue, valueUsed bool) (string, error) { +func (c *converter) StructInitialization(values []transpiler.StructValue, valueUsed bool) (string, error) { helper := c.nextDynamicHelperVar() for _, value := range values { diff --git a/parser/parser.go b/parser/parser.go index b353f9c..07fcf7a 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -387,12 +387,12 @@ func defaultVarValue(valueType ValueType, ctx context) (Expression, error) { case TypeKindString: return StringLiteral{}, nil case TypeKindStruct: - structDeclaration, exists := elementaryDataType.(StructDeclaration) + structDefinition, exists := elementaryDataType.(StructDefinition) if exists { structValues := []StructValue{} - for _, field := range structDeclaration.Fields() { + for _, field := range structDefinition.Fields() { defaultValue, err := defaultVarValue(field.ValueType(), ctx) if err != nil { @@ -406,7 +406,7 @@ func defaultVarValue(valueType ValueType, ctx context) (Expression, error) { value: defaultValue, }) } - return NewStructDefinition(valueType.Type(), structValues...), nil + return NewStructInitialization(valueType.Type(), structValues...), nil } } } else { @@ -1222,21 +1222,21 @@ func (p *Parser) evaluateValueType(ctx context) (ValueType, error) { return evaluatedType, nil } -func (p *Parser) evaluateStructDeclaration(name string, ctx context) (StructDeclaration, error) { +func (p *Parser) evaluateStructDefinition(name string, ctx context) (StructDefinition, error) { structToken := p.eat() if structToken.Type() != lexer.STRUCT { - return StructDeclaration{}, p.expectedKeywordError("struct", structToken) + return StructDefinition{}, p.expectedKeywordError("struct", structToken) } nextToken := p.eat() if nextToken.Type() != lexer.OPENING_CURLY_BRACKET { - return StructDeclaration{}, p.expectedError(`"{"`, nextToken) + return StructDefinition{}, p.expectedError(`"{"`, nextToken) } nextToken = p.eat() if nextToken.Type() != lexer.NEWLINE { - return StructDeclaration{}, p.expectedNewlineError(nextToken) + return StructDefinition{}, p.expectedNewlineError(nextToken) } fields := []StructField{} @@ -1245,18 +1245,18 @@ func (p *Parser) evaluateStructDeclaration(name string, ctx context) (StructDecl nameTokens, err := p.evaluateNames() if err != nil { - return StructDeclaration{}, err + return StructDefinition{}, err } valueTypeToken := p.peek() valueType, err := p.evaluateValueType(ctx) if err != nil { - return StructDeclaration{}, err + return StructDefinition{}, err } // Don't allow nested structs for now. if valueType.Type().Kind() == TypeKindStruct { - return StructDeclaration{}, p.atError("nested structs are not allowed", valueTypeToken) + return StructDefinition{}, p.atError("nested structs are not allowed", valueTypeToken) } for _, nameToken := range nameTokens { @@ -1268,7 +1268,7 @@ func (p *Parser) evaluateStructDeclaration(name string, ctx context) (StructDecl nextToken = p.eat() if nextToken.Type() != lexer.NEWLINE { - return StructDeclaration{}, p.expectedNewlineError(nextToken) + return StructDefinition{}, p.expectedNewlineError(nextToken) } nextToken = p.peek() @@ -1277,7 +1277,7 @@ func (p *Parser) evaluateStructDeclaration(name string, ctx context) (StructDecl break } } - return NewStructDeclaration(name, fields), nil + return NewStructDefinition(name, fields), nil } func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { @@ -1310,12 +1310,12 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { if isAlias { return nil, p.atError("struct alias is not supported", assignToken) } - structDeclaration, err := p.evaluateStructDeclaration(name, ctx) + structDefinition, err := p.evaluateStructDefinition(name, ctx) if err != nil { return nil, err } - valueType = NewValueType(structDeclaration, false) + valueType = NewValueType(structDefinition, false) } else { valueType, err = p.evaluateValueType(ctx) @@ -2584,11 +2584,11 @@ func (p *Parser) evaluateStructEvaluation(importAlias string, ctx context) (Expr if typeDeclarationKind != TypeKindStruct { return nil, p.expectedError(fmt.Sprintf("%s but got %s", TypeKindStruct, typeDeclarationKind), identifierToken) } - structDeclaration := typeDeclaration.(StructDeclaration) + structDefinition := typeDeclaration.(StructDefinition) // Check field. fieldName := fieldToken.Value() - foundField, err := structDeclaration.FindField(fieldName) + foundField, err := structDefinition.FindField(fieldName) if err != nil { return nil, p.atError(err.Error(), fieldToken) @@ -3322,8 +3322,8 @@ func (p *Parser) evaluateStructInitialization(ctx context) (Expression, error) { if err != nil { return nil, err } - structInitialization := intialization.(StructDefinition) - structDeclaration, valid := structValueType.Type().(StructDeclaration) + structInitialization := intialization.(StructInitialization) + structDefinition, valid := structValueType.Type().(StructDefinition) if !valid || structValueType.IsSlice() { return nil, p.expectedError(fmt.Sprintf("struct type but got %s", structValueType.String()), nextToken) @@ -3334,7 +3334,7 @@ func (p *Parser) evaluateStructInitialization(ctx context) (Expression, error) { if len(fieldName) == 0 { return p.expectedError("field name", initValue.nameToken) } - structField, err := structDeclaration.FindField(fieldName) + structField, err := structDefinition.FindField(fieldName) if err != nil { return err @@ -3558,7 +3558,7 @@ func (p *Parser) evaluateStructAssignment(ctx context) (Statement, error) { } else if namedValueBaseType.Kind() != TypeKindStruct { return nil, p.expectedError(fmt.Sprintf("struct but variable is of type %s", namedValueBaseType.Kind()), nameToken) } - structDeclaration := namedValueBaseType.(StructDeclaration) + structDefinition := namedValueBaseType.(StructDefinition) nextToken := p.eat() if nextToken.Type() != lexer.DOT { @@ -3569,7 +3569,7 @@ func (p *Parser) evaluateStructAssignment(ctx context) (Statement, error) { if nextToken.Type() != lexer.IDENTIFIER { return nil, p.expectedError(`struct field`, nextToken) } - structField, err := structDeclaration.FindField(nextToken.Value()) + structField, err := structDefinition.FindField(nextToken.Value()) if err != nil { return nil, p.atError(err.Error(), nextToken) diff --git a/parser/struct.go b/parser/struct.go index b749087..c4aaa9a 100644 --- a/parser/struct.go +++ b/parser/struct.go @@ -15,40 +15,40 @@ func (f StructField) ValueType() ValueType { return f.valueType } -type StructDeclaration struct { +type StructDefinition struct { name string fields []StructField } -func NewStructDeclaration(name string, fields []StructField) StructDeclaration { - return StructDeclaration{ +func NewStructDefinition(name string, fields []StructField) StructDefinition { + return StructDefinition{ name, fields, } } -func (d StructDeclaration) Fields() []StructField { +func (d StructDefinition) Fields() []StructField { return d.fields } -func (d StructDeclaration) Name() string { +func (d StructDefinition) Name() string { return d.name } -func (d StructDeclaration) IsAlias() bool { +func (d StructDefinition) IsAlias() bool { return false } -func (d StructDeclaration) Kind() TypeKind { +func (d StructDefinition) Kind() TypeKind { return TypeKindStruct } -func (d StructDeclaration) Base() Type { +func (d StructDefinition) Base() Type { return nil } -func (d StructDeclaration) Equals(c Type) bool { - compareType, isDeclaration := c.(StructDeclaration) +func (d StructDefinition) Equals(c Type) bool { + compareType, isDeclaration := c.(StructDefinition) if !isDeclaration { return false @@ -70,10 +70,10 @@ func (d StructDeclaration) Equals(c Type) bool { return true } -func (t StructDeclaration) ElementaryType() Type { return elementaryType(t) } -func (t StructDeclaration) AliasedType() Type { return aliasedType(t) } +func (t StructDefinition) ElementaryType() Type { return elementaryType(t) } +func (t StructDefinition) AliasedType() Type { return aliasedType(t) } -func (d StructDeclaration) FindField(name string) (StructField, error) { +func (d StructDefinition) FindField(name string) (StructField, error) { for _, field := range d.Fields() { if field.Name() == name { return field, nil @@ -101,31 +101,31 @@ func (v StructValue) Value() Expression { return v.value } -type StructDefinition struct { +type StructInitialization struct { t Type values []StructValue } -func NewStructDefinition(t Type, values ...StructValue) StructDefinition { - return StructDefinition{ +func NewStructInitialization(t Type, values ...StructValue) StructInitialization { + return StructInitialization{ t, values, } } -func (d StructDefinition) StatementType() StatementType { +func (d StructInitialization) StatementType() StatementType { return STATEMENT_TYPE_STRUCT_DEFINITION } -func (d StructDefinition) ValueType() ValueType { +func (d StructInitialization) ValueType() ValueType { return NewValueType(d.t, false) } -func (d StructDefinition) IsConstant() bool { +func (d StructInitialization) IsConstant() bool { return false } -func (d StructDefinition) Values() []StructValue { +func (d StructInitialization) Values() []StructValue { return d.values } diff --git a/transpiler/converter.go b/transpiler/converter.go index 56685b1..7382ccf 100644 --- a/transpiler/converter.go +++ b/transpiler/converter.go @@ -71,7 +71,7 @@ type Converter interface { VarDefinition(name string, value string, global bool) error VarAssignment(name string, value string, global bool) error SliceAssignment(name string, index string, value string, defaultValue string, global bool) error - StructAssignment(name string, field string, value string, dglobal bool) error + StructAssignment(name string, field string, value string, global bool) error FuncStart(name string, params []string, returnTypes []parser.ValueType) error FuncEnd() error Return(values []ReturnValue) error @@ -102,7 +102,7 @@ type Converter interface { SliceInstantiation(values []string, valueUsed bool) (string, error) SliceEvaluation(name string, index string, valueUsed bool) (string, error) SliceLen(name string, valueUsed bool) (string, error) - StructDefinition(values []StructValue, valueUsed bool) (string, error) + StructInitialization(values []StructValue, valueUsed bool) (string, error) StructEvaluation(name string, field string, valueUsed bool) (string, error) StringSubscript(value string, startIndex string, endIndex string, valueUsed bool) (string, error) StringLen(value string, valueUsed bool) (string, error) diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index efba90d..623aec9 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -80,14 +80,14 @@ func (t *transpiler) evaluateValueTypeDefaultValue(valueType parser.ValueType) ( case parser.TypeKindString: defaultValue = conv.StringToString("") case parser.TypeKindStruct: - structDeclaration, valid := valueType.Type().(parser.StructDeclaration) + structDefinition, valid := valueType.Type().(parser.StructDefinition) if !valid { return "", errors.New("struct declaration could not be evaluated") } values := []parser.StructValue{} - for _, field := range structDeclaration.Fields() { + for _, field := range structDefinition.Fields() { fieldValueType := field.ValueType() defaultValueTemp, err := t.evaluateValueTypeDefaultValue(fieldValueType) @@ -98,8 +98,8 @@ func (t *transpiler) evaluateValueTypeDefaultValue(valueType parser.ValueType) ( } // Create helper struct definition. - structDefinition := parser.NewStructDefinition(structDeclaration, values...) - result, err := t.evaluateExpression(structDefinition, true) + structInitialization := parser.NewStructInitialization(structDefinition, values...) + result, err := t.evaluateExpression(structInitialization, true) if err != nil { return "", err @@ -682,7 +682,7 @@ func (t *transpiler) evaluateFunctionCall(functionCall parser.FunctionCall, valu param := params[i] switch evaluationType := arg.ValueType().Type().(type) { - case parser.StructDeclaration: + case parser.StructDefinition: // If passed argument is a struct, the values need to be copied to avoid manipulation of the original. paramName := param.LayerName() @@ -772,7 +772,7 @@ func (t *transpiler) evaluateSliceInstantiation(instantiation parser.SliceInstan return newExpressionResult(s), nil } -func (t *transpiler) evaluateStructDefinition(definition parser.StructDefinition, valueUsed bool) (expressionResult, error) { +func (t *transpiler) evaluateStructInitialization(definition parser.StructInitialization, valueUsed bool) (expressionResult, error) { values := []StructValue{} for _, value := range definition.Values() { @@ -786,7 +786,7 @@ func (t *transpiler) evaluateStructDefinition(definition parser.StructDefinition value: result.firstValue(), }) } - s, err := t.converter.StructDefinition(values, valueUsed) + s, err := t.converter.StructInitialization(values, valueUsed) if err != nil { return expressionResult{}, err @@ -992,7 +992,7 @@ func (t *transpiler) evaluateExpression(expression parser.Expression, valueUsed case parser.STATEMENT_TYPE_SLICE_INSTANTIATION: return t.evaluateSliceInstantiation(expression.(parser.SliceInstantiation), valueUsed) case parser.STATEMENT_TYPE_STRUCT_DEFINITION: - return t.evaluateStructDefinition(expression.(parser.StructDefinition), valueUsed) + return t.evaluateStructInitialization(expression.(parser.StructInitialization), valueUsed) case parser.STATEMENT_TYPE_INPUT: return t.evaluateInput(expression.(parser.Input), valueUsed) case parser.STATEMENT_TYPE_COPY: