From 3738ee59f8d924cb07fb5d4f7fef05e252ea7e1e Mon Sep 17 00:00:00 2001 From: "Nahuel J. Sacchetti" Date: Sun, 29 Sep 2024 02:26:20 -0500 Subject: [PATCH] feat: (WIP) support parapoly with $ sign --- compiler/frontend.go | 178 ++++++++++++++++++++++--------------------- compiler/ir.go | 25 ++++-- compiler/scanner.go | 15 ++++ compiler/validate.go | 74 ++++++++++++------ 4 files changed, 176 insertions(+), 116 deletions(-) diff --git a/compiler/frontend.go b/compiler/frontend.go index a8fcd4d..ca57011 100644 --- a/compiler/frontend.go +++ b/compiler/frontend.go @@ -215,21 +215,7 @@ func registerFunction(token Token) { if !check(TOKEN_RIGHT_ARROW) && !check(TOKEN_PAREN_OPEN) { for !check(TOKEN_RIGHT_ARROW) && !check(TOKEN_PAREN_OPEN) && !check(TOKEN_EOF) { advance() - t := parser.previous - targ := DATA_EMPTY - - switch t.typ { - case TOKEN_DTYPE_BOOL: targ = DATA_BOOL - case TOKEN_DTYPE_CHAR: targ = DATA_CHAR - case TOKEN_DTYPE_INT: targ = DATA_INT - case TOKEN_DTYPE_PTR: targ = DATA_PTR - default: - msg := fmt.Sprintf(MsgParseTypeUnknown, t.value) - errorAt(&t, msg) - ExitWithError(CodeParseError) - } - - function.args = append(function.args, targ) + parseArityInFunction(parser.previous, &function, "argument") } } @@ -240,21 +226,7 @@ func registerFunction(token Token) { for !check(TOKEN_PAREN_OPEN) { advance() - t := parser.previous - tret := DATA_EMPTY - - switch t.typ { - case TOKEN_DTYPE_BOOL: tret = DATA_BOOL - case TOKEN_DTYPE_CHAR: tret = DATA_CHAR - case TOKEN_DTYPE_INT: tret = DATA_INT - case TOKEN_DTYPE_PTR: tret = DATA_PTR - default: - msg := fmt.Sprintf(MsgParseTypeUnknown, t.value) - errorAt(&t, msg) - ExitWithError(CodeParseError) - } - - function.rets = append(function.rets, tret) + parseArityInFunction(parser.previous, &function, "return") } } @@ -470,42 +442,13 @@ func addExtern(token Token) { for !check(TOKEN_RIGHT_ARROW) && !check(TOKEN_PAREN_OPEN) && !check(TOKEN_EOF) { advance() - var arg DataType - t := parser.previous - - switch t.typ { - case TOKEN_DTYPE_BOOL: arg = DATA_BOOL - case TOKEN_DTYPE_CHAR: arg = DATA_CHAR - case TOKEN_DTYPE_INT: arg = DATA_INT - case TOKEN_DTYPE_PTR: arg = DATA_PTR - default: - msg := fmt.Sprintf(MsgParseTypeUnknown, t.value) - errorAt(&t, msg) - ExitWithError(CodeParseError) - } - - value.args = append(value.args, arg) + parseArityInAssembly(parser.previous, &value.arguments) } if match(TOKEN_RIGHT_ARROW) { for !check(TOKEN_PAREN_OPEN) && !check(TOKEN_EOF) { advance() - var arg DataType - - t := parser.previous - - switch t.typ { - case TOKEN_DTYPE_BOOL: arg = DATA_BOOL - case TOKEN_DTYPE_CHAR: arg = DATA_CHAR - case TOKEN_DTYPE_INT: arg = DATA_INT - case TOKEN_DTYPE_PTR: arg = DATA_PTR - default: - msg := fmt.Sprintf(MsgParseTypeUnknown, t.value) - errorAt(&t, msg) - ExitWithError(CodeParseError) - } - - value.rets = append(value.rets, arg) + parseArityInAssembly(parser.previous, &value.returns) } } @@ -745,46 +688,109 @@ func parseToken(token Token) { } } +func parseArityInAssembly(token Token, args *Arity) { + var newArg Argument + + switch token.typ { + case TOKEN_DTYPE_BOOL: + newArg.typ = DATA_BOOL + case TOKEN_DTYPE_CHAR: + newArg.typ = DATA_CHAR + case TOKEN_DTYPE_INT: + newArg.typ = DATA_INT + case TOKEN_DTYPE_PTR: + newArg.typ = DATA_PTR + default: + msg := fmt.Sprintf(MsgParseTypeUnknown, token.value.(string)) + errorAt(&token, msg) + ExitWithError(CodeParseError) + } + + args.types = append(args.types, newArg) +} + +func parseArityInFunction(token Token, function *Function, parsing string) { + var newArg Argument + + parsingArguments := parsing == "argument" + + switch token.typ { + case TOKEN_DTYPE_ANY: + if !parser.internal { + errorAt(&token, "TODO: ERROR MESSAGE. CANT USE ANY IN CUSTOM CODE") + ExitWithError(CodeParseError) + } + + newArg.typ = DATA_ANY + case TOKEN_DTYPE_BOOL: + newArg.typ = DATA_BOOL + case TOKEN_DTYPE_CHAR: + newArg.typ = DATA_CHAR + case TOKEN_DTYPE_INT: + newArg.typ = DATA_INT + case TOKEN_DTYPE_PTR: + newArg.typ = DATA_PTR + case TOKEN_DTYPE_PARAPOLY: + if !parsingArguments { + errorAt(&token, "TODO: CANT USE THIS TYPE IN RETURNS") + ExitWithError(CodeParseError) + } + + newArg.typ = DATA_INFER + newArg.name = token.value.(string) + function.arguments.parapoly = true + case TOKEN_WORD: + w := token.value.(string) + + if parsingArguments { + msg := fmt.Sprintf(MsgParseTypeUnknown, w) + errorAt(&token, msg) + ExitWithError(CodeParseError) + } + + funcArgs := function.arguments + argTest := Argument{name: w, typ: DATA_INFER} + + if funcArgs.parapoly && Contains(funcArgs.types, argTest) { + newArg.typ = DATA_INFER + newArg.name = token.value.(string) + function.returns.parapoly = true + } else { + errorAt(&token, "TODO: Definition for this type doesn't exist") + ExitWithError(CodeParseError) + } + default: + msg := fmt.Sprintf(MsgParseTypeUnknown, token.value.(string)) + errorAt(&token, msg) + ExitWithError(CodeParseError) + } + + if parsingArguments { + function.arguments.types = append(function.arguments.types, newArg) + } else { + function.returns.types = append(function.returns.types, newArg) + } +} + func parseFunction(token Token) { // Recreate a temporary function entry to match with the already registered // function in TheProgram. That's why this step doesn't have any error checking. var testFunc Function advance() - testFunc.name = parser.previous.value.(string) if !check(TOKEN_RIGHT_ARROW) && !check(TOKEN_PAREN_OPEN) { for !check(TOKEN_RIGHT_ARROW) && !check(TOKEN_PAREN_OPEN) && !check(TOKEN_EOF) { advance() - t := parser.previous - targ := DATA_EMPTY - - switch t.typ { - case TOKEN_DTYPE_BOOL: targ = DATA_BOOL - case TOKEN_DTYPE_CHAR: targ = DATA_CHAR - case TOKEN_DTYPE_INT: targ = DATA_INT - case TOKEN_DTYPE_PTR: targ = DATA_PTR - } - - testFunc.args = append(testFunc.args, targ) + parseArityInFunction(parser.previous, &testFunc, "argument") } } if match(TOKEN_RIGHT_ARROW) { for !check(TOKEN_PAREN_OPEN) { advance() - t := parser.previous - tret := DATA_EMPTY - - switch t.typ { - case TOKEN_DTYPE_BOOL: tret = DATA_BOOL - case TOKEN_DTYPE_CHAR: tret = DATA_CHAR - case TOKEN_DTYPE_INT: tret = DATA_INT - case TOKEN_DTYPE_PTR: tret = DATA_PTR - } - - testFunc.rets = append(testFunc.rets, tret) + parseArityInFunction(parser.previous, &testFunc, "return") } } @@ -795,8 +801,8 @@ func parseFunction(token Token) { // and the returns. for index, f := range TheProgram.chunks { if !f.parsed && f.name == testFunc.name && - reflect.DeepEqual(f.args, testFunc.args) && - reflect.DeepEqual(f.rets, testFunc.rets) { + reflect.DeepEqual(f.arguments, testFunc.arguments) && + reflect.DeepEqual(f.returns, testFunc.returns) { frontend.current = &TheProgram.chunks[index] break } diff --git a/compiler/ir.go b/compiler/ir.go index c4df6a2..3048b30 100644 --- a/compiler/ir.go +++ b/compiler/ir.go @@ -56,12 +56,13 @@ const ( type DataType int const ( - DATA_EMPTY DataType = iota + DATA_NONE DataType = iota + DATA_ANY DATA_BOOL DATA_CHAR + DATA_INFER DATA_INT DATA_PTR - DATA_ANY ) type Bound struct { @@ -74,13 +75,23 @@ type Program struct { variables []Object } +type Argument struct { + name string + typ DataType +} + +type Arity struct { + parapoly bool + types []Argument +} + type Function struct { ip int name string + arguments Arity + returns Arity loc Location bindings []Bound - args []DataType - rets []DataType code []Code parsed bool called bool @@ -88,9 +99,9 @@ type Function struct { } type Extern struct { - args []DataType - body []string - rets []DataType + arguments Arity + returns Arity + body []string } type Code struct { diff --git a/compiler/scanner.go b/compiler/scanner.go index 1685035..bd7070d 100644 --- a/compiler/scanner.go +++ b/compiler/scanner.go @@ -17,9 +17,11 @@ const ( TOKEN_TRUE // Types + TOKEN_DTYPE_ANY TOKEN_DTYPE_BOOL TOKEN_DTYPE_CHAR TOKEN_DTYPE_INT + TOKEN_DTYPE_PARAPOLY TOKEN_DTYPE_PTR // Reserved Words @@ -90,6 +92,7 @@ var reservedWords = []reserved{ reserved{typ: TOKEN_CONST, word: "const" }, reserved{typ: TOKEN_DIV, word: "div" }, reserved{typ: TOKEN_DROP, word: "drop" }, + reserved{typ: TOKEN_DTYPE_ANY, word: "any" }, reserved{typ: TOKEN_DTYPE_BOOL, word: "bool" }, reserved{typ: TOKEN_DTYPE_CHAR, word: "char" }, reserved{typ: TOKEN_DTYPE_INT, word: "int" }, @@ -254,6 +257,16 @@ func makeWord(c byte, line string, index *int) { makeToken(TOKEN_WORD, word) } +func makeParapolyToken(c byte, line string, index *int) { + word := "" + + for Advance(&c, line, index) && !IsSpace(c) { + word += string(c) + } + + makeToken(TOKEN_DTYPE_PARAPOLY, word) +} + func TokenizeFile(f string, s string) []Token { scanner.filename = f scanner.source = s @@ -275,6 +288,8 @@ func TokenizeFile(f string, s string) []Token { } switch { + case c == '$': + makeParapolyToken(c, line, &index) case c == '"': makeString(c, line, &index) case c == '\'': diff --git a/compiler/validate.go b/compiler/validate.go index 73f9f7b..692320a 100644 --- a/compiler/validate.go +++ b/compiler/validate.go @@ -73,9 +73,10 @@ func getOperationName(code Code) string { func getDataTypeName(v DataType) string { r := "" switch v { - case DATA_EMPTY: r = "" + case DATA_NONE: r = "" case DATA_BOOL: r = "bool" case DATA_CHAR: r = "char" + case DATA_INFER: r = "$type" case DATA_INT: r = "int" case DATA_PTR: r = "ptr" case DATA_ANY: r = "any" @@ -88,7 +89,7 @@ func getDataTypeNames(dt []DataType) string { for i, v := range dt { r += getDataTypeName(v) - if i != len(dt) -1 && v != DATA_EMPTY { + if i != len(dt) -1 && v != DATA_NONE { r += " " } } @@ -104,7 +105,7 @@ func assertArgumentTypes(test []DataType, want [][]DataType, code Code, loc Loca for i, t := range test { if w[i] == DATA_ANY { - if t == DATA_EMPTY { + if t == DATA_NONE { err = true break } @@ -141,8 +142,8 @@ func assertArgumentType(test []DataType, want []DataType, code Code, loc Locatio errFound := false for i, t := range test { - if want[i] == DATA_ANY { - if t == DATA_EMPTY { + if want[i] == DATA_ANY || want[i] == DATA_INFER { + if t == DATA_NONE { errFound = true break } @@ -197,11 +198,11 @@ func (this *Typecheck) push(t DataType) { func (this *Typecheck) pop() DataType { if this.stackCount == 0 { - return DATA_EMPTY + return DATA_NONE } this.stackCount-- v := this.stack[this.stackCount] - this.stack[this.stackCount] = DATA_EMPTY + this.stack[this.stackCount] = DATA_NONE return v } @@ -210,11 +211,15 @@ func ValidateRun() { for ifunction, function := range TheProgram.chunks { var binds []DataType + argumentTypes := function.arguments.types + returnTypes := function.returns.types + + if function.name == "main" { mainHandled = true dce.push(function.ip) - if len(function.args) > 0 || len(function.rets) > 0 { + if len(argumentTypes) > 0 || len(returnTypes) > 0 { ReportErrorAtLocation( MsgTypecheckMainFunctionNoArgumentsOrReturn, function.loc, @@ -223,10 +228,10 @@ func ValidateRun() { } } - expectedReturnCount := len(function.rets) + expectedReturnCount := len(returnTypes) - for _, dt := range function.args { - tc.push(dt) + for _, t := range argumentTypes { + tc.push(t.typ) } for icode, code := range function.code { @@ -304,24 +309,28 @@ func ValidateRun() { tc.push(DATA_BOOL) case OP_EXTERN: var have []DataType + var want []DataType value := code.value.(Extern) - for range value.args { + for _, d := range value.arguments.types { t := tc.pop() have = append([]DataType{t}, have...) + want = append(want, d.typ) } - assertArgumentType(have, value.args, code, loc) + assertArgumentType(have, want, code, loc) - for _, dt := range value.rets { - tc.push(dt) + for _, dt := range value.returns.types { + tc.push(dt.typ) } case OP_FUNCTION_CALL: var have []DataType - calls := code.value.([]FunctionCall) + var want []DataType var funcRef Function var fns []Function + calls := code.value.([]FunctionCall) + for _, c := range calls { fns = append(fns, findFunctionByIP(c.ip)) } @@ -337,11 +346,16 @@ func ValidateRun() { // (simulating we pop from it) and then shrink it to the number // of values expected in arguments (since stack simulation in // typechecking is not a dynamic array). - reverseStackOrder := tc.stackCount - len(f.args) + var argTypes []DataType + reverseStackOrder := tc.stackCount - len(f.arguments.types) stackReversed := tc.stack[reverseStackOrder:] - stackReducedToArgsLen := stackReversed[:len(f.args)] + stackReducedToArgsLen := stackReversed[:len(f.arguments.types)] - if reflect.DeepEqual(f.args, stackReducedToArgsLen) { + for _, d := range f.arguments.types { + argTypes = append(argTypes, d.typ) + } + + if reflect.DeepEqual(argTypes, stackReducedToArgsLen) { funcRef = f break } @@ -355,15 +369,29 @@ func ValidateRun() { TheProgram.chunks[ifunction].code[icode].value = FunctionCall{name: funcRef.name, ip: funcRef.ip} - for range funcRef.args { + var inferredDataType DataType + + for _, d := range funcRef.arguments.types { t := tc.pop() have = append([]DataType{t}, have...) + want = append(want, d.typ) + + if d.typ == DATA_INFER && inferredDataType == DATA_NONE { + inferredDataType = t + } } - assertArgumentType(have, funcRef.args, code, loc) + assertArgumentType(have, want, code, loc) - for _, dt := range funcRef.rets { - tc.push(dt) + for _, d := range funcRef.returns.types { + if d.typ == DATA_INFER && inferredDataType == DATA_NONE { + ReportErrorAtLocation("TODO ERROR MESSAGE", funcRef.loc) + ExitWithError(CodeTypecheckError) + } else if d.typ == DATA_INFER { + tc.push(inferredDataType) + } else { + tc.push(d.typ) + } } case OP_JUMP_IF_FALSE: a := tc.pop()