From b56c1fc4856f81313b56535fe9d44b89be4f17ed Mon Sep 17 00:00:00 2001 From: Ugric Date: Mon, 20 Mar 2023 00:00:17 +0000 Subject: [PATCH] add for loop --- modules/csv/init.ar | 12 ++--- src/built-ins.go | 2 +- src/file.go | 103 +++++++++++++++++-------------------- src/forloop.go | 120 ++++++++++++++++++++++++++++++++++++++++++++ src/import.go | 2 +- src/run.go | 14 ++++-- src/shell.go | 5 +- src/string.go | 5 +- src/translate.go | 2 + src/whileloop.go | 6 ++- test.ar | 3 ++ test.csv | 2 + 12 files changed, 203 insertions(+), 73 deletions(-) create mode 100644 src/forloop.go create mode 100644 test.ar create mode 100644 test.csv diff --git a/modules/csv/init.ar b/modules/csv/init.ar index 91b3dd4..d2accd1 100644 --- a/modules/csv/init.ar +++ b/modules/csv/init.ar @@ -1,16 +1,14 @@ let read(path) = do - file = open(path, "r") - text = file.text() + f = file.read(path) + text = f.text() data = [] splitlines = text.split("\n") - i = 0 - while (i < splitlines.length) do + for (i from 0 to splitlines.length) do values = [] in_quotes = false start = 0 line = splitlines[i] - j = 0 - while (j < line.length) do + for (j from 0 to line.length) do char = line[j] if (char == "," && not in_quotes) do value = line[start:j].rightstrip("\r").strip() @@ -27,8 +25,6 @@ let read(path) = do value = value[1:value.length - 1] value = value.replace("\"\"", "\"") values.append(value) - j = j + 1 data.append(values) - i = i + 1 return data \ No newline at end of file diff --git a/src/built-ins.go b/src/built-ins.go index 029968c..f91ef90 100644 --- a/src/built-ins.go +++ b/src/built-ins.go @@ -130,7 +130,7 @@ func init() { return nil, ArErr{TYPE: "TypeError", message: "Cannot ceil '" + typeof(a[0]) + "'", EXISTS: true} }} vars.obj["sqrt"] = builtinFunc{"sqrt", ArgonSqrt} - vars.obj["open"] = builtinFunc{"open", ArOpen} + vars.obj["file"] = ArFile vars.obj["random"] = ArRandom vars.obj["json"] = ArJSON vars.obj["sin"] = ArSin diff --git a/src/file.go b/src/file.go index a983f80..b69ef2f 100644 --- a/src/file.go +++ b/src/file.go @@ -7,49 +7,59 @@ import ( "os" ) -func ArOpen(args ...any) (any, ArErr) { - if len(args) > 2 { - return ArObject{}, ArErr{TYPE: "Runtime Error", message: "open takes 1 or 2 argument, got " + fmt.Sprint(len(args)), EXISTS: true} +var ArFile = Map(anymap{ + "read": builtinFunc{"read", ArRead}, + "write": builtinFunc{"write", ArWrite}, +}) + +func readtext(file *os.File) (string, error) { + var buf bytes.Buffer + _, err := io.Copy(&buf, file) + if err != nil { + return "", err + } + return buf.String(), nil +} + +func ArRead(args ...any) (any, ArErr) { + if len(args) != 1 { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "read takes 1 argument, got " + fmt.Sprint(len(args)), EXISTS: true} } if typeof(args[0]) != "string" { - return ArObject{}, ArErr{TYPE: "Runtime Error", message: "open takes a string not type '" + typeof(args[0]) + "'", EXISTS: true} + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "read takes a string not type '" + typeof(args[0]) + "'", EXISTS: true} } - path := args[0].(string) - mode := "r" - if len(args) == 2 { - if typeof(args[1]) != "string" { - return ArObject{}, ArErr{TYPE: "Runtime Error", message: "open takes a string not type '" + typeof(args[1]) + "'", EXISTS: true} - } - mode = args[1].(string) + filename := args[0].(string) + file, err := os.Open(filename) + if err != nil { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} } - if mode != "r" && mode != "w" { - return ArObject{}, ArErr{TYPE: "Runtime Error", message: "open mode must be 'r', or 'w'", EXISTS: true} + return Map(anymap{ + "text": builtinFunc{"text", func(...any) (any, ArErr) { + text, err := readtext(file) + if err != nil { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} + } + return text, ArErr{} + }}, + "json": builtinFunc{"json", func(...any) (any, ArErr) { + text, err := readtext(file) + if err != nil { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} + } + return jsonparse(text), ArErr{} + }}, + }), ArErr{} +} + +func ArWrite(args ...any) (any, ArErr) { + if len(args) != 1 { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "write takes 1 argument, got " + fmt.Sprint(len(args)), EXISTS: true} } - if mode == "r" { - file, err := os.Open(path) - if err != nil { - return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} - } - return Map(anymap{ - "text": builtinFunc{"text", func(...any) (any, ArErr) { - text, err := readtext(file) - if err != nil { - return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} - } - return ArString(text), ArErr{} - }, - }, - "json": builtinFunc{"json", func(...any) (any, ArErr) { - text, err := readtext(file) - if err != nil { - return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} - } - return jsonparse(text), ArErr{} - }, - }, - }), ArErr{} + if typeof(args[0]) != "string" { + return ArObject{}, ArErr{TYPE: "Runtime Error", message: "write takes a string not type '" + typeof(args[0]) + "'", EXISTS: true} } - file, err := os.Create(path) + filename := args[0].(string) + file, err := os.Create(filename) if err != nil { return ArObject{}, ArErr{TYPE: "Runtime Error", message: err.Error(), EXISTS: true} } @@ -75,25 +85,6 @@ func ArOpen(args ...any) (any, ArErr) { file.Write([]byte(jsonstr)) return nil, ArErr{} }}, - "append": builtinFunc{"append", func(args ...any) (any, ArErr) { - if len(args) != 1 { - return ArObject{}, ArErr{TYPE: "Runtime Error", message: "append takes 1 argument, got " + fmt.Sprint(len(args)), EXISTS: true} - } - if typeof(args[0]) != "string" { - return ArObject{}, ArErr{TYPE: "Runtime Error", message: "append takes a string not type '" + typeof(args[0]) + "'", EXISTS: true} - } - file.Write([]byte(args[0].(string))) - return nil, ArErr{} - }}, }), ArErr{} } - -func readtext(file *os.File) (string, error) { - var buf bytes.Buffer - _, err := io.Copy(&buf, file) - if err != nil { - return "", err - } - return buf.String(), nil -} diff --git a/src/forloop.go b/src/forloop.go new file mode 100644 index 0000000..3165599 --- /dev/null +++ b/src/forloop.go @@ -0,0 +1,120 @@ +package main + +import ( + "strings" +) + +var forloopCompile = makeRegex(`( *)for( +)\(( *)` + spacelessVariable + `( +)from( +)(\n|.)+to( +)(\n|.)+(( +)step( +)(\n|.)+)?( *)\)( *)(\n|.)+`) + +type forLoop struct { + variable string + from any + to any + step any + body any + line int + code string + path string +} + +func isForLoop(code UNPARSEcode) bool { + return forloopCompile.MatchString(code.code) +} + +func parseForLoop(code UNPARSEcode, index int, codelines []UNPARSEcode) (forLoop, bool, ArErr, int) { + totalstep := 0 + trimmed := strings.TrimSpace(strings.TrimSpace(code.code)[3:])[1:] + split := strings.SplitN(trimmed, " from ", 2) + name := strings.TrimSpace(split[0]) + split = strings.SplitN(split[1], " to ", 2) + from := strings.TrimSpace(split[0]) + fromval, worked, err, fromstep := translateVal(UNPARSEcode{code: from, realcode: code.realcode, line: index + 1, path: code.path}, index, codelines, 0) + if !worked { + return forLoop{}, worked, err, fromstep + } + totalstep += fromstep + tosplit := strings.Split(split[1], ")") + for i := len(tosplit) - 1; i >= 0; i-- { + innertotalstep := 0 + val := strings.Join(tosplit[:i], ")") + valsplit := strings.SplitN(val, " step ", 2) + var stepval any + if len(valsplit) == 2 { + step := strings.TrimSpace(valsplit[1]) + stepval_, worked, err, stepstep := translateVal(UNPARSEcode{code: step, realcode: code.realcode, line: code.line, path: code.path}, index, codelines, 0) + if !worked { + if i == 0 { + return forLoop{}, worked, err, stepstep + } + continue + } + innertotalstep += stepstep - 1 + stepval = stepval_ + } else { + stepval = newNumber().SetInt64(1) + } + to := strings.TrimSpace(valsplit[0]) + toval, worked, err, tostep := translateVal(UNPARSEcode{code: to, realcode: code.realcode, line: code.line, path: code.path}, index, codelines, 0) + if !worked { + if i == 0 { + return forLoop{}, worked, err, tostep + } + continue + } + innertotalstep += tostep - 1 + body := strings.Join(tosplit[i:], ")") + bodyval, worked, err, bodystep := translateVal(UNPARSEcode{code: body, realcode: code.realcode, line: code.line, path: code.path}, index, codelines, 1) + if !worked { + if i == 0 { + return forLoop{}, worked, err, bodystep + } + continue + } + innertotalstep += bodystep - 1 + return forLoop{variable: name, from: fromval, to: toval, step: stepval, body: bodyval, line: code.line, code: code.code, path: code.path}, true, ArErr{}, totalstep + innertotalstep + } + return forLoop{}, false, ArErr{"Syntax Error", "invalid for loop", code.line, code.path, code.realcode, true}, 1 +} +func runForLoop(loop forLoop, stack stack, stacklevel int) (any, ArErr) { + fromval, err := runVal(loop.from, stack, stacklevel+1) + if err.EXISTS { + return nil, err + } + if typeof(fromval) != "number" { + return nil, ArErr{"Type Error", "for loop from value must be a number", loop.line, loop.path, loop.code, true} + } + from := fromval.(number) + toval, err := runVal(loop.to, stack, stacklevel+1) + if err.EXISTS { + return nil, err + } + if typeof(toval) != "number" { + return nil, ArErr{"Type Error", "for loop to value must be a number", loop.line, loop.path, loop.code, true} + } + to := toval.(number) + stepval, err := runVal(loop.step, stack, stacklevel+1) + if err.EXISTS { + return nil, err + } + if typeof(stepval) != "number" { + return nil, ArErr{"Type Error", "for loop step value must be a number", loop.line, loop.path, loop.code, true} + } + step := stepval.(number) + for i := newNumber().Set(from); i.Cmp(to) == -1; i = i.Add(i, step) { + resp, err := runVal(loop.body, append(stack, Map(anymap{ + loop.variable: newNumber().Set(i), + })), stacklevel+1) + if err.EXISTS { + return nil, err + } + switch x := resp.(type) { + case Return: + return x, ArErr{} + case Break: + return nil, ArErr{} + case Continue: + continue + } + } + return nil, ArErr{} +} diff --git a/src/import.go b/src/import.go index 803fbbc..9997141 100644 --- a/src/import.go +++ b/src/import.go @@ -134,7 +134,7 @@ func importMod(realpath string, origin string, main bool) (ArObject, ArErr) { "scope": global, }), }) - _, runimeErr, _ := run(translated, stack{vars, localvars, global}) + _, runimeErr := ThrowOnNonLoop(run(translated, stack{vars, localvars, global})) importing[p] = false if runimeErr.EXISTS { return ArObject{}, runimeErr diff --git a/src/run.go b/src/run.go index 764efaa..2832743 100644 --- a/src/run.go +++ b/src/run.go @@ -159,6 +159,14 @@ func runVal(line any, stack stack, stacklevel int) (any, ArErr) { break } return runWhileLoop(x, stack, stacklevel+1) + case forLoop: + if stackoverflow { + linenum = x.line + path = x.path + code = x.code + break + } + return runForLoop(x, stack, stacklevel+1) case CreateArray: if stackoverflow { linenum = x.line @@ -203,14 +211,14 @@ func runVal(line any, stack stack, stacklevel int) (any, ArErr) { } // returns error -func run(translated []any, stack stack) (any, ArErr, any) { +func run(translated []any, stack stack) (any, ArErr) { var output any = nil for _, val := range translated { val, err := runVal(val, stack, 0) output = val if err.EXISTS { - return nil, err, output + return output, err } } - return nil, ArErr{}, output + return output, ArErr{} } diff --git a/src/shell.go b/src/shell.go index dac69b8..f3d23be 100644 --- a/src/shell.go +++ b/src/shell.go @@ -41,10 +41,13 @@ func shell() { if translationerr.EXISTS { panicErr(translationerr) } - _, runimeErr, output := run(translated, global) + output, runimeErr := ThrowOnNonLoop(run(translated, global)) + output = openReturn(output) + if runimeErr.EXISTS { panicErr(runimeErr) } else if count == 1 { + fmt.Println(anyToArgon(output, true, true, 3, 0, true, 1)) } } diff --git a/src/string.go b/src/string.go index f1644fd..7bded14 100644 --- a/src/string.go +++ b/src/string.go @@ -187,8 +187,11 @@ func ArString(str string) ArObject { if len(a) != 1 { return nil, ArErr{"TypeError", "expected 1 argument, got " + fmt.Sprint(len(a)), 0, "", "", true} } + if typeof(a[0]) != "array" { + return nil, ArErr{"TypeError", "expected array, got " + typeof(a[0]), 0, "", "", true} + } output := []string{str} - for _, v := range a { + for _, v := range a[0].([]any) { if typeof(v) != "string" { return nil, ArErr{"TypeError", "expected string, got " + typeof(v), 0, "", "", true} } diff --git a/src/translate.go b/src/translate.go index 0bba0f3..5269122 100644 --- a/src/translate.go +++ b/src/translate.go @@ -38,6 +38,8 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine i return parseWhileLoop(code, index, codelines) } else if isForeverLoop(code) { return parseForeverLoop(code, index, codelines) + } else if isForLoop(code) { + return parseForLoop(code, index, codelines) } else if isGenericImport(code) { return parseGenericImport(code, index, codelines) } diff --git a/src/whileloop.go b/src/whileloop.go index 981c210..ef4687e 100644 --- a/src/whileloop.go +++ b/src/whileloop.go @@ -108,15 +108,17 @@ func parseForeverLoop(code UNPARSEcode, index int, codeline []UNPARSEcode) (whil } func runWhileLoop(loop whileLoop, stack stack, stacklevel int) (any, ArErr) { + newstack := append(stack, newscope()) for { - condition, err := runVal(loop.condition, stack, stacklevel+1) + condition, err := runVal(loop.condition, newstack, stacklevel+1) + newbodystack := append(newstack, newscope()) if err.EXISTS { return nil, err } if !anyToBool(condition) { break } - resp, err := runVal(loop.body, stack, stacklevel+1) + resp, err := runVal(loop.body, newbodystack, stacklevel+1) if err.EXISTS { return nil, err } diff --git a/test.ar b/test.ar new file mode 100644 index 0000000..378fbf8 --- /dev/null +++ b/test.ar @@ -0,0 +1,3 @@ +import "csv" as csv + +term.log(csv.read("test.csv")) \ No newline at end of file diff --git a/test.csv b/test.csv new file mode 100644 index 0000000..d298e50 --- /dev/null +++ b/test.csv @@ -0,0 +1,2 @@ +hello,world +1,2,3,4,5,6,7,8,9,10 \ No newline at end of file