diff --git a/README.md b/README.md index d054a28..dd8e64d 100644 --- a/README.md +++ b/README.md @@ -30,20 +30,44 @@ 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) +``` + +### 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 @@ -196,6 +220,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 330a093..8812193 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,8 @@ const ( // Keywords. IMPORT + TYPE_DECLARATION + CONST_DEFINITION VAR_DEFINITION FUNCTION_DEFINITION RETURN @@ -70,6 +69,7 @@ const ( RANGE BREAK CONTINUE + IOTA // Builtin functions. LEN @@ -90,13 +90,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 @@ -180,6 +173,8 @@ var nonAlphabeticTokens = []tokenMapping{ var keywords = map[string]TokenType{ // Common keywords. "import": IMPORT, + "type": TYPE_DECLARATION, + "const": CONST_DEFINITION, "var": VAR_DEFINITION, "func": FUNCTION_DEFINITION, "return": RETURN, @@ -192,6 +187,7 @@ var keywords = map[string]TokenType{ "range": RANGE, "break": BREAK, "continue": CONTINUE, + "iota": IOTA, "nil": NIL_LITERAL, // Builtin functions. @@ -204,12 +200,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/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/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/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 new file mode 100644 index 0000000..b90e05c --- /dev/null +++ b/parser/const.go @@ -0,0 +1,70 @@ +package parser + +type Const struct { + name string + valueType ValueType + global bool + 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 +} + +func (c Const) ValueType() ValueType { + return c.valueType +} + +func (c Const) IsConstant() bool { + return true +} + +func (c *Const) SetValueType(valueType ValueType) { + c.valueType = valueType +} + +func (c Const) Global() bool { + return c.global +} + +func (c Const) Public() bool { + return c.public +} + +type ConstDefinition struct { + constants []Const + values []Expression +} + +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 +} + +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/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/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/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/namedvalue.go b/parser/namedvalue.go new file mode 100644 index 0000000..2bf514f --- /dev/null +++ b/parser/namedvalue.go @@ -0,0 +1,25 @@ +package parser + +type NamedValue interface { + Name() string + ValueType() ValueType + Global() bool + 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 819565f..7cd7e08 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 ( @@ -30,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 { @@ -41,19 +35,60 @@ func scopesToString(scopes []scope) []string { return strings } +type typeDefinition struct { + 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. - 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. + iotaCounter int } func newContext() context { - return context{ - imports: map[string]string{}, - variables: map[string]Variable{}, - functions: map[string]FunctionDefinition{}, + c := context{ + imports: map[string]string{}, + types: map[string]typeDefinition{}, + namedValues: map[string]NamedValue{}, + functions: map[string]FunctionDefinition{}, + iotaCounter: 0, } + + // Define elementary types. + c.addType(DATA_TYPE_BOOLEAN, NewValueType(DATA_TYPE_BOOLEAN, false), false, true) + c.addType(DATA_TYPE_INTEGER, NewValueType(DATA_TYPE_INTEGER, false), false, true) + c.addType(DATA_TYPE_STRING, NewValueType(DATA_TYPE_STRING, false), false, true) + + // Define error alias. + c.addType(DATA_TYPE_ERROR, NewValueType(DATA_TYPE_STRING, false), true, false) + + 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 { @@ -73,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) @@ -97,14 +136,32 @@ func (c context) addImport(alias string, hash string) error { 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) addType(typeName string, valueType ValueType, isAlias bool, isElementary bool) error { + _, exists := c.findType(typeName, false) + + if exists { + return fmt.Errorf("%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, + } + return nil +} + +func (c context) addNamedValues(prefix string, global bool, namedValues ...NamedValue) error { + for _, namedValue := range namedValues { + prefixedName, err := c.buildPrefixedName(namedValue.Name(), prefix, global, false) if err != nil { return err } - c.variables[prefixedName] = variable + c.namedValues[prefixedName] = namedValue } return nil } @@ -126,14 +183,35 @@ func (c context) findImport(alias string) (string, bool) { return hash, exists } -func (c context) findVariable(name string, prefix string, global bool) (Variable, bool) { +func (c context) findType(typeName string, searchUntilElementary bool) (foundTypeDefinition, bool) { + var foundDefinition foundTypeDefinition + typeDefinition, exists := c.types[typeName] + + if exists { + foundDefinitionTemp := foundTypeDefinition{ + typeDefinition: typeDefinition, + name: typeName, + } + + // If the defined type is an alias, trace it down to the root. + if typeDefinition.isAlias || (searchUntilElementary && !typeDefinition.isElementary) { + foundDefinitionTemp, exists = c.findType(typeDefinition.valueType.DataType(), searchUntilElementary) + } + if exists { + foundDefinition = foundDefinitionTemp + } + } + return foundDefinition, exists +} + +func (c context) 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) { @@ -148,10 +226,12 @@ func (c context) findFunction(name string, prefix string) (FunctionDefinition, b func (c context) clone() context { return context{ - imports: maps.Clone(c.imports), - 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), + iotaCounter: 0, } } @@ -162,6 +242,7 @@ type evaluatedImport struct { type evaluatedValues struct { values []Expression + tokens []lexer.Token } func (ev evaluatedValues) isMultiReturnCall() (bool, Call) { @@ -285,20 +366,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()) } @@ -309,7 +394,7 @@ func incrementDecrementStatement(variable Variable, increment bool) Statement { if !increment { operation = BINARY_OPERATOR_SUBTRACTION } - return VariableAssignment{ + return VariableAssignmentValueAssignment{ variables: []Variable{variable}, values: []Expression{ BinaryOperation{ @@ -362,6 +447,18 @@ 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) 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) } @@ -429,12 +526,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.findVariable(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 } @@ -482,7 +584,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 { @@ -504,6 +606,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() @@ -513,6 +616,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 := "" @@ -540,6 +644,7 @@ func (p *Parser) evaluateValues(ctx context) (evaluatedValues, error) { } return evaluatedValues{ values: expressions, + tokens: tokens, }, nil } @@ -738,19 +843,29 @@ 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 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() - 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_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 } } case STATEMENT_TYPE_FUNCTION_DEFINITION: @@ -826,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() @@ -849,19 +964,37 @@ func (p *Parser) evaluateBlockContent(terminationTokenTypes []lexer.TokenType, c global := ctx.global() switch stmt.StatementType() { - case STATEMENT_TYPE_VAR_DEFINITION: - // Store new variable. - err = ctx.addVariables(prefix, global, stmt.(VariableDefinition).Variables()...) - - if err != nil { - return nil, err - } - case STATEMENT_TYPE_VAR_DEFINITION_CALL_ASSIGNMENT: - // Store new variable. - err = ctx.addVariables(prefix, global, stmt.(VariableDefinitionCallAssignment).Variables()...) - - 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: // Store new function. @@ -932,7 +1065,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,212 +1082,395 @@ 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()] + 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) evaluateVarDefinition(ctx context) (Statement, error) { - // Possible variable declarations/definitions: - // var v int - // var v int = 1 - // var v = 1 - // v := 1 - isShortVarInit := p.isShortVarInit() +func (p *Parser) evaluateTypeDeclaration(ctx context) (Statement, error) { + typeToken := p.eat() - // Eat "var" token only, if the variable is not defined using the short init operator (:=). - if !isShortVarInit { - varToken := p.eat() + if typeToken.Type() != lexer.TYPE_DECLARATION { + return nil, p.expectedKeywordError("type", typeToken) + } + nameToken := p.eat() - if varToken.Type() != lexer.VAR_DEFINITION { - return nil, p.expectedError("variable definition", varToken) - } + if nameToken.Type() != lexer.IDENTIFIER { + return nil, p.expectedIdentifierError(nameToken) } - nameTokens, err := p.evaluateVarNames() + 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 } - nameTokensLength := len(nameTokens) - firstNameToken := nameTokens[0] + name := nameToken.Value() + err = ctx.addType(name, valueType, isAlias, false) - // Check if all variables are already defined. - if nameTokensLength > 1 { - alreadyDefined := 0 + if err != nil { + return nil, p.atError(err.Error(), valueTypeToken) + } + return TypeDeclaration{name}, nil +} - for _, nameToken := range nameTokens { - err := p.checkNewVariableNameToken(nameToken, ctx) +func (p *Parser) evaluateNamedValueDefinition(evalConst bool, ctx context) (Statement, error) { + isShortVarInit := !evalConst && p.isShortVarInit() + noun := "variable" - if err != nil { - // Only allow "re-definition" of variable via the short init operator. - if !isShortVarInit { - return nil, err - } - alreadyDefined++ + if evalConst { + noun = "constant" + ctx.pushScope(SCOPE_CONST) + } + + // Eat "var" token only, if the variable is not defined using the short init operator (:=). + if !isShortVarInit { + keywordToken := p.eat() + varTokenType := keywordToken.Type() + + 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) } + } + grouped := p.peek().Type() == lexer.OPENING_ROUND_BRACKET + + if grouped { + p.eat() // Eat round bracket. + nextToken := p.eat() - if alreadyDefined == nameTokensLength { - return nil, p.atError("no new variables", firstNameToken) + if nextToken.Type() != lexer.NEWLINE { + return nil, p.expectedNewlineError(nextToken) } - } else { - err := p.checkNewVariableNameToken(firstNameToken, ctx) + } + namedValuesDefinition := NamedValuesDefinition{} + useIota := false + + 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 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 err != nil { + // Only allow "re-definition" of variable via the short init operator. + if !isShortVarInit { + return nil, err + } + alreadyDefined++ + } + } + + 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() + specifiedType := NewValueType(DATA_TYPE_UNKNOWN, false) + namedValues := []NamedValue{} + reuseIota := false - // 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() - variables := []Variable{} + if isShortVarInit { + nextToken := p.eat() // Eat short init operator. - // 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.findVariable(name, prefix, global) - variableValueType := variable.ValueType() + if nextToken.Type() != lexer.SHORT_INIT_OPERATOR { + return nil, p.expectedError("short initialization operator", nextToken) + } + } 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(`variable "%s" already exists but has type %s`, 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) + 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 { + // 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() + } } - variables = append(variables, NewVariable(storedName, specifiedType, global, isPublic(name))) - } - values := []Expression{} + nextToken := p.peek() + nextTokenType := nextToken.Type() - // 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 !exists { + if evalConst { + namedValue = Const{} + } else { + namedValue = Variable{} + } + } + variableValueType := namedValue.ValueType() - if err != nil { - return nil, err - } - values = evaluatedVals.values - valuesTypes := []ValueType{} - isMultiReturnFuncCall, call := evaluatedVals.isMultiReturnCall() + // 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 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 global { + storedName = buildPrefixedName(prefix, name) + } + var newNamedValue NamedValue + isPublicValue := isPublic(name) + + if evalConst { + newNamedValue = NewConst(storedName, specifiedType, global, isPublicValue) + } else { + newNamedValue = NewVariable(storedName, specifiedType, global, isPublicValue) + } + 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) + + // Check if iota is used. + if slices.ContainsFunc(evaluatedVals.values, func(value Expression) bool { + return value.StatementType() == STATEMENT_TYPE_IOTA + }) { + useIota = true + reuseIota = true } - } - valuesTypesLen := len(valuesTypes) - variablesLen := len(variables) - // Check if the amount of values is equal to the amount of variable names. - if valuesTypesLen != variablesLen { - pluralInit := "" - pluralValues := "" + 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() - if valuesTypesLen != 1 { - pluralInit = "s" + // 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 variablesLen != 1 { - pluralValues = "s" + valuesTypesLen := len(valuesTypes) + variablesLen := len(namedValues) + + // Check if the amount of values is equal to the amount of variable names. + if valuesTypesLen != variablesLen { + // If only one constant needs to be initialized and iota can be used, use it. + if evalConst && variablesLen == 1 && reuseIota { + iotaExpr := IntegerLiteral{ctx.iotaCounter} + + 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 variable%s", valuesTypesLen, pluralInit, variablesLen, pluralValues), nextToken) - } - // 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 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, variable := range variables { - valueValueType := valuesTypes[i] - variableValueType := variable.ValueType() + // Check if variables exist and if, check if the types match. + for i, namedValue := range namedValues { + valueValueType := valuesTypes[i] + 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. - } 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) + 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) + } } - } - // If it's a function call multi assignment, build return value here. - if isMultiReturnFuncCall { - 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 } - } - // If no value has been specified, define default value. - if len(values) == 0 { - for _, variable := range variables { - value, err := defaultVarValue(variable.ValueType()) + 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 err != nil { - return nil, err + 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() } - values = append(values, value) + + // 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) + } + } + + if evalConst { + constants := []Const{} + + for _, namedValue := range namedValues { + constants = append(constants, namedValue.(Const)) + } + assignment = ConstDefinition{ + constants, + values, + } + } else { + variables := []Variable{} + + for _, namedValue := range namedValues { + variables = append(variables, namedValue.(Variable)) + } + assignment = VariableDefinitionValueAssignment{ + variables, + values, + } + } + } + namedValuesDefinition.AddAssignment(assignment) + + // If it's not a grouped definition, no looping is required. + if !grouped { + break + } + nextToken = p.eat() + + if nextToken.Type() != lexer.NEWLINE { + return nil, p.expectedNewlineError(nextToken) + } + nextToken = p.peek() + + if nextToken.Type() == lexer.CLOSING_ROUND_BRACKET { + p.eat() // Eat closing round bracket and break. + break } } - variable := VariableDefinition{ - variables, - values, + + // Pop SCOPE_CONST. + if evalConst { + ctx.popScope() } - return variable, nil + return namedValuesDefinition, 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 @@ -1197,13 +1513,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) + return nil, p.variableNotDefinedError(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) @@ -1214,7 +1533,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{ @@ -1227,7 +1546,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 @@ -1268,13 +1587,15 @@ 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()) + 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) } 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) @@ -1288,7 +1609,7 @@ func (p *Parser) evaluateVarAssignment(ctx context) (Statement, error) { call, }, nil } - return VariableAssignment{ + return VariableAssignmentValueAssignment{ variables: variables, values: evaluatedVals.values, }, nil @@ -1311,12 +1632,12 @@ 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) } - valueType, err := p.evaluateValueType() + valueType, err := p.evaluateValueType(ctx) if err != nil { return nil, err @@ -1363,7 +1684,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() }) @@ -1395,8 +1716,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 @@ -1420,7 +1741,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 @@ -1517,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} @@ -1737,7 +2069,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 @@ -1753,7 +2085,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 @@ -1799,24 +2131,24 @@ 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{ + VariableAssignmentValueAssignment{ variables: []Variable{valueVar}, values: []Expression{iterableEvaluation}, }, } } - init := VariableAssignment{ + init := VariableAssignmentValueAssignment{ variables: []Variable{indexVar}, values: []Expression{IntegerLiteral{0}}, } @@ -1861,24 +2193,45 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { if err != nil { return nil, err } - switch init.StatementType() { - case STATEMENT_TYPE_VAR_DEFINITION: - // Store new variable. - err = ctx.addVariables(p.prefix, false, init.(VariableDefinition).Variables()...) - - 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()...) + prefix := p.prefix - if err != nil { - return nil, err + switch init.StatementType() { + 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 VariableDefinitionCallAssignment: + // Store new variable. + for _, variable := range t.Variables() { + 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() @@ -1915,7 +2268,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) } @@ -1947,20 +2300,71 @@ func (p *Parser) evaluateFor(ctx context) (Statement, error) { return stmt, nil } -func (p *Parser) evaluateVarEvaluation(ctx context) (Expression, error) { +func (p *Parser) evaluateTypeDefinition(ctx context) (Expression, error) { + identifierToken := p.eat() // Eat identifier token. + + if identifierToken.Type() != lexer.IDENTIFIER { + return nil, p.expectedIdentifierError(identifierToken) + } + typeName := identifierToken.Value() + foundElementaryDefinition, exists := ctx.findType(typeName, true) + + if !exists { + return nil, p.notDefinedError("type", typeName, identifierToken) + } + nextToken := p.eat() + + if nextToken.Type() != lexer.OPENING_ROUND_BRACKET { + return nil, p.expectedError(`"("`, nextToken) + } + nextToken = p.peek() + expr, err := p.evaluateExpression(ctx) + + if err != nil { + return nil, err + } + exprValueType := expr.ValueType() + exprBaseTypeDefinition, _ := ctx.findType(exprValueType.DataType(), true) + exprBaseValueType := exprBaseTypeDefinition.valueType + foundElementaryDefinitionValueType := foundElementaryDefinition.valueType + baseDefinition, _ := ctx.findType(typeName, false) + baseTypeName := baseDefinition.name + + if !foundElementaryDefinitionValueType.Equals(exprBaseValueType) { + return nil, p.atError(fmt.Sprintf(`%s cannot be converted into %s`, exprValueType.String(), baseTypeName), nextToken) + } + nextToken = p.eat() + + if nextToken.Type() != lexer.CLOSING_ROUND_BRACKET { + return nil, p.expectedError(`")"`, nextToken) + } + + return TypeDefinition{ + value: expr, + valueType: NewValueType(baseTypeName, exprValueType.IsSlice()), + }, nil +} + +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) + return nil, p.variableNotDefinedError(name, identifierToken) + } + + if namedValue.IsConstant() { + return ConstEvaluation{ + Const: namedValue.(Const), + }, nil } return VariableEvaluation{ - Variable: variable, + Variable: namedValue.(Variable), }, nil } @@ -2026,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) @@ -2064,11 +2472,18 @@ 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.evaluateTypeDefinition(ctx) + } else { + expr, err = p.evaluateFunctionCall(ctx) + } case lexer.OPENING_SQUARE_BRACKET: expr, err = p.evaluateSubscript(ctx) default: - expr, err = p.evaluateVarEvaluation(ctx) + expr, err = p.evaluateNamedValueEvaluation(ctx) } default: @@ -2147,6 +2562,10 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) { tokenType := token.Type() 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: @@ -2185,7 +2604,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() { @@ -2365,7 +2784,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() @@ -2429,7 +2848,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) @@ -2494,7 +2913,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 @@ -2558,7 +2977,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: @@ -2673,12 +3092,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) + return nil, p.variableNotDefinedError(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) @@ -2722,7 +3143,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 @@ -2735,12 +3156,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) + return nil, p.variableNotDefinedError(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) @@ -2756,7 +3180,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/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 new file mode 100644 index 0000000..9411655 --- /dev/null +++ b/parser/type.go @@ -0,0 +1,34 @@ +package parser + +type TypeDeclaration struct { + name string +} + +func (t TypeDeclaration) StatementType() StatementType { + return STATEMENT_TYPE_TYPE_DECLARATION +} + +func (t TypeDeclaration) Name() string { + return t.name +} + +type TypeDefinition struct { + value Expression + valueType ValueType +} + +func (t TypeDefinition) StatementType() StatementType { + return STATEMENT_TYPE_TYPE_DEFINITION +} + +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 a38d93a..f9754d4 100644 --- a/parser/types.go +++ b/parser/types.go @@ -1,9 +1,12 @@ package parser -import "fmt" +import ( + "fmt" +) type StatementType string -type DataType string +type AssignmentType string +type DataType = string type CompareOperator = string type UnaryOperator = string type BinaryOperator = string @@ -59,44 +62,56 @@ func (vt ValueType) isNonSliceType(dataType DataType) bool { } const ( - STATEMENT_TYPE_PROGRAM StatementType = "program" - 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_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_IOTA StatementType = "iota" + 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 ( @@ -105,7 +120,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 ( 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 0b71669..c200be5 100644 --- a/parser/variable.go +++ b/parser/variable.go @@ -24,6 +24,14 @@ func (v Variable) ValueType() ValueType { return v.valueType } +func (v Variable) IsConstant() bool { + return false +} + +func (v *Variable) SetValueType(valueType ValueType) { + v.valueType = valueType +} + func (v Variable) Global() bool { return v.global } @@ -32,20 +40,31 @@ func (v Variable) Public() bool { return v.public } -type VariableDefinition struct { +type VariableDefinitionValueAssignment struct { variables []Variable values []Expression } -func (v VariableDefinition) StatementType() StatementType { - return STATEMENT_TYPE_VAR_DEFINITION +func NewVariableDefinition(variables []Variable, values []Expression) VariableDefinitionValueAssignment { + return VariableDefinitionValueAssignment{ + variables, + values, + } +} + +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 } @@ -58,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 } @@ -67,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 } @@ -92,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 } @@ -104,6 +151,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/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 } diff --git a/tests/const.go b/tests/const.go new file mode 100644 index 0000000..e5c417a --- /dev/null +++ b/tests/const.go @@ -0,0 +1,99 @@ +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 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 + 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..f89279a --- /dev/null +++ b/tests/const_linux_test.go @@ -0,0 +1,33 @@ +package tests + +import ( + "testing" +) + +func TestDefineConstantsSuccess(t *testing.T) { + testDefineConstantsSuccess(t, transpileBash) +} + +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) +} + +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..7fa8528 --- /dev/null +++ b/tests/const_windows_test.go @@ -0,0 +1,33 @@ +package tests + +import ( + "testing" +) + +func TestDefineConstantsSuccess(t *testing.T) { + testDefineConstantsSuccess(t, transpileBatch) +} + +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) +} + +func TestAssignFail(t *testing.T) { + testAssignFail(t, transpileBatch) +} + +func TestAssignFromFunctionFail(t *testing.T) { + testAssignFromFunctionFail(t, transpileBatch) +} diff --git a/tests/type_linux_test.go b/tests/type_linux_test.go new file mode 100644 index 0000000..d94951f --- /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 TestAssignDifferentDefinedTypeFail(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..ff91013 --- /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 TestAssignDifferentDefinedTypeFail(t *testing.T) { + testAssignDifferentDefinedTypeFail(t, transpileBatch) +} diff --git a/transpiler/transpiler.go b/transpiler/transpiler.go index 6787bb6..bc32460 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) evaluateTypeDefinition(instantiation parser.TypeDefinition, 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 } @@ -365,7 +369,28 @@ func (t *transpiler) evaluateFor(forStatement parser.For) error { return conv.ForEnd() } -func (t *transpiler) evaluateVarDefinition(definition parser.VariableDefinition) error { +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{} + + // 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.VariableDefinitionValueAssignment) error { for i, variable := range definition.Variables() { result, err := t.evaluateExpression(definition.Values()[i], true) @@ -406,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) @@ -468,6 +493,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()) @@ -773,12 +805,18 @@ 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_VAR_DEFINITION: - return t.evaluateVarDefinition(statement.(parser.VariableDefinition)) + 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_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: @@ -816,6 +854,8 @@ func (t *transpiler) evaluateExpression(expression parser.Expression, valueUsed expressionType := expression.StatementType() switch expressionType { + 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: @@ -830,6 +870,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: