From 3736214c5a425e183af4296c278712a9082afc16 Mon Sep 17 00:00:00 2001 From: Celestino Amoroso Date: Tue, 28 May 2024 07:26:05 +0200 Subject: [PATCH] A lot of changes. Main ones are: - fraction type renamed as FractionType and moved from operator-fraction.go to fraction-type.go - ListType moved from operator-list.go to list-type.go - all test file were renamed adding the "t_" prefix - defined a test template in file t_temple_test.go - new test file t_relational_test.go where relational tests are collected - lists can now compared as set using operators <, <=, >, and >= (IMPORTANT: here = menas same content, not same list) --- fraction-type.go | 401 +++++++++++++++++++++++++ func-base.go | 6 +- func-math.go | 12 +- list-type.go | 102 +++++++ operand-list.go | 77 ----- operator-fraction.go | 356 +--------------------- operator-rel.go | 213 +++++++------ ast_test.go => t_ast_test.go | 2 +- dict_test.go => t_dict_test.go | 2 +- expr_test.go => t_expr_test.go | 2 +- funcs_test.go => t_funcs_test.go | 2 +- graph_test.go => t_graph_test.go | 5 +- helpers_test.go => t_helpers_test.go | 2 +- index_test.go => t_index_test.go | 2 +- iterator_test.go => t_iterator_test.go | 2 +- list_test.go => t_list_test.go | 2 +- parser_test.go => t_parser_test.go | 2 +- t_relational_test.go | 49 +++ scanner_test.go => t_scanner_test.go | 2 +- strings_test.go => t_strings_test.go | 2 +- t_template_test.go | 21 ++ term_test.go => t_term_test.go | 2 +- token_test.go => t_token_test.go | 2 +- utils_test.go => t_utils_test.go | 2 +- utils.go | 6 +- 25 files changed, 722 insertions(+), 554 deletions(-) create mode 100644 fraction-type.go create mode 100644 list-type.go rename ast_test.go => t_ast_test.go (98%) rename dict_test.go => t_dict_test.go (99%) rename expr_test.go => t_expr_test.go (99%) rename funcs_test.go => t_funcs_test.go (99%) rename graph_test.go => t_graph_test.go (84%) rename helpers_test.go => t_helpers_test.go (98%) rename index_test.go => t_index_test.go (97%) rename iterator_test.go => t_iterator_test.go (98%) rename list_test.go => t_list_test.go (99%) rename parser_test.go => t_parser_test.go (99%) create mode 100644 t_relational_test.go rename scanner_test.go => t_scanner_test.go (99%) rename strings_test.go => t_strings_test.go (95%) create mode 100644 t_template_test.go rename term_test.go => t_term_test.go (98%) rename token_test.go => t_token_test.go (97%) rename utils_test.go => t_utils_test.go (99%) diff --git a/fraction-type.go b/fraction-type.go new file mode 100644 index 0000000..da0c59a --- /dev/null +++ b/fraction-type.go @@ -0,0 +1,401 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// fraction-type.go +package expr + +//https://www.youmath.it/lezioni/algebra-elementare/lezioni-di-algebra-e-aritmetica-per-scuole-medie/553-dalle-frazioni-a-numeri-decimali.html + +import ( + "errors" + "fmt" + "math" + "strconv" + "strings" +) + +type FractionType struct { + num, den int64 +} + +func newFraction(num, den int64) *FractionType { + num, den = simplifyIntegers(num, den) + return &FractionType{num, den} +} + +func float64ToFraction(f float64) (fract *FractionType, err error) { + var sign string + intPart, decPart := math.Modf(f) + if decPart < 0.0 { + sign = "-" + intPart = -intPart + decPart = -decPart + } + dec := fmt.Sprintf("%.12f", decPart) + s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:]) + // fmt.Printf("S: '%s'\n",s) + return makeGeneratingFraction(s) +} + +// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39 +/* +func _float64ToFraction(f float64) (num, den int64, err error) { + const expMask = 1<<11 - 1 + bits := math.Float64bits(f) + mantissa := bits & (1<<52 - 1) + exp := int((bits >> 52) & expMask) + switch exp { + case expMask: // non-finite + err = errors.New("infite") + return + case 0: // denormal + exp -= 1022 + default: // normal + mantissa |= 1 << 52 + exp -= 1023 + } + + shift := 52 - exp + + // Optimization (?): partially pre-normalise. + for mantissa&1 == 0 && shift > 0 { + mantissa >>= 1 + shift-- + } + + if f < 0 { + num = -int64(mantissa) + } else { + num = int64(mantissa) + } + den = int64(1) + + if shift > 0 { + den = den << shift + } else { + num = num << (-shift) + } + return +} +*/ + +func makeGeneratingFraction(s string) (f *FractionType, err error) { + var num, den int64 + var sign int64 = 1 + var parts []string + if len(s) == 0 { + goto exit + } + if s[0] == '-' { + sign = int64(-1) + s = s[1:] + } else if s[0] == '+' { + s = s[1:] + } + if strings.HasSuffix(s, "()") { + s = s[0 : len(s)-2] + } + parts = strings.SplitN(s, ".", 2) + if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil { + return + } + if len(parts) == 1 { + f = newFraction(sign*num, 1) + } else if len(parts) == 2 { + subParts := strings.SplitN(parts[1], "(", 2) + if len(subParts) == 1 { + den = 1 + dec := parts[1] + lsd := len(dec) + for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- { + lsd-- + } + for _, c := range dec[0:lsd] { + if c < '0' || c > '9' { + return nil, errExpectedGot("fract", "digit", c) + } + num = num*10 + int64(c-'0') + den = den * 10 + } + f = newFraction(sign*num, den) + } else if len(subParts) == 2 { + sub := num + mul := int64(1) + for _, c := range subParts[0] { + if c < '0' || c > '9' { + return nil, errExpectedGot("fract", "digit", c) + } + num = num*10 + int64(c-'0') + sub = sub*10 + int64(c-'0') + mul *= 10 + } + if len(subParts) == 2 { + if s[len(s)-1] != ')' { + goto exit + } + p := subParts[1][0 : len(subParts[1])-1] + for _, c := range p { + if c < '0' || c > '9' { + return nil, errExpectedGot("fract", "digit", c) + } + num = num*10 + int64(c-'0') + den = den*10 + 9 + } + den *= mul + } + num -= sub + f = newFraction(sign*num, den) + } + } +exit: + if f == nil { + err = errors.New("bad syntax") + } + return +} + +func (f *FractionType) toFloat() float64 { + return float64(f.num) / float64(f.den) +} + +func (f *FractionType) String() string { + return f.ToString(0) +} + +func (f *FractionType) ToString(opt FmtOpt) string { + var sb strings.Builder + if opt&MultiLine == 0 { + sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den)) + } else { + var s, num string + if f.num < 0 && opt&TTY == 0 { + num = strconv.FormatInt(-f.num, 10) + s = "-" + } else { + num = strconv.FormatInt(f.num, 10) + } + den := strconv.FormatInt(f.den, 10) + size := max(len(num), len(den)) + if opt&TTY != 0 { + sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num))) + } else { + if len(s) > 0 { + sb.WriteString(" ") + } + sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num))) + sb.WriteByte('\n') + if len(s) > 0 { + sb.WriteString(s) + sb.WriteByte(' ') + } + sb.WriteString(strings.Repeat("-", size)) + sb.WriteByte('\n') + if len(s) > 0 { + sb.WriteString(" ") + } + } + sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den))) + } + + return sb.String() +} + +func (f *FractionType) TypeName() string { + return "fraction" +} + +// -------- fraction utility functions + +// greatest common divider +func gcd(a, b int64) (g int64) { + if a < 0 { + a = -a + } + if b < 0 { + b = -b + } + if a < b { + a, b = b, a + } + r := a % b + for r > 0 { + a, b = b, r + r = a % b + } + g = b + return +} + +// lower common multiple +func lcm(a, b int64) (l int64) { + g := gcd(a, b) + l = a * b / g + return +} + +// Sum two fractions +func sumFract(f1, f2 *FractionType) (sum *FractionType) { + m := lcm(f1.den, f2.den) + sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m) + return +} + +// Multiply two fractions +func mulFract(f1, f2 *FractionType) (prod *FractionType) { + prod = newFraction(f1.num*f2.num, f1.den*f2.den) + return +} + +func anyToFract(v any) (f *FractionType, err error) { + var ok bool + if f, ok = v.(*FractionType); !ok { + if n, ok := v.(int64); ok { + f = intToFraction(n) + } + } + if f == nil { + err = errExpectedGot("fract", typeFraction, v) + } + return +} + +func anyPairToFract(v1, v2 any) (f1, f2 *FractionType, err error) { + if f1, err = anyToFract(v1); err != nil { + return + } + if f2, err = anyToFract(v2); err != nil { + return + } + return +} + +func sumAnyFract(af1, af2 any) (sum any, err error) { + var f1, f2 *FractionType + if f1, f2, err = anyPairToFract(af1, af2); err != nil { + return + } + f := sumFract(f1, f2) + if f.num == 0 { + sum = 0 + } else { + sum = simplifyFraction(f) + } + return +} + +// Returns +// +// <0 if af1 < af2 +// =0 if af1 == af2 +// >0 if af1 > af2 +// err if af1 or af2 is not convertible to fraction +func cmpAnyFract(af1, af2 any) (result int, err error) { + var f1, f2 *FractionType + if f1, f2, err = anyPairToFract(af1, af2); err != nil { + return + } + result = cmpFract(f1, f2) + return +} + +// Returns +// +// <0 if af1 < af2 +// =0 if af1 == af2 +// >0 if af1 > af2 +func cmpFract(f1, f2 *FractionType) (result int) { + f2.num = -f2.num + f := sumFract(f1, f2) + if f.num < 0 { + result = -1 + } else if f.num > 0 { + result = 1 + } else { + result = 0 + } + return +} + +func subAnyFract(af1, af2 any) (sum any, err error) { + var f1, f2 *FractionType + if f1, f2, err = anyPairToFract(af1, af2); err != nil { + return + } + f2.num = -f2.num + f := sumFract(f1, f2) + if f.num == 0 { + sum = 0 + } else { + sum = simplifyFraction(f) + } + return +} + +func mulAnyFract(af1, af2 any) (prod any, err error) { + var f1, f2 *FractionType + if f1, f2, err = anyPairToFract(af1, af2); err != nil { + return + } + if f1.num == 0 || f2.num == 0 { + prod = 0 + } else { + f := &FractionType{f1.num * f2.num, f1.den * f2.den} + prod = simplifyFraction(f) + } + return +} + +func divAnyFract(af1, af2 any) (quot any, err error) { + var f1, f2 *FractionType + if f1, f2, err = anyPairToFract(af1, af2); err != nil { + return + } + if f2.num == 0 { + err = errors.New("division by zero") + return + return + } + if f1.num == 0 || f2.den == 0 { + quot = 0 + } else { + f := &FractionType{f1.num * f2.den, f1.den * f2.num} + quot = simplifyFraction(f) + } + return +} + +func simplifyFraction(f *FractionType) (v any) { + f.num, f.den = simplifyIntegers(f.num, f.den) + if f.den == 1 { + v = f.num + } else { + v = &FractionType{f.num, f.den} + } + return v +} + +func simplifyIntegers(num, den int64) (a, b int64) { + if num == 0 { + return 0, 1 + } + if den == 0 { + panic("fraction with denominator == 0") + } + if den < 0 { + den = -den + num = -num + } + g := gcd(num, den) + a = num / g + b = den / g + return +} + +func intToFraction(n int64) *FractionType { + return &FractionType{n, 1} +} + +func isFraction(v any) (ok bool) { + _, ok = v.(*FractionType) + return ok +} diff --git a/func-base.go b/func-base.go index 6bb86ef..e1568ab 100644 --- a/func-base.go +++ b/func-base.go @@ -58,7 +58,7 @@ func boolFunc(ctx ExprContext, name string, args []any) (result any, err error) switch v := args[0].(type) { case int64: result = (v != 0) - case *fraction: + case *FractionType: result = v.num != 0 case float64: result = v != 0.0 @@ -112,7 +112,7 @@ func decFunc(ctx ExprContext, name string, args []any) (result any, err error) { if f, err = strconv.ParseFloat(v, 64); err == nil { result = f } - case *fraction: + case *FractionType: result = v.toFloat() default: err = errCantConvert(name, v, "float") @@ -145,7 +145,7 @@ func fractFunc(ctx ExprContext, name string, args []any) (result any, err error) } case string: result, err = makeGeneratingFraction(v) - case *fraction: + case *FractionType: result = v default: err = errCantConvert(name, v, "float") diff --git a/func-math.go b/func-math.go index 84fef10..738e8bf 100644 --- a/func-math.go +++ b/func-math.go @@ -21,7 +21,7 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result var sumAsFloat, sumAsFract bool var floatSum float64 = 0.0 var intSum int64 = 0 - var fractSum *fraction + var fractSum *FractionType var v any level++ @@ -61,9 +61,9 @@ func doAdd(ctx ExprContext, name string, it Iterator, count, level int) (result if sumAsFloat { floatSum += numAsFloat(v) } else if sumAsFract { - var item *fraction + var item *FractionType var ok bool - if item, ok = v.(*fraction); !ok { + if item, ok = v.(*FractionType); !ok { iv, _ := v.(int64) item = newFraction(iv, 1) } @@ -95,7 +95,7 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result var mulAsFloat, mulAsFract bool var floatProd float64 = 1.0 var intProd int64 = 1 - var fractProd *fraction + var fractProd *FractionType var v any level++ @@ -136,9 +136,9 @@ func doMul(ctx ExprContext, name string, it Iterator, count, level int) (result if mulAsFloat { floatProd *= numAsFloat(v) } else if mulAsFract { - var item *fraction + var item *FractionType var ok bool - if item, ok = v.(*fraction); !ok { + if item, ok = v.(*FractionType); !ok { iv, _ := v.(int64) item = newFraction(iv, 1) } diff --git a/list-type.go b/list-type.go new file mode 100644 index 0000000..439ed7a --- /dev/null +++ b/list-type.go @@ -0,0 +1,102 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// list-type.go +package expr + +import ( + "fmt" + "reflect" + "strings" +) + +type ListType []any + +func newListA(listAny ...any) (list *ListType) { + return newList(listAny) +} + +func newList(listAny []any) (list *ListType) { + if listAny != nil { + ls := make(ListType, len(listAny)) + for i, item := range listAny { + ls[i] = item + } + list = &ls + } + return +} + +func (ls *ListType) ToString(opt FmtOpt) (s string) { + var sb strings.Builder + sb.WriteByte('[') + if len(*ls) > 0 { + if opt&MultiLine != 0 { + sb.WriteString("\n ") + } + for i, item := range []any(*ls) { + if i > 0 { + if opt&MultiLine != 0 { + sb.WriteString(",\n ") + } else { + sb.WriteString(", ") + } + } + if s, ok := item.(string); ok { + sb.WriteByte('"') + sb.WriteString(s) + sb.WriteByte('"') + } else { + sb.WriteString(fmt.Sprintf("%v", item)) + } + } + if opt&MultiLine != 0 { + sb.WriteByte('\n') + } + } + sb.WriteByte(']') + s = sb.String() + if opt&Truncate != 0 && len(s) > TruncateSize { + s = TruncateString(s) + } + return +} + +func (ls *ListType) String() string { + return ls.ToString(0) +} + +func (ls *ListType) TypeName() string { + return "list" +} + +func (list *ListType) indexDeepCmp(target any) (index int) { + index = -1 + for i, item := range *list { + if reflect.DeepEqual(item, target) { + index = i + break + } + } + return +} + +func (ls *ListType) contains(t *ListType) (answer bool) { + if len(*ls) >= len(*t) { + answer = true + for _, item := range *t { + if answer = ls.indexDeepCmp(item) >= 0; !answer { + break + } + } + } + return +} + +// func equal(a,b *ListType) (eq bool) { +// if len(*a) == len(*b) { +// eq = true +// for i, v := range +// } +// return +// } diff --git a/operand-list.go b/operand-list.go index e3d39e1..6f10a88 100644 --- a/operand-list.go +++ b/operand-list.go @@ -4,83 +4,6 @@ // operand-list.go package expr -import ( - "fmt" - "reflect" - "strings" -) - -type ListType []any - -func newListA(listAny ...any) (list *ListType) { - return newList(listAny) -} - -func newList(listAny []any) (list *ListType) { - if listAny != nil { - ls := make(ListType, len(listAny)) - for i, item := range listAny { - ls[i] = item - } - list = &ls - } - return -} - -func (ls *ListType) ToString(opt FmtOpt) (s string) { - var sb strings.Builder - sb.WriteByte('[') - if len(*ls) > 0 { - if opt&MultiLine != 0 { - sb.WriteString("\n ") - } - for i, item := range []any(*ls) { - if i > 0 { - if opt&MultiLine != 0 { - sb.WriteString(",\n ") - } else { - sb.WriteString(", ") - } - } - if s, ok := item.(string); ok { - sb.WriteByte('"') - sb.WriteString(s) - sb.WriteByte('"') - } else { - sb.WriteString(fmt.Sprintf("%v", item)) - } - } - if opt&MultiLine != 0 { - sb.WriteByte('\n') - } - } - sb.WriteByte(']') - s = sb.String() - if opt&Truncate != 0 && len(s) > TruncateSize { - s = TruncateString(s) - } - return -} - -func (ls *ListType) String() string { - return ls.ToString(0) -} - -func (ls *ListType) TypeName() string { - return "list" -} - -func (list *ListType) indexDeepCmp(target any) (index int) { - index = -1 - for i, item := range *list { - if reflect.DeepEqual(item, target) { - index = i - break - } - } - return -} - // -------- list term func newListTermA(args ...*term) *term { return newListTerm(0, 0, args) diff --git a/operator-fraction.go b/operator-fraction.go index 9802c7d..47e117a 100644 --- a/operator-fraction.go +++ b/operator-fraction.go @@ -9,205 +9,8 @@ package expr import ( "errors" "fmt" - "math" - "strconv" - "strings" ) -type fraction struct { - num, den int64 -} - -func newFraction(num, den int64) *fraction { - /* if den < 0 { - den = -den - num = -num - }*/ - num, den = simplifyIntegers(num, den) - return &fraction{num, den} -} - -func float64ToFraction(f float64) (fract *fraction, err error) { - var sign string - intPart, decPart := math.Modf(f) - if decPart < 0.0 { - sign = "-" - intPart = -intPart - decPart = -decPart - } - dec := fmt.Sprintf("%.12f", decPart) - s := fmt.Sprintf("%s%.f%s", sign, intPart, dec[1:]) - // fmt.Printf("S: '%s'\n",s) - return makeGeneratingFraction(s) -} - -// Based on https://cs.opensource.google/go/go/+/refs/tags/go1.22.3:src/math/big/rat.go;l=39 -/* -func _float64ToFraction(f float64) (num, den int64, err error) { - const expMask = 1<<11 - 1 - bits := math.Float64bits(f) - mantissa := bits & (1<<52 - 1) - exp := int((bits >> 52) & expMask) - switch exp { - case expMask: // non-finite - err = errors.New("infite") - return - case 0: // denormal - exp -= 1022 - default: // normal - mantissa |= 1 << 52 - exp -= 1023 - } - - shift := 52 - exp - - // Optimization (?): partially pre-normalise. - for mantissa&1 == 0 && shift > 0 { - mantissa >>= 1 - shift-- - } - - if f < 0 { - num = -int64(mantissa) - } else { - num = int64(mantissa) - } - den = int64(1) - - if shift > 0 { - den = den << shift - } else { - num = num << (-shift) - } - return -} -*/ - -func makeGeneratingFraction(s string) (f *fraction, err error) { - var num, den int64 - var sign int64 = 1 - var parts []string - if len(s) == 0 { - goto exit - } - if s[0] == '-' { - sign = int64(-1) - s = s[1:] - } else if s[0] == '+' { - s = s[1:] - } - if strings.HasSuffix(s, "()") { - s = s[0 : len(s)-2] - } - parts = strings.SplitN(s, ".", 2) - if num, err = strconv.ParseInt(parts[0], 10, 64); err != nil { - return - } - if len(parts) == 1 { - f = newFraction(sign*num, 1) - } else if len(parts) == 2 { - subParts := strings.SplitN(parts[1], "(", 2) - if len(subParts) == 1 { - den = 1 - dec := parts[1] - lsd := len(dec) - for i := lsd - 1; i >= 0 && dec[i] == '0'; i-- { - lsd-- - } - for _, c := range dec[0:lsd] { - if c < '0' || c > '9' { - return nil, errExpectedGot("fract", "digit", c) - } - num = num*10 + int64(c-'0') - den = den * 10 - } - f = newFraction(sign*num, den) - } else if len(subParts) == 2 { - sub := num - mul := int64(1) - for _, c := range subParts[0] { - if c < '0' || c > '9' { - return nil, errExpectedGot("fract", "digit", c) - } - num = num*10 + int64(c-'0') - sub = sub*10 + int64(c-'0') - mul *= 10 - } - if len(subParts) == 2 { - if s[len(s)-1] != ')' { - goto exit - } - p := subParts[1][0 : len(subParts[1])-1] - for _, c := range p { - if c < '0' || c > '9' { - return nil, errExpectedGot("fract", "digit", c) - } - num = num*10 + int64(c-'0') - den = den*10 + 9 - } - den *= mul - } - num -= sub - f = newFraction(sign*num, den) - } - } -exit: - if f == nil { - err = errors.New("bad syntax") - } - return -} - -func (f *fraction) toFloat() float64 { - return float64(f.num) / float64(f.den) -} - -func (f *fraction) String() string { - return f.ToString(0) -} - -func (f *fraction) ToString(opt FmtOpt) string { - var sb strings.Builder - if opt&MultiLine == 0 { - sb.WriteString(fmt.Sprintf("%d|%d", f.num, f.den)) - } else { - var s, num string - if f.num < 0 && opt&TTY == 0 { - num = strconv.FormatInt(-f.num, 10) - s = "-" - } else { - num = strconv.FormatInt(f.num, 10) - } - den := strconv.FormatInt(f.den, 10) - size := max(len(num), len(den)) - if opt&TTY != 0 { - sb.WriteString(fmt.Sprintf("\x1b[4m%[1]*s\x1b[0m\n", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, s+num))) - } else { - if len(s) > 0 { - sb.WriteString(" ") - } - sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(num))/2, num))) - sb.WriteByte('\n') - if len(s) > 0 { - sb.WriteString(s) - sb.WriteByte(' ') - } - sb.WriteString(strings.Repeat("-", size)) - sb.WriteByte('\n') - if len(s) > 0 { - sb.WriteString(" ") - } - } - sb.WriteString(fmt.Sprintf("%[1]*s", -size, fmt.Sprintf("%[1]*s", (size+len(den))/2, den))) - } - - return sb.String() -} - -func (f *fraction) TypeName() string { - return "fraction" -} - // -------- fraction term func newFractionTerm(tk *Token) *term { return &term{ @@ -252,168 +55,11 @@ func evalFraction(ctx ExprContext, self *term) (v any, err error) { if den == 1 { v = num } else { - v = &fraction{num, den} + v = &FractionType{num, den} } return } -func gcd(a, b int64) (g int64) { - if a < 0 { - a = -a - } - if b < 0 { - b = -b - } - if a < b { - a, b = b, a - } - r := a % b - for r > 0 { - a, b = b, r - r = a % b - } - g = b - return -} - -func lcm(a, b int64) (l int64) { - g := gcd(a, b) - l = a * b / g - return -} - -func sumFract(f1, f2 *fraction) (sum *fraction) { - m := lcm(f1.den, f2.den) - sum = newFraction(f1.num*(m/f1.den)+f2.num*(m/f2.den), m) - return -} - -func mulFract(f1, f2 *fraction) (prod *fraction) { - prod = newFraction(f1.num*f2.num, f1.den*f2.den) - return -} - -func anyToFract(v any) (f *fraction, err error) { - var ok bool - if f, ok = v.(*fraction); !ok { - if n, ok := v.(int64); ok { - f = intToFraction(n) - } - } - if f == nil { - err = errExpectedGot("fract", typeFraction, v) - } - return -} - -func anyPairToFract(v1, v2 any) (f1, f2 *fraction, err error) { - if f1, err = anyToFract(v1); err != nil { - return - } - if f2, err = anyToFract(v2); err != nil { - return - } - return -} - -func sumAnyFract(af1, af2 any) (sum any, err error) { - var f1, f2 *fraction - if f1, f2, err = anyPairToFract(af1, af2); err != nil { - return - } - f := sumFract(f1, f2) - if f.num == 0 { - sum = 0 - } else { - sum = simplifyFraction(f) - } - return -} - -func subAnyFract(af1, af2 any) (sum any, err error) { - var f1, f2 *fraction - if f1, f2, err = anyPairToFract(af1, af2); err != nil { - return - } - f2.num = -f2.num - f := sumFract(f1, f2) - if f.num == 0 { - sum = 0 - } else { - sum = simplifyFraction(f) - } - return -} - -func mulAnyFract(af1, af2 any) (prod any, err error) { - var f1, f2 *fraction - if f1, f2, err = anyPairToFract(af1, af2); err != nil { - return - } - if f1.num == 0 || f2.num == 0 { - prod = 0 - } else { - f := &fraction{f1.num * f2.num, f1.den * f2.den} - prod = simplifyFraction(f) - } - return -} - -func divAnyFract(af1, af2 any) (quot any, err error) { - var f1, f2 *fraction - if f1, f2, err = anyPairToFract(af1, af2); err != nil { - return - } - if f2.num == 0 { - err = errors.New("division by zero") - return - return - } - if f1.num == 0 || f2.den == 0 { - quot = 0 - } else { - f := &fraction{f1.num * f2.den, f1.den * f2.num} - quot = simplifyFraction(f) - } - return -} - -func simplifyFraction(f *fraction) (v any) { - f.num, f.den = simplifyIntegers(f.num, f.den) - if f.den == 1 { - v = f.num - } else { - v = &fraction{f.num, f.den} - } - return v -} - -func simplifyIntegers(num, den int64) (a, b int64) { - if num == 0 { - return 0, 1 - } - if den == 0 { - panic("fraction with denominator == 0") - } - if den < 0 { - den = -den - num = -num - } - g := gcd(num, den) - a = num / g - b = den / g - return -} - -func intToFraction(n int64) *fraction { - return &fraction{n, 1} -} - -func isFraction(v any) (ok bool) { - _, ok = v.(*fraction) - return ok -} - // init func init() { registerTermConstructor(SymVertBar, newFractionTerm) diff --git a/operator-rel.go b/operator-rel.go index 1ffef42..59a7279 100644 --- a/operator-rel.go +++ b/operator-rel.go @@ -4,13 +4,13 @@ // operator-rel.go package expr +import "reflect" + //-------- equal term func newEqualTerm(tk *Token) (inst *term) { return &term{ - tk: *tk, - // class: classOperator, - // kind: kindBool, + tk: *tk, children: make([]*term, 0, 2), position: posInfix, priority: priRelational, @@ -18,6 +18,29 @@ func newEqualTerm(tk *Token) (inst *term) { } } +func equals(a, b any) (eq bool, err error) { + if isNumOrFract(a) && isNumOrFract(b) { + if IsNumber(a) && IsNumber(b) { + if IsInteger(a) && IsInteger(b) { + li, _ := a.(int64) + ri, _ := b.(int64) + eq = li == ri + } else { + eq = numAsFloat(a) == numAsFloat(b) + } + } else { + var cmp int + if cmp, err = cmpAnyFract(a, b); err == nil { + eq = cmp == 0 + } + } + } else { + eq = reflect.DeepEqual(a, b) + } + + return +} + func evalEqual(ctx ExprContext, self *term) (v any, err error) { var leftValue, rightValue any @@ -25,21 +48,7 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) { return } - if IsNumber(leftValue) && IsNumber(rightValue) { - if IsInteger(leftValue) && IsInteger(rightValue) { - li, _ := leftValue.(int64) - ri, _ := rightValue.(int64) - v = li == ri - } else { - v = numAsFloat(leftValue) == numAsFloat(rightValue) - } - } else if IsString(leftValue) && IsString(rightValue) { - ls, _ := leftValue.(string) - rs, _ := rightValue.(string) - v = ls == rs - } else { - err = self.errIncompatibleTypes(leftValue, rightValue) - } + v, err = equals(leftValue, rightValue) return } @@ -47,9 +56,7 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) { func newNotEqualTerm(tk *Token) (inst *term) { return &term{ - tk: *tk, - // class: classOperator, - // kind: kindBool, + tk: *tk, children: make([]*term, 0, 2), position: posInfix, priority: priRelational, @@ -65,38 +72,11 @@ func evalNotEqual(ctx ExprContext, self *term) (v any, err error) { return } -// func evalNotEqual(ctx exprContext, self *term) (v any, err error) { -// var leftValue, rightValue any - -// if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { -// return -// } - -// if isNumber(leftValue) && isNumber(rightValue) { -// if isInteger(leftValue) && isInteger(rightValue) { -// li, _ := leftValue.(int64) -// ri, _ := rightValue.(int64) -// v = li != ri -// } else { -// v = numAsFloat(leftValue) != numAsFloat(rightValue) -// } -// } else if isString(leftValue) && isString(rightValue) { -// ls, _ := leftValue.(string) -// rs, _ := rightValue.(string) -// v = ls != rs -// } else { -// err = self.errIncompatibleTypes(leftValue, rightValue) -// } -// return -// } - //-------- less term func newLessTerm(tk *Token) (inst *term) { return &term{ - tk: *tk, - // class: classOperator, - // kind: kindBool, + tk: *tk, children: make([]*term, 0, 2), position: posInfix, priority: priRelational, @@ -104,28 +84,52 @@ func newLessTerm(tk *Token) (inst *term) { } } +func lessThan(self *term, a, b any) (isLess bool, err error) { + if isNumOrFract(a) && isNumOrFract(b) { + if IsNumber(a) && IsNumber(b) { + if IsInteger(a) && IsInteger(b) { + li, _ := a.(int64) + ri, _ := b.(int64) + isLess = li < ri + } else { + isLess = numAsFloat(a) < numAsFloat(b) + } + } else { + var cmp int + if cmp, err = cmpAnyFract(a, b); err == nil { + isLess = cmp < 0 + } + } + } else if IsString(a) && IsString(b) { + ls, _ := a.(string) + rs, _ := b.(string) + isLess = ls < rs + // Inclusion test + } else if IsList(a) && IsList(b) { + aList, _ := a.(*ListType) + bList, _ := b.(*ListType) + isLess = len(*aList) < len(*bList) + if isLess { + for _, item := range *aList { + if bList.indexDeepCmp(item) < 0 { + isLess = false + break + } + } + } + } else { + err = self.errIncompatibleTypes(a, b) + } + return +} + func evalLess(ctx ExprContext, self *term) (v any, err error) { var leftValue, rightValue any if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { return } - - if IsNumber(leftValue) && IsNumber(rightValue) { - if IsInteger(leftValue) && IsInteger(rightValue) { - li, _ := leftValue.(int64) - ri, _ := rightValue.(int64) - v = li < ri - } else { - v = numAsFloat(leftValue) < numAsFloat(rightValue) - } - } else if IsString(leftValue) && IsString(rightValue) { - ls, _ := leftValue.(string) - rs, _ := rightValue.(string) - v = ls < rs - } else { - err = self.errIncompatibleTypes(leftValue, rightValue) - } + v, err = lessThan(self, leftValue, rightValue) return } @@ -141,6 +145,33 @@ func newLessEqualTerm(tk *Token) (inst *term) { } } +func sameContent(a, b any) (same bool, err error) { + la, _ := a.(*ListType) + lb, _ := b.(*ListType) + if len(*la) == len(*lb) { + same = true + for i, item := range *la { + if same, err = equals(item, (*lb)[i]); err != nil || !same { + break + } + } + } + return +} + +func lessThanOrEqual(self *term, a, b any) (isLessEq bool, err error) { + if isLessEq, err = lessThan(self, a, b); err == nil { + if !isLessEq { + if IsList(a) && IsList(b) { + isLessEq, err = sameContent(a, b) + } else { + isLessEq, err = equals(a, b) + } + } + } + return +} + func evalLessEqual(ctx ExprContext, self *term) (v any, err error) { var leftValue, rightValue any @@ -148,21 +179,8 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) { return } - if IsNumber(leftValue) && IsNumber(rightValue) { - if IsInteger(leftValue) && IsInteger(rightValue) { - li, _ := leftValue.(int64) - ri, _ := rightValue.(int64) - v = li <= ri - } else { - v = numAsFloat(leftValue) <= numAsFloat(rightValue) - } - } else if IsString(leftValue) && IsString(rightValue) { - ls, _ := leftValue.(string) - rs, _ := rightValue.(string) - v = ls <= rs - } else { - err = self.errIncompatibleTypes(leftValue, rightValue) - } + v, err = lessThanOrEqual(self, leftValue, rightValue) + return } @@ -170,9 +188,7 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) { func newGreaterTerm(tk *Token) (inst *term) { return &term{ - tk: *tk, - // class: classOperator, - // kind: kindBool, + tk: *tk, children: make([]*term, 0, 2), position: posInfix, priority: priRelational, @@ -181,10 +197,18 @@ func newGreaterTerm(tk *Token) (inst *term) { } func evalGreater(ctx ExprContext, self *term) (v any, err error) { - if v, err = evalLessEqual(ctx, self); err == nil { - b, _ := toBool(v) - v = !b + var leftValue, rightValue any + + if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { + return } + + v, err = lessThan(self, rightValue, leftValue) + + // if v, err = evalLessEqual(ctx, self); err == nil { + // b, _ := toBool(v) + // v = !b + // } return } @@ -192,9 +216,7 @@ func evalGreater(ctx ExprContext, self *term) (v any, err error) { func newGreaterEqualTerm(tk *Token) (inst *term) { return &term{ - tk: *tk, - // class: classOperator, - // kind: kindBool, + tk: *tk, children: make([]*term, 0, 2), position: posInfix, priority: priRelational, @@ -203,10 +225,17 @@ func newGreaterEqualTerm(tk *Token) (inst *term) { } func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) { - if v, err = evalLess(ctx, self); err == nil { - b, _ := toBool(v) - v = !b + var leftValue, rightValue any + + if leftValue, rightValue, err = self.evalInfix(ctx); err != nil { + return } + + v, err = lessThanOrEqual(self, rightValue, leftValue) + // if v, err = evalLess(ctx, self); err == nil { + // b, _ := toBool(v) + // v = !b + // } return } diff --git a/ast_test.go b/t_ast_test.go similarity index 98% rename from ast_test.go rename to t_ast_test.go index 8b1a394..cc5e499 100644 --- a/ast_test.go +++ b/t_ast_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// ast_test.go +// t_ast_test.go package expr import ( diff --git a/dict_test.go b/t_dict_test.go similarity index 99% rename from dict_test.go rename to t_dict_test.go index 1f293d1..0245fc1 100644 --- a/dict_test.go +++ b/t_dict_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// dict_test.go +// t_dict_test.go package expr import ( diff --git a/expr_test.go b/t_expr_test.go similarity index 99% rename from expr_test.go rename to t_expr_test.go index 0238353..78586ea 100644 --- a/expr_test.go +++ b/t_expr_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// expr_test.go +// t_expr_test.go package expr import ( diff --git a/funcs_test.go b/t_funcs_test.go similarity index 99% rename from funcs_test.go rename to t_funcs_test.go index 748638d..099d45d 100644 --- a/funcs_test.go +++ b/t_funcs_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// funcs_test.go +// t_funcs_test.go package expr import ( diff --git a/graph_test.go b/t_graph_test.go similarity index 84% rename from graph_test.go rename to t_graph_test.go index 045dd75..d706d60 100644 --- a/graph_test.go +++ b/t_graph_test.go @@ -1,10 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). -// All rights reserved. - -// graph_test.go +// t_graph_test.go package expr import ( diff --git a/helpers_test.go b/t_helpers_test.go similarity index 98% rename from helpers_test.go rename to t_helpers_test.go index 745bacf..4c06ee0 100644 --- a/helpers_test.go +++ b/t_helpers_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// helpers_test.go +// t_helpers_test.go package expr import ( diff --git a/index_test.go b/t_index_test.go similarity index 97% rename from index_test.go rename to t_index_test.go index 5608514..e5797c6 100644 --- a/index_test.go +++ b/t_index_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// collection_test.go +// t_index_test.go package expr import ( diff --git a/iterator_test.go b/t_iterator_test.go similarity index 98% rename from iterator_test.go rename to t_iterator_test.go index 7a5c880..4f07590 100644 --- a/iterator_test.go +++ b/t_iterator_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// iterator_test.go +// t_iterator_test.go package expr import "testing" diff --git a/list_test.go b/t_list_test.go similarity index 99% rename from list_test.go rename to t_list_test.go index 8905aa5..26fc48b 100644 --- a/list_test.go +++ b/t_list_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// list_test.go +// t_list_test.go package expr import ( diff --git a/parser_test.go b/t_parser_test.go similarity index 99% rename from parser_test.go rename to t_parser_test.go index 551bac7..c2ab827 100644 --- a/parser_test.go +++ b/t_parser_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// parser_test.go +// t_parser_test.go package expr import ( diff --git a/t_relational_test.go b/t_relational_test.go new file mode 100644 index 0000000..39c18b8 --- /dev/null +++ b/t_relational_test.go @@ -0,0 +1,49 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// t_template_test.go +package expr + +import ( + "testing" +) + +func TestRelational(t *testing.T) { + section := "Relational" + inputs := []inputType{ + /* 1 */ {`1 == 3-2`, true, nil}, + /* 2 */ {`"a" == "a"`, true, nil}, + /* 3 */ {`true == false`, false, nil}, + /* 4 */ {`1.0 == 3.0-2`, true, nil}, + /* 5 */ {`[1,2] == [2,1]`, false, nil}, + /* 6 */ {`1 != 3-2`, false, nil}, + /* 7 */ {`"a" != "a"`, false, nil}, + /* 8 */ {`true != false`, true, nil}, + /* 9 */ {`1.0 != 3.0-2`, false, nil}, + /* 10 */ {`[1,2] != [2,1]`, true, nil}, + /* 11 */ {`1|2 == 1|3`, false, nil}, + /* 12 */ {`1|2 != 1|3`, true, nil}, + /* 13 */ {`1|2 == 4|8`, true, nil}, + /* 14 */ {`1 < 8`, true, nil}, + /* 15 */ {`1 <= 8`, true, nil}, + /* 16 */ {`"a" < "b"`, true, nil}, + /* 17 */ {`"a" <= "b"`, true, nil}, + /* 18 */ {`1.0 < 8`, true, nil}, + /* 19 */ {`1.0 <= 8`, true, nil}, + /* 20 */ {`1.0 <= 1.0`, true, nil}, + /* 21 */ {`1.0 == 1`, true, nil}, + /* 22 */ {`1|2 < 1|3`, false, nil}, + /* 23 */ {`1|2 <= 1|3`, false, nil}, + /* 24 */ {`1|2 > 1|3`, true, nil}, + /* 25 */ {`1|2 >= 1|3`, true, nil}, + /* 26 */ {`[1,2,3] > [2]`, true, nil}, + /* 27 */ {`[1,2,3] > [9]`, false, nil}, + /* 28 */ {`[1,2,3] >= [6]`, false, nil}, + /* 29 */ {`[1,2,3] >= [2,6]`, false, nil}, + } + + // t.Setenv("EXPR_PATH", ".") + + // parserTestSpec(t, section, inputs, 27) + parserTest(t, section, inputs) +} diff --git a/scanner_test.go b/t_scanner_test.go similarity index 99% rename from scanner_test.go rename to t_scanner_test.go index 81653c5..643f495 100644 --- a/scanner_test.go +++ b/t_scanner_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// scanner_test.go +// t_scanner_test.go package expr import ( diff --git a/strings_test.go b/t_strings_test.go similarity index 95% rename from strings_test.go rename to t_strings_test.go index 97842d2..2de9f07 100644 --- a/strings_test.go +++ b/t_strings_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// strings_test.go +// t_strings_test.go package expr import ( diff --git a/t_template_test.go b/t_template_test.go new file mode 100644 index 0000000..35ffbec --- /dev/null +++ b/t_template_test.go @@ -0,0 +1,21 @@ +// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). +// All rights reserved. + +// t_template_test.go +package expr + +import ( + "testing" +) + +func TestSomething(t *testing.T) { + section := "Something" + inputs := []inputType{ + /* 1 */ {`1`, int64(1), nil}, + } + + // t.Setenv("EXPR_PATH", ".") + + // parserTestSpec(t, section, inputs, 1) + parserTest(t, section, inputs) +} diff --git a/term_test.go b/t_term_test.go similarity index 98% rename from term_test.go rename to t_term_test.go index 633c039..9089ca6 100644 --- a/term_test.go +++ b/t_term_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// term_test.go +// t_term_test.go package expr import ( diff --git a/token_test.go b/t_token_test.go similarity index 97% rename from token_test.go rename to t_token_test.go index 3d2315b..aef7a65 100644 --- a/token_test.go +++ b/t_token_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// token_test.go +// t_token_test.go package expr import ( diff --git a/utils_test.go b/t_utils_test.go similarity index 99% rename from utils_test.go rename to t_utils_test.go index 9f997b1..383d2e5 100644 --- a/utils_test.go +++ b/t_utils_test.go @@ -1,7 +1,7 @@ // Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com). // All rights reserved. -// utils_test.go +// t_utils_test.go package expr import ( diff --git a/utils.go b/utils.go index 93c9bd2..06b7d8d 100644 --- a/utils.go +++ b/utils.go @@ -40,12 +40,12 @@ func IsDict(v any) (ok bool) { } func IsFract(v any) (ok bool) { - _, ok = v.(*fraction) + _, ok = v.(*FractionType) return ok } func IsRational(v any) (ok bool) { - if _, ok = v.(*fraction); !ok { + if _, ok = v.(*FractionType); !ok { _, ok = v.(int64) } return ok @@ -76,7 +76,7 @@ func isIterator(v any) (ok bool) { func numAsFloat(v any) (f float64) { var ok bool if f, ok = v.(float64); !ok { - if fract, ok := v.(*fraction); ok { + if fract, ok := v.(*FractionType); ok { f = fract.toFloat() } else { i, _ := v.(int64)