From a8fd0de91f9443f76adae1a0eb0c1e082f004fc4 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Sun, 10 Aug 2025 17:58:36 +0200 Subject: [PATCH 01/27] Slightly improve error and context handling --- parser/parser.go | 50 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index a816fd1..819565f 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -48,6 +48,14 @@ type context struct { scopeStack []scope // Stores the current scopes. } +func newContext() context { + return context{ + imports: map[string]string{}, + variables: map[string]Variable{}, + functions: map[string]FunctionDefinition{}, + } +} + func (c context) currentScope() scope { return c.scopeStack[len(c.scopeStack)-1] } @@ -342,6 +350,18 @@ func (p *Parser) expectedError(what string, token lexer.Token) error { return p.atError(fmt.Sprintf("expected %s", what), token) } +func (p *Parser) expectedKeywordError(keyword string, token lexer.Token) error { + return p.expectedError(fmt.Sprintf("%s-keyword", keyword), token) +} + +func (p *Parser) expectedIdentifierError(token lexer.Token) error { + return p.expectedError("identifier", token) +} + +func (p *Parser) expectedNewlineError(token lexer.Token) error { + return p.expectedError("newline", token) +} + func (p Parser) peek() lexer.Token { return p.peekAt(0) } @@ -527,7 +547,7 @@ func (p *Parser) evaluateBuiltInFunction(tokenType lexer.TokenType, keyword stri keywordToken := p.eat() if keywordToken.Type() != tokenType { - return nil, p.expectedError(fmt.Sprintf("%s-keyword", keyword), keywordToken) + return nil, p.expectedKeywordError(keyword, keywordToken) } nextToken := p.eat() @@ -580,11 +600,7 @@ func (p *Parser) evaluateBuiltInFunction(tokenType lexer.TokenType, keyword stri } func (p *Parser) evaluateProgram() (Program, error) { - ctx := context{ - imports: map[string]string{}, - variables: map[string]Variable{}, - functions: map[string]FunctionDefinition{}, - } + ctx := newContext() statements, err := p.evaluateImports(ctx) if err != nil { @@ -633,7 +649,7 @@ func (p *Parser) evaluateImports(ctx context) ([]Statement, error) { nextToken = p.eat() if nextToken.Type() != lexer.NEWLINE { - return nil, p.expectedError("newline", nextToken) + return nil, p.expectedNewlineError(nextToken) } } @@ -787,7 +803,7 @@ func (p *Parser) evaluateBlockBegin() error { newlineToken := p.eat() if newlineToken.Type() != lexer.NEWLINE { - return p.expectedError("newline", newlineToken) + return p.expectedNewlineError(newlineToken) } return nil } @@ -1471,7 +1487,7 @@ func (p *Parser) evaluateReturn(ctx context) (Statement, error) { return nil, p.expectedError(fmt.Sprintf("return within %s-scope", SCOPE_FUNCTION), returnToken) } if returnToken.Type() != lexer.RETURN { - return nil, p.expectedError("return-keyword", returnToken) + return nil, p.expectedKeywordError("return", returnToken) } evaluatedVals, err := p.evaluateValues(ctx) @@ -1532,7 +1548,7 @@ func (p *Parser) evaluateIf(ctx context) (Statement, error) { // "if" needs to start with if-token. if ifRequired { if nextTokenType != lexer.IF { - return nil, p.expectedError("if-keyword", nextToken) + return nil, p.expectedKeywordError("if", nextToken) } p.eat() } else { @@ -1595,7 +1611,7 @@ func (p *Parser) evaluateSwitch(ctx context) (Statement, error) { switchToken := p.eat() if switchToken.Type() != lexer.SWITCH { - return nil, p.expectedError("switch-keyword", switchToken) + return nil, p.expectedKeywordError("switch", switchToken) } var switchExpr Expression var err error @@ -1624,7 +1640,7 @@ func (p *Parser) evaluateSwitch(ctx context) (Statement, error) { nextToken := p.eat() if nextToken.Type() != lexer.NEWLINE { - return nil, p.expectedError("newline", nextToken) + return nil, p.expectedNewlineError(nextToken) } fakeIf := If{ ifBranch: IfBranch{ @@ -1708,7 +1724,7 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { forToken := p.eat() if forToken.Type() != lexer.FOR { - return nil, p.expectedError("for-keyword", forToken) + return nil, p.expectedKeywordError("for", forToken) } var stmt Statement nextToken := p.peek() @@ -1735,7 +1751,7 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { nextToken = p.eat() if nextToken.Type() != lexer.IDENTIFIER { - return nil, p.expectedError("identifier", nextToken) + return nil, p.expectedIdentifierError(nextToken) } err = p.checkNewVariableNameToken(nextToken, ctx) @@ -1753,7 +1769,7 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { nextToken = p.eat() if nextToken.Type() != lexer.RANGE { - return nil, p.expectedError("range-keyword", nextToken) + return nil, p.expectedKeywordError("range", nextToken) } nextToken := p.peek() iterableExpression, err := p.evaluateExpression(ctx) @@ -1935,7 +1951,7 @@ func (p *Parser) evaluateVarEvaluation(ctx context) (Expression, error) { identifierToken := p.eat() // Eat identifier token. if identifierToken.Type() != lexer.IDENTIFIER { - return nil, p.expectedError("identifier", identifierToken) + return nil, p.expectedIdentifierError(identifierToken) } name := identifierToken.Value() variable, exists := ctx.findVariable(name, p.prefix, ctx.global()) @@ -2716,7 +2732,7 @@ func (p *Parser) evaluateIncrementDecrement(ctx context) (Statement, error) { identifierToken := p.eat() if identifierToken.Type() != lexer.IDENTIFIER { - return nil, p.expectedError("identifier", identifierToken) + return nil, p.expectedIdentifierError(identifierToken) } name := identifierToken.Value() definedVariable, exists := ctx.findVariable(name, p.prefix, ctx.global()) From ff72995d797f583ada18b82a159dd154b04f83e8 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Sun, 10 Aug 2025 18:00:52 +0200 Subject: [PATCH 02/27] Move default value evaluation to separate function --- transpiler/transpiler.go | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 83ec4fd..6787bb6 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -67,6 +67,23 @@ func (t *transpiler) Transpile(path string, converter Converter) (string, error) return t.converter.Dump() } +func (t *transpiler) evaluateValueTypeDefaultValue(valueType parser.ValueType) (string, error) { + var defaultValue string + conv := t.converter + + switch valueType.DataType() { + case parser.DATA_TYPE_BOOLEAN: + defaultValue = BoolToString(false) + case parser.DATA_TYPE_INTEGER: + defaultValue = IntToString(0) + case parser.DATA_TYPE_STRING: + defaultValue = conv.StringToString("") + default: + return "", fmt.Errorf(`no default value defined for %s`, valueType.String()) + } + return defaultValue, nil +} + func (t *transpiler) evaluateIndex(index parser.Expression, valueUsed bool) (expressionResult, error) { return t.evaluateExpression(index, true) } @@ -442,19 +459,11 @@ func (t *transpiler) evaluateSliceAssignment(assignment parser.SliceAssignment) if err != nil { return err } - var defaultValue string - conv := t.converter valueType := value.ValueType() + defaultValue, err := t.evaluateValueTypeDefaultValue(valueType) - switch valueType.DataType() { - case parser.DATA_TYPE_BOOLEAN: - defaultValue = BoolToString(false) - case parser.DATA_TYPE_INTEGER: - defaultValue = IntToString(0) - case parser.DATA_TYPE_STRING: - defaultValue = conv.StringToString("") - default: - return fmt.Errorf(`no default value defined for %s`, valueType.String()) + if err != nil { + return err } return t.converter.SliceAssignment(assignment.Name(), indexResult.firstValue(), valueResult.firstValue(), defaultValue, assignment.Global()) } From d20be40a886cf9de765b9bb41f4220f18a705b4b Mon Sep 17 00:00:00 2001 From: monstermichl Date: Sun, 10 Aug 2025 18:01:40 +0200 Subject: [PATCH 03/27] Replace tabs by spaces --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 49939ff..d054a28 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ tsh.exe -i helloworld.tsh -t batch -t bash -o . // helloworld.tsh func hello() string { - return "hello" + return "hello" } func buildGreeting(p string) string { - return hello() + " " + p + return hello() + " " + p } greeting := buildGreeting("world") From c81e3c866ecc885fd06a0335b2a356faf1d844cb Mon Sep 17 00:00:00 2001 From: monstermichl Date: Sun, 10 Aug 2025 18:04:34 +0200 Subject: [PATCH 04/27] Start struct definition implementation --- lexer/lexer.go | 4 ++ parser/parser.go | 112 ++++++++++++++++++++++++++++++++++++++++ parser/struct.go | 31 +++++++++++ parser/types.go | 1 + transpiler/converter.go | 6 +++ 5 files changed, 154 insertions(+) create mode 100644 parser/struct.go diff --git a/lexer/lexer.go b/lexer/lexer.go index 330a093..35d8312 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -70,6 +70,8 @@ const ( RANGE BREAK CONTINUE + TYPE + STRUCT // Builtin functions. LEN @@ -193,6 +195,8 @@ var keywords = map[string]TokenType{ "break": BREAK, "continue": CONTINUE, "nil": NIL_LITERAL, + "type": TYPE, + "struct": STRUCT, // Builtin functions. "len": LEN, diff --git a/parser/parser.go b/parser/parser.go index 819565f..2e364e9 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -45,6 +45,7 @@ type context struct { imports map[string]string // Maps import aliases to file hashes. variables map[string]Variable // Stores the variable name to variable relation. functions map[string]FunctionDefinition // Stores the function name to function relation. + structs map[string]StructDefinition // Stores the struct name to struct relation. scopeStack []scope // Stores the current scopes. } @@ -53,6 +54,7 @@ func newContext() context { imports: map[string]string{}, variables: map[string]Variable{}, functions: map[string]FunctionDefinition{}, + structs: map[string]StructDefinition{}, } } @@ -121,6 +123,18 @@ func (c context) addFunctions(prefix string, global bool, functions ...FunctionD return nil } +func (c context) addStructs(prefix string, global bool, structs ...StructDefinition) error { + for _, strct := range structs { + prefixedName, err := c.buildPrefixedName(strct.Name(), prefix, global, false) + + if err != nil { + return err + } + c.structs[prefixedName] = strct + } + return nil +} + func (c context) findImport(alias string) (string, bool) { hash, exists := c.imports[alias] return hash, exists @@ -146,11 +160,22 @@ func (c context) findFunction(name string, prefix string) (FunctionDefinition, b return function, exists } +func (c context) findStruct(name string, prefix string, global bool) (StructDefinition, bool) { + prefixedName, err := c.buildPrefixedName(name, prefix, global, true) + + if err != nil { + return StructDefinition{}, false + } + strct, exists := c.structs[prefixedName] + return strct, exists +} + func (c context) clone() context { return context{ imports: maps.Clone(c.imports), variables: maps.Clone(c.variables), functions: maps.Clone(c.functions), + structs: maps.Clone(c.structs), scopeStack: slices.Clone(c.scopeStack), } } @@ -867,6 +892,13 @@ func (p *Parser) evaluateBlockContent(terminationTokenTypes []lexer.TokenType, c // Store new function. err = ctx.addFunctions(prefix, global, stmt.(FunctionDefinition)) + if err != nil { + return nil, err + } + case STATEMENT_TYPE_STRUCT_DEFINITION: + // Store new type. + err = ctx.addStructs(prefix, global, stmt.(StructDefinition)) + if err != nil { return nil, err } @@ -1535,6 +1567,84 @@ func (p *Parser) evaluateContinue(ctx context) (Statement, error) { return Continue{}, nil } +func (p *Parser) evaluateTypeDefinition(ctx context) (Statement, error) { + nextToken := p.eat() + + if nextToken.Type() != lexer.TYPE { + return nil, p.expectedKeywordError("type", nextToken) + } + nextToken = p.eat() + + if nextToken.Type() != lexer.IDENTIFIER { + return nil, p.expectedIdentifierError(nextToken) + } + structName := nextToken.Value() + _, exists := ctx.findStruct(structName, p.prefix, ctx.global()) + + if exists { + return nil, p.atError(fmt.Sprintf(`the struct "%s" has already been defined`, structName), nextToken) + } + nextToken = p.eat() + + // For now, only struct definitions are allowed. + if nextToken.Type() != lexer.STRUCT { + return nil, p.expectedKeywordError("struct", nextToken) + } + 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) + } + structDefinition := StructDefinition{ + name: structName, + } + propertyNames := []string{} + + for { + nextToken = p.peek() + nextTokenType := nextToken.Type() + + if slices.Contains([]lexer.TokenType{lexer.CLOSING_CURLY_BRACKET, lexer.EOF}, nextTokenType) { + break + } + p.eat() + + if nextTokenType != lexer.IDENTIFIER { + return nil, p.expectedIdentifierError(nextToken) + } + propertyName := nextToken.Value() + + if slices.Contains(propertyNames, propertyName) { + return nil, p.atError(fmt.Sprintf(`struct "%s" already contains a property "%s"`, structName, propertyName), nextToken) + } + valueType, err := p.evaluateValueType() + + if err != nil { + return nil, err + } + structDefinition.members = append(structDefinition.members, StructMember{ + propertyName, + valueType, + }) + nextToken = p.eat() + + if nextToken.Type() != lexer.NEWLINE { + return nil, p.expectedNewlineError(nextToken) + } + } + nextToken = p.eat() + + if nextToken.Type() != lexer.CLOSING_CURLY_BRACKET { + return nil, p.expectedError(`"}"`, nextToken) + } + return structDefinition, nil +} + func (p *Parser) evaluateIf(ctx context) (Statement, error) { var ifStatement If @@ -2169,6 +2279,8 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) { stmt, err = p.evaluateWrite(ctx) case lexer.PANIC: stmt, err = p.evaluatePanic(ctx) + case lexer.TYPE: + stmt, err = p.evaluateTypeDefinition(ctx) default: // Variable initialization also starts with identifier but is a statement (e.g. x := 1234). if p.isShortVarInit() { diff --git a/parser/struct.go b/parser/struct.go new file mode 100644 index 0000000..533ae4d --- /dev/null +++ b/parser/struct.go @@ -0,0 +1,31 @@ +package parser + +type StructMember struct { + name string + valueType ValueType +} + +func (m StructMember) Name() string { + return m.name +} + +func (m StructMember) ValueType() ValueType { + return m.valueType +} + +type StructDefinition struct { + name string + members []StructMember +} + +func (d StructDefinition) StatementType() StatementType { + return STATEMENT_TYPE_STRUCT_DEFINITION +} + +func (d StructDefinition) Name() string { + return d.name +} + +func (d StructDefinition) Members() []StructMember { + return d.members +} diff --git a/parser/types.go b/parser/types.go index a38d93a..90e6717 100644 --- a/parser/types.go +++ b/parser/types.go @@ -84,6 +84,7 @@ const ( STATEMENT_TYPE_FOR_RANGE StatementType = "for range" STATEMENT_TYPE_BREAK StatementType = "break" STATEMENT_TYPE_CONTINUE StatementType = "continue" + STATEMENT_TYPE_STRUCT_DEFINITION StatementType = "struct definition" STATEMENT_TYPE_INSTANTIATION StatementType = "instantiation" STATEMENT_TYPE_PRINT StatementType = "print" STATEMENT_TYPE_ITOA StatementType = "itoa" diff --git a/transpiler/converter.go b/transpiler/converter.go index c0561c4..18aa6e8 100644 --- a/transpiler/converter.go +++ b/transpiler/converter.go @@ -46,6 +46,12 @@ func (rv ReturnValue) ValueType() parser.ValueType { return rv.valueType } +type StructMember struct { + name string + value string + valueType parser.ValueType +} + type Converter interface { // Common methods StringToString(value string) string From a1e5d1e525f532afd980de74ad9ddccf1360090f Mon Sep 17 00:00:00 2001 From: monstermichl Date: Sun, 31 Aug 2025 09:38:24 +0200 Subject: [PATCH 05/27] Start preparation for custom defined types (https://github.com/monstermichl/TypeShell/issues/45) --- lexer/lexer.go | 11 ++------ parser/parser.go | 68 ++++++++++++++++++++++++++++++++++++------------ parser/types.go | 4 +-- 3 files changed, 55 insertions(+), 28 deletions(-) diff --git a/lexer/lexer.go b/lexer/lexer.go index 330a093..b81b821 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -42,9 +42,6 @@ const ( STRING_LITERAL NIL_LITERAL - // Types. - DATA_TYPE - // Separators. COMMA COLON @@ -58,6 +55,7 @@ const ( // Keywords. IMPORT + TYPE VAR_DEFINITION FUNCTION_DEFINITION RETURN @@ -180,6 +178,7 @@ var nonAlphabeticTokens = []tokenMapping{ var keywords = map[string]TokenType{ // Common keywords. "import": IMPORT, + "type": TYPE, "var": VAR_DEFINITION, "func": FUNCTION_DEFINITION, "return": RETURN, @@ -204,12 +203,6 @@ var keywords = map[string]TokenType{ "read": READ, "write": WRITE, "panic": PANIC, - - // Types. - DATA_TYPE_BOOLEAN: DATA_TYPE, - DATA_TYPE_INTEGER: DATA_TYPE, - DATA_TYPE_STRING: DATA_TYPE, - DATA_TYPE_ERROR: DATA_TYPE, } func newToken(value string, tokenType TokenType, row int, column int) Token { diff --git a/parser/parser.go b/parser/parser.go index 819565f..4bfe3c7 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -15,13 +15,6 @@ import ( "github.com/monstermichl/typeshell/lexer" ) -var typeMapping = map[lexer.VarType]DataType{ - lexer.DATA_TYPE_BOOLEAN: DATA_TYPE_BOOLEAN, - lexer.DATA_TYPE_INTEGER: DATA_TYPE_INTEGER, - lexer.DATA_TYPE_STRING: DATA_TYPE_STRING, - lexer.DATA_TYPE_ERROR: DATA_TYPE_STRING, // error is internally just a string to make heandling easier. -} - type scope string const ( @@ -41,19 +34,35 @@ func scopesToString(scopes []scope) []string { return strings } +type typeDefinition struct { + dataType DataType + isAlias bool + isElementary bool +} + type context struct { imports map[string]string // Maps import aliases to file hashes. + types map[string]typeDefinition // Stores the defined types. variables map[string]Variable // Stores the variable name to variable relation. functions map[string]FunctionDefinition // Stores the function name to function relation. scopeStack []scope // Stores the current scopes. } func newContext() context { - return context{ + c := context{ imports: map[string]string{}, + types: map[string]typeDefinition{}, variables: map[string]Variable{}, functions: map[string]FunctionDefinition{}, } + + // Define elementary types. + c.addType(DATA_TYPE_BOOLEAN, DATA_TYPE_BOOLEAN, false, true) + c.addType(DATA_TYPE_INTEGER, DATA_TYPE_INTEGER, false, true) + c.addType(DATA_TYPE_STRING, DATA_TYPE_STRING, false, true) + c.addType(DATA_TYPE_ERROR, DATA_TYPE_STRING, false, true) + + return c } func (c context) currentScope() scope { @@ -97,6 +106,15 @@ func (c context) addImport(alias string, hash string) error { return nil } +func (c context) addType(name string, dataType DataType, isAlias bool, isElementary bool) error { + c.types[name] = typeDefinition{ + dataType, + isAlias, + isElementary, + } + return nil +} + func (c context) addVariables(prefix string, global bool, variables ...Variable) error { for _, variable := range variables { prefixedName, err := c.buildPrefixedName(variable.Name(), prefix, global, false) @@ -126,6 +144,21 @@ func (c context) findImport(alias string) (string, bool) { return hash, exists } +func (c context) findType(dataType DataType) (DataType, bool) { + var foundType string + typeDefinition, exists := c.types[dataType] + + if exists { + if typeDefinition.isAlias { + foundType, exists = c.findType(typeDefinition.dataType) + } + if exists { + foundType = typeDefinition.dataType + } + } + return foundType, exists +} + func (c context) findVariable(name string, prefix string, global bool) (Variable, bool) { prefixedName, err := c.buildPrefixedName(name, prefix, global, true) @@ -149,6 +182,7 @@ func (c context) findFunction(name string, prefix string) (FunctionDefinition, b func (c context) clone() context { return context{ imports: maps.Clone(c.imports), + types: maps.Clone(c.types), variables: maps.Clone(c.variables), functions: maps.Clone(c.functions), scopeStack: slices.Clone(c.scopeStack), @@ -932,7 +966,7 @@ func (p *Parser) evaluateBlock(callback blockCallback, ctx context, scope scope) return statements, nil } -func (p *Parser) evaluateValueType() (ValueType, error) { +func (p *Parser) evaluateValueType(ctx context) (ValueType, error) { nextToken := p.peek() evaluatedType := NewValueType(DATA_TYPE_UNKNOWN, false) @@ -949,11 +983,11 @@ func (p *Parser) evaluateValueType() (ValueType, error) { } // Evaluate data type. - if nextToken.Type() != lexer.DATA_TYPE { + if nextToken.Type() != lexer.IDENTIFIER { return evaluatedType, p.expectedError("data type", nextToken) } p.eat() // Eat data type token. - dataType, exists := typeMapping[nextToken.Value()] + dataType, exists := ctx.findType(nextToken.Value()) if !exists { return evaluatedType, p.expectedError("valid data type", nextToken) @@ -1024,8 +1058,8 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { nextToken := p.peek() // If next token starts a type definition, evaluate value type. - if slices.Contains([]lexer.TokenType{lexer.DATA_TYPE, lexer.OPENING_SQUARE_BRACKET}, nextToken.Type()) { - specifiedTypeTemp, err := p.evaluateValueType() + if slices.Contains([]lexer.TokenType{lexer.IDENTIFIER, lexer.OPENING_SQUARE_BRACKET}, nextToken.Type()) { + specifiedTypeTemp, err := p.evaluateValueType(ctx) if err != nil { return nil, err @@ -1316,7 +1350,7 @@ func (p *Parser) evaluateParams(ctx context) ([]Variable, error) { if exists { return params, fmt.Errorf("scope already contains a variable with the name %s", name) } - valueType, err := p.evaluateValueType() + valueType, err := p.evaluateValueType(ctx) if err != nil { return nil, err @@ -1395,8 +1429,8 @@ func (p *Parser) evaluateFunctionDefinition(ctx context) (Statement, error) { for { // Check if a return type has been specified. - if slices.Contains([]lexer.TokenType{lexer.DATA_TYPE, lexer.OPENING_SQUARE_BRACKET}, returnTypeToken.Type()) { - returnTypeTemp, err := p.evaluateValueType() + if slices.Contains([]lexer.TokenType{lexer.IDENTIFIER, lexer.OPENING_SQUARE_BRACKET}, returnTypeToken.Type()) { + returnTypeTemp, err := p.evaluateValueType(ctx) if err != nil { return nil, err @@ -2494,7 +2528,7 @@ func (p *Parser) evaluateAppCall(ctx context) (Call, error) { func (p *Parser) evaluateSliceInstantiation(ctx context) (Expression, error) { nextToken := p.peek() - sliceValueType, err := p.evaluateValueType() + sliceValueType, err := p.evaluateValueType(ctx) if err != nil { return nil, err diff --git a/parser/types.go b/parser/types.go index a38d93a..7770b39 100644 --- a/parser/types.go +++ b/parser/types.go @@ -3,7 +3,7 @@ package parser import "fmt" type StatementType string -type DataType string +type DataType = string type CompareOperator = string type UnaryOperator = string type BinaryOperator = string @@ -105,7 +105,7 @@ const ( DATA_TYPE_BOOLEAN DataType = "bool" DATA_TYPE_INTEGER DataType = "int" DATA_TYPE_STRING DataType = "string" - DATA_TYPE_ERROR DataType = DATA_TYPE_STRING + DATA_TYPE_ERROR DataType = "error" ) const ( From cc7950eeb95d2c104a18b8f9ac6cfa78759ff81a Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 4 Sep 2025 16:36:28 +0200 Subject: [PATCH 06/27] Implement first working draft of type declarations/definitions (slices not yet supported) (https://github.com/monstermichl/TypeShell/issues/45) --- README.md | 19 +++++ lexer/lexer.go | 4 +- parser/parser.go | 176 ++++++++++++++++++++++++++++++++------- parser/type.go | 30 +++++++ parser/types.go | 2 + transpiler/transpiler.go | 8 ++ 6 files changed, 205 insertions(+), 34 deletions(-) create mode 100644 parser/type.go diff --git a/README.md b/README.md index d054a28..2d4555b 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,25 @@ import ( print(strings.Contains("Hello World", "World")) // Prints 1. ``` +### Type declarations +TypeShell supports the declaration of types. However, types which result in slices are not supported yet. + +```golang +// Define a type. +type myType int + +var a myType +a = myType(24) +``` + +```golang +// Define an alias. +type myType = int + +var a myType +a = 24 +``` + ### Builtin ```golang // Returns the length of a slice or a string. diff --git a/lexer/lexer.go b/lexer/lexer.go index b81b821..f8beab7 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -55,7 +55,7 @@ const ( // Keywords. IMPORT - TYPE + TYPE_DEFINITION VAR_DEFINITION FUNCTION_DEFINITION RETURN @@ -178,7 +178,7 @@ var nonAlphabeticTokens = []tokenMapping{ var keywords = map[string]TokenType{ // Common keywords. "import": IMPORT, - "type": TYPE, + "type": TYPE_DEFINITION, "var": VAR_DEFINITION, "func": FUNCTION_DEFINITION, "return": RETURN, diff --git a/parser/parser.go b/parser/parser.go index 4bfe3c7..55d878e 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -35,11 +35,16 @@ func scopesToString(scopes []scope) []string { } type typeDefinition struct { - dataType DataType + valueType ValueType isAlias bool isElementary bool } +type foundTypeDefinition struct { + typeDefinition + name string +} + type context struct { imports map[string]string // Maps import aliases to file hashes. types map[string]typeDefinition // Stores the defined types. @@ -57,10 +62,12 @@ func newContext() context { } // Define elementary types. - c.addType(DATA_TYPE_BOOLEAN, DATA_TYPE_BOOLEAN, false, true) - c.addType(DATA_TYPE_INTEGER, DATA_TYPE_INTEGER, false, true) - c.addType(DATA_TYPE_STRING, DATA_TYPE_STRING, false, true) - c.addType(DATA_TYPE_ERROR, DATA_TYPE_STRING, false, true) + 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) return c } @@ -106,9 +113,18 @@ func (c context) addImport(alias string, hash string) error { return nil } -func (c context) addType(name string, dataType DataType, isAlias bool, isElementary bool) error { - c.types[name] = typeDefinition{ - dataType, +func (c context) addType(typeName string, valueType ValueType, isAlias bool, isElementary bool) error { + _, exists := c.findType(typeName, false) + + if exists { + return fmt.Errorf(`type "%s" has already been defined`, typeName) + } + if valueType.IsSlice() { + // TODO: Add support. + return errors.New("slices are not allowed yet in type definitions") + } + c.types[typeName] = typeDefinition{ + valueType, isAlias, isElementary, } @@ -144,19 +160,25 @@ func (c context) findImport(alias string) (string, bool) { return hash, exists } -func (c context) findType(dataType DataType) (DataType, bool) { - var foundType string - typeDefinition, exists := c.types[dataType] +func (c context) findType(typeName string, searchUntilElementary bool) (foundTypeDefinition, bool) { + var foundDefinition foundTypeDefinition + typeDefinition, exists := c.types[typeName] if exists { - if typeDefinition.isAlias { - foundType, exists = c.findType(typeDefinition.dataType) + 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 { - foundType = typeDefinition.dataType + foundDefinition = foundDefinitionTemp } } - return foundType, exists + return foundDefinition, exists } func (c context) findVariable(name string, prefix string, global bool) (Variable, bool) { @@ -319,20 +341,24 @@ func allowedCompareOperators(t ValueType) []CompareOperator { return operators } -func defaultVarValue(valueType ValueType) (Expression, error) { - dataType := valueType.DataType() +func defaultVarValue(valueType ValueType, ctx context) (Expression, error) { + foundType, exists := ctx.findType(valueType.DataType(), true) - if !valueType.IsSlice() { - switch dataType { - case DATA_TYPE_BOOLEAN: - return BooleanLiteral{}, nil - case DATA_TYPE_INTEGER: - return IntegerLiteral{}, nil - case DATA_TYPE_STRING: - return StringLiteral{}, nil + if exists { + dataType := foundType.valueType.dataType + + if !valueType.IsSlice() { + switch dataType { + case DATA_TYPE_BOOLEAN: + return BooleanLiteral{}, nil + case DATA_TYPE_INTEGER: + return IntegerLiteral{}, nil + case DATA_TYPE_STRING: + return StringLiteral{}, nil + } + } else { + return SliceInstantiation{dataType: dataType}, nil } - } else { - return SliceInstantiation{dataType: dataType}, nil } return nil, fmt.Errorf("no default value found for type %s", valueType.String()) } @@ -987,15 +1013,49 @@ func (p *Parser) evaluateValueType(ctx context) (ValueType, error) { return evaluatedType, p.expectedError("data type", nextToken) } p.eat() // Eat data type token. - dataType, exists := ctx.findType(nextToken.Value()) + foundDefinition, exists := ctx.findType(nextToken.Value(), false) if !exists { return evaluatedType, p.expectedError("valid data type", nextToken) } - evaluatedType.dataType = dataType + evaluatedType.dataType = foundDefinition.name return evaluatedType, nil } +func (p *Parser) evaluateTypeDefinition(ctx context) (Statement, error) { + typeToken := p.eat() + + if typeToken.Type() != lexer.TYPE_DEFINITION { + return nil, p.expectedKeywordError("type", typeToken) + } + nameToken := p.eat() + + if nameToken.Type() != lexer.IDENTIFIER { + return nil, p.expectedIdentifierError(nameToken) + } + isAlias := false + + // 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 { + isAlias = true + p.eat() + } + valueTypeToken := p.peek() + valueType, err := p.evaluateValueType(ctx) + + if err != nil { + return nil, err + } + name := nameToken.Value() + err = ctx.addType(name, valueType, isAlias, false) + + if err != nil { + return nil, p.atError(err.Error(), valueTypeToken) + } + return TypeDefinition{name}, nil +} + func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { // Possible variable declarations/definitions: // var v int @@ -1172,7 +1232,7 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { // If no value has been specified, define default value. if len(values) == 0 { for _, variable := range variables { - value, err := defaultVarValue(variable.ValueType()) + value, err := defaultVarValue(variable.ValueType(), ctx) if err != nil { return nil, err @@ -1981,6 +2041,49 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { return stmt, nil } +func (p *Parser) evaluateTypeInstantiation(ctx context) (Expression, error) { + identifierToken := p.eat() // Eat identifier token. + + if identifierToken.Type() != lexer.IDENTIFIER { + return nil, p.expectedIdentifierError(identifierToken) + } + typeName := identifierToken.Value() + foundBaseDefinition, exists := ctx.findType(typeName, true) + + if !exists { + return nil, p.atError(fmt.Sprintf("type %s has not been defined", typeName), identifierToken) + } + nextToken := p.eat() + + if nextToken.Type() != lexer.OPENING_ROUND_BRACKET { + return nil, p.expectedError(`"("`, nextToken) + } + nextToken = p.peek() + expr, err := p.evaluateSingleExpression(ctx) + + if err != nil { + return nil, err + } + exprValueType := expr.ValueType() + exprBaseTypeDefinition, _ := ctx.findType(exprValueType.DataType(), true) + exprBaseValueType := exprBaseTypeDefinition.valueType + foundBaseDefinitionValueType := foundBaseDefinition.valueType + + if !foundBaseDefinitionValueType.Equals(exprBaseValueType) { + return nil, p.atError(fmt.Sprintf(`%s cannot be converted into %s`, exprValueType.String(), typeName), nextToken) + } + nextToken = p.eat() + + if nextToken.Type() != lexer.CLOSING_ROUND_BRACKET { + return nil, p.expectedError(`")"`, nextToken) + } + + return TypeInstantiation{ + value: expr, + valueType: NewValueType(typeName, exprValueType.IsSlice()), + }, nil +} + func (p *Parser) evaluateVarEvaluation(ctx context) (Expression, error) { identifierToken := p.eat() // Eat identifier token. @@ -2098,7 +2201,14 @@ func (p *Parser) evaluateSingleExpression(ctx context) (Expression, error) { // a variable evaluation. switch nextToken.Type() { case lexer.OPENING_ROUND_BRACKET, lexer.DOT: - expr, err = p.evaluateFunctionCall(ctx) + // If a type exists with the provided name, it's a type-cast/-instantiation. + _, exists := ctx.findType(value, false) + + if exists { + expr, err = p.evaluateTypeInstantiation(ctx) + } else { + expr, err = p.evaluateFunctionCall(ctx) + } case lexer.OPENING_SQUARE_BRACKET: expr, err = p.evaluateSubscript(ctx) default: @@ -2181,6 +2291,8 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) { tokenType := token.Type() switch tokenType { + case lexer.TYPE_DEFINITION: + stmt, err = p.evaluateTypeDefinition(ctx) case lexer.VAR_DEFINITION: stmt, err = p.evaluateVarDefinition(ctx) case lexer.FUNCTION_DEFINITION: @@ -2399,7 +2511,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 %s (%s) but got %s", lastParamType.String(), param.Name(), lastArgType.String()), argToken) + return nil, p.expectedError(fmt.Sprintf("parameter of type %s (%s) but got %s", lastParamType.String(), param.Name(), lastArgType.String()), argToken) } } nextToken = p.peek() diff --git a/parser/type.go b/parser/type.go new file mode 100644 index 0000000..7738736 --- /dev/null +++ b/parser/type.go @@ -0,0 +1,30 @@ +package parser + +type TypeDefinition struct { + name string +} + +func (t TypeDefinition) StatementType() StatementType { + return STATEMENT_TYPE_TYPE_DEFINITION +} + +func (t TypeDefinition) Name() string { + return t.name +} + +type TypeInstantiation struct { + value Expression + valueType ValueType +} + +func (t TypeInstantiation) StatementType() StatementType { + return STATEMENT_TYPE_TYPE_INSTANTIATION +} + +func (t TypeInstantiation) ValueType() ValueType { + return t.valueType +} + +func (t TypeInstantiation) Value() Expression { + return t.value +} diff --git a/parser/types.go b/parser/types.go index 7770b39..8d82720 100644 --- a/parser/types.go +++ b/parser/types.go @@ -60,6 +60,8 @@ func (vt ValueType) isNonSliceType(dataType DataType) bool { const ( STATEMENT_TYPE_PROGRAM StatementType = "program" + STATEMENT_TYPE_TYPE_DEFINITION StatementType = "type definition" + STATEMENT_TYPE_TYPE_INSTANTIATION StatementType = "type instantiation" STATEMENT_TYPE_BOOL_LITERAL StatementType = "boolean" STATEMENT_TYPE_INT_LITERAL StatementType = "integer" STATEMENT_TYPE_STRING_LITERAL StatementType = "string" diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 6787bb6..4a760ee 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -106,6 +106,10 @@ func (t *transpiler) evaluateProgram(program parser.Program) error { return err } +func (t *transpiler) evaluateTypeInstantiation(instantiation parser.TypeInstantiation, valueUsed bool) (expressionResult, error) { + return t.evaluateExpression(instantiation.Value(), valueUsed) +} + func (t *transpiler) evaluateBooleanLiteral(literal parser.BooleanLiteral, valueUsed bool) (expressionResult, error) { return newExpressionResult(BoolToString(literal.Value())), nil } @@ -773,6 +777,8 @@ func (t *transpiler) evaluate(statement parser.Statement) error { switch statementType { case parser.STATEMENT_TYPE_PROGRAM: return t.evaluateProgram(statement.(parser.Program)) + case parser.STATEMENT_TYPE_TYPE_DEFINITION: + return nil // Nothing to handle here, types are just relevant for the parser. case parser.STATEMENT_TYPE_VAR_DEFINITION: return t.evaluateVarDefinition(statement.(parser.VariableDefinition)) case parser.STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT: @@ -816,6 +822,8 @@ func (t *transpiler) evaluateExpression(expression parser.Expression, valueUsed expressionType := expression.StatementType() switch expressionType { + case parser.STATEMENT_TYPE_TYPE_INSTANTIATION: + return t.evaluateTypeInstantiation(expression.(parser.TypeInstantiation), valueUsed) case parser.STATEMENT_TYPE_BOOL_LITERAL: return t.evaluateBooleanLiteral(expression.(parser.BooleanLiteral), valueUsed) case parser.STATEMENT_TYPE_INT_LITERAL: From 9bef94e07e44f7ee151d9cebf4d448dcbb7e264e Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 4 Sep 2025 16:48:10 +0200 Subject: [PATCH 07/27] Do some renaming (https://github.com/monstermichl/TypeShell/issues/45) --- parser/parser.go | 4 ++-- parser/type.go | 18 +++++++++--------- parser/types.go | 2 +- transpiler/transpiler.go | 8 ++++---- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index 55d878e..d533a4d 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1053,7 +1053,7 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Statement, error) { if err != nil { return nil, p.atError(err.Error(), valueTypeToken) } - return TypeDefinition{name}, nil + return TypeDeclaration{name}, nil } func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { @@ -2078,7 +2078,7 @@ func (p *Parser) evaluateTypeInstantiation(ctx context) (Expression, error) { return nil, p.expectedError(`")"`, nextToken) } - return TypeInstantiation{ + return TypeDefinition{ value: expr, valueType: NewValueType(typeName, exprValueType.IsSlice()), }, nil diff --git a/parser/type.go b/parser/type.go index 7738736..13a74ef 100644 --- a/parser/type.go +++ b/parser/type.go @@ -1,30 +1,30 @@ package parser -type TypeDefinition struct { +type TypeDeclaration struct { name string } -func (t TypeDefinition) StatementType() StatementType { - return STATEMENT_TYPE_TYPE_DEFINITION +func (t TypeDeclaration) StatementType() StatementType { + return STATEMENT_TYPE_TYPE_DECLARATION } -func (t TypeDefinition) Name() string { +func (t TypeDeclaration) Name() string { return t.name } -type TypeInstantiation struct { +type TypeDefinition struct { value Expression valueType ValueType } -func (t TypeInstantiation) StatementType() StatementType { - return STATEMENT_TYPE_TYPE_INSTANTIATION +func (t TypeDefinition) StatementType() StatementType { + return STATEMENT_TYPE_TYPE_DEFINITION } -func (t TypeInstantiation) ValueType() ValueType { +func (t TypeDefinition) ValueType() ValueType { return t.valueType } -func (t TypeInstantiation) Value() Expression { +func (t TypeDefinition) Value() Expression { return t.value } diff --git a/parser/types.go b/parser/types.go index 8d82720..dc86e7a 100644 --- a/parser/types.go +++ b/parser/types.go @@ -60,8 +60,8 @@ func (vt ValueType) isNonSliceType(dataType DataType) bool { const ( STATEMENT_TYPE_PROGRAM StatementType = "program" + STATEMENT_TYPE_TYPE_DECLARATION StatementType = "type declaration" STATEMENT_TYPE_TYPE_DEFINITION StatementType = "type definition" - STATEMENT_TYPE_TYPE_INSTANTIATION StatementType = "type instantiation" STATEMENT_TYPE_BOOL_LITERAL StatementType = "boolean" STATEMENT_TYPE_INT_LITERAL StatementType = "integer" STATEMENT_TYPE_STRING_LITERAL StatementType = "string" diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 4a760ee..bde46c0 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -106,7 +106,7 @@ func (t *transpiler) evaluateProgram(program parser.Program) error { return err } -func (t *transpiler) evaluateTypeInstantiation(instantiation parser.TypeInstantiation, valueUsed bool) (expressionResult, error) { +func (t *transpiler) evaluateTypeDefinition(instantiation parser.TypeDefinition, valueUsed bool) (expressionResult, error) { return t.evaluateExpression(instantiation.Value(), valueUsed) } @@ -777,7 +777,7 @@ func (t *transpiler) evaluate(statement parser.Statement) error { switch statementType { case parser.STATEMENT_TYPE_PROGRAM: return t.evaluateProgram(statement.(parser.Program)) - case parser.STATEMENT_TYPE_TYPE_DEFINITION: + case parser.STATEMENT_TYPE_TYPE_DECLARATION: return nil // Nothing to handle here, types are just relevant for the parser. case parser.STATEMENT_TYPE_VAR_DEFINITION: return t.evaluateVarDefinition(statement.(parser.VariableDefinition)) @@ -822,8 +822,8 @@ func (t *transpiler) evaluateExpression(expression parser.Expression, valueUsed expressionType := expression.StatementType() switch expressionType { - case parser.STATEMENT_TYPE_TYPE_INSTANTIATION: - return t.evaluateTypeInstantiation(expression.(parser.TypeInstantiation), valueUsed) + case parser.STATEMENT_TYPE_TYPE_DEFINITION: + return t.evaluateTypeDefinition(expression.(parser.TypeDefinition), valueUsed) case parser.STATEMENT_TYPE_BOOL_LITERAL: return t.evaluateBooleanLiteral(expression.(parser.BooleanLiteral), valueUsed) case parser.STATEMENT_TYPE_INT_LITERAL: From d00c08a50c55011d7afc776671003f97ac224632 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 4 Sep 2025 17:34:59 +0200 Subject: [PATCH 08/27] Do some renaming (https://github.com/monstermichl/TypeShell/issues/45) --- lexer/lexer.go | 4 ++-- parser/parser.go | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lexer/lexer.go b/lexer/lexer.go index f8beab7..b534895 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -55,7 +55,7 @@ const ( // Keywords. IMPORT - TYPE_DEFINITION + TYPE_DECLARATION VAR_DEFINITION FUNCTION_DEFINITION RETURN @@ -178,7 +178,7 @@ var nonAlphabeticTokens = []tokenMapping{ var keywords = map[string]TokenType{ // Common keywords. "import": IMPORT, - "type": TYPE_DEFINITION, + "type": TYPE_DECLARATION, "var": VAR_DEFINITION, "func": FUNCTION_DEFINITION, "return": RETURN, diff --git a/parser/parser.go b/parser/parser.go index d533a4d..d608b40 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -117,7 +117,7 @@ func (c context) addType(typeName string, valueType ValueType, isAlias bool, isE _, exists := c.findType(typeName, false) if exists { - return fmt.Errorf(`type "%s" has already been defined`, typeName) + return fmt.Errorf("%s has already been defined", typeName) } if valueType.IsSlice() { // TODO: Add support. @@ -1022,10 +1022,10 @@ func (p *Parser) evaluateValueType(ctx context) (ValueType, error) { return evaluatedType, nil } -func (p *Parser) evaluateTypeDefinition(ctx context) (Statement, error) { +func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { typeToken := p.eat() - if typeToken.Type() != lexer.TYPE_DEFINITION { + if typeToken.Type() != lexer.TYPE_DECLARATION { return nil, p.expectedKeywordError("type", typeToken) } nameToken := p.eat() @@ -2041,7 +2041,7 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { return stmt, nil } -func (p *Parser) evaluateTypeInstantiation(ctx context) (Expression, error) { +func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { identifierToken := p.eat() // Eat identifier token. if identifierToken.Type() != lexer.IDENTIFIER { @@ -2205,7 +2205,7 @@ func (p *Parser) evaluateSingleExpression(ctx context) (Expression, error) { _, exists := ctx.findType(value, false) if exists { - expr, err = p.evaluateTypeInstantiation(ctx) + expr, err = p.evaluateTypeDefinition(ctx) } else { expr, err = p.evaluateFunctionCall(ctx) } @@ -2291,8 +2291,8 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) { tokenType := token.Type() switch tokenType { - case lexer.TYPE_DEFINITION: - stmt, err = p.evaluateTypeDefinition(ctx) + case lexer.TYPE_DECLARATION: + stmt, err = p.evaluateTypeDeclaration(ctx) case lexer.VAR_DEFINITION: stmt, err = p.evaluateVarDefinition(ctx) case lexer.FUNCTION_DEFINITION: From d6a99186f83a4c50b01e506f2f811c4a105361bd Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 4 Sep 2025 17:48:53 +0200 Subject: [PATCH 09/27] Fix type definition base type handling (https://github.com/monstermichl/TypeShell/issues/45) --- parser/parser.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index d608b40..6a25f74 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -2048,7 +2048,7 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { return nil, p.expectedIdentifierError(identifierToken) } typeName := identifierToken.Value() - foundBaseDefinition, exists := ctx.findType(typeName, true) + foundElementaryDefinition, exists := ctx.findType(typeName, true) if !exists { return nil, p.atError(fmt.Sprintf("type %s has not been defined", typeName), identifierToken) @@ -2067,10 +2067,12 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { exprValueType := expr.ValueType() exprBaseTypeDefinition, _ := ctx.findType(exprValueType.DataType(), true) exprBaseValueType := exprBaseTypeDefinition.valueType - foundBaseDefinitionValueType := foundBaseDefinition.valueType + foundElementaryDefinitionValueType := foundElementaryDefinition.valueType + baseDefinition, _ := ctx.findType(typeName, false) + baseTypeName := baseDefinition.name - if !foundBaseDefinitionValueType.Equals(exprBaseValueType) { - return nil, p.atError(fmt.Sprintf(`%s cannot be converted into %s`, exprValueType.String(), typeName), nextToken) + if !foundElementaryDefinitionValueType.Equals(exprBaseValueType) { + return nil, p.atError(fmt.Sprintf(`%s cannot be converted into %s`, exprValueType.String(), baseTypeName), nextToken) } nextToken = p.eat() @@ -2080,7 +2082,7 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { return TypeDefinition{ value: expr, - valueType: NewValueType(typeName, exprValueType.IsSlice()), + valueType: NewValueType(baseTypeName, exprValueType.IsSlice()), }, nil } From 2fe40468b2dd96f4df2fd3c542d537a15c418e72 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 4 Sep 2025 17:50:16 +0200 Subject: [PATCH 10/27] Add tests (https://github.com/monstermichl/TypeShell/issues/45) --- tests/type_linux_test.go | 57 ++++++++++++ tests/type_test.go | 185 +++++++++++++++++++++++++++++++++++++ tests/type_windows_test.go | 57 ++++++++++++ 3 files changed, 299 insertions(+) create mode 100644 tests/type_linux_test.go create mode 100644 tests/type_test.go create mode 100644 tests/type_windows_test.go diff --git a/tests/type_linux_test.go b/tests/type_linux_test.go new file mode 100644 index 0000000..5566026 --- /dev/null +++ b/tests/type_linux_test.go @@ -0,0 +1,57 @@ +package tests + +import ( + "testing" +) + +func TestTypeDeclarationAndDefinitionSuccess(t *testing.T) { + testTypeDeclarationAndDefinitionSuccess(t, transpileBash) +} + +func TestTypeDeclarationAndAssignmentFail(t *testing.T) { + testTypeDeclarationAndAssignmentFail(t, transpileBash) +} + +func TestTypeAliasAndAssignmentSuccess(t *testing.T) { + testTypeAliasAndAssignmentSuccess(t, transpileBash) +} + +func TestTypeDeclarationAndDefinitionInFunctionSuccess(t *testing.T) { + testTypeDeclarationAndDefinitionInFunctionSuccess(t, transpileBash) +} + +func TestTypeDeclarationAndAssignmentInFunctionFail(t *testing.T) { + testTypeDeclarationAndAssignmentInFunctionFail(t, transpileBash) +} + +func TestTypeAliasAndAssignmentInFunctionSuccess(t *testing.T) { + testTypeAliasAndAssignmentInFunctionSuccess(t, transpileBash) +} + +func TestTypeDeclaredInFunctionUsedOutsideFail(t *testing.T) { + testTypeDeclaredInFunctionUsedOutsideFail(t, transpileBash) +} + +func TestTypeDeclaredInIfUsedOutsideFail(t *testing.T) { + testTypeDeclaredInIfUsedOutsideFail(t, transpileBash) +} + +func TestDeclareTypeTwiceFail(t *testing.T) { + testDeclareTypeTwiceFail(t, transpileBash) +} + +func TestPassDeclaredTypeToFunctionSuccess(t *testing.T) { + testPassDeclaredTypeToFunctionSuccess(t, transpileBash) +} + +func TestPassBaseTypeToFunctionFail(t *testing.T) { + testPassBaseTypeToFunctionFail(t, transpileBash) +} + +func TestPassValueWithSameBaseTypeToFunctionSuccess(t *testing.T) { + testPassValueWithSameBaseTypeToFunctionSuccess(t, transpileBash) +} + +func TtestAssignDifferentDefinedTypeFail(t *testing.T) { + testAssignDifferentDefinedTypeFail(t, transpileBash) +} diff --git a/tests/type_test.go b/tests/type_test.go new file mode 100644 index 0000000..5662ab8 --- /dev/null +++ b/tests/type_test.go @@ -0,0 +1,185 @@ +package tests + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func testTypeDeclarationAndDefinitionSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type testType int + + var a testType + a = testType(1) + + print(a) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "1", output) + }) +} + +func testTypeDeclarationAndAssignmentFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type testType int + + var a testType + a = 1 + + print(a) + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "expected testType but got int") + }) +} + +func testTypeAliasAndAssignmentSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type testType = int + + var a testType + a = 1 + + print(a) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "1", output) + }) +} + +func testTypeDeclarationAndDefinitionInFunctionSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + func main() { + type testType int + + var a testType + a = testType(1) + + print(a) + } + main() + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "1", output) + }) +} + +func testTypeDeclarationAndAssignmentInFunctionFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + func main() { + type testType int + + var a testType + a = 1 + + print(a) + } + main() + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "expected testType but got int") + }) +} + +func testTypeAliasAndAssignmentInFunctionSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + func main() { + type testType = int + + var a testType + a = 1 + + print(a) + } + main() + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "1", output) + }) +} + +func testTypeDeclaredInFunctionUsedOutsideFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + func main() { + type testType int + } + var a testType + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "expected valid data type") + }) +} + +func testTypeDeclaredInIfUsedOutsideFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + if true { + type testType int + } + var a testType + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "expected valid data type") + }) +} + +func testDeclareTypeTwiceFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myType int + type myType string + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "myType has already been defined") + }) +} + +func testPassDeclaredTypeToFunctionSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myType string + + func test(param myType) { + print(param) + } + test(myType("test")) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "test", output) + }) +} + +func testPassBaseTypeToFunctionFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myType string + + func test(param myType) { + print(param) + } + test("test") + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "expected parameter of type myType (param) but got string") + }) +} + +func testPassValueWithSameBaseTypeToFunctionSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myType1 = string + type myType2 = string + type myType3 = myType2 + type myType4 = myType3 + + func test(param myType2) { + print(param) + } + test(myType4("test")) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "test", output) + }) +} + +func testAssignDifferentDefinedTypeFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + type myType1 string + type myType2 string + + var a myType1 + a = myType2("test") + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "expected myType1 but got myType2") + }) +} diff --git a/tests/type_windows_test.go b/tests/type_windows_test.go new file mode 100644 index 0000000..9cd5dbb --- /dev/null +++ b/tests/type_windows_test.go @@ -0,0 +1,57 @@ +package tests + +import ( + "testing" +) + +func TestTypeDeclarationAndDefinitionSuccess(t *testing.T) { + testTypeDeclarationAndDefinitionSuccess(t, transpileBatch) +} + +func TestTypeDeclarationAndAssignmentFail(t *testing.T) { + testTypeDeclarationAndAssignmentFail(t, transpileBatch) +} + +func TestTypeAliasAndAssignmentSuccess(t *testing.T) { + testTypeAliasAndAssignmentSuccess(t, transpileBatch) +} + +func TestTypeDeclarationAndDefinitionInFunctionSuccess(t *testing.T) { + testTypeDeclarationAndDefinitionInFunctionSuccess(t, transpileBatch) +} + +func TestTypeDeclarationAndAssignmentInFunctionFail(t *testing.T) { + testTypeDeclarationAndAssignmentInFunctionFail(t, transpileBatch) +} + +func TestTypeAliasAndAssignmentInFunctionSuccess(t *testing.T) { + testTypeAliasAndAssignmentInFunctionSuccess(t, transpileBatch) +} + +func TestTypeDeclaredInFunctionUsedOutsideFail(t *testing.T) { + testTypeDeclaredInFunctionUsedOutsideFail(t, transpileBatch) +} + +func TestTypeDeclaredInIfUsedOutsideFail(t *testing.T) { + testTypeDeclaredInIfUsedOutsideFail(t, transpileBatch) +} + +func TestDeclareTypeTwiceFail(t *testing.T) { + testDeclareTypeTwiceFail(t, transpileBatch) +} + +func TestPassDeclaredTypeToFunctionSuccess(t *testing.T) { + testPassDeclaredTypeToFunctionSuccess(t, transpileBatch) +} + +func TestPassBaseTypeToFunctionFail(t *testing.T) { + testPassBaseTypeToFunctionFail(t, transpileBatch) +} + +func TestPassValueWithSameBaseTypeToFunctionSuccess(t *testing.T) { + testPassValueWithSameBaseTypeToFunctionSuccess(t, transpileBatch) +} + +func TtestAssignDifferentDefinedTypeFail(t *testing.T) { + testAssignDifferentDefinedTypeFail(t, transpileBatch) +} From b9b5637bb5f5533d51f4e5f1513f19212c6eb51a Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 4 Sep 2025 17:52:07 +0200 Subject: [PATCH 11/27] Revert "Start struct definition implementation" This reverts commit c81e3c866ecc885fd06a0335b2a356faf1d844cb. --- lexer/lexer.go | 4 -- parser/parser.go | 112 ---------------------------------------- parser/struct.go | 31 ----------- parser/types.go | 1 - transpiler/converter.go | 6 --- 5 files changed, 154 deletions(-) delete mode 100644 parser/struct.go diff --git a/lexer/lexer.go b/lexer/lexer.go index 35d8312..330a093 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -70,8 +70,6 @@ const ( RANGE BREAK CONTINUE - TYPE - STRUCT // Builtin functions. LEN @@ -195,8 +193,6 @@ var keywords = map[string]TokenType{ "break": BREAK, "continue": CONTINUE, "nil": NIL_LITERAL, - "type": TYPE, - "struct": STRUCT, // Builtin functions. "len": LEN, diff --git a/parser/parser.go b/parser/parser.go index 2e364e9..819565f 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -45,7 +45,6 @@ type context struct { imports map[string]string // Maps import aliases to file hashes. variables map[string]Variable // Stores the variable name to variable relation. functions map[string]FunctionDefinition // Stores the function name to function relation. - structs map[string]StructDefinition // Stores the struct name to struct relation. scopeStack []scope // Stores the current scopes. } @@ -54,7 +53,6 @@ func newContext() context { imports: map[string]string{}, variables: map[string]Variable{}, functions: map[string]FunctionDefinition{}, - structs: map[string]StructDefinition{}, } } @@ -123,18 +121,6 @@ func (c context) addFunctions(prefix string, global bool, functions ...FunctionD return nil } -func (c context) addStructs(prefix string, global bool, structs ...StructDefinition) error { - for _, strct := range structs { - prefixedName, err := c.buildPrefixedName(strct.Name(), prefix, global, false) - - if err != nil { - return err - } - c.structs[prefixedName] = strct - } - return nil -} - func (c context) findImport(alias string) (string, bool) { hash, exists := c.imports[alias] return hash, exists @@ -160,22 +146,11 @@ func (c context) findFunction(name string, prefix string) (FunctionDefinition, b return function, exists } -func (c context) findStruct(name string, prefix string, global bool) (StructDefinition, bool) { - prefixedName, err := c.buildPrefixedName(name, prefix, global, true) - - if err != nil { - return StructDefinition{}, false - } - strct, exists := c.structs[prefixedName] - return strct, exists -} - func (c context) clone() context { return context{ imports: maps.Clone(c.imports), variables: maps.Clone(c.variables), functions: maps.Clone(c.functions), - structs: maps.Clone(c.structs), scopeStack: slices.Clone(c.scopeStack), } } @@ -892,13 +867,6 @@ func (p *Parser) evaluateBlockContent(terminationTokenTypes []lexer.TokenType, c // Store new function. err = ctx.addFunctions(prefix, global, stmt.(FunctionDefinition)) - if err != nil { - return nil, err - } - case STATEMENT_TYPE_STRUCT_DEFINITION: - // Store new type. - err = ctx.addStructs(prefix, global, stmt.(StructDefinition)) - if err != nil { return nil, err } @@ -1567,84 +1535,6 @@ func (p *Parser) evaluateContinue(ctx context) (Statement, error) { return Continue{}, nil } -func (p *Parser) evaluateTypeDefinition(ctx context) (Statement, error) { - nextToken := p.eat() - - if nextToken.Type() != lexer.TYPE { - return nil, p.expectedKeywordError("type", nextToken) - } - nextToken = p.eat() - - if nextToken.Type() != lexer.IDENTIFIER { - return nil, p.expectedIdentifierError(nextToken) - } - structName := nextToken.Value() - _, exists := ctx.findStruct(structName, p.prefix, ctx.global()) - - if exists { - return nil, p.atError(fmt.Sprintf(`the struct "%s" has already been defined`, structName), nextToken) - } - nextToken = p.eat() - - // For now, only struct definitions are allowed. - if nextToken.Type() != lexer.STRUCT { - return nil, p.expectedKeywordError("struct", nextToken) - } - 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) - } - structDefinition := StructDefinition{ - name: structName, - } - propertyNames := []string{} - - for { - nextToken = p.peek() - nextTokenType := nextToken.Type() - - if slices.Contains([]lexer.TokenType{lexer.CLOSING_CURLY_BRACKET, lexer.EOF}, nextTokenType) { - break - } - p.eat() - - if nextTokenType != lexer.IDENTIFIER { - return nil, p.expectedIdentifierError(nextToken) - } - propertyName := nextToken.Value() - - if slices.Contains(propertyNames, propertyName) { - return nil, p.atError(fmt.Sprintf(`struct "%s" already contains a property "%s"`, structName, propertyName), nextToken) - } - valueType, err := p.evaluateValueType() - - if err != nil { - return nil, err - } - structDefinition.members = append(structDefinition.members, StructMember{ - propertyName, - valueType, - }) - nextToken = p.eat() - - if nextToken.Type() != lexer.NEWLINE { - return nil, p.expectedNewlineError(nextToken) - } - } - nextToken = p.eat() - - if nextToken.Type() != lexer.CLOSING_CURLY_BRACKET { - return nil, p.expectedError(`"}"`, nextToken) - } - return structDefinition, nil -} - func (p *Parser) evaluateIf(ctx context) (Statement, error) { var ifStatement If @@ -2279,8 +2169,6 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) { stmt, err = p.evaluateWrite(ctx) case lexer.PANIC: stmt, err = p.evaluatePanic(ctx) - case lexer.TYPE: - stmt, err = p.evaluateTypeDefinition(ctx) default: // Variable initialization also starts with identifier but is a statement (e.g. x := 1234). if p.isShortVarInit() { diff --git a/parser/struct.go b/parser/struct.go deleted file mode 100644 index 533ae4d..0000000 --- a/parser/struct.go +++ /dev/null @@ -1,31 +0,0 @@ -package parser - -type StructMember struct { - name string - valueType ValueType -} - -func (m StructMember) Name() string { - return m.name -} - -func (m StructMember) ValueType() ValueType { - return m.valueType -} - -type StructDefinition struct { - name string - members []StructMember -} - -func (d StructDefinition) StatementType() StatementType { - return STATEMENT_TYPE_STRUCT_DEFINITION -} - -func (d StructDefinition) Name() string { - return d.name -} - -func (d StructDefinition) Members() []StructMember { - return d.members -} diff --git a/parser/types.go b/parser/types.go index 90e6717..a38d93a 100644 --- a/parser/types.go +++ b/parser/types.go @@ -84,7 +84,6 @@ const ( STATEMENT_TYPE_FOR_RANGE StatementType = "for range" STATEMENT_TYPE_BREAK StatementType = "break" STATEMENT_TYPE_CONTINUE StatementType = "continue" - STATEMENT_TYPE_STRUCT_DEFINITION StatementType = "struct definition" STATEMENT_TYPE_INSTANTIATION StatementType = "instantiation" STATEMENT_TYPE_PRINT StatementType = "print" STATEMENT_TYPE_ITOA StatementType = "itoa" diff --git a/transpiler/converter.go b/transpiler/converter.go index 18aa6e8..c0561c4 100644 --- a/transpiler/converter.go +++ b/transpiler/converter.go @@ -46,12 +46,6 @@ func (rv ReturnValue) ValueType() parser.ValueType { return rv.valueType } -type StructMember struct { - name string - value string - valueType parser.ValueType -} - type Converter interface { // Common methods StringToString(value string) string From 4ec043eb73760eda3e5a2575a3f93212ab309a77 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 4 Sep 2025 17:53:44 +0200 Subject: [PATCH 12/27] Fix typo (https://github.com/monstermichl/TypeShell/issues/45) --- tests/type_linux_test.go | 2 +- tests/type_windows_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/type_linux_test.go b/tests/type_linux_test.go index 5566026..d94951f 100644 --- a/tests/type_linux_test.go +++ b/tests/type_linux_test.go @@ -52,6 +52,6 @@ func TestPassValueWithSameBaseTypeToFunctionSuccess(t *testing.T) { testPassValueWithSameBaseTypeToFunctionSuccess(t, transpileBash) } -func TtestAssignDifferentDefinedTypeFail(t *testing.T) { +func TestAssignDifferentDefinedTypeFail(t *testing.T) { testAssignDifferentDefinedTypeFail(t, transpileBash) } diff --git a/tests/type_windows_test.go b/tests/type_windows_test.go index 9cd5dbb..ff91013 100644 --- a/tests/type_windows_test.go +++ b/tests/type_windows_test.go @@ -52,6 +52,6 @@ func TestPassValueWithSameBaseTypeToFunctionSuccess(t *testing.T) { testPassValueWithSameBaseTypeToFunctionSuccess(t, transpileBatch) } -func TtestAssignDifferentDefinedTypeFail(t *testing.T) { +func TestAssignDifferentDefinedTypeFail(t *testing.T) { testAssignDifferentDefinedTypeFail(t, transpileBatch) } From be1a2f37375a2f21dc8bd696463958cb930ef96c Mon Sep 17 00:00:00 2001 From: monstermichl Date: Thu, 4 Sep 2025 21:03:31 +0200 Subject: [PATCH 13/27] Start constants implementation (https://github.com/monstermichl/TypeShell/issues/41) --- lexer/lexer.go | 9 +-- parser/const.go | 53 +++++++++++++++ parser/namedvalue.go | 9 +++ parser/parser.go | 156 +++++++++++++++++++++++++++---------------- parser/types.go | 2 + parser/variable.go | 4 ++ 6 files changed, 167 insertions(+), 66 deletions(-) create mode 100644 parser/const.go create mode 100644 parser/namedvalue.go diff --git a/lexer/lexer.go b/lexer/lexer.go index b534895..3a837e7 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -56,6 +56,7 @@ const ( // Keywords. IMPORT TYPE_DECLARATION + CONST_DEFINITION VAR_DEFINITION FUNCTION_DEFINITION RETURN @@ -88,13 +89,6 @@ const ( EOF ) -const ( - DATA_TYPE_BOOLEAN VarType = "bool" - DATA_TYPE_INTEGER VarType = "int" - DATA_TYPE_STRING VarType = "string" - DATA_TYPE_ERROR VarType = "error" -) - type Token struct { tokenType TokenType value string @@ -179,6 +173,7 @@ var keywords = map[string]TokenType{ // Common keywords. "import": IMPORT, "type": TYPE_DECLARATION, + "const": CONST_DEFINITION, "var": VAR_DEFINITION, "func": FUNCTION_DEFINITION, "return": RETURN, diff --git a/parser/const.go b/parser/const.go new file mode 100644 index 0000000..f015aec --- /dev/null +++ b/parser/const.go @@ -0,0 +1,53 @@ +package parser + +type Const struct { + name string + valueType ValueType + global bool + public bool +} + +func (c Const) Name() string { + return c.name +} + +func (c Const) ValueType() ValueType { + return c.valueType +} + +func (c Const) Global() bool { + return c.global +} + +func (c Const) Public() bool { + return c.public +} + +func (c Const) IsConstant() bool { + return true +} + +type ConstDefinition struct { + constants []Const + values []Expression +} + +func (c ConstDefinition) StatementType() StatementType { + return STATEMENT_TYPE_CONST_DEFINITION +} + +func (c ConstDefinition) Constants() []Const { + return c.constants +} + +func (c ConstDefinition) Values() []Expression { + return c.values +} + +type ConstEvaluation struct { + Const +} + +func (c ConstEvaluation) StatementType() StatementType { + return STATEMENT_TYPE_CONST_EVALUATION +} diff --git a/parser/namedvalue.go b/parser/namedvalue.go new file mode 100644 index 0000000..cf2a090 --- /dev/null +++ b/parser/namedvalue.go @@ -0,0 +1,9 @@ +package parser + +type NamedValue interface { + Name() string + ValueType() ValueType + Global() bool + Public() bool + IsConstant() bool +} diff --git a/parser/parser.go b/parser/parser.go index 6a25f74..6e815d1 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -46,19 +46,19 @@ type foundTypeDefinition struct { } type context struct { - imports map[string]string // Maps import aliases to file hashes. - types map[string]typeDefinition // Stores the defined types. - variables map[string]Variable // Stores the variable name to variable relation. - functions map[string]FunctionDefinition // Stores the function name to function relation. - scopeStack []scope // Stores the current scopes. + imports map[string]string // Maps import aliases to file hashes. + types map[string]typeDefinition // Stores the defined 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. } func newContext() context { c := context{ - imports: map[string]string{}, - types: map[string]typeDefinition{}, - variables: map[string]Variable{}, - functions: map[string]FunctionDefinition{}, + imports: map[string]string{}, + types: map[string]typeDefinition{}, + namedValues: map[string]NamedValue{}, + functions: map[string]FunctionDefinition{}, } // Define elementary types. @@ -131,14 +131,14 @@ func (c context) addType(typeName string, valueType ValueType, isAlias bool, isE return nil } -func (c context) addVariables(prefix string, global bool, variables ...Variable) error { - for _, variable := range variables { - prefixedName, err := c.buildPrefixedName(variable.Name(), prefix, global, false) +func (c context) addNamedValues(prefix string, global bool, namedValues ...NamedValue) error { + for _, namedValue := range namedValues { + prefixedName, err := c.buildPrefixedName(namedValue.Name(), prefix, global, false) if err != nil { return err } - c.variables[prefixedName] = variable + c.namedValues[prefixedName] = namedValue } return nil } @@ -181,14 +181,14 @@ func (c context) findType(typeName string, searchUntilElementary bool) (foundTyp return foundDefinition, exists } -func (c context) findVariable(name string, prefix string, global bool) (Variable, bool) { +func (c context) findNamedValue(name string, prefix string, global bool) (NamedValue, bool) { prefixedName, err := c.buildPrefixedName(name, prefix, global, true) if err != nil { return Variable{}, false } - variable, exists := c.variables[prefixedName] - return variable, exists + namedValue, exists := c.namedValues[prefixedName] + return namedValue, exists } func (c context) findFunction(name string, prefix string) (FunctionDefinition, bool) { @@ -203,11 +203,11 @@ func (c context) findFunction(name string, prefix string) (FunctionDefinition, b func (c context) clone() context { return context{ - imports: maps.Clone(c.imports), - types: maps.Clone(c.types), - variables: maps.Clone(c.variables), - functions: maps.Clone(c.functions), - scopeStack: slices.Clone(c.scopeStack), + imports: maps.Clone(c.imports), + types: maps.Clone(c.types), + namedValues: maps.Clone(c.namedValues), + functions: maps.Clone(c.functions), + scopeStack: slices.Clone(c.scopeStack), } } @@ -422,6 +422,10 @@ func (p *Parser) expectedNewlineError(token lexer.Token) error { return p.expectedError("newline", token) } +func (p *Parser) constantError(constant string, token lexer.Token) error { + return p.atError(fmt.Sprintf("cannot assign a value to constant %s", constant), token) +} + func (p Parser) peek() lexer.Token { return p.peekAt(0) } @@ -491,7 +495,7 @@ func (p *Parser) isShortVarInit() bool { func (p *Parser) checkNewVariableNameToken(token lexer.Token, ctx context) error { name := token.Value() - _, exists := ctx.findVariable(name, p.prefix, ctx.global()) + _, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if exists { return p.atError(fmt.Sprintf("variable %s has already been defined", name), token) @@ -809,8 +813,8 @@ func (p *Parser) evaluateImports(ctx context) ([]Statement, error) { for _, variable := range definedVariable.Variables() { name := variable.Name() - if _, exists = ctx.variables[name]; !exists && variable.Public() { - ctx.variables[name] = variable + if _, exists = ctx.namedValues[name]; !exists && variable.Public() { + ctx.namedValues[name] = variable } } case STATEMENT_TYPE_FUNCTION_DEFINITION: @@ -910,18 +914,22 @@ func (p *Parser) evaluateBlockContent(terminationTokenTypes []lexer.TokenType, c switch stmt.StatementType() { case STATEMENT_TYPE_VAR_DEFINITION: - // Store new variable. - err = ctx.addVariables(prefix, global, stmt.(VariableDefinition).Variables()...) + // Store new variables. + for _, variable := range stmt.(VariableDefinition).Variables() { + err = ctx.addNamedValues(prefix, global, variable) - if err != nil { - return nil, err + if err != nil { + return nil, err + } } case STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT: - // Store new variable. - err = ctx.addVariables(prefix, global, stmt.(VariableDefinitionCallAssignment).Variables()...) + // Store new variables. + for _, variable := range stmt.(VariableDefinitionCallAssignment).Variables() { + err = ctx.addNamedValues(prefix, global, variable) - if err != nil { - return nil, err + if err != nil { + return nil, err + } } case STATEMENT_TYPE_FUNCTION_DEFINITION: // Store new function. @@ -1056,6 +1064,10 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { return TypeDeclaration{name}, nil } +func (p *Parser) evaluateConstDefinition(ctx context) (Statement, error) { + return ConstDefinition{}, nil +} + func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { // Possible variable declarations/definitions: // var v int @@ -1146,7 +1158,11 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { prefix := p.prefix global := ctx.global() name := nameToken.Value() - variable, exists := ctx.findVariable(name, prefix, global) + variable, exists := ctx.findNamedValue(name, prefix, global) + + if !exists { + variable = Variable{} + } variableValueType := variable.ValueType() // If the variable already exists, make sure it has the same type as the specified type. @@ -1291,13 +1307,16 @@ func (p *Parser) evaluateCompoundAssignment(ctx context) (Statement, error) { name := nameToken.Value() // Make sure variable has been defined. - definedVariable, exists := ctx.findVariable(name, p.prefix, ctx.global()) + namedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if !exists { return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), nameToken) + } else if namedValue.IsConstant() { + return nil, p.constantError(name, nameToken) } + definedVariable := namedValue.(Variable) valueType := valuesTypes[0] - expectedValueType := definedVariable.ValueType() + expectedValueType := namedValue.ValueType() if valueType != expectedValueType { return nil, p.expectedError(fmt.Sprintf("%s but got %s", expectedValueType.String(), valueType.String()), valuesToken) @@ -1362,7 +1381,7 @@ func (p *Parser) evaluateVarAssignment(ctx context) (Statement, error) { name := nameToken.Value() // Make sure variable has been defined. - definedVariable, exists := ctx.findVariable(name, p.prefix, ctx.global()) + definedVariable, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if !exists { return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), nameToken) @@ -1405,7 +1424,7 @@ func (p *Parser) evaluateParams(ctx context) ([]Variable, error) { p.eat() name := nameToken.Value() - _, exists := ctx.findVariable(name, p.prefix, false) + _, exists := ctx.findNamedValue(name, p.prefix, false) if exists { return params, fmt.Errorf("scope already contains a variable with the name %s", name) @@ -1457,7 +1476,7 @@ func (p *Parser) evaluateFunctionDefinition(ctx context) (Statement, error) { ctx = ctx.clone() // Remove all variables which are not global. - maps.DeleteFunc(ctx.variables, func(_ string, v Variable) bool { + maps.DeleteFunc(ctx.namedValues, func(_ string, v NamedValue) bool { return !v.Global() }) @@ -1514,7 +1533,7 @@ func (p *Parser) evaluateFunctionDefinition(ctx context) (Statement, error) { // Add parameters to variables. for _, param := range params { - err := ctx.addVariables(p.prefix, false, param) + err := ctx.addNamedValues(p.prefix, false, param) if err != nil { return nil, err @@ -1893,14 +1912,14 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { forRangeStatements := []Statement{} // Add count variable. - ctx.addVariables(p.prefix, false, indexVar) + ctx.addNamedValues(p.prefix, false, indexVar) // If no value variable has been provided, there's no need to add it. if hasNamedVar { valueVar := NewVariable(valueVarName, iterableValueType, false, false) // Add value variable. - ctx.addVariables(p.prefix, false, valueVar) + ctx.addNamedValues(p.prefix, false, valueVar) forRangeStatements = []Statement{ VariableAssignment{ @@ -1955,20 +1974,26 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { if err != nil { return nil, err } + prefix := p.prefix + switch init.StatementType() { case STATEMENT_TYPE_VAR_DEFINITION: // Store new variable. - err = ctx.addVariables(p.prefix, false, init.(VariableDefinition).Variables()...) + for _, variable := range init.(VariableDefinition).Variables() { + err = ctx.addNamedValues(prefix, false, variable) - if err != nil { - return nil, err + if err != nil { + return nil, err + } } case STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT: // Store new variable. - err = ctx.addVariables(p.prefix, false, init.(VariableDefinitionCallAssignment).Variables()...) + for _, variable := range init.(VariableDefinitionCallAssignment).Variables() { + err = ctx.addNamedValues(prefix, false, variable) - if err != nil { - return nil, err + if err != nil { + return nil, err + } } case STATEMENT_TYPE_VAR_ASSIGNMENT: default: @@ -2086,20 +2111,26 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { }, nil } -func (p *Parser) evaluateVarEvaluation(ctx context) (Expression, error) { +func (p *Parser) evaluateNamedValueEvaluation(ctx context) (Expression, error) { identifierToken := p.eat() // Eat identifier token. if identifierToken.Type() != lexer.IDENTIFIER { return nil, p.expectedIdentifierError(identifierToken) } name := identifierToken.Value() - variable, exists := ctx.findVariable(name, p.prefix, ctx.global()) + namedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if !exists { return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), identifierToken) } + + if namedValue.IsConstant() { + return ConstEvaluation{ + Const: namedValue.(Const), + }, nil + } return VariableEvaluation{ - Variable: variable, + Variable: namedValue.(Variable), }, nil } @@ -2214,7 +2245,7 @@ func (p *Parser) evaluateSingleExpression(ctx context) (Expression, error) { case lexer.OPENING_SQUARE_BRACKET: expr, err = p.evaluateSubscript(ctx) default: - expr, err = p.evaluateVarEvaluation(ctx) + expr, err = p.evaluateNamedValueEvaluation(ctx) } default: @@ -2295,6 +2326,8 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) { switch tokenType { case lexer.TYPE_DECLARATION: stmt, err = p.evaluateTypeDeclaration(ctx) + case lexer.CONST_DEFINITION: + stmt, err = p.evaluateConstDefinition(ctx) case lexer.VAR_DEFINITION: stmt, err = p.evaluateVarDefinition(ctx) case lexer.FUNCTION_DEFINITION: @@ -2333,7 +2366,7 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) { stmt, err = p.evaluateVarAssignment(ctx) default: // Handle slice assignment. - variable, exists := ctx.findVariable(token.Value(), p.prefix, ctx.global()) + 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() { @@ -2706,7 +2739,7 @@ func (p *Parser) evaluateSubscript(ctx context) (Expression, error) { switch valueToken.Type() { case lexer.IDENTIFIER: - value, err = p.evaluateVarEvaluation(ctx) + value, err = p.evaluateNamedValueEvaluation(ctx) case lexer.STRING_LITERAL: value, err = p.evaluateExpression(ctx) default: @@ -2821,12 +2854,14 @@ func (p *Parser) evaluateSliceAssignment(ctx context) (Statement, error) { return nil, p.expectedError("slice variable", nameToken) } name := nameToken.Value() - variable, exists := ctx.findVariable(name, p.prefix, ctx.global()) + namedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if !exists { return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), nameToken) + } else if namedValue.IsConstant() { + return nil, p.constantError(name, nameToken) } - variableValueType := variable.ValueType() + variableValueType := namedValue.ValueType() if !variableValueType.IsSlice() { return nil, p.expectedError(fmt.Sprintf("slice but variable is of type %s", variableValueType.String()), nameToken) @@ -2870,7 +2905,7 @@ func (p *Parser) evaluateSliceAssignment(ctx context) (Statement, error) { return nil, p.expectedError(fmt.Sprintf("%s value but got %s", variableDataType, assignedDataType), valueToken) } return SliceAssignment{ - Variable: variable, + Variable: namedValue.(Variable), index: index, value: value, }, nil @@ -2883,12 +2918,15 @@ func (p *Parser) evaluateIncrementDecrement(ctx context) (Statement, error) { return nil, p.expectedIdentifierError(identifierToken) } name := identifierToken.Value() - definedVariable, exists := ctx.findVariable(name, p.prefix, ctx.global()) + namedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if !exists { return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), identifierToken) + } else if namedValue.IsConstant() { + return nil, p.constantError(name, identifierToken) } - valueType := definedVariable.ValueType() + variable := namedValue.(Variable) + 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) @@ -2904,7 +2942,7 @@ func (p *Parser) evaluateIncrementDecrement(ctx context) (Statement, error) { default: return nil, p.expectedError(`"++" or "--"`, operationToken) } - return incrementDecrementStatement(definedVariable, increment), nil + return incrementDecrementStatement(variable, increment), nil } func (p *Parser) evaluateLen(ctx context) (Expression, error) { diff --git a/parser/types.go b/parser/types.go index dc86e7a..4518166 100644 --- a/parser/types.go +++ b/parser/types.go @@ -71,6 +71,8 @@ const ( STATEMENT_TYPE_BINARY_OPERATION StatementType = "binary operation" STATEMENT_TYPE_LOGICAL_OPERATION StatementType = "logical operation" STATEMENT_TYPE_COMPARISON StatementType = "comparison" + STATEMENT_TYPE_CONST_DEFINITION StatementType = "constant definition" + STATEMENT_TYPE_CONST_EVALUATION StatementType = "constant evaluation" STATEMENT_TYPE_VAR_DEFINITION StatementType = "variable definition" STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT StatementType = "variable definition func assignment" STATEMENT_TYPE_VAR_ASSIGNMENT StatementType = "variable assignment" diff --git a/parser/variable.go b/parser/variable.go index 0b71669..44dfbc6 100644 --- a/parser/variable.go +++ b/parser/variable.go @@ -32,6 +32,10 @@ func (v Variable) Public() bool { return v.public } +func (v Variable) IsConstant() bool { + return false +} + type VariableDefinition struct { variables []Variable values []Expression From 529c48b03f3681660120fc11e24bf24b827d5d48 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 09:37:26 +0200 Subject: [PATCH 14/27] Use a common function for variable and constant evaluation (evaluateNamedValueDefinition) (https://github.com/monstermichl/TypeShell/issues/41) --- parser/const.go | 13 ++++ parser/parser.go | 161 +++++++++++++++++++++++++++++++++------------ parser/types.go | 14 +++- parser/variable.go | 4 ++ 4 files changed, 148 insertions(+), 44 deletions(-) diff --git a/parser/const.go b/parser/const.go index f015aec..7278f6c 100644 --- a/parser/const.go +++ b/parser/const.go @@ -7,6 +7,15 @@ type Const struct { public bool } +func NewConst(name string, valueType ValueType, global bool, public bool) Const { + return Const{ + name, + valueType, + global, + public, + } +} + func (c Const) Name() string { return c.name } @@ -15,6 +24,10 @@ func (c Const) ValueType() ValueType { return c.valueType } +func (c *Const) SetValueType(valueType ValueType) { + c.valueType = valueType +} + func (c Const) Global() bool { return c.global } diff --git a/parser/parser.go b/parser/parser.go index 6e815d1..f74ae9f 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -218,6 +218,7 @@ type evaluatedImport struct { type evaluatedValues struct { values []Expression + tokens []lexer.Token } func (ev evaluatedValues) isMultiReturnCall() (bool, Call) { @@ -236,6 +237,17 @@ func (ev evaluatedValues) isMultiReturnCall() (bool, Call) { return multi, call } +func (ev evaluatedValues) areConstants() bool { + areConst := len(ev.values) > 0 + + for _, value := range ev.values { + if !value.StatementType().IsConstant() { + return false + } + } + return areConst +} + type blockCallback func(statements []Statement, last bool) error type Parser struct { @@ -493,12 +505,17 @@ func (p *Parser) isShortVarInit() bool { return err == nil } -func (p *Parser) checkNewVariableNameToken(token lexer.Token, ctx context) error { +func (p *Parser) checkNewNamedValueNameToken(token lexer.Token, ctx context) error { name := token.Value() - _, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) + foundNamedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if exists { - return p.atError(fmt.Sprintf("variable %s has already been defined", name), token) + namedValueType := "variable" + + if foundNamedValue.IsConstant() { + namedValueType = "constant" + } + return p.atError(fmt.Sprintf("%s %s has already been defined", namedValueType, name), token) } return nil } @@ -546,7 +563,7 @@ func (p *Parser) cleanProgram(program Program) (Program, error) { }, nil } -func (p *Parser) evaluateVarNames() ([]lexer.Token, error) { +func (p *Parser) evaluateNames() ([]lexer.Token, error) { nameTokens := []lexer.Token{} for { @@ -568,6 +585,7 @@ func (p *Parser) evaluateVarNames() ([]lexer.Token, error) { func (p *Parser) evaluateValues(ctx context) (evaluatedValues, error) { expressions := []Expression{} + tokens := []lexer.Token{} for { exprToken := p.peek() @@ -577,6 +595,7 @@ func (p *Parser) evaluateValues(ctx context) (evaluatedValues, error) { return evaluatedValues{}, err } expressions = append(expressions, expr) + tokens = append(tokens, exprToken) nextToken := p.peek() returnValuesLength := -1 funcName := "" @@ -604,6 +623,7 @@ func (p *Parser) evaluateValues(ctx context) (evaluatedValues, error) { } return evaluatedValues{ values: expressions, + tokens: tokens, }, nil } @@ -1064,27 +1084,28 @@ func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { return TypeDeclaration{name}, nil } -func (p *Parser) evaluateConstDefinition(ctx context) (Statement, error) { - return ConstDefinition{}, nil -} +func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Statement, error) { + isShortVarInit := !evalConst && p.isShortVarInit() + noun := "variable" -func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { - // Possible variable declarations/definitions: - // var v int - // var v int = 1 - // var v = 1 - // v := 1 - isShortVarInit := p.isShortVarInit() + if evalConst { + noun = "constant" + } // Eat "var" token only, if the variable is not defined using the short init operator (:=). if !isShortVarInit { - varToken := p.eat() + keywordToken := p.eat() + varTokenType := keywordToken.Type() - if varToken.Type() != lexer.VAR_DEFINITION { - return nil, p.expectedError("variable definition", varToken) + if !evalConst { + if varTokenType != lexer.VAR_DEFINITION { + return nil, p.expectedKeywordError("var", keywordToken) + } + } else if varTokenType != lexer.CONST_DEFINITION { + return nil, p.expectedKeywordError("const", keywordToken) } } - nameTokens, err := p.evaluateVarNames() + nameTokens, err := p.evaluateNames() if err != nil { return nil, err @@ -1092,12 +1113,12 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { nameTokensLength := len(nameTokens) firstNameToken := nameTokens[0] - // Check if all variables are already defined. + // Check if all named values are already defined. if nameTokensLength > 1 { alreadyDefined := 0 for _, nameToken := range nameTokens { - err := p.checkNewVariableNameToken(nameToken, ctx) + err := p.checkNewNamedValueNameToken(nameToken, ctx) if err != nil { // Only allow "re-definition" of variable via the short init operator. @@ -1109,10 +1130,10 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { } if alreadyDefined == nameTokensLength { - return nil, p.atError("no new variables", firstNameToken) + return nil, p.atError(fmt.Sprintf("no new %ss", noun), firstNameToken) } } else { - err := p.checkNewVariableNameToken(firstNameToken, ctx) + err := p.checkNewNamedValueNameToken(firstNameToken, ctx) if err != nil { return nil, err @@ -1151,30 +1172,42 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { } nextToken := p.peek() nextTokenType := nextToken.Type() - variables := []Variable{} + namedValues := []NamedValue{} // Fill variables slice (might not contain the final type after this step). for _, nameToken := range nameTokens { prefix := p.prefix global := ctx.global() name := nameToken.Value() - variable, exists := ctx.findNamedValue(name, prefix, global) + namedValue, exists := ctx.findNamedValue(name, prefix, global) if !exists { - variable = Variable{} + if evalConst { + namedValue = Const{} + } else { + namedValue = Variable{} + } } - variableValueType := variable.ValueType() + 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) { - return nil, p.atError(fmt.Sprintf(`variable "%s" already exists but has type %s`, name, variableValueType.String()), nextToken) + return nil, p.atError(fmt.Sprintf(`%s %s already exists but has type %s`, noun, name, variableValueType.String()), nextToken) } storedName := name if global { storedName = buildPrefixedName(prefix, name) } - variables = append(variables, NewVariable(storedName, specifiedType, global, isPublic(name))) + var newNamedValue NamedValue + isPublicValue := isPublic(name) + + if evalConst { + newNamedValue = NewVariable(storedName, specifiedType, global, isPublicValue) + } else { + newNamedValue = NewVariable(storedName, specifiedType, global, isPublicValue) + } + namedValues = append(namedValues, newNamedValue) } values := []Expression{} @@ -1184,6 +1217,8 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { if err != nil { return nil, err + } else if evalConst && !evaluatedVals.areConstants() { + return nil, p.expectedError("constant values", nextToken) } values = evaluatedVals.values valuesTypes := []ValueType{} @@ -1198,7 +1233,7 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { } } valuesTypesLen := len(valuesTypes) - variablesLen := len(variables) + variablesLen := len(namedValues) // Check if the amount of values is equal to the amount of variable names. if valuesTypesLen != variablesLen { @@ -1211,7 +1246,7 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { if variablesLen != 1 { pluralValues = "s" } - return nil, p.atError(fmt.Sprintf("got %d initialisation value%s but %d variable%s", valuesTypesLen, pluralInit, variablesLen, pluralValues), nextToken) + return nil, p.atError(fmt.Sprintf("got %d initialisation value%s but %d %s%s", valuesTypesLen, pluralInit, variablesLen, noun, pluralValues), nextToken) } // If a type has been specified, make sure the returned types fit this type. @@ -1224,19 +1259,32 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { } // Check if variables exist and if, check if the types match. - for i, variable := range variables { + for i, namedValue := range namedValues { valueValueType := valuesTypes[i] - variableValueType := variable.ValueType() + variableValueType := namedValue.ValueType() if variableValueType.DataType() == DATA_TYPE_UNKNOWN { - variables[i].valueType = valueValueType // Use index here to make sure the original variable is modified, not the copy. + var updatedNamedValue NamedValue + name, global, public := namedValue.Name(), namedValue.Global(), namedValue.Public() + + if evalConst { + updatedNamedValue = NewConst(name, valueValueType, global, public) + } else { + updatedNamedValue = NewVariable(name, valueValueType, global, public) + } + namedValues[i] = updatedNamedValue } else if !variableValueType.Equals(valueValueType) { - return nil, p.expectedError(fmt.Sprintf("%s but got %s for variable %s", variableValueType.String(), valueValueType.String(), variable.Name()), nextToken) + return nil, p.expectedError(fmt.Sprintf("%s but got %s for %s %s", variableValueType.String(), valueValueType.String(), noun, namedValue.Name()), nextToken) } } // If it's a function call multi assignment, build return value here. if isMultiReturnFuncCall { + variables := []Variable{} + + for _, namedValue := range namedValues { + variables = append(variables, namedValue.(Variable)) + } call := VariableDefinitionCallAssignment{ variables, call, @@ -1247,7 +1295,7 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { // If no value has been specified, define default value. if len(values) == 0 { - for _, variable := range variables { + for _, variable := range namedValues { value, err := defaultVarValue(variable.ValueType(), ctx) if err != nil { @@ -1256,15 +1304,42 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { values = append(values, value) } } - variable := VariableDefinition{ - variables, - values, + var stmt Statement + + if evalConst { + constants := []Const{} + + for _, namedValue := range namedValues { + constants = append(constants, namedValue.(Const)) + } + stmt = ConstDefinition{ + constants, + values, + } + } else { + variables := []Variable{} + + for _, namedValue := range namedValues { + variables = append(variables, namedValue.(Variable)) + } + stmt = VariableDefinition{ + variables, + values, + } } - return variable, nil + return stmt, nil +} + +func (p *Parser) evaluateConstDefinition(ctx context) (Statement, error) { + return p.evaluateNamedValueDefinition(true, ctx) +} + +func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) { + return p.evaluateNamedValueDefinition(false, ctx) } func (p *Parser) evaluateCompoundAssignment(ctx context) (Statement, error) { - nameTokens, err := p.evaluateVarNames() + nameTokens, err := p.evaluateNames() if err != nil { return nil, err @@ -1340,7 +1415,7 @@ func (p *Parser) evaluateCompoundAssignment(ctx context) (Statement, error) { } func (p *Parser) evaluateVarAssignment(ctx context) (Statement, error) { - nameTokens, err := p.evaluateVarNames() + nameTokens, err := p.evaluateNames() if err != nil { return nil, err @@ -1850,7 +1925,7 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { // If next token is an identifier and the one after it a comma or a short-init operator and range keyword, parse a for-range statement. if nextTokenType == lexer.IDENTIFIER && (nextAfterNextTokenType == lexer.COMMA || (nextAfterNextTokenType == lexer.SHORT_INIT_OPERATOR && p.peekAt(2).Type() == lexer.RANGE)) { p.eat() - err := p.checkNewVariableNameToken(nextToken, ctx) + err := p.checkNewNamedValueNameToken(nextToken, ctx) if err != nil { return nil, err @@ -1866,7 +1941,7 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { if nextToken.Type() != lexer.IDENTIFIER { return nil, p.expectedIdentifierError(nextToken) } - err = p.checkNewVariableNameToken(nextToken, ctx) + err = p.checkNewNamedValueNameToken(nextToken, ctx) if err != nil { return nil, err diff --git a/parser/types.go b/parser/types.go index 4518166..d391c5d 100644 --- a/parser/types.go +++ b/parser/types.go @@ -1,6 +1,9 @@ package parser -import "fmt" +import ( + "fmt" + "slices" +) type StatementType string type DataType = string @@ -9,6 +12,15 @@ type UnaryOperator = string type BinaryOperator = string type LogicalOperator = string +func (s StatementType) IsConstant() bool { + return slices.Contains([]StatementType{ + STATEMENT_TYPE_BOOL_LITERAL, + STATEMENT_TYPE_STRING_LITERAL, + STATEMENT_TYPE_INT_LITERAL, + STATEMENT_TYPE_CONST_EVALUATION, + }, s) +} + type ValueType struct { dataType DataType isSlice bool diff --git a/parser/variable.go b/parser/variable.go index 44dfbc6..4a05fdb 100644 --- a/parser/variable.go +++ b/parser/variable.go @@ -24,6 +24,10 @@ func (v Variable) ValueType() ValueType { return v.valueType } +func (v *Variable) SetValueType(valueType ValueType) { + v.valueType = valueType +} + func (v Variable) Global() bool { return v.global } From 08e90cf2c33d2e1d98296675cb54583d4c016591 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 09:37:41 +0200 Subject: [PATCH 15/27] Improve error message handling --- parser/parser.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index f74ae9f..800fc02 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -438,6 +438,14 @@ func (p *Parser) constantError(constant string, token lexer.Token) error { return p.atError(fmt.Sprintf("cannot assign a value to constant %s", constant), token) } +func (p *Parser) notDefinedError(what string, name string, token lexer.Token) error { + return p.atError(fmt.Sprintf("%s %s has not been defined", what, name), token) +} + +func (p *Parser) variableNotDefinedError(variable string, token lexer.Token) error { + return p.notDefinedError("variable", variable, token) +} + func (p Parser) peek() lexer.Token { return p.peekAt(0) } @@ -1385,7 +1393,7 @@ func (p *Parser) evaluateCompoundAssignment(ctx context) (Statement, error) { namedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if !exists { - return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), nameToken) + return nil, p.variableNotDefinedError(name, nameToken) } else if namedValue.IsConstant() { return nil, p.constantError(name, nameToken) } @@ -1459,7 +1467,7 @@ func (p *Parser) evaluateVarAssignment(ctx context) (Statement, error) { definedVariable, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if !exists { - return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), nameToken) + return nil, p.variableNotDefinedError(name, nameToken) } valueType := valuesTypes[i] expectedValueType := definedVariable.ValueType() @@ -2151,7 +2159,7 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { foundElementaryDefinition, exists := ctx.findType(typeName, true) if !exists { - return nil, p.atError(fmt.Sprintf("type %s has not been defined", typeName), identifierToken) + return nil, p.notDefinedError("type", typeName, identifierToken) } nextToken := p.eat() @@ -2196,7 +2204,7 @@ func (p *Parser) evaluateNamedValueEvaluation(ctx context) (Expression, error) { namedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if !exists { - return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), identifierToken) + return nil, p.variableNotDefinedError(name, identifierToken) } if namedValue.IsConstant() { @@ -2685,7 +2693,7 @@ func (p *Parser) evaluateFunctionCall(ctx context) (Call, error) { definedFunction, exists := ctx.findFunction(name, prefix) if !exists { - return nil, p.atError(fmt.Sprintf("function %s has not been defined", dotedName), nextToken) + return nil, p.notDefinedError("function", dotedName, nextToken) } args, err := p.evaluateArguments("function", dotedName, definedFunction.params, ctx) @@ -2932,7 +2940,7 @@ func (p *Parser) evaluateSliceAssignment(ctx context) (Statement, error) { namedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if !exists { - return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), nameToken) + return nil, p.variableNotDefinedError(name, nameToken) } else if namedValue.IsConstant() { return nil, p.constantError(name, nameToken) } @@ -2996,7 +3004,7 @@ func (p *Parser) evaluateIncrementDecrement(ctx context) (Statement, error) { namedValue, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) if !exists { - return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), identifierToken) + return nil, p.variableNotDefinedError(name, identifierToken) } else if namedValue.IsConstant() { return nil, p.constantError(name, identifierToken) } From 635ab1ea5bc4d5c2323116fce5ed9d9a45de1b4c Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 09:58:44 +0200 Subject: [PATCH 16/27] Add first working draft of single line const definition (https://github.com/monstermichl/TypeShell/issues/41) --- parser/parser.go | 30 +++++++++++++++++++++++++++++- parser/variable.go | 18 ++++++++++++++++++ transpiler/transpiler.go | 21 +++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/parser/parser.go b/parser/parser.go index 800fc02..4e61f44 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -830,7 +830,7 @@ func (p *Parser) evaluateImports(ctx context) ([]Statement, error) { } statements := []Statement{} - // Add functions add variables. + // Add functions, variables and constants. for _, statement := range statementsTemp { exists := false @@ -841,6 +841,16 @@ func (p *Parser) evaluateImports(ctx context) ([]Statement, error) { for _, variable := range definedVariable.Variables() { name := variable.Name() + if _, exists = ctx.namedValues[name]; !exists && variable.Public() { + ctx.namedValues[name] = variable + } + } + case STATEMENT_TYPE_CONST_DEFINITION: + definedConstant := statement.(ConstDefinition) + + for _, variable := range definedConstant.Constants() { + name := variable.Name() + if _, exists = ctx.namedValues[name]; !exists && variable.Public() { ctx.namedValues[name] = variable } @@ -955,6 +965,15 @@ func (p *Parser) evaluateBlockContent(terminationTokenTypes []lexer.TokenType, c for _, variable := range stmt.(VariableDefinitionCallAssignment).Variables() { err = ctx.addNamedValues(prefix, global, variable) + if err != nil { + return nil, err + } + } + case STATEMENT_TYPE_CONST_DEFINITION: + // Store new constants. + for _, variable := range stmt.(ConstDefinition).Constants() { + err = ctx.addNamedValues(prefix, global, variable) + if err != nil { return nil, err } @@ -2074,6 +2093,15 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { for _, variable := range init.(VariableDefinitionCallAssignment).Variables() { err = ctx.addNamedValues(prefix, false, variable) + if err != nil { + return nil, err + } + } + case STATEMENT_TYPE_CONST_DEFINITION: + // Store new variable. + for _, variable := range init.(ConstDefinition).Constants() { + err = ctx.addNamedValues(prefix, false, variable) + if err != nil { return nil, err } diff --git a/parser/variable.go b/parser/variable.go index 4a05fdb..0fb3b37 100644 --- a/parser/variable.go +++ b/parser/variable.go @@ -45,6 +45,13 @@ type VariableDefinition struct { values []Expression } +func NewVariableDefinition(variables []Variable, values []Expression) VariableDefinition { + return VariableDefinition{ + variables, + values, + } +} + func (v VariableDefinition) StatementType() StatementType { return STATEMENT_TYPE_VAR_DEFINITION } @@ -112,6 +119,17 @@ type VariableEvaluation struct { Variable } +func NewVariableEvaluation(name string, valueType ValueType, global bool, public bool) VariableEvaluation { + return VariableEvaluation{ + Variable{ + name, + valueType, + global, + public, + }, + } +} + func (e VariableEvaluation) StatementType() StatementType { return STATEMENT_TYPE_VAR_EVALUATION } diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index bde46c0..7916cdf 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -369,6 +369,16 @@ func (t *transpiler) evaluateFor(forStatement parser.For) error { return conv.ForEnd() } +func (t *transpiler) evaluateConstDefinition(definition parser.ConstDefinition) error { + variables := []parser.Variable{} + + // Map const definition to var definition since constant check has already been performed by parser. + for _, constant := range definition.Constants() { + variables = append(variables, parser.NewVariable(constant.Name(), constant.ValueType(), constant.Global(), constant.Public())) + } + return t.evaluateVarDefinition(parser.NewVariableDefinition(variables, definition.Values())) +} + func (t *transpiler) evaluateVarDefinition(definition parser.VariableDefinition) error { for i, variable := range definition.Variables() { result, err := t.evaluateExpression(definition.Values()[i], true) @@ -472,6 +482,13 @@ func (t *transpiler) evaluateSliceAssignment(assignment parser.SliceAssignment) return t.converter.SliceAssignment(assignment.Name(), indexResult.firstValue(), valueResult.firstValue(), defaultValue, 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.Name(), evaluation.ValueType(), evaluation.Global(), evaluation.Public()) + + return t.evaluateVarEvaluation(varEvaluation, valueUsed) +} + func (t *transpiler) evaluateVarEvaluation(evaluation parser.VariableEvaluation, valueUsed bool) (expressionResult, error) { s, err := t.converter.VarEvaluation(evaluation.Name(), valueUsed, evaluation.Global()) @@ -779,6 +796,8 @@ func (t *transpiler) evaluate(statement parser.Statement) error { return t.evaluateProgram(statement.(parser.Program)) case parser.STATEMENT_TYPE_TYPE_DECLARATION: return nil // Nothing to handle here, types are just relevant for the parser. + case parser.STATEMENT_TYPE_CONST_DEFINITION: + return t.evaluateConstDefinition(statement.(parser.ConstDefinition)) case parser.STATEMENT_TYPE_VAR_DEFINITION: return t.evaluateVarDefinition(statement.(parser.VariableDefinition)) case parser.STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT: @@ -838,6 +857,8 @@ func (t *transpiler) evaluateExpression(expression parser.Expression, valueUsed return t.evaluateCompareOperation(expression.(parser.Comparison), valueUsed) case parser.STATEMENT_TYPE_LOGICAL_OPERATION: return t.evaluateLogicalOperation(expression.(parser.LogicalOperation), valueUsed) + case parser.STATEMENT_TYPE_CONST_EVALUATION: + return t.evaluateConstEvaluation(expression.(parser.ConstEvaluation), valueUsed) case parser.STATEMENT_TYPE_VAR_EVALUATION: return t.evaluateVarEvaluation(expression.(parser.VariableEvaluation), valueUsed) case parser.STATEMENT_TYPE_SLICE_EVALUATION: From af7dd1d2a15561ea0e82eec24ee10257e858cef4 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 10:01:29 +0200 Subject: [PATCH 17/27] Fix Const instantiation (https://github.com/monstermichl/TypeShell/issues/41) --- parser/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/parser.go b/parser/parser.go index 4e61f44..a59c560 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1230,7 +1230,7 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat isPublicValue := isPublic(name) if evalConst { - newNamedValue = NewVariable(storedName, specifiedType, global, isPublicValue) + newNamedValue = NewConst(storedName, specifiedType, global, isPublicValue) } else { newNamedValue = NewVariable(storedName, specifiedType, global, isPublicValue) } From c0fa740667bd6ae66eb88a38573678421e936af0 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 12:51:13 +0200 Subject: [PATCH 18/27] Define in Expression if it's constant to be able to decide if the assigned value to a constant is actually constant (https://github.com/monstermichl/TypeShell/issues/41) --- parser/appcall.go | 4 ++++ parser/binaryoperation.go | 10 +++++++--- parser/comparison.go | 4 ++++ parser/const.go | 8 ++++---- parser/copy.go | 4 ++++ parser/exists.go | 4 ++++ parser/expression.go | 1 + parser/function.go | 8 ++++++++ parser/group.go | 4 ++++ parser/input.go | 4 ++++ parser/itoa.go | 4 ++++ parser/len.go | 4 ++++ parser/literals.go | 12 ++++++++++++ parser/logicaloperation.go | 4 ++++ parser/parser.go | 19 ++++++------------- parser/read.go | 4 ++++ parser/slice.go | 8 ++++++++ parser/string.go | 4 ++++ parser/type.go | 4 ++++ parser/types.go | 10 ---------- parser/unaryoperation.go | 4 ++++ parser/variable.go | 8 ++++---- parser/write.go | 4 ++++ 23 files changed, 106 insertions(+), 34 deletions(-) diff --git a/parser/appcall.go b/parser/appcall.go index df144da..1880573 100644 --- a/parser/appcall.go +++ b/parser/appcall.go @@ -14,6 +14,10 @@ func (a AppCall) ValueType() ValueType { return NewValueType(DATA_TYPE_MULTIPLE, false) } +func (a AppCall) IsConstant() bool { + return false +} + func (a AppCall) Name() string { return a.name } diff --git a/parser/binaryoperation.go b/parser/binaryoperation.go index dbe5eb3..02ccbca 100644 --- a/parser/binaryoperation.go +++ b/parser/binaryoperation.go @@ -1,9 +1,9 @@ package parser type BinaryOperation struct { - left Expression - operator BinaryOperator - right Expression + left Expression + operator BinaryOperator + right Expression } func (b BinaryOperation) StatementType() StatementType { @@ -14,6 +14,10 @@ func (b BinaryOperation) ValueType() ValueType { return b.left.ValueType() } +func (b BinaryOperation) IsConstant() bool { + return b.Left().IsConstant() && b.Right().IsConstant() +} + func (b BinaryOperation) Left() Expression { return b.left } diff --git a/parser/comparison.go b/parser/comparison.go index b70717a..24c02a7 100644 --- a/parser/comparison.go +++ b/parser/comparison.go @@ -22,6 +22,10 @@ func (c Comparison) ValueType() ValueType { return ValueType{dataType: DATA_TYPE_BOOLEAN} } +func (c Comparison) IsConstant() bool { + return c.Left().IsConstant() && c.Right().IsConstant() +} + func (c Comparison) Left() Expression { return c.left } diff --git a/parser/const.go b/parser/const.go index 7278f6c..dbfa0df 100644 --- a/parser/const.go +++ b/parser/const.go @@ -24,6 +24,10 @@ func (c Const) ValueType() ValueType { return c.valueType } +func (c Const) IsConstant() bool { + return true +} + func (c *Const) SetValueType(valueType ValueType) { c.valueType = valueType } @@ -36,10 +40,6 @@ func (c Const) Public() bool { return c.public } -func (c Const) IsConstant() bool { - return true -} - type ConstDefinition struct { constants []Const values []Expression diff --git a/parser/copy.go b/parser/copy.go index fc7538d..bf13050 100644 --- a/parser/copy.go +++ b/parser/copy.go @@ -13,6 +13,10 @@ func (c Copy) ValueType() ValueType { return NewValueType(DATA_TYPE_INTEGER, false) } +func (c Copy) IsConstant() bool { + return false +} + func (c Copy) Source() Expression { return c.source } diff --git a/parser/exists.go b/parser/exists.go index 4af1209..2c1402a 100644 --- a/parser/exists.go +++ b/parser/exists.go @@ -12,6 +12,10 @@ func (e Exists) ValueType() ValueType { return NewValueType(DATA_TYPE_BOOLEAN, false) } +func (e Exists) IsConstant() bool { + return false +} + func (e Exists) Path() Expression { return e.path } diff --git a/parser/expression.go b/parser/expression.go index 59310c3..f799b98 100644 --- a/parser/expression.go +++ b/parser/expression.go @@ -4,4 +4,5 @@ type Expression interface { // An expression is a super-type of statement which results in a value. Statement ValueType() ValueType + IsConstant() bool } diff --git a/parser/function.go b/parser/function.go index c330183..ed46f3f 100644 --- a/parser/function.go +++ b/parser/function.go @@ -20,6 +20,10 @@ func (e FunctionDefinition) ValueType() ValueType { return functionValueType(e.returnTypes) } +func (e FunctionDefinition) IsConstant() bool { + return false +} + func (e FunctionDefinition) ReturnTypes() []ValueType { return e.returnTypes } @@ -54,6 +58,10 @@ func (e FunctionCall) ValueType() ValueType { return functionValueType(e.returnTypes) } +func (e FunctionCall) IsConstant() bool { + return false +} + func (e FunctionCall) ReturnTypes() []ValueType { return e.returnTypes } diff --git a/parser/group.go b/parser/group.go index 34eea1e..96ba29e 100644 --- a/parser/group.go +++ b/parser/group.go @@ -12,6 +12,10 @@ func (e Group) ValueType() ValueType { return e.Child().ValueType() } +func (e Group) IsConstant() bool { + return e.Child().IsConstant() +} + func (e Group) Child() Expression { return e.child } diff --git a/parser/input.go b/parser/input.go index 9d7fbd5..77be324 100644 --- a/parser/input.go +++ b/parser/input.go @@ -12,6 +12,10 @@ func (i Input) ValueType() ValueType { return ValueType{dataType: DATA_TYPE_STRING} } +func (i Input) IsConstant() bool { + return false +} + func (i Input) Prompt() Expression { return i.prompt } diff --git a/parser/itoa.go b/parser/itoa.go index 237cefc..89a30ed 100644 --- a/parser/itoa.go +++ b/parser/itoa.go @@ -12,6 +12,10 @@ func (e Itoa) ValueType() ValueType { return NewValueType(DATA_TYPE_STRING, false) } +func (o Itoa) IsConstant() bool { + return false +} + func (e Itoa) Value() Expression { return e.value } diff --git a/parser/len.go b/parser/len.go index 3146c28..067ec1e 100644 --- a/parser/len.go +++ b/parser/len.go @@ -12,6 +12,10 @@ func (l Len) ValueType() ValueType { return NewValueType(DATA_TYPE_INTEGER, false) } +func (l Len) IsConstant() bool { + return false +} + func (l Len) Expression() Expression { return l.expression } diff --git a/parser/literals.go b/parser/literals.go index 8171ee3..16f6eb8 100644 --- a/parser/literals.go +++ b/parser/literals.go @@ -12,6 +12,10 @@ func (l BooleanLiteral) ValueType() ValueType { return ValueType{dataType: DATA_TYPE_BOOLEAN} } +func (l BooleanLiteral) IsConstant() bool { + return true +} + func (l BooleanLiteral) Value() bool { return l.value } @@ -28,6 +32,10 @@ func (l IntegerLiteral) ValueType() ValueType { return ValueType{dataType: DATA_TYPE_INTEGER} } +func (l IntegerLiteral) IsConstant() bool { + return true +} + func (l IntegerLiteral) Value() int { return l.value } @@ -44,6 +52,10 @@ func (l StringLiteral) ValueType() ValueType { return ValueType{dataType: DATA_TYPE_STRING} } +func (l StringLiteral) IsConstant() bool { + return true +} + func (l StringLiteral) Value() string { return l.value } diff --git a/parser/logicaloperation.go b/parser/logicaloperation.go index 4ae8003..b1f7357 100644 --- a/parser/logicaloperation.go +++ b/parser/logicaloperation.go @@ -14,6 +14,10 @@ func (l LogicalOperation) ValueType() ValueType { return ValueType{dataType: DATA_TYPE_BOOLEAN} } +func (l LogicalOperation) IsConstant() bool { + return l.Left().IsConstant() && l.Right().IsConstant() +} + func (l LogicalOperation) Left() Expression { return l.left } diff --git a/parser/parser.go b/parser/parser.go index a59c560..eb62408 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -237,17 +237,6 @@ func (ev evaluatedValues) isMultiReturnCall() (bool, Call) { return multi, call } -func (ev evaluatedValues) areConstants() bool { - areConst := len(ev.values) > 0 - - for _, value := range ev.values { - if !value.StatementType().IsConstant() { - return false - } - } - return areConst -} - type blockCallback func(statements []Statement, last bool) error type Parser struct { @@ -1244,8 +1233,12 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat if err != nil { return nil, err - } else if evalConst && !evaluatedVals.areConstants() { - return nil, p.expectedError("constant values", nextToken) + } else if evalConst { + for i, evaluatedVal := range evaluatedVals.values { + if !evaluatedVal.IsConstant() { + return nil, p.expectedError("constant values", evaluatedVals.tokens[i]) + } + } } values = evaluatedVals.values valuesTypes := []ValueType{} diff --git a/parser/read.go b/parser/read.go index 48690cd..7e65730 100644 --- a/parser/read.go +++ b/parser/read.go @@ -12,6 +12,10 @@ func (r Read) ValueType() ValueType { return NewValueType(DATA_TYPE_STRING, false) } +func (r Read) IsConstant() bool { + return false +} + func (r Read) Path() Expression { return r.path } diff --git a/parser/slice.go b/parser/slice.go index 705fe27..199ce9f 100644 --- a/parser/slice.go +++ b/parser/slice.go @@ -13,6 +13,10 @@ func (s SliceInstantiation) ValueType() ValueType { return ValueType{dataType: s.dataType, isSlice: true} } +func (s SliceInstantiation) IsConstant() bool { + return false +} + func (s SliceInstantiation) Values() []Expression { return s.values } @@ -39,6 +43,10 @@ func (s SliceEvaluation) ValueType() ValueType { return ValueType{dataType: s.dataType} } +func (s SliceEvaluation) IsConstant() bool { + return false +} + type SliceAssignment struct { Variable index Expression diff --git a/parser/string.go b/parser/string.go index 0a80b43..ef49e8c 100644 --- a/parser/string.go +++ b/parser/string.go @@ -14,6 +14,10 @@ func (s StringSubscript) ValueType() ValueType { return NewValueType(DATA_TYPE_STRING, false) } +func (s StringSubscript) IsConstant() bool { + return false +} + func (s StringSubscript) Value() Expression { return s.value } diff --git a/parser/type.go b/parser/type.go index 13a74ef..9411655 100644 --- a/parser/type.go +++ b/parser/type.go @@ -25,6 +25,10 @@ func (t TypeDefinition) ValueType() ValueType { return t.valueType } +func (t TypeDefinition) IsConstant() bool { + return t.Value().IsConstant() +} + func (t TypeDefinition) Value() Expression { return t.value } diff --git a/parser/types.go b/parser/types.go index d391c5d..40ab466 100644 --- a/parser/types.go +++ b/parser/types.go @@ -2,7 +2,6 @@ package parser import ( "fmt" - "slices" ) type StatementType string @@ -12,15 +11,6 @@ type UnaryOperator = string type BinaryOperator = string type LogicalOperator = string -func (s StatementType) IsConstant() bool { - return slices.Contains([]StatementType{ - STATEMENT_TYPE_BOOL_LITERAL, - STATEMENT_TYPE_STRING_LITERAL, - STATEMENT_TYPE_INT_LITERAL, - STATEMENT_TYPE_CONST_EVALUATION, - }, s) -} - type ValueType struct { dataType DataType isSlice bool diff --git a/parser/unaryoperation.go b/parser/unaryoperation.go index 5d6c02f..91b6fdc 100644 --- a/parser/unaryoperation.go +++ b/parser/unaryoperation.go @@ -14,6 +14,10 @@ func (b UnaryOperation) ValueType() ValueType { return b.valueType } +func (b UnaryOperation) IsConstant() bool { + return b.Expression().IsConstant() +} + func (b UnaryOperation) Expression() Expression { return b.expr } diff --git a/parser/variable.go b/parser/variable.go index 0fb3b37..d91bf91 100644 --- a/parser/variable.go +++ b/parser/variable.go @@ -24,6 +24,10 @@ func (v Variable) ValueType() ValueType { return v.valueType } +func (v Variable) IsConstant() bool { + return false +} + func (v *Variable) SetValueType(valueType ValueType) { v.valueType = valueType } @@ -36,10 +40,6 @@ func (v Variable) Public() bool { return v.public } -func (v Variable) IsConstant() bool { - return false -} - type VariableDefinition struct { variables []Variable values []Expression diff --git a/parser/write.go b/parser/write.go index 67ee93e..d5fc30a 100644 --- a/parser/write.go +++ b/parser/write.go @@ -14,6 +14,10 @@ func (w Write) ValueType() ValueType { return NewValueType(DATA_TYPE_ERROR, false) } +func (w Write) IsConstant() bool { + return false +} + func (w Write) Path() Expression { return w.path } From 2831525e2c0424545075985c9ed2c0cf34850cdd Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 12:51:26 +0200 Subject: [PATCH 19/27] Fix expression evaluation (https://github.com/monstermichl/TypeShell/issues/41) --- parser/parser.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/parser.go b/parser/parser.go index eb62408..02ad73d 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -2188,7 +2188,7 @@ func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { return nil, p.expectedError(`"("`, nextToken) } nextToken = p.peek() - expr, err := p.evaluateSingleExpression(ctx) + expr, err := p.evaluateExpression(ctx) if err != nil { return nil, err From c26ce6b580b1d7209ebd73763857dd3374ecd552 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 13:07:50 +0200 Subject: [PATCH 20/27] Make sure values are assigned at constant definition (https://github.com/monstermichl/TypeShell/issues/41) --- parser/parser.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/parser/parser.go b/parser/parser.go index 02ad73d..e8617a3 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1226,6 +1226,7 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat namedValues = append(namedValues, newNamedValue) } values := []Expression{} + firstValueToken := p.peek() // TODO: Improve check (avoid NEWLINE and EOF check). if nextTokenType != lexer.NEWLINE && nextTokenType != lexer.EOF { @@ -1312,9 +1313,14 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat return call, nil } } + lenValues := len(values) + + if evalConst && lenValues != nameTokensLength { + return nil, p.atError("all constants must be initialized", firstValueToken) + } // If no value has been specified, define default value. - if len(values) == 0 { + if lenValues == 0 { for _, variable := range namedValues { value, err := defaultVarValue(variable.ValueType(), ctx) From 346f334ebb462e6e13bd354090f8a56c5b62c738 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 13:09:49 +0200 Subject: [PATCH 21/27] Make sure value cannot be assigned to named value if it's a constant (https://github.com/monstermichl/TypeShell/issues/41) --- parser/parser.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/parser/parser.go b/parser/parser.go index e8617a3..f71ff89 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1482,13 +1482,15 @@ func (p *Parser) evaluateVarAssignment(ctx context) (Statement, error) { name := nameToken.Value() // Make sure variable has been defined. - definedVariable, exists := ctx.findNamedValue(name, p.prefix, ctx.global()) + 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) } valueType := valuesTypes[i] - expectedValueType := definedVariable.ValueType() + expectedValueType := namedValue.ValueType() if valueType != expectedValueType { return nil, p.expectedError(fmt.Sprintf("%s but got %s", expectedValueType.String(), valueType.String()), valuesToken) From b393eed707bb68687d9aa8825f350c6d5d91123e Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 13:19:04 +0200 Subject: [PATCH 22/27] Add tests (https://github.com/monstermichl/TypeShell/issues/41) --- parser/parser.go | 2 +- tests/const.go | 67 +++++++++++++++++++++++++++++++++++++ tests/const_linux_test.go | 25 ++++++++++++++ tests/const_windows_test.go | 25 ++++++++++++++ 4 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 tests/const.go create mode 100644 tests/const_linux_test.go create mode 100644 tests/const_windows_test.go diff --git a/parser/parser.go b/parser/parser.go index f71ff89..12f21eb 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -1237,7 +1237,7 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat } else if evalConst { for i, evaluatedVal := range evaluatedVals.values { if !evaluatedVal.IsConstant() { - return nil, p.expectedError("constant values", evaluatedVals.tokens[i]) + return nil, p.expectedError("constant value", evaluatedVals.tokens[i]) } } } diff --git a/tests/const.go b/tests/const.go new file mode 100644 index 0000000..7209e00 --- /dev/null +++ b/tests/const.go @@ -0,0 +1,67 @@ +package tests + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func testDefineConstantsSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + const a = 0 + const b int = 1 + const c, d = 2, 4 - 1 + const e, f int = 4, c + d + + print(a, b, c, d, e, f) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "0 1 2 3 4 5", output) + }) +} + +func testDefineConstantsInFunctionSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + func main() { + const a = 0 + const b int = 1 + const c, d = 2, 4 - 1 + const e, f int = 4, c + d + + print(a, b, c, d, e, f) + } + main() + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "0 1 2 3 4 5", output) + }) +} + +func testDefineSameConstantFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + const a = 1 + const a = 2 + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "constant a has already been defined") + }) +} + +func testAssignFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + const a = 1 + a = 2 + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "cannot assign a value to constant a") + }) +} + +func testAssignFromFunctionFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + func one() int { + return 1 + } + const a = one() + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "expected constant value") + }) +} diff --git a/tests/const_linux_test.go b/tests/const_linux_test.go new file mode 100644 index 0000000..7b51e30 --- /dev/null +++ b/tests/const_linux_test.go @@ -0,0 +1,25 @@ +package tests + +import ( + "testing" +) + +func TestDefineConstantsSuccess(t *testing.T) { + testDefineConstantsSuccess(t, transpileBash) +} + +func TestDefineConstantsInFunctionSuccess(t *testing.T) { + testDefineConstantsInFunctionSuccess(t, transpileBash) +} + +func TestDefineSameConstantFail(t *testing.T) { + testDefineSameConstantFail(t, transpileBash) +} + +func TestAssignFail(t *testing.T) { + testAssignFail(t, transpileBash) +} + +func TestAssignFromFunctionFail(t *testing.T) { + testAssignFromFunctionFail(t, transpileBash) +} diff --git a/tests/const_windows_test.go b/tests/const_windows_test.go new file mode 100644 index 0000000..871368f --- /dev/null +++ b/tests/const_windows_test.go @@ -0,0 +1,25 @@ +package tests + +import ( + "testing" +) + +func TestDefineConstantsSuccess(t *testing.T) { + testDefineConstantsSuccess(t, transpileBatch) +} + +func TestDefineConstantsInFunctionSuccess(t *testing.T) { + testDefineConstantsInFunctionSuccess(t, transpileBatch) +} + +func TestDefineSameConstantFail(t *testing.T) { + testDefineSameConstantFail(t, transpileBatch) +} + +func TestAssignFail(t *testing.T) { + testAssignFail(t, transpileBatch) +} + +func TestAssignFromFunctionFail(t *testing.T) { + testAssignFromFunctionFail(t, transpileBatch) +} From 399da7a6331b3acbb3c67099688ca73e303260ad Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 15:52:39 +0200 Subject: [PATCH 23/27] Allow constant and variable definitions via grouping syntax (https://github.com/monstermichl/TypeShell/issues/41) --- README.md | 16 +- parser/assignment.go | 6 + parser/const.go | 4 + parser/namedvalue.go | 16 ++ parser/parser.go | 508 +++++++++++++++++++++------------------ parser/types.go | 92 +++---- parser/variable.go | 54 ++++- transpiler/transpiler.go | 25 +- 8 files changed, 426 insertions(+), 295 deletions(-) create mode 100644 parser/assignment.go diff --git a/README.md b/README.md index 2d4555b..4f00a06 100644 --- a/README.md +++ b/README.md @@ -30,20 +30,28 @@ Supported variable types are *bool*, *int*, *string* and *error*. ```golang // Variable definition with default value. var a int -var a, b int +var b, c int ``` ```golang // Variable definition with assigned value. var a int = 5 -var a, b int = divisionWithRemainder(5, 2) +var b, c int = divisionWithRemainder(5, 2) +``` + +```golang +// Variable definition via grouping. +var ( + a = 5 + b, c int = divisionWithRemainder(5, 2) +) ``` ```golang // Variable definition short form. a := 5 -a, b := 5, 6 -a, b := divisionWithRemainder(5, 2) +b, c := 5, 6 +d, e := divisionWithRemainder(5, 2) ``` ### Control flow diff --git a/parser/assignment.go b/parser/assignment.go new file mode 100644 index 0000000..5d774c1 --- /dev/null +++ b/parser/assignment.go @@ -0,0 +1,6 @@ +package parser + +type Assignment interface { + Statement + AssignmentType() AssignmentType +} diff --git a/parser/const.go b/parser/const.go index dbfa0df..b90e05c 100644 --- a/parser/const.go +++ b/parser/const.go @@ -49,6 +49,10 @@ func (c ConstDefinition) StatementType() StatementType { return STATEMENT_TYPE_CONST_DEFINITION } +func (c ConstDefinition) AssignmentType() AssignmentType { + return ASSIGNMENT_TYPE_VALUE +} + func (c ConstDefinition) Constants() []Const { return c.constants } diff --git a/parser/namedvalue.go b/parser/namedvalue.go index cf2a090..2bf514f 100644 --- a/parser/namedvalue.go +++ b/parser/namedvalue.go @@ -7,3 +7,19 @@ type NamedValue interface { Public() bool IsConstant() bool } + +type NamedValuesDefinition struct { + assignments []Assignment +} + +func (v NamedValuesDefinition) StatementType() StatementType { + return STATEMENT_TYPE_NAMED_VALUES_DEFINITION +} + +func (v *NamedValuesDefinition) AddAssignment(assignment Assignment) { + v.assignments = append(v.assignments, assignment) +} + +func (v NamedValuesDefinition) Assignments() []Assignment { + return v.assignments +} diff --git a/parser/parser.go b/parser/parser.go index 12f21eb..9e79ffa 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -370,7 +370,7 @@ func incrementDecrementStatement(variable Variable, increment bool) Statement { if !increment { operation = BINARY_OPERATOR_SUBTRACTION } - return VariableAssignment{ + return VariableAssignmentValueAssignment{ variables: []Variable{variable}, values: []Expression{ BinaryOperation{ @@ -824,8 +824,8 @@ func (p *Parser) evaluateImports(ctx context) ([]Statement, error) { exists := false switch statement.StatementType() { - case STATEMENT_TYPE_VAR_DEFINITION: - definedVariable := statement.(VariableDefinition) + case STATEMENT_TYPE_VAR_DEFINITION_VALUE_ASSIGNMENT: + definedVariable := statement.(VariableDefinitionValueAssignment) for _, variable := range definedVariable.Variables() { name := variable.Name() @@ -940,31 +940,36 @@ func (p *Parser) evaluateBlockContent(terminationTokenTypes []lexer.TokenType, c global := ctx.global() switch stmt.StatementType() { - case STATEMENT_TYPE_VAR_DEFINITION: - // Store new variables. - for _, variable := range stmt.(VariableDefinition).Variables() { - err = ctx.addNamedValues(prefix, global, variable) - - if err != nil { - return nil, err - } - } - case STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT: - // Store new variables. - for _, variable := range stmt.(VariableDefinitionCallAssignment).Variables() { - err = ctx.addNamedValues(prefix, global, variable) - - if err != nil { - return nil, err - } - } - case STATEMENT_TYPE_CONST_DEFINITION: - // Store new constants. - for _, variable := range stmt.(ConstDefinition).Constants() { - err = ctx.addNamedValues(prefix, global, variable) - - if err != nil { - return nil, err + case STATEMENT_TYPE_NAMED_VALUES_DEFINITION: + for _, assignment := range stmt.(NamedValuesDefinition).Assignments() { + switch t := assignment.(type) { + case VariableDefinitionValueAssignment: + // Store new variables. + for _, variable := range t.Variables() { + err = ctx.addNamedValues(prefix, global, variable) + + if err != nil { + return nil, err + } + } + case VariableDefinitionCallAssignment: + // Store new variables. + for _, variable := range t.Variables() { + err = ctx.addNamedValues(prefix, global, variable) + + if err != nil { + return nil, err + } + } + case ConstDefinition: + // Store new constants. + for _, variable := range t.Constants() { + err = ctx.addNamedValues(prefix, global, variable) + + if err != nil { + return nil, err + } + } } } case STATEMENT_TYPE_FUNCTION_DEFINITION: @@ -1121,239 +1126,272 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat return nil, p.expectedKeywordError("const", keywordToken) } } - nameTokens, err := p.evaluateNames() - - if err != nil { - return nil, err - } - nameTokensLength := len(nameTokens) - firstNameToken := nameTokens[0] + grouped := p.peek().Type() == lexer.OPENING_ROUND_BRACKET - // Check if all named values are already defined. - if nameTokensLength > 1 { - alreadyDefined := 0 - - for _, nameToken := range nameTokens { - err := p.checkNewNamedValueNameToken(nameToken, ctx) + if grouped { + p.eat() // Eat round bracket. + nextToken := p.eat() - if err != nil { - // Only allow "re-definition" of variable via the short init operator. - if !isShortVarInit { - return nil, err - } - alreadyDefined++ - } + if nextToken.Type() != lexer.NEWLINE { + return nil, p.expectedNewlineError(nextToken) } + } + namedValuesDefinition := NamedValuesDefinition{} - if alreadyDefined == nameTokensLength { - return nil, p.atError(fmt.Sprintf("no new %ss", noun), firstNameToken) - } - } else { - err := p.checkNewNamedValueNameToken(firstNameToken, ctx) + for { + nameTokens, err := p.evaluateNames() if err != nil { return nil, err } - } - specifiedType := NewValueType(DATA_TYPE_UNKNOWN, false) + nameTokensLength := len(nameTokens) + firstNameToken := nameTokens[0] - if isShortVarInit { - nextToken := p.eat() // Eat short init operator. + // Check if all named values are already defined. + if nameTokensLength > 1 { + alreadyDefined := 0 - if nextToken.Type() != lexer.SHORT_INIT_OPERATOR { - return nil, p.expectedError("short initialization operator", nextToken) - } - } else { - nextToken := p.peek() + for _, nameToken := range nameTokens { + err := p.checkNewNamedValueNameToken(nameToken, ctx) + + if err != nil { + // Only allow "re-definition" of variable via the short init operator. + if !isShortVarInit { + return nil, err + } + alreadyDefined++ + } + } - // If next token starts a type definition, evaluate value type. - if slices.Contains([]lexer.TokenType{lexer.IDENTIFIER, lexer.OPENING_SQUARE_BRACKET}, nextToken.Type()) { - specifiedTypeTemp, err := p.evaluateValueType(ctx) + if alreadyDefined == nameTokensLength { + return nil, p.atError(fmt.Sprintf("no new %ss", noun), firstNameToken) + } + } else { + err := p.checkNewNamedValueNameToken(firstNameToken, ctx) if err != nil { return nil, err } - specifiedType = specifiedTypeTemp - nextToken = p.peek() } - nextTokenType := nextToken.Type() - dataType := specifiedType.DataType() - - // 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 { - return nil, p.expectedError("data type or value assignment", nextToken) - } else if nextTokenType == lexer.ASSIGN_OPERATOR { - p.eat() - } - } - nextToken := p.peek() - nextTokenType := nextToken.Type() - namedValues := []NamedValue{} + specifiedType := NewValueType(DATA_TYPE_UNKNOWN, false) - // Fill variables slice (might not contain the final type after this step). - for _, nameToken := range nameTokens { - prefix := p.prefix - global := ctx.global() - name := nameToken.Value() - namedValue, exists := ctx.findNamedValue(name, prefix, global) + if isShortVarInit { + nextToken := p.eat() // Eat short init operator. - if !exists { - if evalConst { - namedValue = Const{} - } else { - namedValue = Variable{} + if nextToken.Type() != lexer.SHORT_INIT_OPERATOR { + return nil, p.expectedError("short initialization operator", nextToken) } - } - variableValueType := namedValue.ValueType() + } else { + nextToken := p.peek() - // 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) { - return nil, p.atError(fmt.Sprintf(`%s %s already exists but has type %s`, noun, name, variableValueType.String()), nextToken) - } - storedName := name + // If next token starts a type definition, evaluate value type. + if slices.Contains([]lexer.TokenType{lexer.IDENTIFIER, lexer.OPENING_SQUARE_BRACKET}, nextToken.Type()) { + specifiedTypeTemp, err := p.evaluateValueType(ctx) - if global { - storedName = buildPrefixedName(prefix, name) - } - var newNamedValue NamedValue - isPublicValue := isPublic(name) + if err != nil { + return nil, err + } + specifiedType = specifiedTypeTemp + nextToken = p.peek() + } + nextTokenType := nextToken.Type() + dataType := specifiedType.DataType() - if evalConst { - newNamedValue = NewConst(storedName, specifiedType, global, isPublicValue) - } else { - newNamedValue = NewVariable(storedName, specifiedType, global, isPublicValue) + // 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 { + return nil, p.expectedError("data type or value assignment", nextToken) + } else if nextTokenType == lexer.ASSIGN_OPERATOR { + p.eat() + } } - namedValues = append(namedValues, newNamedValue) - } - values := []Expression{} - firstValueToken := p.peek() + nextToken := p.peek() + nextTokenType := nextToken.Type() + namedValues := []NamedValue{} - // TODO: Improve check (avoid NEWLINE and EOF check). - if nextTokenType != lexer.NEWLINE && nextTokenType != lexer.EOF { - evaluatedVals, err := p.evaluateValues(ctx) + // Fill variables slice (might not contain the final type after this step). + for _, nameToken := range nameTokens { + prefix := p.prefix + global := ctx.global() + name := nameToken.Value() + namedValue, exists := ctx.findNamedValue(name, prefix, global) - if err != nil { - return nil, err - } else if evalConst { - for i, evaluatedVal := range evaluatedVals.values { - if !evaluatedVal.IsConstant() { - return nil, p.expectedError("constant value", evaluatedVals.tokens[i]) + if !exists { + if evalConst { + namedValue = Const{} + } else { + namedValue = Variable{} } } - } - values = evaluatedVals.values - valuesTypes := []ValueType{} - isMultiReturnFuncCall, call := evaluatedVals.isMultiReturnCall() + variableValueType := namedValue.ValueType() - // If multi-return function, get function return types, else get value types. - if isMultiReturnFuncCall { - valuesTypes = call.ReturnTypes() - } else { - for _, valueTemp := range values { - valuesTypes = append(valuesTypes, valueTemp.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) { + return nil, p.atError(fmt.Sprintf(`%s %s already exists but has type %s`, noun, name, variableValueType.String()), nextToken) } - } - valuesTypesLen := len(valuesTypes) - variablesLen := len(namedValues) - - // Check if the amount of values is equal to the amount of variable names. - if valuesTypesLen != variablesLen { - pluralInit := "" - pluralValues := "" + storedName := name - if valuesTypesLen != 1 { - pluralInit = "s" + if global { + storedName = buildPrefixedName(prefix, name) } - if variablesLen != 1 { - pluralValues = "s" + var newNamedValue NamedValue + isPublicValue := isPublic(name) + + if evalConst { + newNamedValue = NewConst(storedName, specifiedType, global, isPublicValue) + } else { + newNamedValue = NewVariable(storedName, specifiedType, global, isPublicValue) } - return nil, p.atError(fmt.Sprintf("got %d initialisation value%s but %d %s%s", valuesTypesLen, pluralInit, variablesLen, noun, pluralValues), nextToken) + namedValues = append(namedValues, newNamedValue) } + values := []Expression{} + firstValueToken := p.peek() + var assignment Assignment + + // TODO: Improve check (avoid NEWLINE and EOF check). + if nextTokenType != lexer.NEWLINE && nextTokenType != lexer.EOF { + evaluatedVals, err := p.evaluateValues(ctx) - // If a type has been specified, make sure the returned types fit this type. - if specifiedType.DataType() != DATA_TYPE_UNKNOWN { - for _, valueType := range valuesTypes { - if !valueType.Equals(specifiedType) { - return nil, p.expectedError(fmt.Sprintf("%s but got %s", specifiedType.String(), valueType.String()), nextToken) + if err != nil { + return nil, err + } else if evalConst { + for i, evaluatedVal := range evaluatedVals.values { + if !evaluatedVal.IsConstant() { + return nil, p.expectedError("constant value", evaluatedVals.tokens[i]) + } } } - } + values = evaluatedVals.values + valuesTypes := []ValueType{} + isMultiReturnFuncCall, call := evaluatedVals.isMultiReturnCall() - // Check if variables exist and if, check if the types match. - for i, namedValue := range namedValues { - valueValueType := valuesTypes[i] - variableValueType := namedValue.ValueType() + // If multi-return function, get function return types, else get value types. + if isMultiReturnFuncCall { + valuesTypes = call.ReturnTypes() + } else { + for _, valueTemp := range values { + valuesTypes = append(valuesTypes, valueTemp.ValueType()) + } + } + valuesTypesLen := len(valuesTypes) + variablesLen := len(namedValues) - if variableValueType.DataType() == DATA_TYPE_UNKNOWN { - var updatedNamedValue NamedValue - name, global, public := namedValue.Name(), namedValue.Global(), namedValue.Public() + // Check if the amount of values is equal to the amount of variable names. + if valuesTypesLen != variablesLen { + pluralInit := "" + pluralValues := "" - if evalConst { - updatedNamedValue = NewConst(name, valueValueType, global, public) - } else { - updatedNamedValue = NewVariable(name, valueValueType, global, public) + if valuesTypesLen != 1 { + pluralInit = "s" + } + if variablesLen != 1 { + pluralValues = "s" } - namedValues[i] = updatedNamedValue - } else if !variableValueType.Equals(valueValueType) { - return nil, p.expectedError(fmt.Sprintf("%s but got %s for %s %s", variableValueType.String(), valueValueType.String(), noun, namedValue.Name()), nextToken) + return nil, p.atError(fmt.Sprintf("got %d initialisation value%s but %d %s%s", valuesTypesLen, pluralInit, variablesLen, noun, pluralValues), nextToken) } - } - // If it's a function call multi assignment, build return value here. - if isMultiReturnFuncCall { - variables := []Variable{} + // If a type has been specified, make sure the returned types fit this type. + if specifiedType.DataType() != DATA_TYPE_UNKNOWN { + for _, valueType := range valuesTypes { + if !valueType.Equals(specifiedType) { + return nil, p.expectedError(fmt.Sprintf("%s but got %s", specifiedType.String(), valueType.String()), nextToken) + } + } + } + + // Check if variables exist and if, check if the types match. + for i, namedValue := range namedValues { + valueValueType := valuesTypes[i] + variableValueType := namedValue.ValueType() - for _, namedValue := range namedValues { - variables = append(variables, namedValue.(Variable)) + if variableValueType.DataType() == DATA_TYPE_UNKNOWN { + var updatedNamedValue NamedValue + name, global, public := namedValue.Name(), namedValue.Global(), namedValue.Public() + + if evalConst { + updatedNamedValue = NewConst(name, valueValueType, global, public) + } else { + updatedNamedValue = NewVariable(name, valueValueType, global, public) + } + namedValues[i] = updatedNamedValue + } else if !variableValueType.Equals(valueValueType) { + return nil, p.expectedError(fmt.Sprintf("%s but got %s for %s %s", variableValueType.String(), valueValueType.String(), noun, namedValue.Name()), nextToken) + } } - call := VariableDefinitionCallAssignment{ - variables, - call, + + // If it's a function call multi assignment, build return value here. + if isMultiReturnFuncCall { + variables := []Variable{} + + for _, namedValue := range namedValues { + variables = append(variables, namedValue.(Variable)) + } + assignment = VariableDefinitionCallAssignment{ + variables, + call, + } } - return call, nil } - } - lenValues := len(values) - if evalConst && lenValues != nameTokensLength { - return nil, p.atError("all constants must be initialized", firstValueToken) - } + if assignment == nil { + lenValues := len(values) - // If no value has been specified, define default value. - if lenValues == 0 { - for _, variable := range namedValues { - value, err := defaultVarValue(variable.ValueType(), ctx) + if evalConst && lenValues != nameTokensLength { + return nil, p.atError("all constants must be initialized", firstValueToken) + } - if err != nil { - return nil, err + // If no value has been specified, define default value. + if lenValues == 0 { + for _, variable := range namedValues { + value, err := defaultVarValue(variable.ValueType(), ctx) + + if err != nil { + return nil, err + } + values = append(values, value) + } } - values = append(values, value) - } - } - var stmt Statement - if evalConst { - constants := []Const{} + if evalConst { + constants := []Const{} + + for _, namedValue := range namedValues { + constants = append(constants, namedValue.(Const)) + } + assignment = ConstDefinition{ + constants, + values, + } + } else { + variables := []Variable{} - for _, namedValue := range namedValues { - constants = append(constants, namedValue.(Const)) + for _, namedValue := range namedValues { + variables = append(variables, namedValue.(Variable)) + } + assignment = VariableDefinitionValueAssignment{ + variables, + values, + } + } } - stmt = ConstDefinition{ - constants, - values, + namedValuesDefinition.AddAssignment(assignment) + + // If it's not a grouped definition, no looping is required. + if !grouped { + break } - } else { - variables := []Variable{} + nextToken = p.eat() - for _, namedValue := range namedValues { - variables = append(variables, namedValue.(Variable)) + if nextToken.Type() != lexer.NEWLINE { + return nil, p.expectedNewlineError(nextToken) } - stmt = VariableDefinition{ - variables, - values, + nextToken = p.peek() + + if nextToken.Type() == lexer.CLOSING_ROUND_BRACKET { + p.eat() // Eat closing round bracket and break. + break } } - return stmt, nil + return namedValuesDefinition, nil } func (p *Parser) evaluateConstDefinition(ctx context) (Statement, error) { @@ -1428,7 +1466,7 @@ func (p *Parser) evaluateCompoundAssignment(ctx context) (Statement, error) { if !slices.Contains(allowedBinaryOperators(valueType), binaryOperator) { return nil, p.expectedError(fmt.Sprintf(`valid %s compound assign operator but got "%s"`, valueType.String(), assignOperator), assignToken) } - return VariableAssignment{ + return VariableAssignmentValueAssignment{ variables: []Variable{definedVariable}, values: []Expression{ BinaryOperation{ @@ -1504,7 +1542,7 @@ func (p *Parser) evaluateVarAssignment(ctx context) (Statement, error) { call, }, nil } - return VariableAssignment{ + return VariableAssignmentValueAssignment{ variables: variables, values: evaluatedVals.values, }, nil @@ -2025,14 +2063,14 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { ctx.addNamedValues(p.prefix, false, valueVar) forRangeStatements = []Statement{ - VariableAssignment{ + VariableAssignmentValueAssignment{ variables: []Variable{valueVar}, values: []Expression{iterableEvaluation}, }, } } - init := VariableAssignment{ + init := VariableAssignmentValueAssignment{ variables: []Variable{indexVar}, values: []Expression{IntegerLiteral{0}}, } @@ -2080,36 +2118,42 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { prefix := p.prefix switch init.StatementType() { - case STATEMENT_TYPE_VAR_DEFINITION: - // Store new variable. - for _, variable := range init.(VariableDefinition).Variables() { - err = ctx.addNamedValues(prefix, false, variable) - - if err != nil { - return nil, err + case STATEMENT_TYPE_NAMED_VALUES_DEFINITION: + assignment := init.(NamedValuesDefinition).Assignments()[0] + + switch t := assignment.(type) { + case VariableDefinitionValueAssignment: + // Store new variable. + for _, variable := range t.Variables() { + err = ctx.addNamedValues(prefix, false, variable) + + if err != nil { + return nil, err + } } - } - case STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT: - // Store new variable. - for _, variable := range init.(VariableDefinitionCallAssignment).Variables() { - err = ctx.addNamedValues(prefix, false, variable) - - if err != nil { - return nil, err + case VariableDefinitionCallAssignment: + // Store new variable. + for _, variable := range t.Variables() { + err = ctx.addNamedValues(prefix, false, variable) + + if err != nil { + return nil, err + } } - } - case STATEMENT_TYPE_CONST_DEFINITION: - // Store new variable. - for _, variable := range init.(ConstDefinition).Constants() { - err = ctx.addNamedValues(prefix, false, variable) - - if err != nil { - return nil, err + case ConstDefinition: + // Store new variable. + for _, variable := range t.Constants() { + err = ctx.addNamedValues(prefix, false, variable) + + if err != nil { + return nil, err + } } } - case STATEMENT_TYPE_VAR_ASSIGNMENT: + case STATEMENT_TYPE_VAR_ASSIGNMENT_VALUE_ASSIGNMENT: default: return nil, p.expectedError("variable assignment or variable definition", nextToken) + } } nextToken = p.eat() @@ -2146,7 +2190,7 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { return nil, err } switch increment.StatementType() { - case STATEMENT_TYPE_VAR_ASSIGNMENT: + case STATEMENT_TYPE_VAR_ASSIGNMENT_VALUE_ASSIGNMENT: default: return nil, p.expectedError("variable assignment", nextToken) } diff --git a/parser/types.go b/parser/types.go index 40ab466..2f94f2c 100644 --- a/parser/types.go +++ b/parser/types.go @@ -5,6 +5,7 @@ import ( ) type StatementType string +type AssignmentType string type DataType = string type CompareOperator = string type UnaryOperator = string @@ -61,48 +62,55 @@ func (vt ValueType) isNonSliceType(dataType DataType) bool { } const ( - STATEMENT_TYPE_PROGRAM StatementType = "program" - STATEMENT_TYPE_TYPE_DECLARATION StatementType = "type declaration" - STATEMENT_TYPE_TYPE_DEFINITION StatementType = "type definition" - STATEMENT_TYPE_BOOL_LITERAL StatementType = "boolean" - STATEMENT_TYPE_INT_LITERAL StatementType = "integer" - STATEMENT_TYPE_STRING_LITERAL StatementType = "string" - STATEMENT_TYPE_STRING_SUBSCRIPT StatementType = "string subscript" - STATEMENT_TYPE_NIL_LITERAL StatementType = "nil" - STATEMENT_TYPE_UNARY_OPERATION StatementType = "unary operation" - STATEMENT_TYPE_BINARY_OPERATION StatementType = "binary operation" - STATEMENT_TYPE_LOGICAL_OPERATION StatementType = "logical operation" - STATEMENT_TYPE_COMPARISON StatementType = "comparison" - STATEMENT_TYPE_CONST_DEFINITION StatementType = "constant definition" - STATEMENT_TYPE_CONST_EVALUATION StatementType = "constant evaluation" - STATEMENT_TYPE_VAR_DEFINITION StatementType = "variable definition" - STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT StatementType = "variable definition func assignment" - STATEMENT_TYPE_VAR_ASSIGNMENT StatementType = "variable assignment" - STATEMENT_TYPE_VAR_ASSIGNMENT_CALL_ASSIGNMENT StatementType = "variable assignment func assignment" - STATEMENT_TYPE_VAR_EVALUATION StatementType = "variable evaluation" - STATEMENT_TYPE_GROUP StatementType = "group" - STATEMENT_TYPE_FUNCTION_DEFINITION StatementType = "function definition" - STATEMENT_TYPE_FUNCTION_CALL StatementType = "function call" - STATEMENT_TYPE_APP_CALL StatementType = "app call" - STATEMENT_TYPE_RETURN StatementType = "return" - STATEMENT_TYPE_IF StatementType = "if" - STATEMENT_TYPE_FOR StatementType = "for" - STATEMENT_TYPE_FOR_RANGE StatementType = "for range" - STATEMENT_TYPE_BREAK StatementType = "break" - STATEMENT_TYPE_CONTINUE StatementType = "continue" - STATEMENT_TYPE_INSTANTIATION StatementType = "instantiation" - STATEMENT_TYPE_PRINT StatementType = "print" - STATEMENT_TYPE_ITOA StatementType = "itoa" - STATEMENT_TYPE_EXISTS StatementType = "exists" - STATEMENT_TYPE_PANIC StatementType = "panic" - STATEMENT_TYPE_LEN StatementType = "len" - STATEMENT_TYPE_INPUT StatementType = "input" - STATEMENT_TYPE_COPY StatementType = "copy" - STATEMENT_TYPE_READ StatementType = "read" - STATEMENT_TYPE_WRITE StatementType = "write" - STATEMENT_TYPE_SLICE_INSTANTIATION StatementType = "slice instantiation" - STATEMENT_TYPE_SLICE_ASSIGNMENT StatementType = "slice assignment" - STATEMENT_TYPE_SLICE_EVALUATION StatementType = "slice evaluation" + STATEMENT_TYPE_PROGRAM StatementType = "program" + STATEMENT_TYPE_TYPE_DECLARATION StatementType = "type declaration" + STATEMENT_TYPE_TYPE_DEFINITION StatementType = "type definition" + STATEMENT_TYPE_BOOL_LITERAL StatementType = "boolean" + STATEMENT_TYPE_INT_LITERAL StatementType = "integer" + STATEMENT_TYPE_STRING_LITERAL StatementType = "string" + STATEMENT_TYPE_STRING_SUBSCRIPT StatementType = "string subscript" + STATEMENT_TYPE_NIL_LITERAL StatementType = "nil" + STATEMENT_TYPE_UNARY_OPERATION StatementType = "unary operation" + STATEMENT_TYPE_BINARY_OPERATION StatementType = "binary operation" + STATEMENT_TYPE_LOGICAL_OPERATION StatementType = "logical operation" + STATEMENT_TYPE_COMPARISON StatementType = "comparison" + STATEMENT_TYPE_CONST_DEFINITION StatementType = "constant definition" + STATEMENT_TYPE_CONST_EVALUATION StatementType = "constant evaluation" + STATEMENT_TYPE_NAMED_VALUES_DEFINITION StatementType = "named values definition" + STATEMENT_TYPE_VAR_DEFINITION_VALUE_ASSIGNMENT StatementType = "variable definition value assignment" + STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT StatementType = "variable definition call assignment" + STATEMENT_TYPE_VAR_ASSIGNMENT StatementType = "variable assignment" + STATEMENT_TYPE_VAR_ASSIGNMENT_VALUE_ASSIGNMENT StatementType = "variable assignment value assignment" + STATEMENT_TYPE_VAR_ASSIGNMENT_CALL_ASSIGNMENT StatementType = "variable assignment call assignment" + STATEMENT_TYPE_VAR_EVALUATION StatementType = "variable evaluation" + STATEMENT_TYPE_GROUP StatementType = "group" + STATEMENT_TYPE_FUNCTION_DEFINITION StatementType = "function definition" + STATEMENT_TYPE_FUNCTION_CALL StatementType = "function call" + STATEMENT_TYPE_APP_CALL StatementType = "app call" + STATEMENT_TYPE_RETURN StatementType = "return" + STATEMENT_TYPE_IF StatementType = "if" + STATEMENT_TYPE_FOR StatementType = "for" + STATEMENT_TYPE_FOR_RANGE StatementType = "for range" + STATEMENT_TYPE_BREAK StatementType = "break" + STATEMENT_TYPE_CONTINUE StatementType = "continue" + STATEMENT_TYPE_INSTANTIATION StatementType = "instantiation" + STATEMENT_TYPE_PRINT StatementType = "print" + STATEMENT_TYPE_ITOA StatementType = "itoa" + STATEMENT_TYPE_EXISTS StatementType = "exists" + STATEMENT_TYPE_PANIC StatementType = "panic" + STATEMENT_TYPE_LEN StatementType = "len" + STATEMENT_TYPE_INPUT StatementType = "input" + STATEMENT_TYPE_COPY StatementType = "copy" + STATEMENT_TYPE_READ StatementType = "read" + STATEMENT_TYPE_WRITE StatementType = "write" + STATEMENT_TYPE_SLICE_INSTANTIATION StatementType = "slice instantiation" + STATEMENT_TYPE_SLICE_ASSIGNMENT StatementType = "slice assignment" + STATEMENT_TYPE_SLICE_EVALUATION StatementType = "slice evaluation" +) + +const ( + ASSIGNMENT_TYPE_VALUE AssignmentType = "value" + ASSIGNMENT_TYPE_CALL AssignmentType = "call" ) const ( diff --git a/parser/variable.go b/parser/variable.go index d91bf91..c200be5 100644 --- a/parser/variable.go +++ b/parser/variable.go @@ -40,27 +40,31 @@ func (v Variable) Public() bool { return v.public } -type VariableDefinition struct { +type VariableDefinitionValueAssignment struct { variables []Variable values []Expression } -func NewVariableDefinition(variables []Variable, values []Expression) VariableDefinition { - return VariableDefinition{ +func NewVariableDefinition(variables []Variable, values []Expression) VariableDefinitionValueAssignment { + return VariableDefinitionValueAssignment{ variables, values, } } -func (v VariableDefinition) StatementType() StatementType { - return STATEMENT_TYPE_VAR_DEFINITION +func (v VariableDefinitionValueAssignment) StatementType() StatementType { + return STATEMENT_TYPE_VAR_DEFINITION_VALUE_ASSIGNMENT } -func (v VariableDefinition) Variables() []Variable { +func (v VariableDefinitionValueAssignment) AssignmentType() AssignmentType { + return ASSIGNMENT_TYPE_VALUE +} + +func (v VariableDefinitionValueAssignment) Variables() []Variable { return v.variables } -func (v VariableDefinition) Values() []Expression { +func (v VariableDefinitionValueAssignment) Values() []Expression { return v.values } @@ -73,6 +77,10 @@ func (v VariableDefinitionCallAssignment) StatementType() StatementType { return STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT } +func (v VariableDefinitionCallAssignment) AssignmentType() AssignmentType { + return ASSIGNMENT_TYPE_CALL +} + func (v VariableDefinitionCallAssignment) Variables() []Variable { return v.variables } @@ -82,19 +90,39 @@ func (v VariableDefinitionCallAssignment) Call() Call { } type VariableAssignment struct { - variables []Variable - values []Expression + assignments []Assignment } func (v VariableAssignment) StatementType() StatementType { return STATEMENT_TYPE_VAR_ASSIGNMENT } -func (v VariableAssignment) Variables() []Variable { +func (v *VariableAssignment) AddAssignment(assignment Assignment) { + v.assignments = append(v.assignments, assignment) +} + +func (v VariableAssignment) Assignments() []Assignment { + return v.assignments +} + +type VariableAssignmentValueAssignment struct { + variables []Variable + values []Expression +} + +func (v VariableAssignmentValueAssignment) StatementType() StatementType { + return STATEMENT_TYPE_VAR_ASSIGNMENT_VALUE_ASSIGNMENT +} + +func (v VariableAssignmentValueAssignment) AssignmentType() AssignmentType { + return ASSIGNMENT_TYPE_VALUE +} + +func (v VariableAssignmentValueAssignment) Variables() []Variable { return v.variables } -func (v VariableAssignment) Values() []Expression { +func (v VariableAssignmentValueAssignment) Values() []Expression { return v.values } @@ -107,6 +135,10 @@ func (v VariableAssignmentCallAssignment) StatementType() StatementType { return STATEMENT_TYPE_VAR_ASSIGNMENT_CALL_ASSIGNMENT } +func (v VariableAssignmentCallAssignment) AssignmentType() AssignmentType { + return ASSIGNMENT_TYPE_CALL +} + func (v VariableAssignmentCallAssignment) Variables() []Variable { return v.variables } diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 7916cdf..bc32460 100644 --- a/transpiler/transpiler.go +++ b/transpiler/transpiler.go @@ -369,6 +369,17 @@ func (t *transpiler) evaluateFor(forStatement parser.For) error { return conv.ForEnd() } +func (t *transpiler) evaluateNamedValuesDefinition(definition parser.NamedValuesDefinition) error { + for _, assignment := range definition.Assignments() { + err := t.evaluate(assignment) + + if err != nil { + return err + } + } + return nil +} + func (t *transpiler) evaluateConstDefinition(definition parser.ConstDefinition) error { variables := []parser.Variable{} @@ -379,7 +390,7 @@ func (t *transpiler) evaluateConstDefinition(definition parser.ConstDefinition) return t.evaluateVarDefinition(parser.NewVariableDefinition(variables, definition.Values())) } -func (t *transpiler) evaluateVarDefinition(definition parser.VariableDefinition) error { +func (t *transpiler) evaluateVarDefinition(definition parser.VariableDefinitionValueAssignment) error { for i, variable := range definition.Variables() { result, err := t.evaluateExpression(definition.Values()[i], true) @@ -420,7 +431,7 @@ func (t *transpiler) evaluateVarDefinitionCallAssignment(definition parser.Varia return nil } -func (t *transpiler) evaluateVarAssignment(assignment parser.VariableAssignment) error { +func (t *transpiler) evaluateVarAssignment(assignment parser.VariableAssignmentValueAssignment) error { for i, variable := range assignment.Variables() { result, err := t.evaluateExpression(assignment.Values()[i], true) @@ -796,14 +807,16 @@ func (t *transpiler) evaluate(statement parser.Statement) error { return t.evaluateProgram(statement.(parser.Program)) case parser.STATEMENT_TYPE_TYPE_DECLARATION: return nil // Nothing to handle here, types are just relevant for the parser. + case parser.STATEMENT_TYPE_NAMED_VALUES_DEFINITION: + return t.evaluateNamedValuesDefinition(statement.(parser.NamedValuesDefinition)) case parser.STATEMENT_TYPE_CONST_DEFINITION: return t.evaluateConstDefinition(statement.(parser.ConstDefinition)) - case parser.STATEMENT_TYPE_VAR_DEFINITION: - return t.evaluateVarDefinition(statement.(parser.VariableDefinition)) + case parser.STATEMENT_TYPE_VAR_DEFINITION_VALUE_ASSIGNMENT: + return t.evaluateVarDefinition(statement.(parser.VariableDefinitionValueAssignment)) case parser.STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT: return t.evaluateVarDefinitionCallAssignment(statement.(parser.VariableDefinitionCallAssignment)) - case parser.STATEMENT_TYPE_VAR_ASSIGNMENT: - return t.evaluateVarAssignment(statement.(parser.VariableAssignment)) + case parser.STATEMENT_TYPE_VAR_ASSIGNMENT_VALUE_ASSIGNMENT: + return t.evaluateVarAssignment(statement.(parser.VariableAssignmentValueAssignment)) case parser.STATEMENT_TYPE_VAR_ASSIGNMENT_CALL_ASSIGNMENT: return t.evaluateVarAssignmentCallAssignment(statement.(parser.VariableAssignmentCallAssignment)) case parser.STATEMENT_TYPE_SLICE_ASSIGNMENT: From beda7132c82e604f9ab68b0a0426a5a08b0cfd09 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 18:19:44 +0200 Subject: [PATCH 24/27] Add simple iota implementation (only simple increment for now) (https://github.com/monstermichl/TypeShell/issues/41) --- lexer/lexer.go | 2 + parser/iota.go | 16 ++++++++ parser/parser.go | 104 ++++++++++++++++++++++++++++++++++++++++++----- parser/types.go | 1 + 4 files changed, 112 insertions(+), 11 deletions(-) create mode 100644 parser/iota.go diff --git a/lexer/lexer.go b/lexer/lexer.go index 3a837e7..8812193 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -69,6 +69,7 @@ const ( RANGE BREAK CONTINUE + IOTA // Builtin functions. LEN @@ -186,6 +187,7 @@ var keywords = map[string]TokenType{ "range": RANGE, "break": BREAK, "continue": CONTINUE, + "iota": IOTA, "nil": NIL_LITERAL, // Builtin functions. diff --git a/parser/iota.go b/parser/iota.go new file mode 100644 index 0000000..78d5829 --- /dev/null +++ b/parser/iota.go @@ -0,0 +1,16 @@ +package parser + +type Iota struct { +} + +func (i Iota) StatementType() StatementType { + return STATEMENT_TYPE_IOTA +} + +func (i Iota) ValueType() ValueType { + return NewValueType(DATA_TYPE_INTEGER, false) +} + +func (i Iota) IsConstant() bool { + return true +} diff --git a/parser/parser.go b/parser/parser.go index 9e79ffa..7cd7e08 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -23,6 +23,7 @@ const ( SCOPE_IF scope = "if" SCOPE_FOR scope = "for" SCOPE_SWITCH scope = "switch" + SCOPE_CONST scope = "const" ) func scopesToString(scopes []scope) []string { @@ -51,6 +52,7 @@ type context struct { 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. + iotaCounter int } func newContext() context { @@ -59,6 +61,7 @@ func newContext() context { types: map[string]typeDefinition{}, namedValues: map[string]NamedValue{}, functions: map[string]FunctionDefinition{}, + iotaCounter: 0, } // Define elementary types. @@ -72,6 +75,22 @@ func newContext() context { return c } +func (c *context) pushScope(scope scope) error { + c.scopeStack = append(c.scopeStack, scope) + + // If new const scope is pushed, reset iota counter. + if scope == SCOPE_CONST { + c.iotaCounter = 0 + } + return nil +} + +func (c *context) popScope() error { + i := len(c.scopeStack) - 1 + c.scopeStack = slices.Delete(c.scopeStack, i, i+1) + return nil +} + func (c context) currentScope() scope { return c.scopeStack[len(c.scopeStack)-1] } @@ -89,6 +108,10 @@ func (c context) findScope(s scope) bool { return false } +func (c *context) incrementIota() { + c.iotaCounter++ +} + func (c context) buildPrefixedName(name string, prefix string, global bool, checkExistence bool) (string, error) { name = strings.TrimSpace(name) @@ -208,6 +231,7 @@ func (c context) clone() context { namedValues: maps.Clone(c.namedValues), functions: maps.Clone(c.functions), scopeStack: slices.Clone(c.scopeStack), + iotaCounter: 0, } } @@ -917,7 +941,7 @@ func (p *Parser) evaluateBlockContent(terminationTokenTypes []lexer.TokenType, c ctx = ctx.clone() // Add scope to context. - ctx.scopeStack = append(ctx.scopeStack, scope) + ctx.pushScope(scope) for loop { token := p.peek() @@ -1111,6 +1135,7 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat if evalConst { noun = "constant" + ctx.pushScope(SCOPE_CONST) } // Eat "var" token only, if the variable is not defined using the short init operator (:=). @@ -1137,6 +1162,7 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat } } namedValuesDefinition := NamedValuesDefinition{} + useIota := false for { nameTokens, err := p.evaluateNames() @@ -1174,6 +1200,8 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat } } specifiedType := NewValueType(DATA_TYPE_UNKNOWN, false) + namedValues := []NamedValue{} + reuseIota := false if isShortVarInit { nextToken := p.eat() // Eat short init operator. @@ -1199,14 +1227,19 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat // 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 { - return nil, p.expectedError("data type or value assignment", nextToken) + // 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 { + reuseIota = true + } else { + return nil, p.expectedError("data type or value assignment", nextToken) + } } else if nextTokenType == lexer.ASSIGN_OPERATOR { p.eat() } } nextToken := p.peek() nextTokenType := nextToken.Type() - namedValues := []NamedValue{} // Fill variables slice (might not contain the final type after this step). for _, nameToken := range nameTokens { @@ -1251,6 +1284,14 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat if nextTokenType != lexer.NEWLINE && nextTokenType != lexer.EOF { evaluatedVals, err := p.evaluateValues(ctx) + // Check if iota is used. + if slices.ContainsFunc(evaluatedVals.values, func(value Expression) bool { + return value.StatementType() == STATEMENT_TYPE_IOTA + }) { + useIota = true + reuseIota = true + } + if err != nil { return nil, err } else if evalConst { @@ -1277,16 +1318,24 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat // Check if the amount of values is equal to the amount of variable names. if valuesTypesLen != variablesLen { - pluralInit := "" - pluralValues := "" + // If only one constant needs to be initialized and iota can be used, use it. + if evalConst && variablesLen == 1 && reuseIota { + iotaExpr := IntegerLiteral{ctx.iotaCounter} - if valuesTypesLen != 1 { - pluralInit = "s" - } - if variablesLen != 1 { - pluralValues = "s" + values = append(values, iotaExpr) + valuesTypes = append(valuesTypes, iotaExpr.ValueType()) + } else { + pluralInit := "" + pluralValues := "" + + if valuesTypesLen != 1 { + pluralInit = "s" + } + if variablesLen != 1 { + pluralValues = "s" + } + return nil, p.atError(fmt.Sprintf("got %d initialisation value%s but %d %s%s", valuesTypesLen, pluralInit, variablesLen, noun, pluralValues), nextToken) } - return nil, p.atError(fmt.Sprintf("got %d initialisation value%s but %d %s%s", valuesTypesLen, pluralInit, variablesLen, noun, pluralValues), nextToken) } // If a type has been specified, make sure the returned types fit this type. @@ -1333,12 +1382,25 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat } if assignment == nil { + // If only one value needs to be initialized and iota has been assigned previously, use iota. + if evalConst && useIota && nameTokensLength == 1 && len(values) < nameTokensLength { + values = append(values, Iota{}) + } lenValues := len(values) if evalConst && lenValues != nameTokensLength { return nil, p.atError("all constants must be initialized", firstValueToken) } + // Increase iota counter. + for i, value := range values { + // Replace iota by actual values. + if value.StatementType() == STATEMENT_TYPE_IOTA { + values[i] = IntegerLiteral{ctx.iotaCounter} + } + ctx.incrementIota() + } + // If no value has been specified, define default value. if lenValues == 0 { for _, variable := range namedValues { @@ -1391,6 +1453,11 @@ func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Stat break } } + + // Pop SCOPE_CONST. + if evalConst { + ctx.popScope() + } return namedValuesDefinition, nil } @@ -1771,6 +1838,17 @@ func (p *Parser) evaluateBreak(ctx context) (Statement, error) { return Break{}, nil } +func (p *Parser) evaluateIota(ctx context) (Expression, error) { + iotaToken := p.eat() + + if iotaToken.Type() != lexer.IOTA { + return nil, p.expectedKeywordError("iota", iotaToken) + } else if ctx.currentScope() != SCOPE_CONST { + return nil, p.atError("cannot use iota outside constant declaration", iotaToken) + } + return Iota{}, nil +} + func (p *Parser) evaluateContinue(ctx context) (Statement, error) { continueToken := p.eat() breakScopes := []scope{SCOPE_FOR} @@ -2352,6 +2430,10 @@ func (p *Parser) evaluateSingleExpression(ctx context) (Expression, error) { case lexer.OPENING_SQUARE_BRACKET: expr, err = p.evaluateSliceInstantiation(ctx) + // Handle iota. + case lexer.IOTA: + expr, err = p.evaluateIota(ctx) + // Handle input. case lexer.INPUT: expr, err = p.evaluateInput(ctx) diff --git a/parser/types.go b/parser/types.go index 2f94f2c..f9754d4 100644 --- a/parser/types.go +++ b/parser/types.go @@ -93,6 +93,7 @@ const ( STATEMENT_TYPE_FOR_RANGE StatementType = "for range" STATEMENT_TYPE_BREAK StatementType = "break" STATEMENT_TYPE_CONTINUE StatementType = "continue" + STATEMENT_TYPE_IOTA StatementType = "iota" STATEMENT_TYPE_INSTANTIATION StatementType = "instantiation" STATEMENT_TYPE_PRINT StatementType = "print" STATEMENT_TYPE_ITOA StatementType = "itoa" From edcddc1397a840c63d7ac968acd020d391c36cdf Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 18:32:15 +0200 Subject: [PATCH 25/27] Add tests (https://github.com/monstermichl/TypeShell/issues/41) --- tests/const.go | 32 ++++++++++++++++++++++++++++++++ tests/const_linux_test.go | 8 ++++++++ tests/const_windows_test.go | 8 ++++++++ 3 files changed, 48 insertions(+) diff --git a/tests/const.go b/tests/const.go index 7209e00..e5c417a 100644 --- a/tests/const.go +++ b/tests/const.go @@ -37,6 +37,38 @@ func testDefineConstantsInFunctionSuccess(t *testing.T, transpilerFunc transpile }) } +func testDefineConstantsGroupedSuccess(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + const ( + a = 0 + b, c = 1, 2 + d = iota + e = 4 + f + g, h = iota, iota + ) + + print(a, b, c, d, e, f, g, h) + `, func(output string, err error) { + require.Nil(t, err) + require.Equal(t, "0 1 2 3 4 5 6 7", output) + }) +} + +func testDefineConstantsMissingValueFail(t *testing.T, transpilerFunc transpilerFunc) { + transpilerFunc(t, ` + const ( + a = 0 + b, c = 1, 2 + d + e = 4 + f, g = iota, iota + ) + `, func(output string, err error) { + require.EqualError(t, shortenError(err), "expected data type or value assignment") + }) +} + func testDefineSameConstantFail(t *testing.T, transpilerFunc transpilerFunc) { transpilerFunc(t, ` const a = 1 diff --git a/tests/const_linux_test.go b/tests/const_linux_test.go index 7b51e30..f89279a 100644 --- a/tests/const_linux_test.go +++ b/tests/const_linux_test.go @@ -12,6 +12,14 @@ func TestDefineConstantsInFunctionSuccess(t *testing.T) { testDefineConstantsInFunctionSuccess(t, transpileBash) } +func TestDefineConstantsGroupedSuccess(t *testing.T) { + testDefineConstantsGroupedSuccess(t, transpileBash) +} + +func TestDefineConstantsMissingValueFail(t *testing.T) { + testDefineConstantsMissingValueFail(t, transpileBash) +} + func TestDefineSameConstantFail(t *testing.T) { testDefineSameConstantFail(t, transpileBash) } diff --git a/tests/const_windows_test.go b/tests/const_windows_test.go index 871368f..7fa8528 100644 --- a/tests/const_windows_test.go +++ b/tests/const_windows_test.go @@ -12,6 +12,14 @@ func TestDefineConstantsInFunctionSuccess(t *testing.T) { testDefineConstantsInFunctionSuccess(t, transpileBatch) } +func TestDefineConstantsGroupedSuccess(t *testing.T) { + testDefineConstantsGroupedSuccess(t, transpileBatch) +} + +func TestDefineConstantsMissingValueFail(t *testing.T) { + testDefineConstantsMissingValueFail(t, transpileBatch) +} + func TestDefineSameConstantFail(t *testing.T) { testDefineSameConstantFail(t, transpileBatch) } From 2b8f1669da78ce3cac597f4395c66ada69a19330 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 19:22:27 +0200 Subject: [PATCH 26/27] Update README (https://github.com/monstermichl/TypeShell/issues/41) --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 4f00a06..b47a8b9 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,22 @@ b, c := 5, 6 d, e := divisionWithRemainder(5, 2) ``` +### Constants +```golang +// Constant definition. +const a = 0 +const b, c = 1, 2 +``` + +```golang +// constant definition via grouping. +const ( + a = -1 + b = iota + c +) +``` + ### Control flow ```golang // If-statement. From 829aeceb875ac6ea48fd979845c3cd8ecd3d3217 Mon Sep 17 00:00:00 2001 From: monstermichl Date: Fri, 5 Sep 2025 19:25:49 +0200 Subject: [PATCH 27/27] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b47a8b9..dd8e64d 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ const b, c = 1, 2 ``` ```golang -// constant definition via grouping. +// Constant definition via grouping. const ( a = -1 b = iota