diff --git a/go.mod b/go.mod index 06e880e..07656db 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,15 @@ module wbell.dev/m/v2 go 1.19 -require github.com/wadey/go-rounding v1.1.0 \ No newline at end of file +require ( + github.com/fatih/color v1.14.1 + github.com/wadey/go-rounding v1.1.0 +) + +require ( + github.com/jwalton/go-supportscolor v1.1.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.17 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d // indirect +) diff --git a/go.sum b/go.sum index d3d8501..88ecf7d 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,18 @@ +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/jwalton/go-supportscolor v1.1.0 h1:HsXFJdMPjRUAx8cIW6g30hVSFYaxh9yRQwEWgkAR7lQ= +github.com/jwalton/go-supportscolor v1.1.0/go.mod h1:hFVUAZV2cWg+WFFC4v8pT2X/S2qUUBYMioBD9AINXGs= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/wadey/go-rounding v1.1.0 h1:RAs9dMkB/uUHFv9ljlbRFC8/kBrQ5jhwt1GQq+2cciY= github.com/wadey/go-rounding v1.1.0/go.mod h1:/uD953tCL6Fea2Yp+LZBBp8d60QSObkMJxY6SPOJ5QE= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/src/anyToBool.go b/src/anyToBool.go deleted file mode 100644 index 7492d7c..0000000 --- a/src/anyToBool.go +++ /dev/null @@ -1,24 +0,0 @@ -package main - -func anyToBool(x any) bool { - switch x := x.(type) { - case string: - return x != "" - case number: - return x.Cmp(newNumber()) != 0 - case bool: - return x - case nil: - return false - case ArMap: - return len(x) != 0 - case builtinFunc: - return true - case Callable: - return true - case ArClass: - return true - default: - return true - } -} diff --git a/src/boolean.go b/src/boolean.go new file mode 100644 index 0000000..64acccd --- /dev/null +++ b/src/boolean.go @@ -0,0 +1,42 @@ +package main + +import "strings" + +func anyToBool(x any) bool { + switch x := x.(type) { + case string: + return x != "" + case number: + return x.Cmp(newNumber()) != 0 + case bool: + return x + case nil: + return false + case ArMap: + return len(x) != 0 + case builtinFunc: + return true + case Callable: + return true + case ArClass: + return true + default: + return true + } +} + +var booleanCompile = makeRegex(`( )*(true|false|null)( )*`) + +func isBoolean(code UNPARSEcode) bool { + return booleanCompile.MatchString(code.code) +} + +func parseBoolean(code UNPARSEcode) (any, bool, ArErr, int) { + trim := strings.TrimSpace(code.code) + if trim == "true" { + return true, true, ArErr{}, 1 + } else if trim == "false" { + return false, true, ArErr{}, 1 + } + return nil, true, ArErr{}, 1 +} diff --git a/src/built-in-functions.go b/src/built-in-functions.go index ebce748..07955d9 100644 --- a/src/built-in-functions.go +++ b/src/built-in-functions.go @@ -23,7 +23,7 @@ func ArgonNumber(args ...any) (any, ArErr) { } switch x := args[0].(type) { case string: - if !numberCompile.MatchString(x) { + if !isNumber(UNPARSEcode{code: x}) { return nil, ArErr{TYPE: "Conversion Error", message: "Cannot convert " + anyToArgon(x, true, true, 3, 0, false, 0) + " to a number", EXISTS: true} } N, _ := newNumber().SetString(x) diff --git a/src/built-ins.go b/src/built-ins.go index b78aafe..c8adef6 100644 --- a/src/built-ins.go +++ b/src/built-ins.go @@ -1,15 +1,15 @@ package main -import "github.com/wadey/go-rounding" +import ( + "fmt" + "strings" +) var vars = scope{} func init() { vars["global"] = vars vars["term"] = ArTerm - vars["true"] = true - vars["false"] = false - vars["null"] = nil vars["input"] = builtinFunc{"input", ArgonInput} vars["number"] = builtinFunc{"number", ArgonNumber} vars["string"] = builtinFunc{"string", ArgonString} @@ -36,9 +36,16 @@ func init() { newmap[i] = string(v) } return newmap, ArErr{} - case []any: + case ArArray: newmap := ArMap{} for i, v := range x { + switch y := v.(type) { + case ArArray: + if len(y) == 2 { + newmap[y[0]] = y[1] + continue + } + } newmap[i] = v } return newmap, ArErr{} @@ -60,13 +67,19 @@ func init() { return newarray, ArErr{} case ArMap: newarray := ArArray{} - for _, v := range x { - newarray = append(newarray, v) + for key, val := range x { + newarray = append(newarray, ArArray{key, val}) } return newarray, ArErr{} } return nil, ArErr{TYPE: "TypeError", message: "Cannot create array from '" + typeof(a[0]) + "'", EXISTS: true} }} + vars["boolean"] = builtinFunc{"boolean", func(a ...any) (any, ArErr) { + if len(a) == 0 { + return false, ArErr{} + } + return anyToBool(a[0]), ArErr{} + }} vars["time"] = ArTime vars["PI"] = PI vars["π"] = PI @@ -95,7 +108,7 @@ func init() { switch x := a[0].(type) { case number: - return rounding.Round(newNumber().Set(x), int(precision.Num().Int64()), rounding.HalfUp), ArErr{} + return round(newNumber().Set(x), int(precision.Num().Int64())), ArErr{} } return nil, ArErr{TYPE: "TypeError", message: "Cannot round '" + typeof(a[0]) + "'", EXISTS: true} }} @@ -106,11 +119,7 @@ func init() { } switch x := a[0].(type) { case number: - n := newNumber().Set(x) - if n.Sign() < 0 { - return rounding.Round(n, 0, rounding.Up), ArErr{} - } - return rounding.Round(n, 0, rounding.Down), ArErr{} + return floor(x), ArErr{} } return nil, ArErr{TYPE: "TypeError", message: "Cannot floor '" + typeof(a[0]) + "'", EXISTS: true} }} @@ -122,13 +131,36 @@ func init() { switch x := a[0].(type) { case number: - n := newNumber().Set(x) - if n.Sign() < 0 { - return rounding.Round(n, 0, rounding.Down), ArErr{} - } - return rounding.Round(n, 0, rounding.Up), ArErr{} + return ceil(x), ArErr{} } return nil, ArErr{TYPE: "TypeError", message: "Cannot ceil '" + typeof(a[0]) + "'", EXISTS: true} }} + vars["append"] = builtinFunc{"append", func(a ...any) (any, ArErr) { + if len(a) != 2 { + return nil, ArErr{TYPE: "append", message: "append takes 2 arguments, got " + fmt.Sprint(len(a)), + EXISTS: true} + } + switch x := a[0].(type) { + case ArArray: + return append(x, a[1]), ArErr{} + case string: + if typeof(a[1]) != "string" { + return nil, ArErr{TYPE: "TypeError", message: "Cannot append '" + typeof(a[1]) + "' to string", EXISTS: true} + } + return strings.Join([]string{x, a[1].(string)}, ""), ArErr{} + case ArMap: + if typeof(a[1]) != "array" { + return nil, ArErr{TYPE: "TypeError", message: "Cannot append '" + typeof(a[1]) + "' to map", EXISTS: true} + } + y := a[1].(ArArray) + if len(y) != 2 { + return nil, ArErr{TYPE: "TypeError", message: "Cannot append '" + typeof(a[1]) + "' to map", EXISTS: true} + } + x[y[0]] = y[1] + return x, ArErr{} + } + return nil, ArErr{TYPE: "TypeError", message: "Cannot append to '" + typeof(a[0]) + "'", EXISTS: true} + }} vars["sqrt"] = builtinFunc{"sqrt", ArgonSqrt} + vars["random"] = ArRandom } diff --git a/src/call.go b/src/call.go index 7961498..d5d5080 100644 --- a/src/call.go +++ b/src/call.go @@ -67,6 +67,10 @@ func runCall(c call, stack stack, stacklevel int) (any, ArErr) { if err.EXISTS { return nil, err } + switch x := callable_.(type) { + case ArMap: + callable_ = x["__call__"] + } callable = callable_ } args := []any{} @@ -82,7 +86,15 @@ func runCall(c call, stack stack, stacklevel int) (any, ArErr) { case builtinFunc: resp, err := x.FUNC(args...) if err.EXISTS { - err = ArErr{err.TYPE, err.message, c.line, c.path, c.code, true} + if err.line == 0 { + err.line = c.line + } + if err.path == "" { + err.path = c.path + } + if err.code == "" { + err.code = c.code + } } return resp, err case Callable: @@ -94,7 +106,7 @@ func runCall(c call, stack stack, stacklevel int) (any, ArErr) { level[param] = args[i] } resp, err := runVal(x.run, append(x.stack, level), stacklevel+1) - return openJump(resp), err + return openReturn(resp), err } return nil, ArErr{"Runtime Error", "type '" + typeof(callable) + "' is not callable", c.line, c.path, c.code, true} } diff --git a/src/dowraps.go b/src/dowraps.go index 046d7da..5ca9edf 100644 --- a/src/dowraps.go +++ b/src/dowraps.go @@ -58,6 +58,8 @@ func runDoWrap(d dowrap, stack stack, stacklevel int) (any, ArErr) { switch x := val.(type) { case Return: return x, ArErr{} + case Break: + return x, ArErr{} } } return nil, ArErr{} diff --git a/src/ifstatement.go b/src/ifstatement.go new file mode 100644 index 0000000..0f2ae9e --- /dev/null +++ b/src/ifstatement.go @@ -0,0 +1,134 @@ +package main + +import ( + "strings" +) + +var ifstatmentCompile = makeRegex(`( *)if( )+\((.|\n)+\)( )+(.|\n)+`) +var elseifstatmentCompile = makeRegex(`( *)else( )+if( )+\((.|\n)+\)( )+(.|\n)+`) +var elseCompile = makeRegex(`( *)else( )+(.|\n)+`) + +type statement struct { + condition any + THEN any + line int + code string + path string +} + +type ifstatement struct { + conditions []statement + ELSE any + line int + code string + path string +} + +func isIfStatement(code UNPARSEcode) bool { + return ifstatmentCompile.MatchString(code.code) +} + +func parseIfStatement(code UNPARSEcode, index int, codeline []UNPARSEcode) (ifstatement, bool, ArErr, int) { + conditions := []statement{} + var ELSE any + i := index + for i < len(codeline) && (elseifstatmentCompile.MatchString(codeline[i].code) || i == index) { + trimmed := strings.TrimSpace(codeline[i].code) + trimmed = strings.TrimSpace(trimmed[strings.Index(trimmed, "("):]) + trimmed = (trimmed[1:]) + split := strings.Split(trimmed, ")") + for j := len(split) - 1; j > 0; j-- { + conditionjoined := strings.Join(split[:j], ")") + thenjoined := strings.Join(split[j:], ")") + outindex := 0 + conditionval, worked, err, step := translateVal( + UNPARSEcode{ + code: conditionjoined, + realcode: codeline[i].realcode, + line: code.line, + path: code.path, + }, + i, + codeline, + 0, + ) + if err.EXISTS || !worked { + if j == 1 { + return ifstatement{}, worked, err, step + } else { + continue + } + } + + outindex += step + thenval, worked, err, step := translateVal( + UNPARSEcode{ + code: thenjoined, + realcode: codeline[i].realcode, + line: code.line, + path: code.path, + }, + i, + codeline, + 2, + ) + if err.EXISTS || !worked { + return ifstatement{}, worked, err, step + } + outindex += step - 1 + conditions = append(conditions, statement{ + condition: conditionval, + THEN: thenval, + line: code.line, + code: code.realcode, + path: code.path, + }) + i += outindex + break + } + } + if i < len(codeline) && elseCompile.MatchString(codeline[i].code) { + trimmed := strings.TrimSpace(codeline[i].code) + trimmed = strings.TrimSpace(trimmed[4:]) + ELSEval, _, err, step := translateVal( + UNPARSEcode{ + code: trimmed, + realcode: codeline[i].realcode, + line: code.line, + path: code.path, + }, + i, + codeline, + 2, + ) + if err.EXISTS { + return ifstatement{}, false, err, step + } + ELSE = ELSEval + i += step + } + return ifstatement{ + conditions: conditions, + ELSE: ELSE, + line: code.line, + code: code.realcode, + path: code.path, + }, true, ArErr{}, i - index +} + +func runIfStatement(code ifstatement, stack stack, stacklevel int) (any, ArErr) { + for _, condition := range code.conditions { + newstack := append(stack, scope{}) + resp, err := runVal(condition.condition, newstack, stacklevel+1) + if err.EXISTS { + return nil, err + } + if anyToBool(resp) { + return runVal(condition.THEN, newstack, stacklevel+1) + } + } + if code.ELSE != nil { + return runVal(code.ELSE, append(stack, scope{}), stacklevel+1) + } + return nil, ArErr{} +} diff --git a/src/ifstatment.go b/src/ifstatment.go deleted file mode 100644 index 06ab7d0..0000000 --- a/src/ifstatment.go +++ /dev/null @@ -1 +0,0 @@ -package main diff --git a/src/import.go b/src/import.go index 05145f6..37501e2 100644 --- a/src/import.go +++ b/src/import.go @@ -44,7 +44,7 @@ func readFile(path string) []UNPARSEcode { return output } -func importMod(realpath string, origin string, main bool) ArErr { +func importMod(realpath string, origin string, main bool) (scope, ArErr) { extention := filepath.Ext(realpath) path := realpath if extention == "" { @@ -52,11 +52,11 @@ func importMod(realpath string, origin string, main bool) ArErr { } ex, err := os.Getwd() if err != nil { - return ArErr{"Import Error", err.Error(), 0, realpath, "", true} + return nil, ArErr{"Import Error", err.Error(), 0, realpath, "", true} } executable, err := os.Executable() if err != nil { - return ArErr{"Import Error", err.Error(), 0, realpath, "", true} + return nil, ArErr{"Import Error", err.Error(), 0, realpath, "", true} } executable = filepath.Dir(executable) isABS := filepath.IsAbs(path) @@ -90,18 +90,18 @@ func importMod(realpath string, origin string, main bool) ArErr { } if !found { - return ArErr{"Import Error", "File does not exist: " + realpath, 0, realpath, "", true} + return nil, ArErr{"Import Error", "File does not exist: " + realpath, 0, realpath, "", true} } codelines := readFile(p) translated, translationerr := translate(codelines) if translationerr.EXISTS { - return translationerr + return nil, translationerr } global := scope{} _, runimeErr, _ := run(translated, stack{vars, global}) if runimeErr.EXISTS { - return runimeErr + return nil, runimeErr } - return ArErr{} + return global, ArErr{} } diff --git a/src/jumpStatments.go b/src/jumpStatements.go similarity index 62% rename from src/jumpStatments.go rename to src/jumpStatements.go index 9261dbb..7671bb0 100644 --- a/src/jumpStatments.go +++ b/src/jumpStatements.go @@ -1,8 +1,11 @@ package main -import "strings" +import ( + "strings" +) var returnCompile = makeRegex(`( *)return( +)(.|\n)+`) +var breakCompile = makeRegex(`( *)break( *)`) type CallReturn struct { value any @@ -18,10 +21,25 @@ type Return struct { path string } +type CallBreak struct { + line int + code string + path string +} +type Break struct { + line int + code string + path string +} + func isReturn(code UNPARSEcode) bool { return returnCompile.MatchString(code.code) } +func isBreak(code UNPARSEcode) bool { + return breakCompile.MatchString(code.code) +} + func parseReturn(code UNPARSEcode, index int, codeline []UNPARSEcode) (CallReturn, bool, ArErr, int) { resp, worked, err, i := translateVal(UNPARSEcode{ code: strings.TrimSpace(code.code)[6:], @@ -54,7 +72,7 @@ func runReturn(code CallReturn, stack stack, stacklevel int) (any, ArErr) { }, ArErr{} } -func openJump(resp any) any { +func openReturn(resp any) any { switch x := resp.(type) { case Return: return x.value @@ -62,3 +80,19 @@ func openJump(resp any) any { return resp } } + +func parseBreak(code UNPARSEcode, index int, codeline []UNPARSEcode) (CallBreak, bool, ArErr, int) { + return CallBreak{ + line: code.line, + code: code.realcode, + path: code.path, + }, true, ArErr{}, 1 +} + +func runBreak(code CallBreak, stack stack, stacklevel int) (any, ArErr) { + return Break{ + line: code.line, + code: code.code, + path: code.path, + }, ArErr{} +} diff --git a/src/main.go b/src/main.go index 7996460..71e84ed 100644 --- a/src/main.go +++ b/src/main.go @@ -19,7 +19,7 @@ func main() { shell() os.Exit(0) } - err := importMod(Args[0], ex, true) + _, err := importMod(Args[0], ex, true) if err.EXISTS { panicErr(err) os.Exit(1) diff --git a/src/mapAndArray.go b/src/mapAndArray.go index 1c875e7..46a1ddc 100644 --- a/src/mapAndArray.go +++ b/src/mapAndArray.go @@ -65,6 +65,7 @@ func mapGet(r ArMapGet, stack stack, stacklevel int) (any, ArErr) { startindex := 0 endindex := 1 step := 1 + slice := false if !r.index { key, err := runVal(r.start, stack, stacklevel+1) @@ -72,7 +73,15 @@ func mapGet(r ArMapGet, stack stack, stacklevel int) (any, ArErr) { return nil, err } if key == "length" { - return len(m), ArErr{} + return newNumber().SetInt64(int64(len(m))), ArErr{} + } + return nil, ArErr{ + "IndexError", + "index not found", + r.line, + r.path, + r.code, + true, } } if r.start != nil { @@ -119,6 +128,7 @@ func mapGet(r ArMapGet, stack stack, stacklevel int) (any, ArErr) { true, } } + slice = true num := eindex.(number) if !num.IsInt() { return nil, ArErr{ @@ -149,6 +159,7 @@ func mapGet(r ArMapGet, stack stack, stacklevel int) (any, ArErr) { true, } } + slice = true num := step.(number) if !num.IsInt() { return nil, ArErr{ @@ -202,6 +213,9 @@ func mapGet(r ArMapGet, stack stack, stacklevel int) (any, ArErr) { true, } } + if !slice { + return m[startindex], ArErr{} + } return m[startindex:endindex:step], ArErr{} case ArClass: if r.numberofindex > 1 { @@ -240,7 +254,7 @@ func mapGet(r ArMapGet, stack stack, stacklevel int) (any, ArErr) { return nil, err } if key == "length" { - return len(m), ArErr{} + return newNumber().SetInt64(int64(len(m))), ArErr{} } } if r.start != nil { diff --git a/src/random.go b/src/random.go new file mode 100644 index 0000000..05b27ec --- /dev/null +++ b/src/random.go @@ -0,0 +1,77 @@ +package main + +import ( + "fmt" + "math/rand" + "time" +) + +func random() number { + return newNumber().SetFloat64( + rand.Float64(), + ) +} + +func randomRange(args ...any) (any, ArErr) { + if len(args) != 2 { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "takes 2 arguments, got " + fmt.Sprint(len(args)), + EXISTS: true, + } + } + if typeof(args[0]) != "number" { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "takes a number not a '" + typeof(args[0]) + "'", + EXISTS: true, + } + } else if typeof(args[1]) != "number" { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "takes a number not a '" + typeof(args[1]) + "'", + EXISTS: true, + } + } + min := args[0].(number) + max := args[1].(number) + if min.Cmp(max) > 0 { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "takes a min less than max", + EXISTS: true, + } + } + difference := newNumber().Sub(max, min) + rand := random() + rand.Mul(rand, difference) + rand.Add(rand, min) + return rand, ArErr{} +} + +var ArRandom = ArMap{ + "__call__": builtinFunc{"random", func(args ...any) (any, ArErr) { + if len(args) != 0 { + return nil, ArErr{ + TYPE: "Runtime Error", + message: "takes 0 arguments, got " + fmt.Sprint(len(args)), + EXISTS: true, + } + } + return random(), ArErr{} + }}, + "int": builtinFunc{"int", func(a ...any) (any, ArErr) { + resp, err := randomRange(a...) + if err.EXISTS { + return nil, err + } + return round(resp.(number), 0), ArErr{} + }}, + "range": builtinFunc{"range", randomRange}, +} + +func init() { + rand.Seed( + time.Now().UnixMicro(), + ) +} diff --git a/src/rounding.go b/src/rounding.go new file mode 100644 index 0000000..fd3a7a5 --- /dev/null +++ b/src/rounding.go @@ -0,0 +1,24 @@ +package main + +import "github.com/wadey/go-rounding" + +func floor(x number) number { + + n := newNumber().Set(x) + if n.Sign() < 0 { + return rounding.Round(n, 0, rounding.Up) + } + return rounding.Round(n, 0, rounding.Down) +} + +func ceil(x number) number { + n := newNumber().Set(x) + if n.Sign() < 0 { + return rounding.Round(n, 0, rounding.Down) + } + return rounding.Round(n, 0, rounding.Up) +} + +func round(x number, precision int) number { + return rounding.Round(newNumber().Set(x), precision, rounding.HalfUp) +} diff --git a/src/run.go b/src/run.go index 73bda09..68d9134 100644 --- a/src/run.go +++ b/src/run.go @@ -57,10 +57,20 @@ func runVal(line any, stack stack, stacklevel int) (any, ArErr) { return runDoWrap(x, stack, stacklevel+1) case CallReturn: return runReturn(x, stack, stacklevel+1) + case CallBreak: + return runBreak(x, stack, stacklevel+1) case ArDelete: return runDelete(x, stack, stacklevel+1) case not: return runNot(x, stack, stacklevel+1) + case ifstatement: + return runIfStatement(x, stack, stacklevel+1) + case whileLoop: + return runWhileLoop(x, stack, stacklevel+1) + case bool: + return x, ArErr{} + case nil: + return nil, ArErr{} } fmt.Println("unreachable", reflect.TypeOf(line)) panic("unreachable") diff --git a/src/shell.go b/src/shell.go index c68a32c..0c03acb 100644 --- a/src/shell.go +++ b/src/shell.go @@ -29,9 +29,10 @@ func shell() { totranslate := []UNPARSEcode{} code := input("\x1b[38;5;240m>>> \x1b[0m\x1b[1;5;240m") fmt.Print("\x1b[0m") - if isEndingWithDo(code) { - indo = true + if code == "" { + continue } + indo = true totranslate = append(totranslate, UNPARSEcode{code, code, 1, ""}) for i := 2; indo; i++ { code := input("\x1b[38;5;240m... \x1b[0m\x1b[1;5;240m") @@ -49,8 +50,7 @@ func shell() { _, runimeErr, output := run(translated, global) if runimeErr.EXISTS { panicErr(runimeErr) - } - if count == 1 { + } else if count == 1 { fmt.Println(anyToArgon(output, true, true, 3, 0, true, 1)) } } diff --git a/src/to-argon.go b/src/to-argon.go index 7bd2165..076a6a0 100644 --- a/src/to-argon.go +++ b/src/to-argon.go @@ -5,21 +5,25 @@ import ( "math" "strconv" "strings" + + "github.com/fatih/color" + "github.com/jwalton/go-supportscolor" ) -func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, color bool, plain int) string { +func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, colored bool, plain int) string { output := []string{} maybenewline := "" if plain == 1 { maybenewline = "\n" } + if colored { + colored = supportscolor.Stdout().SupportsColor + } if depth == 0 { - if color { - output = append(output, "\x1b[38;5;240m") - } - output = append(output, "(...)") - if color { - output = append(output, "\x1b[0m") + if colored { + output = append(output, color.New(38).Sprint("(...)")) + } else { + output = append(output, "(...)") } return strings.Join(output, "") } @@ -29,15 +33,14 @@ func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, color b output = append(output, x) break } - if color { - output = append(output, "\x1b[33;5;240m") - } - output = append(output, strconv.Quote(x)) - if color { - output = append(output, "\x1b[0m") + quoted := strconv.Quote(x) + if colored { + output = append(output, color.New(33).Sprint(quoted)) + } else { + output = append(output, quoted) } case number: - if color { + if colored { output = append(output, "\x1b[34;5;240m") } num, _ := x.Float64() @@ -50,23 +53,23 @@ func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, color b } else { output = append(output, numberToString(x, simplify)) } - if color { + if colored { output = append(output, "\x1b[0m") } case bool: - if color { + if colored { output = append(output, "\x1b[35;5;240m") } output = append(output, strconv.FormatBool(x)) - if color { + if colored { output = append(output, "\x1b[0m") } case nil: - if color { + if colored { output = append(output, "\x1b[31;5;240m") } output = append(output, "null") - if color { + if colored { output = append(output, "\x1b[0m") } case ArMap: @@ -82,7 +85,7 @@ func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, color b } output := []string{} for _, key := range keys { - output = append(output, anyToArgon(key, true, true, depth, (indent+1)*plain, color, plain)+": "+anyToArgon(x[key], true, true, depth-1, indent+1, color, plain)) + output = append(output, anyToArgon(key, true, true, depth, (indent+1)*plain, colored, plain)+": "+anyToArgon(x[key], true, true, depth-1, indent+1, colored, plain)) } return "{" + maybenewline + (strings.Repeat(" ", (indent+1)*plain)) + strings.Join(output, ","+maybenewline+(strings.Repeat(" ", (indent+1)*plain))) + maybenewline + (strings.Repeat(" ", indent*plain)) + "}" case ArArray: @@ -93,42 +96,42 @@ func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, color b if simplify && len(x) >= 100 { for i := 0; i < 10; i++ { item := x[i] - output = append(output, anyToArgon(item, true, true, depth-1, indent+1, color, plain)) + output = append(output, anyToArgon(item, true, true, depth-1, indent+1, colored, plain)) } - if color { + if colored { output = append(output, "\x1b[38;5;240m(...)\x1b[0m") } else { output = append(output, "(...)") } for i := len(x) - 10; i < len(x); i++ { item := x[i] - output = append(output, anyToArgon(item, true, true, depth-1, indent+1, color, plain)) + output = append(output, anyToArgon(item, true, true, depth-1, indent+1, colored, plain)) } } else { for i := 0; i < len(x); i++ { item := x[i] - output = append(output, anyToArgon(item, true, true, depth-1, indent+1, color, plain)) + output = append(output, anyToArgon(item, true, true, depth-1, indent+1, colored, plain)) } } return "[" + maybenewline + (strings.Repeat(" ", (indent+1)*plain)) + strings.Join(output, ","+maybenewline+(strings.Repeat(" ", (indent+1)*plain))) + maybenewline + (strings.Repeat(" ", indent*plain)) + "]" case builtinFunc: - if color { + if colored { output = append(output, "\x1b[38;5;240m") } output = append(output, "") - if color { + if colored { output = append(output, "\x1b[0m") } case Callable: - if color { + if colored { output = append(output, "\x1b[38;5;240m") } output = append(output, "") - if color { + if colored { output = append(output, "\x1b[0m") } case ArClass: - return anyToArgon(x.value, quote, simplify, depth, indent, color, plain) + return anyToArgon(x.value, quote, simplify, depth, indent, colored, plain) default: return fmt.Sprint(x) } diff --git a/src/translate.go b/src/translate.go index d749a7d..d93e0fc 100644 --- a/src/translate.go +++ b/src/translate.go @@ -1,6 +1,8 @@ package main -import "strings" +import ( + "strings" +) type UNPARSEcode struct { code string @@ -22,6 +24,14 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine i } } else if isReturn(code) { return parseReturn(code, index, codelines) + } else if isBreak(code) { + return parseBreak(code, index, codelines) + } else if isIfStatement(code) { + return parseIfStatement(code, index, codelines) + } else if isWhileLoop(code) { + return parseWhileLoop(code, index, codelines) + } else if isForeverLoop(code) { + return parseForeverLoop(code, index, codelines) } } @@ -73,7 +83,9 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine i return call, worked, err, step } } - if isVariable(code) { + if isBoolean(code) { + return parseBoolean(code) + } else if isVariable(code) { return parseVariable(code) } else if isMapGet(code) { return mapGetParse(code, index, codelines) diff --git a/src/variable.go b/src/variable.go index 9f51019..5271fbd 100644 --- a/src/variable.go +++ b/src/variable.go @@ -2,6 +2,7 @@ package main import ( "strings" + "sync" ) var variableCompile = makeRegex(`( *)([a-zA-Z_]|(\p{L}\p{M}*))([a-zA-Z0-9_]|(\p{L}\p{M}*))*( *)`) @@ -10,6 +11,8 @@ var setVariableCompile = makeRegex(`( *)(let( +))(.|\n)+( *)=(.|\n)+`) var autoAsignVariableCompile = makeRegex(`(.|\n)+=(.|\n)+`) var deleteVariableCompile = makeRegex(`( *)delete( +)(.|\n)+( *)`) +var varMutex = sync.RWMutex{} + var blockedVariableNames = map[string]bool{ "if": true, "else": true, @@ -73,7 +76,10 @@ func parseVariable(code UNPARSEcode) (accessVariable, bool, ArErr, int) { func readVariable(v accessVariable, stack stack) (any, ArErr) { for i := len(stack) - 1; i >= 0; i-- { - if val, ok := stack[i][v.name]; ok { + varMutex.RLock() + val, ok := stack[i][v.name] + varMutex.RUnlock() + if ok { return val, ArErr{} } } @@ -196,24 +202,36 @@ func setVariableValue(v setVariable, stack stack, stacklevel int) (any, ArErr) { if err.EXISTS { return nil, err } - resp = openJump(respp) + resp = openReturn(respp) } if v.TYPE == "let" { - if _, ok := stack[len(stack)-1][v.toset.(accessVariable).name]; ok { + varMutex.RLock() + _, ok := stack[len(stack)-1][v.toset.(accessVariable).name] + varMutex.RUnlock() + if ok { return nil, ArErr{"Runtime Error", "variable \"" + v.toset.(accessVariable).name + "\" already exists", v.line, v.path, v.code, true} } + varMutex.Lock() stack[len(stack)-1][v.toset.(accessVariable).name] = resp + varMutex.Unlock() } else { switch x := v.toset.(type) { case accessVariable: for i := len(stack) - 1; i >= 0; i-- { - if _, ok := stack[i][x.name]; ok { + varMutex.RLock() + _, ok := stack[i][x.name] + varMutex.RUnlock() + if ok { + varMutex.Lock() stack[i][x.name] = resp + varMutex.Unlock() return resp, ArErr{} } } + varMutex.Lock() stack[len(stack)-1][x.name] = resp + varMutex.Unlock() case ArMapGet: respp, err := runVal(x.VAL, stack, stacklevel+1) if err.EXISTS { @@ -225,7 +243,9 @@ func setVariableValue(v setVariable, stack stack, stacklevel int) (any, ArErr) { } switch y := respp.(type) { case ArMap: + varMutex.Lock() y[key] = resp + varMutex.Unlock() default: return nil, ArErr{"Runtime Error", "can't set for non map", v.line, v.path, v.code, true} } diff --git a/src/whileloop.go b/src/whileloop.go new file mode 100644 index 0000000..e8b6ec5 --- /dev/null +++ b/src/whileloop.go @@ -0,0 +1,131 @@ +package main + +import ( + "strings" +) + +var whileLoopCompiled = makeRegex(`( *)while( )+\((.|\n)+\)( )+(.|\n)+`) +var foreverLoopCompiled = makeRegex(`( *)forever( )+(.|\n)+`) + +type whileLoop struct { + condition any + body any + line int + code string + path string +} + +func isWhileLoop(code UNPARSEcode) bool { + return whileLoopCompiled.MatchString(code.code) +} + +func isForeverLoop(code UNPARSEcode) bool { + return foreverLoopCompiled.MatchString(code.code) +} + +func parseWhileLoop(code UNPARSEcode, index int, codeline []UNPARSEcode) (whileLoop, bool, ArErr, int) { + trimmed := strings.TrimSpace(code.code) + trimmed = strings.TrimSpace(trimmed[strings.Index(trimmed, "("):]) + trimmed = (trimmed[1:]) + split := strings.Split(trimmed, ")") + for j := len(split) - 1; j > 0; j-- { + conditionjoined := strings.Join(split[:j], ")") + thenjoined := strings.Join(split[j:], ")") + outindex := 0 + conditionval, worked, err, step := translateVal( + UNPARSEcode{ + code: conditionjoined, + realcode: code.realcode, + line: code.line, + path: code.path, + }, + index, + codeline, + 0, + ) + if err.EXISTS || !worked { + if j == 1 { + return whileLoop{}, worked, err, step + } else { + continue + } + } + outindex += step + thenval, worked, err, step := translateVal( + UNPARSEcode{ + code: thenjoined, + realcode: code.realcode, + line: code.line, + path: code.path, + }, + index+outindex-1, + codeline, + 2, + ) + if err.EXISTS || !worked { + return whileLoop{}, worked, err, step + } + outindex += step - 1 + return whileLoop{ + condition: conditionval, + body: thenval, + line: code.line, + code: code.realcode, + path: code.path, + }, true, ArErr{}, outindex + } + return whileLoop{}, false, ArErr{ + "Syntax Error", + "Could not parse while loop", + code.line, + code.path, + code.realcode, + true, + }, 0 +} + +func parseForeverLoop(code UNPARSEcode, index int, codeline []UNPARSEcode) (whileLoop, bool, ArErr, int) { + trimmed := strings.TrimSpace(code.code) + trimmed = strings.TrimSpace(trimmed[7:]) + thenval, worked, err, step := translateVal( + UNPARSEcode{ + code: trimmed, + realcode: code.realcode, + line: code.line, + path: code.path, + }, + index, + codeline, + 2, + ) + return whileLoop{ + condition: true, + body: thenval, + line: code.line, + code: code.realcode, + path: code.path, + }, worked, err, step +} + +func runWhileLoop(loop whileLoop, stack stack, stacklevel int) (any, ArErr) { + for { + condition, err := runVal(loop.condition, stack, stacklevel+1) + if err.EXISTS { + return nil, err + } + if !anyToBool(condition) { + break + } + resp, err := runVal(loop.body, stack, stacklevel+1) + if err.EXISTS { + return nil, err + } + switch x := resp.(type) { + case Return: + return x, ArErr{} + case Break: + return nil, ArErr{} + } + } + return nil, ArErr{} +} diff --git a/test.ar b/test.ar index 4ab8c54..c9573e2 100644 --- a/test.ar +++ b/test.ar @@ -1,15 +1,9 @@ -let maths = map() +a = array() +i = 0 -maths.ln(x) = do - let n = 1e10 - return n * ((x^(1/n)) - 1) - -let __ln10cache = ln(10) - -maths.log(x) = do - return ln(x) / __ln10cache - -maths.logN(n,x) = do - return ln(x) / ln(n) - -term.log(maths.log(10000), array(maths)) \ No newline at end of file +forever do + a = append(a, i) + i = i + 1 + if (i % 1000000 == 0) do + term.log(i) + break \ No newline at end of file