diff --git a/go.mod b/go.mod index 21bcea6..ef9fe89 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,5 @@ module wbell.dev/m/v2 go 1.19 + +require github.com/wadey/go-rounding v1.1.0 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d3d8501 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +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= diff --git a/src/built-ins.go b/src/built-ins.go index 3061898..c68356a 100644 --- a/src/built-ins.go +++ b/src/built-ins.go @@ -1,5 +1,7 @@ package main +import "github.com/wadey/go-rounding" + var vars = scope{} func init() { @@ -41,7 +43,53 @@ func init() { } return newmap, ArErr{} } - return nil, ArErr{TYPE: "TypeError", message: "Cannot create map from " + typeof(a[0]), EXISTS: true} + return nil, ArErr{TYPE: "TypeError", message: "Cannot create map from '" + typeof(a[0]) + "'", EXISTS: true} + }} + vars["array"] = builtinFunc{"array", func(a ...any) (any, ArErr) { + if len(a) == 0 { + return ArArray{}, ArErr{} + } + switch x := a[0].(type) { + case ArArray: + return x, ArErr{} + case string: + newarray := ArArray{} + for _, v := range x { + newarray = append(newarray, string(v)) + } + return newarray, ArErr{} + case ArMap: + newarray := ArArray{} + for _, v := range x { + newarray = append(newarray, v) + } + return newarray, ArErr{} + } + return nil, ArErr{TYPE: "TypeError", message: "Cannot create array from '" + typeof(a[0]) + "'", EXISTS: true} + }} + vars["round"] = builtinFunc{"round", func(a ...any) (any, ArErr) { + if len(a) == 0 { + return nil, ArErr{TYPE: "round", message: "round takes 1 argument", + EXISTS: true} + } + precision := newNumber() + if len(a) > 1 { + switch x := a[1].(type) { + case number: + if !x.IsInt() { + return nil, ArErr{TYPE: "TypeError", message: "Cannot round to '" + typeof(a[1]) + "'", EXISTS: true} + } + precision = x + default: + return nil, ArErr{TYPE: "TypeError", message: "Cannot round to '" + typeof(a[1]) + "'", EXISTS: true} + } + } + + switch x := a[0].(type) { + case number: + return rounding.Round(newNumber().Set(x), int(precision.Num().Int64()), rounding.HalfUp), ArErr{} + } + return nil, ArErr{TYPE: "TypeError", message: "Cannot round '" + typeof(a[0]) + "'", EXISTS: true} }} vars["time"] = ArTime vars["PI"] = PI diff --git a/src/factorial.go b/src/factorial.go new file mode 100644 index 0000000..e77c0b5 --- /dev/null +++ b/src/factorial.go @@ -0,0 +1,59 @@ +package main + +import ( + "strings" +) + +var factorialCompiled = makeRegex(`( *)(.|\n)+\!( *)`) + +type factorial struct { + value any + code string + line int + path string +} + +func parseFactorial(code UNPARSEcode, index int, codeline []UNPARSEcode) (factorial, bool, ArErr, int) { + trim := strings.TrimSpace(code.code) + trim = trim[:len(trim)-1] + val, success, err, i := translateVal(UNPARSEcode{code: trim, realcode: code.realcode, line: 1, path: ""}, 0, []UNPARSEcode{}, false) + if !success { + return factorial{}, false, err, i + } + + return factorial{val, code.code, code.line, code.path}, success, ArErr{}, i +} + +func isFactorial(code UNPARSEcode) bool { + return factorialCompiled.MatchString(code.code) +} + +func fact(n number) number { + if n.Cmp(newNumber().SetInt64(0)) == 0 { + return newNumber().SetInt64(1) + } + result := newNumber().SetInt64(1) + for i := newNumber().SetInt64(2); i.Cmp(n) <= 0; i.Add(i, newNumber().SetInt64(1)) { + result.Mul(result, i) + } + return result +} + +func runFactorial(f factorial, stack stack) (any, ArErr) { + val, err := runVal(f.value, stack) + if err.EXISTS { + return nil, err + } + switch x := val.(type) { + case number: + if !x.IsInt() { + return nil, ArErr{"Runtime Error", "cannot use factorial on non-integer", f.line, f.path, f.code, true} + } + if x.Cmp(newNumber().SetInt64(0)) == -1 { + return nil, ArErr{"Runtime Error", "cannot use factorial on negative number", f.line, f.path, f.code, true} + } + return fact(x), ArErr{} + default: + return nil, ArErr{"Runtime Error", "cannot use factorial on non-number of type '" + typeof(val) + "'", f.line, f.path, f.code, true} + } +} diff --git a/src/number.go b/src/number.go index a8b6041..bff08c1 100644 --- a/src/number.go +++ b/src/number.go @@ -35,40 +35,19 @@ func isAnyNumber(x any) bool { } // converts a number type to a string -func numberToString(num number, fraction int, simplify bool) string { - if fraction != 0 { - str := num.RatString() - if fraction == 1 { - return str - } - split := strings.SplitN(str, "/", 2) - if len(str) == 1 { - return split[0] - } - numerator := split[0] - denominator := split[1] - - super := []string{} - for i := 0; i <= len(numerator); i++ { - super = append(super, superscript[numerator[i]]) - } - sub := []string{} - for i := 0; i < len(denominator); i++ { - sub = append(sub, subscript[denominator[i]]) - } - return strings.Join(super, "") + "/" + strings.Join(sub, "") - } +func numberToString(num number, simplify bool) string { if simplify { - divPI, _ := newNumber().Quo(num, PI).Float64() - floated := float64(int(divPI * 100)) - if divPI == 1 { + divPI := newNumber().Quo(num, PI) + if divPI.Cmp(newNumber().SetInt64(1)) == 0 { return "π" - } else if divPI == -1 { + } else if divPI.Cmp(newNumber().SetInt64(-1)) == 0 { return "-π" - } else if divPI == 0 { + } else if divPI.Cmp(newNumber()) == 0 { return "0" - } else if (divPI*100) == floated && floated != 0 { - return fmt.Sprint(divPI) + "π" + } else if divPI.Denom().Cmp(new(big.Int).SetInt64(1000)) <= 0 { + num := divPI.RatString() + + return fmt.Sprint(num, "π") } } x, _ := num.Float64() diff --git a/src/run.go b/src/run.go index 4593f9f..4dbebdc 100644 --- a/src/run.go +++ b/src/run.go @@ -21,6 +21,8 @@ func runVal(line any, stack stack) (any, ArErr) { return x, ArErr{} case call: return runCall(x, stack) + case factorial: + return runFactorial(x, stack) case accessVariable: return readVariable(x, stack) case ArMapGet: diff --git a/src/term-class.go b/src/term-class.go index b6c6e80..e36a9c5 100644 --- a/src/term-class.go +++ b/src/term-class.go @@ -24,6 +24,14 @@ var plain = ArMap{ fmt.Println(output...) return nil, ArErr{} }}, + "print": builtinFunc{"print", func(args ...any) (any, ArErr) { + output := []any{} + for i := 0; i < len(args); i++ { + output = append(output, anyToArgon(args[i], false, false, 3, 0, false, 0)) + } + fmt.Println(output...) + return nil, ArErr{} + }}, } var ArTerm = ArMap{ diff --git a/src/threading.go b/src/thread.go similarity index 100% rename from src/threading.go rename to src/thread.go diff --git a/src/to-argon.go b/src/to-argon.go index 5ca250a..7bd2165 100644 --- a/src/to-argon.go +++ b/src/to-argon.go @@ -48,7 +48,7 @@ func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, color b } else if math.IsInf(num, -1) { output = append(output, "-infinity") } else { - output = append(output, numberToString(x, 0, simplify)) + output = append(output, numberToString(x, simplify)) } if color { output = append(output, "\x1b[0m") @@ -85,6 +85,32 @@ func anyToArgon(x any, quote bool, simplify bool, depth int, indent int, color b output = append(output, anyToArgon(key, true, true, depth, (indent+1)*plain, color, plain)+": "+anyToArgon(x[key], true, true, depth-1, indent+1, color, plain)) } return "{" + maybenewline + (strings.Repeat(" ", (indent+1)*plain)) + strings.Join(output, ","+maybenewline+(strings.Repeat(" ", (indent+1)*plain))) + maybenewline + (strings.Repeat(" ", indent*plain)) + "}" + case ArArray: + if len(x) == 0 { + return "[]" + } + output := []string{} + 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)) + } + if color { + 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)) + } + } else { + for i := 0; i < len(x); i++ { + item := x[i] + output = append(output, anyToArgon(item, true, true, depth-1, indent+1, color, 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 { output = append(output, "\x1b[38;5;240m") diff --git a/src/translate.go b/src/translate.go index 02db5cd..fa3ad08 100644 --- a/src/translate.go +++ b/src/translate.go @@ -51,6 +51,8 @@ func translateVal(code UNPARSEcode, index int, codelines []UNPARSEcode, isLine b return parseNumber(code) } else if isNegative(code) { return parseNegative(code, index, codelines) + } else if isFactorial(code) { + return parseFactorial(code, index, codelines) } else if isCall(code) { call, worked, err, step := parseCall(code, index, codelines) if worked { diff --git a/test.ar b/test.ar index 1b966bb..c9c7d30 100644 --- a/test.ar +++ b/test.ar @@ -1,7 +1 @@ -let t = thread(f()=time.snooze(1)) - -term.log("start") -t.start() -term.log('started') -let val = t.join() -term.log('joined', val) \ No newline at end of file +range(1e10) \ No newline at end of file