Compare commits

...

9 Commits

10 changed files with 439 additions and 72 deletions

View File

@ -7,6 +7,7 @@ package expr
const (
typeBoolean = "boolean"
typeFloat = "decimal"
typeFraction = "fraction"
typeInt = "integer"
typeNumber = "number"
typeString = "string"

16
formatter.go Normal file
View File

@ -0,0 +1,16 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// formatter.go
package expr
type FmtOpt uint16
const (
TTY FmtOpt = 1 << iota
MultiLine
)
type Formatter interface {
ToString(options FmtOpt) string
}

View File

@ -10,16 +10,17 @@ import (
)
func checkNumberParamExpected(funcName string, paramValue any, paramPos int) (err error) {
if !(isNumber(paramValue) || isList(paramValue) || isIterator(paramValue)) {
if !(isNumber(paramValue) || isList(paramValue) || isFraction(paramValue)) {
err = fmt.Errorf("%s(): param nr %d has wrong type %T, number expected", funcName, paramPos+1, paramValue)
}
return
}
func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
var sumAsFloat = false
var sumAsFloat, sumAsFract bool
var floatSum float64 = 0.0
var intSum int64 = 0
var fractSum *fraction
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
@ -36,19 +37,37 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(array)); err != nil {
if array, ok := v.(*ListType); ok {
if v, err = doAdd(ctx, name, NewFlatArrayIterator(*array)); err != nil {
break
}
}
}
if !sumAsFloat && isFloat(v) {
if !sumAsFloat {
if isFloat(v) {
sumAsFloat = true
if sumAsFract {
floatSum = fractSum.toFloat()
} else {
floatSum = float64(intSum)
}
} else if !sumAsFract && isFraction(v) {
fractSum = newFraction(intSum, 1)
sumAsFract = true
}
}
if sumAsFloat {
floatSum += numAsFloat(v)
} else if sumAsFract {
var item *fraction
var ok bool
if item, ok = v.(*fraction); !ok {
iv, _ := v.(int64)
item = newFraction(iv, 1)
}
fractSum = sumFract(fractSum, item)
} else {
iv, _ := v.(int64)
intSum += iv
@ -58,6 +77,8 @@ func doAdd(ctx ExprContext, name string, it Iterator) (result any, err error) {
err = nil
if sumAsFloat {
result = floatSum
} else if sumAsFract {
result = fractSum
} else {
result = intSum
}
@ -71,28 +92,58 @@ func addFunc(ctx ExprContext, name string, args []any) (result any, err error) {
}
func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
var mulAsFloat = false
var mulAsFloat, mulAsFract bool
var floatProd float64 = 1.0
var intProd int64 = 1
var fractProd *fraction
var v any
for v, err = it.Next(); err == nil; v, err = it.Next() {
if subIter, ok := v.(Iterator); ok {
if v, err = doAdd(ctx, name, subIter); err != nil {
break
}
if subIter.HasOperation(cleanName) {
if _, err = subIter.CallOperation(cleanName, nil); err != nil {
return
}
}
} else {
if err = checkNumberParamExpected(name, v, it.Index()); err != nil {
break
}
if array, ok := v.([]any); ok {
if v, err = doMul(ctx, name, NewFlatArrayIterator(array)); err != nil {
if array, ok := v.(*ListType); ok {
if v, err = doMul(ctx, name, NewFlatArrayIterator(*array)); err != nil {
break
}
}
}
if !mulAsFloat && isFloat(v) {
if !mulAsFloat {
if isFloat(v) {
mulAsFloat = true
if mulAsFract {
floatProd = fractProd.toFloat()
} else {
floatProd = float64(intProd)
}
} else if !mulAsFract && isFraction(v) {
fractProd = newFraction(intProd, 1)
mulAsFract = true
}
}
if mulAsFloat {
floatProd *= numAsFloat(v)
} else if mulAsFract {
var item *fraction
var ok bool
if item, ok = v.(*fraction); !ok {
iv, _ := v.(int64)
item = newFraction(iv, 1)
}
fractProd = mulFract(fractProd, item)
} else {
iv, _ := v.(int64)
intProd *= iv
@ -102,6 +153,8 @@ func doMul(ctx ExprContext, name string, it Iterator) (result any, err error) {
err = nil
if mulAsFloat {
result = floatProd
} else if mulAsFract {
result = fractProd
} else {
result = intProd
}

View File

@ -7,11 +7,11 @@ package expr
import "io"
type FlatArrayIterator struct {
a []any
a ListType
index int
}
func NewFlatArrayIterator(array []any) *FlatArrayIterator {
func NewFlatArrayIterator(array ListType) *FlatArrayIterator {
return &FlatArrayIterator{a: array, index: 0}
}

View File

@ -4,6 +4,44 @@
// operand-list.go
package expr
import (
"fmt"
"strings"
)
type ListType []any
func (ls *ListType) ToString(opt FmtOpt) 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(']')
return sb.String()
}
// -------- list term
func newListTermA(args ...*term) *term {
return newListTerm(args)
@ -23,7 +61,7 @@ func newListTerm(args []*term) *term {
// -------- list func
func evalList(ctx ExprContext, self *term) (v any, err error) {
list, _ := self.value().([]*term)
items := make([]any, len(list))
items := make(ListType, len(list))
for i, tree := range list {
var param any
if param, err = tree.compute(ctx); err != nil {
@ -32,35 +70,7 @@ func evalList(ctx ExprContext, self *term) (v any, err error) {
items[i] = param
}
if err == nil {
v = items
v = &items
}
return
}
// // -------- list term
// func newListTerm(args []*term) *term {
// return &term{
// tk: *NewToken(0, 0, SymList, "[]"),
// parent: nil,
// children: args,
// position: posLeaf,
// priority: priValue,
// evalFunc: evalList,
// }
// }
// // -------- list func
// func evalList(ctx ExprContext, self *term) (v any, err error) {
// items := make([]any, len(self.children))
// for i, tree := range self.children {
// var param any
// if param, err = tree.compute(ctx); err != nil {
// break
// }
// items[i] = param
// }
// if err == nil {
// v = items
// }
// return
// }

274
operator-fraction.go Normal file
View File

@ -0,0 +1,274 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// operand-fraction.go
package expr
import (
"errors"
"fmt"
"strconv"
"strings"
)
type fraction struct {
num, den int64
}
func newFraction(num, den int64) *fraction {
if den < 0 {
den = -den
num = -num
}
return &fraction{num, den}
}
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()
}
// -------- fraction term
func newFractionTerm(tk *Token) *term {
return &term{
tk: *tk,
parent: nil,
children: make([]*term, 0, 2),
position: posInfix,
priority: priFraction,
evalFunc: evalFraction,
}
}
// -------- eval func
func evalFraction(ctx ExprContext, self *term) (v any, err error) {
var numValue, denValue any
var num, den int64
var ok bool
if numValue, denValue, err = self.evalInfix(ctx); err != nil {
return
}
if num, ok = numValue.(int64); !ok {
err = fmt.Errorf("numerator must be integer, got %T (%v)", numValue, numValue)
return
}
if den, ok = denValue.(int64); !ok {
err = fmt.Errorf("denominator must be integer, got %T (%v)", denValue, denValue)
return
}
if den == 0 {
err = errors.New("division by zero")
return
}
if den < 0 {
den = -den
num = -num
}
g := gcd(num, den)
num = num / g
den = den / g
if den == 1 {
v = num
} else {
v = &fraction{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 = &fraction{f1.num*(m/f1.den) + f2.num*(m/f2.den), m}
return
}
func mulFract(f1, f2 *fraction) (prod *fraction) {
prod = &fraction{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) any {
return simplifyIntegers(f.num, f.den)
}
func simplifyIntegers(num, den int64) (v any) {
if den < 0 {
den = -den
num = -num
}
g := gcd(num, den)
num = num / g
den = den / g
if den == 1 {
v = num
} else {
v = &fraction{num, den}
}
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)
}

View File

@ -42,6 +42,8 @@ func evalMultiply(ctx ExprContext, self *term) (v any, err error) {
rightInt, _ := rightValue.(int64)
v = leftInt * rightInt
}
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = mulAnyFract(leftValue, rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
@ -85,6 +87,8 @@ func evalDivide(ctx ExprContext, self *term) (v any, err error) {
v = leftInt / rightInt
}
}
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = divAnyFract(leftValue, rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}

View File

@ -39,22 +39,24 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
v = leftInt + rightInt
}
} else if isList(leftValue) || isList(rightValue) {
var leftList, rightList []any
var leftList, rightList *ListType
var ok bool
if leftList, ok = leftValue.([]any); !ok {
leftList = []any{leftValue}
if leftList, ok = leftValue.(*ListType); !ok {
leftList = &ListType{leftValue}
}
if rightList, ok = rightValue.([]any); !ok {
rightList = []any{rightValue}
if rightList, ok = rightValue.(*ListType); !ok {
rightList = &ListType{rightValue}
}
sumList := make([]any, 0, len(leftList)+len(rightList))
for _, item := range leftList {
sumList := make(ListType, 0, len(*leftList)+len(*rightList))
for _, item := range *leftList {
sumList = append(sumList, item)
}
for _, item := range rightList {
for _, item := range *rightList {
sumList = append(sumList, item)
}
v = sumList
v = &sumList
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = sumAnyFract(leftValue, rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}
@ -89,15 +91,17 @@ func evalMinus(ctx ExprContext, self *term) (v any, err error) {
v = leftInt - rightInt
}
} else if isList(leftValue) && isList(rightValue) {
leftList, _ := leftValue.([]any)
rightList, _ := rightValue.([]any)
diffList := make([]any, 0, len(leftList)-len(rightList))
for _, item := range leftList {
if slices.Index(rightList, item) < 0 {
leftList, _ := leftValue.(*ListType)
rightList, _ := rightValue.(*ListType)
diffList := make(ListType, 0, len(*leftList)-len(*rightList))
for _, item := range *leftList {
if slices.Index(*rightList, item) < 0 {
diffList = append(diffList, item)
}
}
v = diffList
v = &diffList
} else if isFraction(leftValue) || isFraction(rightValue) {
v, err = subAnyFract(leftValue, rightValue)
} else {
err = self.errIncompatibleTypes(leftValue, rightValue)
}

View File

@ -20,6 +20,7 @@ const (
priRelational
priSum
priProduct
priFraction
priSelector
priSign
priFact

View File

@ -25,7 +25,7 @@ func isFloat(v any) (ok bool) {
}
func isList(v any) (ok bool) {
_, ok = v.([]any)
_, ok = v.(*ListType)
return ok
}
@ -55,9 +55,13 @@ 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 {
f = fract.toFloat()
} else {
i, _ := v.(int64)
f = float64(i)
}
}
return
}