Compare commits
	
		
			No commits in common. "4b25a07699c2a20290d0f69312b16ef4d43d6b04" and "e5f63c3d9d5d74f8a0b5db4057b94f8d18ee27cf" have entirely different histories.
		
	
	
		
			4b25a07699
			...
			e5f63c3d9d
		
	
		
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_ast_test.go
 | 
			
		||||
// ast_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@ -13,6 +13,5 @@ const (
 | 
			
		||||
	typeInt      = "integer"
 | 
			
		||||
	typeItem     = "item"
 | 
			
		||||
	typeNumber   = "number"
 | 
			
		||||
	typePair     = "pair"
 | 
			
		||||
	typeString   = "string"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_dict_test.go
 | 
			
		||||
// dict_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@ -24,7 +24,7 @@ func TestDictParser(t *testing.T) {
 | 
			
		||||
		/*  1 */ {`{}`, map[any]any{}, nil},
 | 
			
		||||
		/*  2 */ {`{123}`, nil, errors.New(`[1:6] expected ":", got "}"`)},
 | 
			
		||||
		/*  3 */ {`{1:"one",2:"two",3:"three"}`, map[int64]any{int64(1): "one", int64(2): "two", int64(3): "three"}, nil},
 | 
			
		||||
		/*  4 */ {`{1:"one",2:"two",3:"three"}[3]`, "three", nil},
 | 
			
		||||
		/*  4 */ {`{1:"one",2:"two",3:"three"}.2`, "three", nil},
 | 
			
		||||
		/*  5 */ {`#{1:"one",2:"two",3:"three"}`, int64(3), nil},
 | 
			
		||||
		/*  6 */ {`{1:"one"} + {2:"two"}`, map[any]any{1: "one", 2: "two"}, nil},
 | 
			
		||||
		/*  7 */ {`2 in {1:"one", 2:"two"}`, true, nil},
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_expr_test.go
 | 
			
		||||
// expr_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
							
								
								
									
										401
									
								
								fraction-type.go
									
									
									
									
									
								
							
							
						
						
									
										401
									
								
								fraction-type.go
									
									
									
									
									
								
							@ -1,401 +0,0 @@
 | 
			
		||||
// 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
 | 
			
		||||
}
 | 
			
		||||
@ -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 *FractionType:
 | 
			
		||||
	case *fraction:
 | 
			
		||||
		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 *FractionType:
 | 
			
		||||
	case *fraction:
 | 
			
		||||
		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 *FractionType:
 | 
			
		||||
	case *fraction:
 | 
			
		||||
		result = v
 | 
			
		||||
	default:
 | 
			
		||||
		err = errCantConvert(name, v, "float")
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										12
									
								
								func-math.go
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								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 *FractionType
 | 
			
		||||
	var fractSum *fraction
 | 
			
		||||
	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 *FractionType
 | 
			
		||||
			var item *fraction
 | 
			
		||||
			var ok bool
 | 
			
		||||
			if item, ok = v.(*FractionType); !ok {
 | 
			
		||||
			if item, ok = v.(*fraction); !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 *FractionType
 | 
			
		||||
	var fractProd *fraction
 | 
			
		||||
	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 *FractionType
 | 
			
		||||
			var item *fraction
 | 
			
		||||
			var ok bool
 | 
			
		||||
			if item, ok = v.(*FractionType); !ok {
 | 
			
		||||
			if item, ok = v.(*fraction); !ok {
 | 
			
		||||
				iv, _ := v.(int64)
 | 
			
		||||
				item = newFraction(iv, 1)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_funcs_test.go
 | 
			
		||||
// funcs_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@ -1,7 +1,10 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_graph_test.go
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// graph_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_helpers_test.go
 | 
			
		||||
// helpers_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_iterator_test.go
 | 
			
		||||
// iterator_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
							
								
								
									
										102
									
								
								list-type.go
									
									
									
									
									
								
							
							
						
						
									
										102
									
								
								list-type.go
									
									
									
									
									
								
							@ -1,102 +0,0 @@
 | 
			
		||||
// 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
 | 
			
		||||
// }
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_list_test.go
 | 
			
		||||
// list_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@ -33,19 +33,16 @@ func TestListParser(t *testing.T) {
 | 
			
		||||
		/*  11 */ {`[a=1,b=2,c=3] but a+b+c`, int64(6), nil},
 | 
			
		||||
		/*  12 */ {`[1,2,3] << 2+2`, []any{int64(1), int64(2), int64(3), int64(4)}, nil},
 | 
			
		||||
		/*  13 */ {`2-1 >> [2,3]`, []any{int64(1), int64(2), int64(3)}, nil},
 | 
			
		||||
		/*  14 */ {`[1,2,3][1]`, int64(2), nil},
 | 
			
		||||
		/*  15 */ {`ls=[1,2,3] but ls[1]`, int64(2), nil},
 | 
			
		||||
		/*  16 */ {`ls=[1,2,3] but ls[-1]`, int64(3), nil},
 | 
			
		||||
		/*  17 */ {`list=["one","two","three"]; list[10]`, nil, errors.New(`[1:34] index 10 out of bounds`)},
 | 
			
		||||
		/*  14 */ {`[1,2,3].1`, int64(2), nil},
 | 
			
		||||
		/*  15 */ {`ls=[1,2,3] but ls.1`, int64(2), nil},
 | 
			
		||||
		/*  16 */ {`ls=[1,2,3] but ls.(-1)`, int64(3), nil},
 | 
			
		||||
		/*  17 */ {`list=["one","two","three"]; list.10`, nil, errors.New(`[1:36] index 10 out of bounds`)},
 | 
			
		||||
		/*  18 */ {`["a", "b", "c"]`, newListA("a", "b", "c"), nil},
 | 
			
		||||
		/*  19 */ {`["a", "b", "c"]`, newList([]any{"a", "b", "c"}), nil},
 | 
			
		||||
		/*  20 */ {`#["a", "b", "c"]`, int64(3), nil},
 | 
			
		||||
		/*  21 */ {`"b" in ["a", "b", "c"]`, true, nil},
 | 
			
		||||
		/*  22 */ {`a=[1,2]; (a)<<3`, []any{1, 2, 3}, nil},
 | 
			
		||||
		/*  23 */ {`a=[1,2]; (a)<<3; 1`, []any{1, 2}, nil},
 | 
			
		||||
		/*  24 */ {`["a","b","c","d"][1]`, "b", nil},
 | 
			
		||||
		/*  25 */ {`["a","b","c","d"][1,1]`, nil, errors.New(`[1:19] one index only is allowed`)},
 | 
			
		||||
		/*  26 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
 | 
			
		||||
 | 
			
		||||
		// /*  8 */ {`[int(x)|x=csv("test.csv",1,all(),1)]`, []any{int64(10), int64(40), int64(20)}, nil},
 | 
			
		||||
		// /*  9 */ {`sum(@[int(x)|x=csv("test.csv",1,all(),1)])`, []any{int64(10), int64(40), int64(20)}, nil},
 | 
			
		||||
@ -64,6 +61,8 @@ func TestListParser(t *testing.T) {
 | 
			
		||||
		var gotErr error
 | 
			
		||||
 | 
			
		||||
		ctx := NewSimpleStore()
 | 
			
		||||
		// ctx.SetVar("var1", int64(123))
 | 
			
		||||
		// ctx.SetVar("var2", "abc")
 | 
			
		||||
		ImportMathFuncs(ctx)
 | 
			
		||||
		parser := NewParser(ctx)
 | 
			
		||||
 | 
			
		||||
@ -4,14 +4,91 @@
 | 
			
		||||
// operand-list.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
// -------- list term
 | 
			
		||||
func newListTermA(args ...*term) *term {
 | 
			
		||||
	return newListTerm(0, 0, args)
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"reflect"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ListType []any
 | 
			
		||||
 | 
			
		||||
func newListA(listAny ...any) (list *ListType) {
 | 
			
		||||
	return newList(listAny)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newListTerm(row, col int, args []*term) *term {
 | 
			
		||||
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(args)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newListTerm(args []*term) *term {
 | 
			
		||||
	return &term{
 | 
			
		||||
		tk:       *NewValueToken(row, col, SymList, "[]", args),
 | 
			
		||||
		tk:       *NewValueToken(0, 0, SymList, "[]", args),
 | 
			
		||||
		parent:   nil,
 | 
			
		||||
		children: nil,
 | 
			
		||||
		position: posLeaf,
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,8 @@
 | 
			
		||||
// operator-dot.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
// -------- dot term
 | 
			
		||||
func newDotTerm(tk *Token) (inst *term) {
 | 
			
		||||
	return &term{
 | 
			
		||||
@ -15,6 +17,24 @@ func newDotTerm(tk *Token) (inst *term) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func verifyIndex(ctx ExprContext, indexTerm *term, maxValue int) (index int, err error) {
 | 
			
		||||
	var v int
 | 
			
		||||
	var indexValue any
 | 
			
		||||
	if indexValue, err = indexTerm.compute(ctx); err == nil {
 | 
			
		||||
		if v, err = indexTerm.toInt(indexValue, "index expression value must be integer"); err == nil {
 | 
			
		||||
			if v < 0 && v >= -maxValue {
 | 
			
		||||
				v = maxValue + v
 | 
			
		||||
			}
 | 
			
		||||
			if v >= 0 && v < maxValue {
 | 
			
		||||
				index = v
 | 
			
		||||
			} else {
 | 
			
		||||
				err = indexTerm.Errorf("index %d out of bounds", v)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func evalDot(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
	var leftValue, rightValue any
 | 
			
		||||
 | 
			
		||||
@ -28,8 +48,27 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
	indexTerm := self.children[1]
 | 
			
		||||
 | 
			
		||||
	switch unboxedValue := leftValue.(type) {
 | 
			
		||||
	case *ListType:
 | 
			
		||||
		var index int
 | 
			
		||||
		array := ([]any)(*unboxedValue)
 | 
			
		||||
		if index, err = verifyIndex(ctx, indexTerm, len(array)); err == nil {
 | 
			
		||||
			v = array[index]
 | 
			
		||||
		}
 | 
			
		||||
	case string:
 | 
			
		||||
		var index int
 | 
			
		||||
		if index, err = verifyIndex(ctx, indexTerm, len(unboxedValue)); err == nil {
 | 
			
		||||
			v = string(unboxedValue[index])
 | 
			
		||||
		}
 | 
			
		||||
	case *DictType:
 | 
			
		||||
		var ok bool
 | 
			
		||||
		var indexValue any
 | 
			
		||||
		if indexValue, err = indexTerm.compute(ctx); err == nil {
 | 
			
		||||
			if v, ok = (*unboxedValue)[indexValue]; !ok {
 | 
			
		||||
				err = fmt.Errorf("key %v does not belong to the dictionary", rightValue)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	case ExtIterator:
 | 
			
		||||
		if indexTerm.symbol() == SymVariable /*|| indexTerm.symbol() == SymString */ {
 | 
			
		||||
		if indexTerm.symbol() == SymVariable {
 | 
			
		||||
			opName := indexTerm.source()
 | 
			
		||||
			if unboxedValue.HasOperation(opName) {
 | 
			
		||||
				v, err = unboxedValue.CallOperation(opName, []any{})
 | 
			
		||||
@ -37,13 +76,9 @@ func evalDot(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
				err = indexTerm.Errorf("this iterator do not support the %q command", opName)
 | 
			
		||||
				v = false
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			err = indexTerm.tk.ErrorExpectedGot("identifier")
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		if rightValue, err = self.children[1].compute(ctx); err == nil {
 | 
			
		||||
			err = self.errIncompatibleTypes(leftValue, rightValue)
 | 
			
		||||
		}
 | 
			
		||||
		err = self.errIncompatibleTypes(leftValue, rightValue)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -9,8 +9,205 @@ 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{
 | 
			
		||||
@ -55,11 +252,168 @@ func evalFraction(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
	if den == 1 {
 | 
			
		||||
		v = num
 | 
			
		||||
	} else {
 | 
			
		||||
		v = &FractionType{num, den}
 | 
			
		||||
		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 = 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)
 | 
			
		||||
 | 
			
		||||
@ -1,123 +0,0 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// operator-index.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
// -------- index term
 | 
			
		||||
func newIndexTerm(tk *Token) (inst *term) {
 | 
			
		||||
	return &term{
 | 
			
		||||
		tk:       *tk,
 | 
			
		||||
		children: make([]*term, 0, 2),
 | 
			
		||||
		position: posInfix,
 | 
			
		||||
		priority: priDot,
 | 
			
		||||
		evalFunc: evalIndex,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func verifyKey(indexTerm *term, indexList *ListType) (index any, err error) {
 | 
			
		||||
	index = (*indexList)[0]
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func verifyIndex(indexTerm *term, indexList *ListType, maxValue int) (index int, err error) {
 | 
			
		||||
	var v int
 | 
			
		||||
 | 
			
		||||
	if v, err = toInt((*indexList)[0], "index expression"); err == nil {
 | 
			
		||||
		if v < 0 && v >= -maxValue {
 | 
			
		||||
			v = maxValue + v
 | 
			
		||||
		}
 | 
			
		||||
		if v >= 0 && v < maxValue {
 | 
			
		||||
			index = v
 | 
			
		||||
		} else {
 | 
			
		||||
			err = indexTerm.Errorf("index %d out of bounds", v)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func verifyRange(indexTerm *term, indexList *ListType, maxValue int) (startIndex, endIndex int, err error) {
 | 
			
		||||
	v, _ := ((*indexList)[0]).(*intPair)
 | 
			
		||||
	startIndex = v.a
 | 
			
		||||
	endIndex = v.b
 | 
			
		||||
	if startIndex < 0 && startIndex >= -maxValue {
 | 
			
		||||
		startIndex = maxValue + startIndex
 | 
			
		||||
	}
 | 
			
		||||
	if endIndex < 0 && endIndex >= -maxValue {
 | 
			
		||||
		endIndex = maxValue + endIndex + 1
 | 
			
		||||
	}
 | 
			
		||||
	if startIndex < 0 || startIndex > maxValue {
 | 
			
		||||
		err = indexTerm.Errorf("range start-index %d is out of bounds", startIndex)
 | 
			
		||||
	} else if endIndex < 0 || endIndex > maxValue {
 | 
			
		||||
		err = indexTerm.Errorf("range end-index %d is out of bounds", endIndex)
 | 
			
		||||
	} else if startIndex > endIndex {
 | 
			
		||||
		err = indexTerm.Errorf("range start-index %d must not be greater than end-index %d", startIndex, endIndex)
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func evalIndex(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
	var leftValue, rightValue any
 | 
			
		||||
	var indexList *ListType
 | 
			
		||||
	var ok bool
 | 
			
		||||
 | 
			
		||||
	if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	indexTerm := self.children[1]
 | 
			
		||||
	if indexList, ok = rightValue.(*ListType); !ok {
 | 
			
		||||
		err = self.Errorf("invalid index expression")
 | 
			
		||||
		return
 | 
			
		||||
	} else if len(*indexList) != 1 {
 | 
			
		||||
		err = indexTerm.Errorf("one index only is allowed")
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if IsInteger((*indexList)[0]) {
 | 
			
		||||
		switch unboxedValue := leftValue.(type) {
 | 
			
		||||
		case *ListType:
 | 
			
		||||
			var index int
 | 
			
		||||
			if index, err = verifyIndex(indexTerm, indexList, len(*unboxedValue)); err == nil {
 | 
			
		||||
				v = (*unboxedValue)[index]
 | 
			
		||||
			}
 | 
			
		||||
		case string:
 | 
			
		||||
			var index int
 | 
			
		||||
			if index, err = verifyIndex(indexTerm, indexList, len(unboxedValue)); err == nil {
 | 
			
		||||
				v = string(unboxedValue[index])
 | 
			
		||||
			}
 | 
			
		||||
		case *DictType:
 | 
			
		||||
			var ok bool
 | 
			
		||||
			var indexValue any
 | 
			
		||||
			if indexValue, err = verifyKey(indexTerm, indexList); err == nil {
 | 
			
		||||
				if v, ok = (*unboxedValue)[indexValue]; !ok {
 | 
			
		||||
					err = indexTerm.Errorf("key %v does not belong to the dictionary", rightValue)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			err = self.errIncompatibleTypes(leftValue, rightValue)
 | 
			
		||||
		}
 | 
			
		||||
	} else if isIntPair((*indexList)[0]) {
 | 
			
		||||
		switch unboxedValue := leftValue.(type) {
 | 
			
		||||
		case *ListType:
 | 
			
		||||
			var start, end int
 | 
			
		||||
			if start, end, err = verifyRange(indexTerm, indexList, len(*unboxedValue)); err == nil {
 | 
			
		||||
				sublist := ListType((*unboxedValue)[start:end])
 | 
			
		||||
				v = &sublist
 | 
			
		||||
			}
 | 
			
		||||
		case string:
 | 
			
		||||
			var start, end int
 | 
			
		||||
			if start, end, err = verifyRange(indexTerm, indexList, len(unboxedValue)); err == nil {
 | 
			
		||||
				v = unboxedValue[start:end]
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			err = self.errIncompatibleTypes(leftValue, rightValue)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// init
 | 
			
		||||
func init() {
 | 
			
		||||
	registerTermConstructor(SymIndex, newIndexTerm)
 | 
			
		||||
}
 | 
			
		||||
@ -1,69 +0,0 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// operator-range.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import "fmt"
 | 
			
		||||
 | 
			
		||||
// -------- range term
 | 
			
		||||
type intPair struct {
 | 
			
		||||
	a, b int
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *intPair) TypeName() string {
 | 
			
		||||
	return typePair
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (p *intPair) ToString(opt FmtOpt) string {
 | 
			
		||||
	return fmt.Sprintf("(%d, %d)", p.a, p.b)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isIntPair(v any) bool {
 | 
			
		||||
	_, ok := v.(*intPair)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func newRangeTerm(tk *Token) (inst *term) {
 | 
			
		||||
	return &term{
 | 
			
		||||
		tk:       *tk,
 | 
			
		||||
		children: make([]*term, 0, 2),
 | 
			
		||||
		position: posInfix,
 | 
			
		||||
		priority: priDot,
 | 
			
		||||
		evalFunc: evalRange,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func evalRange(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
	var leftValue, rightValue any
 | 
			
		||||
 | 
			
		||||
	// if err = self.checkOperands(); err != nil {
 | 
			
		||||
	// 	return
 | 
			
		||||
	// }
 | 
			
		||||
	if len(self.children) == 0 {
 | 
			
		||||
		leftValue = int64(0)
 | 
			
		||||
		rightValue = int64(-1)
 | 
			
		||||
	} else if len(self.children) == 1 {
 | 
			
		||||
		if leftValue, err = self.children[0].compute(ctx); err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		rightValue = int64(-1)
 | 
			
		||||
	} else if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !(IsInteger(leftValue) && IsInteger(rightValue)) {
 | 
			
		||||
		err = self.errIncompatibleTypes(leftValue, rightValue)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	startIndex, _ := leftValue.(int64)
 | 
			
		||||
	endIndex, _ := rightValue.(int64)
 | 
			
		||||
 | 
			
		||||
	v = &intPair{int(startIndex), int(endIndex)}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// init
 | 
			
		||||
func init() {
 | 
			
		||||
	registerTermConstructor(SymColon, newRangeTerm)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										204
									
								
								operator-rel.go
									
									
									
									
									
								
							
							
						
						
									
										204
									
								
								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,
 | 
			
		||||
		tk: *tk,
 | 
			
		||||
		// class:    classOperator,
 | 
			
		||||
		// kind:     kindBool,
 | 
			
		||||
		children: make([]*term, 0, 2),
 | 
			
		||||
		position: posInfix,
 | 
			
		||||
		priority: priRelational,
 | 
			
		||||
@ -18,29 +18,6 @@ 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
 | 
			
		||||
 | 
			
		||||
@ -48,7 +25,21 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err = equals(leftValue, rightValue)
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -56,7 +47,9 @@ func evalEqual(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
 | 
			
		||||
func newNotEqualTerm(tk *Token) (inst *term) {
 | 
			
		||||
	return &term{
 | 
			
		||||
		tk:       *tk,
 | 
			
		||||
		tk: *tk,
 | 
			
		||||
		// class:    classOperator,
 | 
			
		||||
		// kind:     kindBool,
 | 
			
		||||
		children: make([]*term, 0, 2),
 | 
			
		||||
		position: posInfix,
 | 
			
		||||
		priority: priRelational,
 | 
			
		||||
@ -72,11 +65,38 @@ 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,
 | 
			
		||||
		tk: *tk,
 | 
			
		||||
		// class:    classOperator,
 | 
			
		||||
		// kind:     kindBool,
 | 
			
		||||
		children: make([]*term, 0, 2),
 | 
			
		||||
		position: posInfix,
 | 
			
		||||
		priority: priRelational,
 | 
			
		||||
@ -84,52 +104,28 @@ 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
 | 
			
		||||
	}
 | 
			
		||||
	v, err = lessThan(self, leftValue, rightValue)
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -145,33 +141,6 @@ 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
 | 
			
		||||
 | 
			
		||||
@ -179,8 +148,21 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err = lessThanOrEqual(self, leftValue, rightValue)
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -188,7 +170,9 @@ func evalLessEqual(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
 | 
			
		||||
func newGreaterTerm(tk *Token) (inst *term) {
 | 
			
		||||
	return &term{
 | 
			
		||||
		tk:       *tk,
 | 
			
		||||
		tk: *tk,
 | 
			
		||||
		// class:    classOperator,
 | 
			
		||||
		// kind:     kindBool,
 | 
			
		||||
		children: make([]*term, 0, 2),
 | 
			
		||||
		position: posInfix,
 | 
			
		||||
		priority: priRelational,
 | 
			
		||||
@ -197,13 +181,10 @@ func newGreaterTerm(tk *Token) (inst *term) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func evalGreater(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
	var leftValue, rightValue any
 | 
			
		||||
 | 
			
		||||
	if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	if v, err = evalLessEqual(ctx, self); err == nil {
 | 
			
		||||
		b, _ := toBool(v)
 | 
			
		||||
		v = !b
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err = lessThan(self, rightValue, leftValue)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -211,7 +192,9 @@ func evalGreater(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
 | 
			
		||||
func newGreaterEqualTerm(tk *Token) (inst *term) {
 | 
			
		||||
	return &term{
 | 
			
		||||
		tk:       *tk,
 | 
			
		||||
		tk: *tk,
 | 
			
		||||
		// class:    classOperator,
 | 
			
		||||
		// kind:     kindBool,
 | 
			
		||||
		children: make([]*term, 0, 2),
 | 
			
		||||
		position: posInfix,
 | 
			
		||||
		priority: priRelational,
 | 
			
		||||
@ -220,13 +203,10 @@ func newGreaterEqualTerm(tk *Token) (inst *term) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func evalGreaterEqual(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
	var leftValue, rightValue any
 | 
			
		||||
 | 
			
		||||
	if leftValue, rightValue, err = self.evalInfix(ctx); err != nil {
 | 
			
		||||
		return
 | 
			
		||||
	if v, err = evalLess(ctx, self); err == nil {
 | 
			
		||||
		b, _ := toBool(v)
 | 
			
		||||
		v = !b
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	v, err = lessThanOrEqual(self, rightValue, leftValue)
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -38,6 +38,23 @@ func evalPlus(ctx ExprContext, self *term) (v any, err error) {
 | 
			
		||||
			rightInt, _ := rightValue.(int64)
 | 
			
		||||
			v = leftInt + rightInt
 | 
			
		||||
		}
 | 
			
		||||
		// } else if IsList(leftValue) || IsList(rightValue) {
 | 
			
		||||
		// 	var leftList, rightList *ListType
 | 
			
		||||
		// 	var ok bool
 | 
			
		||||
		// 	if leftList, ok = leftValue.(*ListType); !ok {
 | 
			
		||||
		// 		leftList = &ListType{leftValue}
 | 
			
		||||
		// 	}
 | 
			
		||||
		// 	if rightList, ok = rightValue.(*ListType); !ok {
 | 
			
		||||
		// 		rightList = &ListType{rightValue}
 | 
			
		||||
		// 	}
 | 
			
		||||
		// 	sumList := make(ListType, 0, len(*leftList)+len(*rightList))
 | 
			
		||||
		// 	for _, item := range *leftList {
 | 
			
		||||
		// 		sumList = append(sumList, item)
 | 
			
		||||
		// 	}
 | 
			
		||||
		// 	for _, item := range *rightList {
 | 
			
		||||
		// 		sumList = append(sumList, item)
 | 
			
		||||
		// 	}
 | 
			
		||||
		// 	v = &sumList
 | 
			
		||||
	} else if IsList(leftValue) && IsList(rightValue) {
 | 
			
		||||
		var leftList, rightList *ListType
 | 
			
		||||
		leftList, _ = leftValue.(*ListType)
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										81
									
								
								parser.go
									
									
									
									
									
								
							
							
						
						
									
										81
									
								
								parser.go
									
									
									
									
									
								
							@ -154,34 +154,15 @@ func (self *parser) parseFuncDef(scanner *scanner) (tree *term, err error) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef bool) (subtree *term, err error) {
 | 
			
		||||
	r, c := scanner.lastPos()
 | 
			
		||||
func (self *parser) parseList(scanner *scanner, allowVarRef bool) (subtree *term, err error) {
 | 
			
		||||
	args := make([]*term, 0)
 | 
			
		||||
	lastSym := SymUnknown
 | 
			
		||||
	itemExpected := false
 | 
			
		||||
	for lastSym != SymClosedSquare && lastSym != SymEos {
 | 
			
		||||
		var subTree *ast
 | 
			
		||||
		zeroRequired := scanner.current.Sym == SymColon
 | 
			
		||||
		if subTree, err = self.parseItem(scanner, allowVarRef, SymComma, SymClosedSquare); err == nil {
 | 
			
		||||
			root := subTree.root
 | 
			
		||||
			if root != nil {
 | 
			
		||||
				if !parsingIndeces && root.symbol() == SymColon {
 | 
			
		||||
					err = root.Errorf("unexpected range expression")
 | 
			
		||||
					break
 | 
			
		||||
				}
 | 
			
		||||
				args = append(args, root)
 | 
			
		||||
				if parsingIndeces && root.symbol() == SymColon && zeroRequired { //len(root.children) == 0 {
 | 
			
		||||
					if len(root.children) == 1 {
 | 
			
		||||
						root.children = append(root.children, root.children[0])
 | 
			
		||||
					} else if len(root.children) > 1 {
 | 
			
		||||
						err = root.Errorf("invalid range specification")
 | 
			
		||||
						break
 | 
			
		||||
					}
 | 
			
		||||
					zeroTk := NewValueToken(root.tk.row, root.tk.col, SymInteger, "0", int64(0))
 | 
			
		||||
					zeroTerm := newTerm(zeroTk)
 | 
			
		||||
					zeroTerm.setParent(root)
 | 
			
		||||
					root.children[0] = zeroTerm
 | 
			
		||||
				}
 | 
			
		||||
			if subTree.root != nil {
 | 
			
		||||
				args = append(args, subTree.root)
 | 
			
		||||
			} else if itemExpected {
 | 
			
		||||
				prev := scanner.Previous()
 | 
			
		||||
				err = prev.ErrorExpectedGot("list-item")
 | 
			
		||||
@ -194,10 +175,11 @@ func (self *parser) parseList(scanner *scanner, parsingIndeces bool, allowVarRef
 | 
			
		||||
		itemExpected = lastSym == SymComma
 | 
			
		||||
	}
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		// TODO Check arguments
 | 
			
		||||
		if lastSym != SymClosedSquare {
 | 
			
		||||
			err = scanner.Previous().ErrorExpectedGot("]")
 | 
			
		||||
		} else {
 | 
			
		||||
			subtree = newListTerm(r, c, args)
 | 
			
		||||
			subtree = newListTerm(args)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
@ -312,14 +294,14 @@ func (self *parser) parseSelectorCase(scanner *scanner, allowVarRef bool, defaul
 | 
			
		||||
			err = tk.Errorf("case list in default clause")
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if filterList, err = self.parseList(scanner, false, allowVarRef); err != nil {
 | 
			
		||||
		if filterList, err = self.parseList(scanner, allowVarRef); err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		tk = scanner.Next()
 | 
			
		||||
		startRow = tk.row
 | 
			
		||||
		startCol = tk.col
 | 
			
		||||
	} else if !defaultCase {
 | 
			
		||||
		filterList = newListTerm(startRow, startCol, make([]*term, 0))
 | 
			
		||||
		filterList = newListTerm(make([]*term, 0))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if tk.Sym == SymOpenBrace {
 | 
			
		||||
@ -370,14 +352,6 @@ func (self *parser) Parse(scanner *scanner, termSymbols ...Symbol) (tree *ast, e
 | 
			
		||||
	return self.parseGeneral(scanner, true, false, termSymbols...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func couldBeACollection(t *term) bool {
 | 
			
		||||
	var sym = SymUnknown
 | 
			
		||||
	if t != nil {
 | 
			
		||||
		sym = t.symbol()
 | 
			
		||||
	}
 | 
			
		||||
	return sym == SymList || sym == SymString || sym == SymDict || sym == SymExpression || sym == SymVariable
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef bool, termSymbols ...Symbol) (tree *ast, err error) {
 | 
			
		||||
	var selectorTerm *term = nil
 | 
			
		||||
	var currentTerm *term = nil
 | 
			
		||||
@ -427,28 +401,15 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
 | 
			
		||||
			}
 | 
			
		||||
		case SymOpenSquare:
 | 
			
		||||
			var listTerm *term
 | 
			
		||||
			parsingIndeces := couldBeACollection(currentTerm)
 | 
			
		||||
			if listTerm, err = self.parseList(scanner, parsingIndeces, allowVarRef); err == nil {
 | 
			
		||||
				if parsingIndeces {
 | 
			
		||||
					indexTk := NewToken(listTerm.tk.row, listTerm.tk.col, SymIndex, listTerm.source())
 | 
			
		||||
					indexTerm := newTerm(indexTk)
 | 
			
		||||
					if err = tree.addTerm(indexTerm); err == nil {
 | 
			
		||||
						err = tree.addTerm(listTerm)
 | 
			
		||||
					}
 | 
			
		||||
				} else {
 | 
			
		||||
					err = tree.addTerm(listTerm)
 | 
			
		||||
				}
 | 
			
		||||
			if listTerm, err = self.parseList(scanner, allowVarRef); err == nil {
 | 
			
		||||
				err = tree.addTerm(listTerm)
 | 
			
		||||
				currentTerm = listTerm
 | 
			
		||||
			}
 | 
			
		||||
		case SymOpenBrace:
 | 
			
		||||
			if currentTerm != nil && currentTerm.symbol() == SymColon {
 | 
			
		||||
				err = currentTerm.Errorf(`selector-case outside of a selector context`)
 | 
			
		||||
			} else {
 | 
			
		||||
				var mapTerm *term
 | 
			
		||||
				if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
 | 
			
		||||
					err = tree.addTerm(mapTerm)
 | 
			
		||||
					currentTerm = mapTerm
 | 
			
		||||
				}
 | 
			
		||||
			var mapTerm *term
 | 
			
		||||
			if mapTerm, err = self.parseDictionary(scanner, allowVarRef); err == nil {
 | 
			
		||||
				err = tree.addTerm(mapTerm)
 | 
			
		||||
				currentTerm = mapTerm
 | 
			
		||||
			}
 | 
			
		||||
		case SymEqual:
 | 
			
		||||
			if err = checkPrevSymbol(lastSym, SymIdentifier, tk); err == nil {
 | 
			
		||||
@ -478,16 +439,14 @@ func (self *parser) parseGeneral(scanner *scanner, allowForest bool, allowVarRef
 | 
			
		||||
			}
 | 
			
		||||
		case SymColon, SymDoubleColon:
 | 
			
		||||
			var caseTerm *term
 | 
			
		||||
			if selectorTerm != nil {
 | 
			
		||||
				if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
 | 
			
		||||
					addSelectorCase(selectorTerm, caseTerm)
 | 
			
		||||
					currentTerm = caseTerm
 | 
			
		||||
					if tk.Sym == SymDoubleColon {
 | 
			
		||||
						selectorTerm = nil
 | 
			
		||||
					}
 | 
			
		||||
			if selectorTerm == nil {
 | 
			
		||||
				err = tk.Errorf("selector-case outside of a selector context")
 | 
			
		||||
			} else if caseTerm, err = self.parseSelectorCase(scanner, allowVarRef, tk.Sym == SymDoubleColon); err == nil {
 | 
			
		||||
				addSelectorCase(selectorTerm, caseTerm)
 | 
			
		||||
				currentTerm = caseTerm
 | 
			
		||||
				if tk.Sym == SymDoubleColon {
 | 
			
		||||
					selectorTerm = nil
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				currentTerm, err = tree.addToken2(tk)
 | 
			
		||||
			}
 | 
			
		||||
		default:
 | 
			
		||||
			currentTerm, err = tree.addToken2(tk)
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_parser_test.go
 | 
			
		||||
// parser_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@ -74,14 +74,6 @@ func (self *scanner) unreadChar() (err error) {
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *scanner) lastPos() (r, c int) {
 | 
			
		||||
	if self.prev != nil {
 | 
			
		||||
		r = self.prev.row
 | 
			
		||||
		c = self.prev.col
 | 
			
		||||
	}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (self *scanner) Previous() *Token {
 | 
			
		||||
	return self.prev
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_scanner_test.go
 | 
			
		||||
// scanner_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_strings_test.go
 | 
			
		||||
// strings_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@ -14,8 +14,8 @@ func TestStringsParser(t *testing.T) {
 | 
			
		||||
		/*  2 */ {`"uno" + 2`, `uno2`, nil},
 | 
			
		||||
		/*  3 */ {`"uno" + (2+1)`, `uno3`, nil},
 | 
			
		||||
		/*  4 */ {`"uno" * (2+1)`, `unounouno`, nil},
 | 
			
		||||
		/*  5 */ {`"abc"[1]`, `b`, nil},
 | 
			
		||||
		/*  6 */ {`#"abc"`, int64(3), nil},
 | 
			
		||||
		/*  5 */ {`"abc".1`, `b`, nil},
 | 
			
		||||
		/*  5 */ {`#"abc"`, int64(3), nil},
 | 
			
		||||
	}
 | 
			
		||||
	parserTest(t, "String", inputs)
 | 
			
		||||
}
 | 
			
		||||
@ -85,7 +85,6 @@ const (
 | 
			
		||||
	SymFuncDef
 | 
			
		||||
	SymList
 | 
			
		||||
	SymDict
 | 
			
		||||
	SymIndex
 | 
			
		||||
	SymExpression
 | 
			
		||||
	SymSelector     // <selector> ::= <expr> "?" <selector-case> {":" <selector-case>} ["::" <default-selector-case>]
 | 
			
		||||
	SymSelectorCase // <selector-case> ::= [<list>] "{" <multi-expr> "}"
 | 
			
		||||
 | 
			
		||||
@ -1,27 +0,0 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_index_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestCollections(t *testing.T) {
 | 
			
		||||
	section := "Collection"
 | 
			
		||||
	inputs := []inputType{
 | 
			
		||||
		/*   1 */ {`"abcdef"[1:3]`, "bc", nil},
 | 
			
		||||
		/*   2 */ {`"abcdef"[:3]`, "abc", nil},
 | 
			
		||||
		/*   3 */ {`"abcdef"[1:]`, "bcdef", nil},
 | 
			
		||||
		/*   4 */ {`"abcdef"[:]`, "abcdef", nil},
 | 
			
		||||
		// /*   5 */ {`[0,1,2,3,4][:]`, ListType{int64(0), int64(1), int64(2), int64(3), int64(4)}, nil},
 | 
			
		||||
		/*   5 */ {`"abcdef"[1:2:3]`, nil, errors.New(`[1:14] left operand '(1, 2)' [pair] and right operand '3' [integer] are not compatible with operator ":"`)},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Setenv("EXPR_PATH", ".")
 | 
			
		||||
 | 
			
		||||
	// parserTestSpec(t, section, inputs, 5)
 | 
			
		||||
	parserTest(t, section, inputs)
 | 
			
		||||
}
 | 
			
		||||
@ -1,49 +0,0 @@
 | 
			
		||||
// 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)
 | 
			
		||||
}
 | 
			
		||||
@ -1,21 +0,0 @@
 | 
			
		||||
// 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)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								term.go
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								term.go
									
									
									
									
									
								
							@ -12,7 +12,6 @@ type termPriority uint32
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	priNone termPriority = iota
 | 
			
		||||
	priRange
 | 
			
		||||
	priBut
 | 
			
		||||
	priAssign
 | 
			
		||||
	priOr
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_term_test.go
 | 
			
		||||
// term_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_token_test.go
 | 
			
		||||
// token_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
							
								
								
									
										6
									
								
								utils.go
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								utils.go
									
									
									
									
									
								
							@ -40,12 +40,12 @@ func IsDict(v any) (ok bool) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsFract(v any) (ok bool) {
 | 
			
		||||
	_, ok = v.(*FractionType)
 | 
			
		||||
	_, ok = v.(*fraction)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsRational(v any) (ok bool) {
 | 
			
		||||
	if _, ok = v.(*FractionType); !ok {
 | 
			
		||||
	if _, ok = v.(*fraction); !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.(*FractionType); ok {
 | 
			
		||||
		if fract, ok := v.(*fraction); ok {
 | 
			
		||||
			f = fract.toFloat()
 | 
			
		||||
		} else {
 | 
			
		||||
			i, _ := v.(int64)
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
 | 
			
		||||
// All rights reserved.
 | 
			
		||||
 | 
			
		||||
// t_utils_test.go
 | 
			
		||||
// utils_test.go
 | 
			
		||||
package expr
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user