Compare commits

..

3 Commits

Author SHA1 Message Date
camoroso d8dc2939b4 Expr.adoc: typos 2026-06-06 06:51:17 +02:00
camoroso 6e868b568d Expr.adoc: $$() corrected a wrong sentences 2026-06-03 09:14:17 +02:00
camoroso 73e4ad7b87 Expr.adoc/groupby: group keys are strings 2026-06-03 09:11:51 +02:00
19 changed files with 33 additions and 1689 deletions
-14
View File
@@ -1,14 +0,0 @@
# Builder resource file
# Created on gio 21 mag 2026, 15:35:18, CEST
# Program name
PROGRAM_NAME="ecli"
# Program version
PROGRAM_VERSION="$(<version.txt)"
# Preset platform
platform=("linux/amd64" "darwin/arm64")
#--- end of file ---
-199
View File
@@ -1,199 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
# All rights reserved.
RESOURCE_FILE=".build.rc"
BUILD_REGISTER=".build_register"
PROGRAM_NAME=""
PROGRAM_VERSION=""
function usage() {
prog=$(basename "${0}")
msgln "USAGE:"
#msgln " ${prog} <program-name> <program-version> <os>/<platform>..."
msgln " ${prog} <os>/<platform>..."
msgln " ${prog} --local Build the local exec"
msgln " ${prog} --init Create the resource file in the current directory"
msgln
if [ -r "${RESOURCE_FILE}" ]; then
msgln "Resource file '${RESOURCE_FILE}' content:"
cat >&2 ${RESOURCE_FILE}
else
msgln "Resource file '${RESOURCE_FILE}' not found"
fi
}
function msgln() {
echo >&2 "${1}"
}
function exitUsage() {
echo >&2 "${1}"
usage
exit 1
}
function exitMsg() {
echo >&2 "${1}"
exit 1
}
# CMDLINE: help
if [ "${1}" == "help,," ] || [ "${1,,}" == "--help" ] || [ "${1,,}" == "-h" ]; then
usage
exit
fi
# CMDLINE: init
if [ "${1,,}" == "--init" ]; then
cat >"${RESOURCE_FILE}" <<eot
# Builder resource file
# Created on $(date)
# Program name
PROGRAM_NAME="name"
# Program version
PROGRAM_VERSION="version"
# Preset platform
platform=("linux/amd64" "darwin/arm64")
#--- end of file ---
eot
msgln "Resource file '${RESOURCE_FILE}' create. Edit it to set valid values."
#${EDITOR-vi} "${RESOURCE_FILE}"
exit
fi
if [ -r "${RESOURCE_FILE}" ]; then
if ! source "${RESOURCE_FILE}"; then
exitMsg "Can't load build resource file '${RESOURCE_FILE}'"
fi
fi
if [ -z "${PROGRAM_NAME}" ]; then
exitUsage "Missing program name"
fi
if [ -z "${PROGRAM_VERSION}" ]; then
exitUsage "Missing program version"
fi
function getBuildCount() {
local reg ver count
if [ -r "${BUILD_REGISTER}" ]; then
reg=$(<"${BUILD_REGISTER}")
else
reg="${PROGRAM_VERSION} 0"
fi
read ver count <<<"${reg}"
if [ "${ver}" != "${PROGRAM_VERSION}" ]; then
count=0
fi
count=$((count+1))
echo >"${BUILD_REGISTER}" "${PROGRAM_VERSION} ${count}"
echo ${count}
}
function build() {
local target=${1} ext cmd
IFS=/ read os cpu <<<"${p}"
#msgln "OS=${os}; CPU=${cpu}"
ext=""
if [ "${os}" == 'windows' ]; then
ext=".exe"
fi
cmd="GOOS='${os}' GOARCH='${cpu}' go build -o '${PROGRAM_NAME}_v${PROGRAM_VERSION}_${os}_${cpu}${ext}'"
eval "${cmd}"
}
function buildLocal() {
local ext cmd
ext=""
if [[ "${OSTYPE}" =~ win.* ]]; then
ext=".exe"
fi
cmd="go build -o '${PROGRAM_NAME}${ext}'"
eval "${cmd}"
}
function gitTag() {
local gopath gopkg mod
local tag
if ! tag=$(git tag -l --sort=-version:refname "v[0-9]*.[0-9]*.[0-9]*"|head -1) || [ -z "${tag}" ]; then
gopath=$(go env GOPATH)
gopkg="${gopath}/pkg/mod/git.portale-stac.it/go-pkg"
if cd "${gopkg}" 2>/dev/null; then
mod=$(ls -1v |grep expr@|tail -1)
tag=${mod##*@}
cd - >/dev/null
fi
fi
echo ${tag}
}
function gitTagDate() {
local tag_name=${1}
local tag_date
if ! tag_date=$(git show --no-patch --format=%ci "${tag_name}") || [ -z "${tag_date}" ]; then
tag_date="n/a"
fi
echo ${tag_date}
}
function createVersionSource() {
local tag tag_date
tag=$(gitTag)
if [ -z "${tag}" ]; then
tag="n/a"
else
tag_date=$(gitTagDate "${tag}")
fi
cat >version.go <<eot
// Copyright (c) 2024-$(date +%Y) Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// version.go
package main
const (
PROGNAME = "${PROGRAM_NAME}"
VERSION = "v${PROGRAM_VERSION}(build $(getBuildCount)),$(date +"%Y/%m/%d") (celestino.amoroso@portale-stac.it)"
EXPR_VERSION = "${tag}"
EXPR_DATE = "${tag_date}"
)
eot
}
## -- TEST -- ##
# echo "Tag: $(gitTag)"
# echo "Tag Date: $(gitTagDate $(gitTag))"
# exit 0
## -- MAIN -- ##
createVersionSource
if [ "${1}" == "--local" ]; then
buildLocal
exit
fi
if [ ${#} -gt 0 ]; then
for p; do
build "${p}"
done
else
for p in ${platform[@]}; do
build "${p}"
done
fi
-84
View File
@@ -1,84 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
# All rights reserved
RESOURCE_FILE=".build.rc"
BUILD_REGISTER=".build_register"
PASSWORD=
GITEA_USER="camoroso"
GITEA_PASSWORD_FILE="${HOME}/.gitea_password"
GITEA_OWNER="go-pkg"
GITEA_HOST="https://git.portale-stac.it"
GITEA_BASE_PATH="api/v1/packages"
GITEA_PKG_TYPE="generic"
function msgln() {
echo >&2 "${1}"
}
function exitMsg() {
echo >&2 "${1}"
exit 1
}
function readBuildCount() {
local reg ver count
if [ -r "${BUILD_REGISTER}" ]; then
reg=$(<"${BUILD_REGISTER}")
else
reg="${PROGRAM_VERSION} 0"
fi
read ver count <<<"${reg}"
if [ "${ver}" != "${PROGRAM_VERSION}" ]; then
count=0
fi
echo ${count}
}
if [ -r "${GITEA_PASSWORD_FILE}" ]; then
if ! PASSWORD=$(<"${GITEA_PASSWORD_FILE}"); then
exitMsg "Can're password file '${GITEA_PASSWORD_FILE}'"
fi
else
exitMsg "Password file '${GITEA_PASSWORD_FILE}' not found"
fi
if [ -z "${PASSWORD}" ]; then
exitMsg "Empty password. Please, check file '${GITEA_PASSWORD_FILE}'"
fi
if [ -r "${RESOURCE_FILE}" ]; then
source "${RESOURCE_FILE}"
else
exitMsg "resource file '${RESOURCE_FILE}' not found"
fi
if [ -r "${BUILD_REGISTER}" ]; then
BUILD_TAG=$(<"${BUILD_REGISTER}")
else
exitMsg "build register file '${BUILD_REGISTER}' not found"
fi
url="${GITEA_HOST}/${GITEA_BASE_PATH}/${GITEA_OWNER}/${GITEA_PKG_TYPE}/${PROGRAM_NAME}/${PROGRAM_VERSION}/files"
#echo "URL: ${url}"
#echo $(curl --user "${GITEA_USER}:${PASSWORD}" -X GET ${url}|jq '.[]."name"')
declare -a files=(
$(curl --no-progress-meter --user "${GITEA_USER}:${PASSWORD}" -X GET ${url}|jq '.[]."name"')
)
for name in ${files[@]}; do
filename=${name:1:${#name}-2}
name_terminal=${filename##*_}
filever=${name_terminal%%.*}
if [ "${BUILD_TAG}" != "${PROGRAM_VERSION} ${filever}" ]; then
msgln "Deleting ${name}"
curl --no-progress-meter --user "${GITEA_USER}:${PASSWORD}" -X DELETE ${GITEA_HOST}/api/packages/${GITEA_OWNER}/${GITEA_PKG_TYPE}/${PROGRAM_NAME}/${PROGRAM_VERSION}/${filename}
# else
# echo "most recent version"
fi
done
#curl --user "${GITEA_USER}:${PASSWORD}" -X GET https://git.portale-stac.it/api/v1/packages/go-pkg/generic/ecli/1.7.0/files
-236
View File
@@ -1,236 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// commands.go
package main
import (
"fmt"
"io"
"os"
"strings"
"git.portale-stac.it/go-pkg/expr"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/utils"
)
type commandFunction func(opt *Options, ctx kern.ExprContext, args []string) (err error)
type command struct {
name string
description string
code commandFunction
}
func (cmd *command) exec(opt *Options, ctx kern.ExprContext, args []string) (err error) {
return cmd.code(opt, ctx, args)
}
type commandHandler struct {
cmdIndex []string
commands map[string]*command
}
func NewCommandHandler() *commandHandler {
return &commandHandler{
cmdIndex: make([]string, 0, 20),
commands: make(map[string]*command),
}
}
// func (h *commandHandler) setContext(ctx expr.ExprContext) {
// h.ctx = ctx
// }
func (h *commandHandler) add(name, description string, f commandFunction) {
h.cmdIndex = append(h.cmdIndex, name)
h.commands[name] = &command{name: name, description: description, code: f}
}
func (h *commandHandler) get(cmdLine string) (cmd *command, args []string) {
if len(cmdLine) > 0 {
tokens := strings.Split(cmdLine, " ")
name := tokens[0]
args = make([]string, 0, len(tokens)-1)
if cmd = h.commands[name]; cmd != nil && len(tokens) > 1 {
for _, tk := range tokens[1:] {
if tk != "" {
args = append(args, tk)
}
}
}
}
return
}
// ------
var cmdHandler *commandHandler
func (h *commandHandler) help() {
fmt.Fprintln(os.Stderr, `--- REPL commands:`)
for _, name := range h.cmdIndex {
cmd := h.commands[name]
fmt.Fprintf(os.Stderr, "%12s -- %s\n", cmd.name, cmd.description)
}
fmt.Fprint(os.Stderr, `
--- Command line options:
-b <builtin> Import builtin modules.
<builtin> can be a list of module names or a glob-pattern.
Use the special value 'all' or the pattern '*' to import all modules.
-B, --list-builtins List all builtin module names
-e <expression> Evaluate <expression> instead of standard-input
-i Force REPL operation when all -e occurences have been processed
-h, --help, help Show this help menu
-m, --modules List all builtin modules
--noout Disable printing of expression results
-p Print prefix form
-t Print tree form
-v, --version Show program version
`)
}
// --------
func cmdExit(opt *Options, ctx kern.ExprContext, args []string) (err error) {
return io.EOF
}
func cmdHelp(opt *Options, ctx kern.ExprContext, args []string) (err error) {
cmdHandler.help()
return
}
func cmdMultiLine(opt *Options, ctx kern.ExprContext, args []string) (err error) {
if opt.formOpt&kern.MultiLine == 0 {
opt.formOpt |= kern.MultiLine
} else {
opt.formOpt &= ^kern.MultiLine
}
return
}
func cmdTty(opt *Options, ctx kern.ExprContext, args []string) (err error) {
if opt.formOpt&kern.TTY == 0 {
opt.formOpt |= kern.TTY
} else {
opt.formOpt &= ^kern.TTY
}
return
}
func execFile(opt *Options, ctx kern.ExprContext, fileName string) (err error) {
var fh *os.File
if fh, err = os.Open(fileName); err == nil {
goBatch(opt, ctx, fh, false)
fh.Close()
}
return
}
func cmdSource(opt *Options, ctx kern.ExprContext, args []string) (err error) {
var target string
for _, arg := range args {
if len(arg) == 0 {
continue
}
// TODO migliorare questa parte: eventualmente valutare un'espressione
if target, err = checkStringLiteral(arg); err != nil {
break
}
if target, err = utils.ExpandPath(target); err != nil {
break
}
if isPattern(target) {
var fileNames []string
if fileNames, err = matchPathPattern(target); err == nil {
for _, fileName := range fileNames {
if err = execFile(opt, ctx, fileName); err != nil {
break
}
}
}
} else {
err = execFile(opt, ctx, target)
}
if err != nil {
break
}
}
return
}
func cmdModules(opt *Options, ctx kern.ExprContext, args []string) (err error) {
expr.IterateBuiltinModules(func(name, description string, imported bool) bool {
var check rune = ' '
if imported {
check = '*'
}
fmt.Printf("%c %20q: %s\n", check, name, description)
return true
})
return
}
func cmdBase(opt *Options, ctx kern.ExprContext, args []string) (err error) {
if len(args) == 0 {
fmt.Println(opt.base)
} else if args[0] == "2" {
opt.baseVerb = "0b%b"
opt.base = 2
} else if args[0] == "8" {
opt.baseVerb = "0o%o"
opt.base = 8
} else if args[0] == "10" {
opt.baseVerb = "%d"
opt.base = 10
} else if args[0] == "16" {
opt.baseVerb = "0x%x"
opt.base = 16
} else {
err = fmt.Errorf("invalid number base %s", args[0])
}
return
}
func cmdOutput(opt *Options, ctx kern.ExprContext, args []string) (err error) {
var outputArg string
if len(args) == 0 {
outputArg = "status"
} else {
outputArg = strings.ToLower(args[0])
}
switch outputArg {
case "on":
opt.output = true
case "off":
opt.output = false
case "status":
if opt.output {
fmt.Println("on")
} else {
fmt.Println("off")
}
default:
err = fmt.Errorf("output: unknown option %q", outputArg)
}
return
}
//------------------
func setupCommands() {
cmdHandler = NewCommandHandler()
cmdHandler.add("base", "Set the integer output base: 2, 8, 10, or 16", cmdBase)
cmdHandler.add("exit", "Exit the program", cmdExit)
cmdHandler.add("help", "Show command list", cmdHelp)
cmdHandler.add("ml", "Enable/Disable multi-line output", cmdMultiLine)
cmdHandler.add("mods", "List builtin modules", cmdModules)
cmdHandler.add("output", "Enable/Disable printing expression results. Options 'on', 'off', 'status'", cmdOutput)
cmdHandler.add("source", "Load a file as input", cmdSource)
cmdHandler.add("tty", "Enable/Disable ansi output", cmdTty)
}
-314
View File
@@ -1,314 +0,0 @@
= Ecli
Expression Calculator Interactive Tool
:authors: Celestino Amoroso
:email: celestino.amoroso@gmail.com
:docinfo: shared
:encoding: utf-8
:toc: right
:toclevels: 4
:icons: font
:icon-set: fi
:numbered:
:data-uri:
:docinfo1:
:sectlinks:
:sectanchors:
:source-highlighter: rouge
:rouge-style: manni
:stylesdir: /home/share/s3-howto/styles
:stylesheet: adoc-colony.css
// Workaround to manage double-column in back-tick quotes
:2c: ::
// Workaround to manage double-plus in back-tick quotes
:plusplus: ++
// Workaround to manage asterisk in back-tick quotes
:star: *
#Generated by Copilot#
== Overview
`ecli` (Expression Calculator Interactive Tool) is an interactive REPL (Read-Eval-Print Loop) application for evaluating expressions using the Expr package. It provides a powerful command-line interface for expression evaluation with support for multiple builtin modules, file operations, and interactive scripting.
The tool combines the expression evaluation capabilities of the Expr package with an interactive shell environment, making it ideal for:
- Interactive expression testing and prototyping
- Batch expression evaluation from scripts
- Data processing and transformation
- Mathematical computations with fractions and complex operators
== Getting Started
=== Installation
To build and install `ecli`:
[source,bash]
----
cd cmd/ecli
./build.bash
----
The compiled binary will be available as `ecli` in the current directory.
=== Basic Usage
Start the interactive REPL:
[source,bash]
----
./ecli
----
You'll see the prompt `>>> ` where you can enter expressions to evaluate.
=== Command Line Options
[cols="1,4", options="header"]
|===
| Option | Description
| `-e <expression>` | Evaluate an expression directly without entering REPL mode
| `-i` | Force REPL operation after processing all `-e` options
| `-b <builtin>` | Import builtin modules (comma-separated list, glob patterns, or 'all')
| `-B, --list-builtins` | List all available builtin module names
| `-m, --modules` | List all builtin modules
| `-p` | Print expressions in prefix form
| `-t` | Print expressions in tree form
| `--noout` | Disable printing of expression results
| `-h, --help` | Show help message
| `-v, --version` | Show program version
|===
== Interactive Commands
Within the REPL, you can use the following commands:
[cols="2,5", options="header"]
|===
| Command | Description
| `help` | Display available commands and command-line options
| `exit` | Exit the REPL
| `multiline` | Toggle multi-line input mode for complex expressions
| `tty` | Toggle TTY mode
| `source <file>` | Execute expressions from a file
|===
== Features
=== Expression Evaluation
`ecli` supports the full expression language provided by the Expr package, including:
- **Arithmetic Operations**: Addition, subtraction, multiplication, division, modulo
- **Bitwise Operations**: AND, OR, XOR, NOT, shift operations
- **Boolean Logic**: AND, OR, NOT operations
- **Relational Operators**: Comparison and equality operators
- **String Operations**: Concatenation and string manipulation
- **Iterators**: Range, list, and custom iterators
- **Functions**: Builtin and user-defined functions
- **Collections**: Lists, dictionaries, and linked lists
- **Fractions**: Support for fractional arithmetic
=== Builtin Modules
`ecli` provides access to various builtin modules through the `-b` option:
[cols="1,4", options="header"]
|===
| Module | Functionality
| `base` | Core expression evaluation functions
| `fmt` | String formatting and output functions
| `string` | String manipulation functions
| `math-arith` | Mathematical and arithmetic operations
| `iterator` | Iterator-related functions
| `os-file` | File I/O operations
| `import` | Module import functionality
|===
Use `-B` or `--list-builtins` to see all available modules:
[source,bash]
----
./ecli --list-builtins
----
== Examples
=== Basic Arithmetic
[source,bash]
----
>>> 2 + 3 * 4
14
>>> (2 + 3) * 4
20
----
=== String Operations
[source,bash]
----
>>> "hello" + " " + "world"
hello world
----
=== Using Iterators
[source,bash]
----
>>> [1, 2, 3, 4, 5] | map(. * 2)
[2, 4, 6, 8, 10]
----
=== Evaluating from Command Line
[source,bash]
----
./ecli -e "2 + 2"
4
----
=== Loading Builtin Modules
[source,bash]
----
./ecli -b "math-arith,string"
----
=== Loading Expressions from Files
Inside the REPL:
[source]
----
>>> source "expressions.expr"
----
Or from command line:
[source,bash]
----
./ecli -e '@include "expressions.expr"'
----
== Configuration
=== Resource Files
`ecli` supports startup resource files:
- `.ecli.rc` - Main configuration file
- `.ecli.rc.d/` - Directory for modular configuration files
These files are automatically loaded at startup if they exist in the current directory or home directory.
== Building from Source
=== Prerequisites
- Go 1.18 or later
- Make or bash shell
=== Build Steps
[source,bash]
----
cd cmd/ecli
./build.bash
----
=== Build Artifacts
The build process generates:
- `ecli` - The main executable
- `version.txt` - Version information
- Platform-specific binaries (e.g., `ecli_v1.17.0_linux_amd64`, `ecli_v1.17.0_darwin_arm64`)
== Advanced Usage
=== Multi-line Input
For complex expressions, toggle multi-line mode:
[source]
----
>>> multiline
>>> result = [1, 2, 3, 4, 5]
... | filter(. > 2)
... | map(. * 2)
>>> result
[6, 8, 10]
----
=== Script Execution
Create a file `calculations.expr`:
[source]
----
x = 10
y = 20
result = x + y * 2
----
Execute it:
[source,bash]
----
./ecli -e '@source "calculations.expr"' -e 'result'
----
=== Chaining Operations
[source]
----
>>> data = [{"name": "alice", "age": 30}, {"name": "bob", "age": 25}]
>>> data | map(.name)
[alice, bob]
----
== Troubleshooting
=== Expression Parsing Errors
If you encounter parsing errors, check:
- Bracket matching and quotation marks
- Operator precedence
- Variable and function names
=== Module Loading Issues
Verify available modules:
[source,bash]
----
./ecli --list-builtins
----
=== File Not Found Errors
Ensure file paths are:
- Properly quoted in expressions
- Relative to the current working directory or absolute paths
- Readable by the current user
== Related Documentation
- link:../../../README.adoc[Expr Package Documentation]
- Expr Expression Language Syntax
- Builtin Modules Reference
== Version History
For version information and changes, see the link:version.txt[version file].
== License
Copyright (c) 2024-2026 Celestino Amoroso (celestino.amoroso@gmail.com). All rights reserved.
-15
View File
@@ -1,15 +0,0 @@
module ecli
go 1.24.0
require (
git.portale-stac.it/go-pkg/expr v0.33.0
git.portale-stac.it/go-pkg/utils v0.3.0
github.com/ergochat/readline v0.1.3
)
require (
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.31.0 // indirect
)
-36
View File
@@ -1,36 +0,0 @@
git.portale-stac.it/go-pkg/expr v0.1.0 h1:7xGEuUhdh6RRFaRbRnLVqVJBmHJWHfqjDBm2K0fIW2s=
git.portale-stac.it/go-pkg/expr v0.1.0/go.mod h1:kUFEQkUMCJ1IiUKkL0P5/vznaAIzFI26Xf5P0rTXqR0=
git.portale-stac.it/go-pkg/expr v0.2.0 h1:AAaVsV0uaC4EikKU91VuubIpbIN7wuya7t4avyFgg+0=
git.portale-stac.it/go-pkg/expr v0.2.0/go.mod h1:DZqqZ3A9h4qEOs7yMvG4VZq7B/xhFsYqC3IKd3M2VKc=
git.portale-stac.it/go-pkg/expr v0.17.0 h1:4ANGwJfwJO3AmnKka4Cf1AO9/ckGLMj8RIWeoDFKawQ=
git.portale-stac.it/go-pkg/expr v0.17.0/go.mod h1:DZqqZ3A9h4qEOs7yMvG4VZq7B/xhFsYqC3IKd3M2VKc=
git.portale-stac.it/go-pkg/expr v0.32.0 h1:ikXqHjJslIGkD79G1/51xe+c25TFi2CslJ6nu8mOuJY=
git.portale-stac.it/go-pkg/expr v0.32.0/go.mod h1:R2TYIahLtD8YDgNEHtgHCQdoEUZ7yCQsMHyJXhJijmw=
git.portale-stac.it/go-pkg/expr v0.33.0 h1:GJ7PPgA1689GSC/cUWGYm08jn7qMmkp0FMQf/As5sCw=
git.portale-stac.it/go-pkg/expr v0.33.0/go.mod h1:R2TYIahLtD8YDgNEHtgHCQdoEUZ7yCQsMHyJXhJijmw=
git.portale-stac.it/go-pkg/utils v0.2.0 h1:2l4IVUhElzjaIUJlahPG2DZTGb9x7OXuFTO4z1K6LmY=
git.portale-stac.it/go-pkg/utils v0.2.0/go.mod h1:PebQ45Qbe89aMTd3wcbcx1bkpNRW4/frNLnpuyZYovU=
git.portale-stac.it/go-pkg/utils v0.3.0 h1:kCJ3+XcekV7in/SieJjiswdtJKMBS0RTJMlG2fW5mK0=
git.portale-stac.it/go-pkg/utils v0.3.0/go.mod h1:PebQ45Qbe89aMTd3wcbcx1bkpNRW4/frNLnpuyZYovU=
github.com/ergochat/readline v0.1.0 h1:KEIiAnyH9qGZB4K8oq5mgDcExlEKwmZDcyyocgJiABc=
github.com/ergochat/readline v0.1.0/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY=
github.com/ergochat/readline v0.1.1 h1:C8Uuo3ybB23GWOt0uxmHbGzKM9owmtXary6Clrj84s0=
github.com/ergochat/readline v0.1.1/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY=
github.com/ergochat/readline v0.1.3 h1:/DytGTmwdUJcLAe3k3VJgowh5vNnsdifYT6uVaf4pSo=
github.com/ergochat/readline v0.1.3/go.mod h1:o3ux9QLHLm77bq7hDB21UTm6HlV2++IPDMfIfKDuOgY=
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
-18
View File
@@ -1,18 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
//go:build graph
// graph.go
package main
import (
"fmt"
"git.portale-stac.it/go-pkg/expr"
)
func printGraph() {
r := expr.NewExprReticle(ast)
fmt.Println(r.String())
}
-415
View File
@@ -1,415 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// main.go
package main
import (
"bufio"
"fmt"
"io"
"os"
"strings"
"git.portale-stac.it/go-pkg/expr"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/expr/scan"
"git.portale-stac.it/go-pkg/utils"
// https://pkg.go.dev/github.com/ergochat/readline#section-readme
"github.com/ergochat/readline"
)
const (
intro = PROGNAME + ` -- Expressions calculator ` + VERSION + `
Based on the Expr package ` + EXPR_VERSION + ` (` + EXPR_DATE + `)
Type help to get the list of available commands
See also https://git.portale-stac.it/go-pkg/expr/src/branch/main/README.adoc
`
mainPrompt = ">>> "
contPrompt = "... "
historyFile = "~/.expr_history"
)
// ------
func errOptValueRequired(opt string) error {
return fmt.Errorf("option %q requires a value", opt)
}
func about() string {
return PROGNAME + " -- " + VERSION + "; Expr package " + EXPR_VERSION
}
func importBuiltins(ctx kern.ExprContext, opt *Options) (err error) {
for _, spec := range opt.builtin {
if moduleSpec, ok := spec.(string); ok {
if moduleSpec == "all" {
moduleSpec = "*"
}
_, err = expr.ImportInContextByGlobPattern(ctx, moduleSpec)
} else if moduleSpec, ok := spec.([]string); ok {
notFoundList := make([]string, 0)
for _, name := range moduleSpec {
if !expr.ImportInContext(ctx, name) {
notFoundList = append(notFoundList, name)
}
}
if len(notFoundList) > 0 {
err = fmt.Errorf("not found modules: %s", strings.Join(notFoundList, ","))
}
}
}
return
}
func initReadlineConfig(cfg *readline.Config) {
if histfile, err := utils.ExpandPath(historyFile); err == nil {
cfg.HistoryFile = histfile
}
cfg.Undo = true
cfg.DisableAutoSaveHistory = true
}
func goInteractiveReadline(opt *Options, ctx kern.ExprContext, r io.Reader) {
var sb strings.Builder
var cfg readline.Config
initReadlineConfig(&cfg)
rl, err := readline.NewFromConfig(&cfg)
if err != nil {
goInteractive(opt, ctx, r)
return
}
defer rl.Close()
fmt.Print(intro)
rl.SetPrompt(mainPrompt)
for line, err := rl.ReadLine(); err == nil; line, err = rl.ReadLine() {
if continuation(&sb, line) {
rl.SetPrompt(contPrompt)
continue
}
rl.SetPrompt(mainPrompt)
sb.WriteString(line)
source := strings.TrimSpace(sb.String())
if source != "" && !strings.HasPrefix(source, "//") {
if cmd, args := cmdHandler.get(source); cmd != nil {
rl.SaveToHistory(source)
if err = cmd.exec(opt, ctx, args); err != nil {
if err == io.EOF {
err = nil
break
} else {
fmt.Fprintln(os.Stderr, "Eval Error:", err)
break
}
}
} else {
rl.SaveToHistory(source)
r := strings.NewReader(source)
compute(opt, ctx, r, true)
}
}
sb.Reset()
}
fmt.Println()
}
func goInteractive(opt *Options, ctx kern.ExprContext, r io.Reader) {
var sb strings.Builder
fmt.Print(intro)
fmt.Print(mainPrompt)
reader := bufio.NewReaderSize(r, 1024)
for line, err := reader.ReadString('\n'); err == nil && line != "exit\n"; line, err = reader.ReadString('\n') {
if continuation(&sb, line) {
continue
}
sb.WriteString(line)
source := strings.TrimSpace(sb.String())
// fmt.Printf("source=%q\n", source)
if source != "" && !strings.HasPrefix(source, "//") {
if cmd, args := cmdHandler.get(source); cmd != nil {
if err = cmd.exec(opt, ctx, args); err != nil {
break
}
} else {
r := strings.NewReader(source)
compute(opt, ctx, r, true)
}
}
sb.Reset()
fmt.Print(mainPrompt)
}
fmt.Println()
}
func continuation(sb *strings.Builder, line string) (cont bool) {
line = strings.TrimSpace(line)
if strings.HasSuffix(line, "\\") {
sb.WriteString(line[0 : len(line)-1])
cont = true
} else if strings.HasSuffix(line, ";") {
sb.WriteString(line)
cont = true
} else if len(line) > 0 {
if scan.StringEndsWithOperator(line) {
sb.WriteString(line)
cont = true
} else {
fullInput := sb.String() + line
if strings.Count(fullInput, "(") > strings.Count(fullInput, ")") ||
strings.Count(fullInput, "[") > strings.Count(fullInput, "]") ||
strings.Count(fullInput, "{") > strings.Count(fullInput, "}") {
sb.WriteString(line)
cont = true
}
}
}
return
}
func goBatch(opt *Options, ctx kern.ExprContext, r io.Reader, outputEnabled bool) {
var sb strings.Builder
reader := bufio.NewReaderSize(r, 1024)
for line, err := reader.ReadString('\n'); err == nil && line != "exit\n"; line, err = reader.ReadString('\n') {
if continuation(&sb, line) {
continue
}
sb.WriteString(line)
source := strings.TrimSpace(sb.String())
// fmt.Printf("source=%q\n", source)
if source != "" && !strings.HasPrefix(source, "//") {
if cmd, args := cmdHandler.get(source); cmd != nil {
if err = cmd.exec(opt, ctx, args); err != nil {
fmt.Fprintln(os.Stderr, "Eval Error:", err)
break
}
} else {
r := strings.NewReader(source)
compute(opt, ctx, r, outputEnabled)
}
}
sb.Reset()
}
}
func compute(opt *Options, ctx kern.ExprContext, r io.Reader, outputEnabled bool) {
scanner := scan.NewScanner(r, scan.DefaultTranslations())
parser := expr.NewParser()
if ast, err := parser.Parse(scanner); err == nil {
if opt.printPrefix {
fmt.Println(ast)
}
if opt.printTree {
printGraph()
}
if result, err := ast.Eval(ctx); err == nil {
if outputEnabled && opt.output {
printResult(opt, result)
}
} else {
fmt.Fprintln(os.Stderr, "Eval Error:", err)
}
} else {
fmt.Fprintln(os.Stderr, "Parse Error:", err)
}
}
func printResult(opt *Options, result any) {
if f, ok := result.(kern.Formatter); ok {
fmt.Println(f.ToString(opt.formOpt))
} else if kern.IsInteger(result) {
fmt.Printf(opt.baseVerb, result)
fmt.Println()
} else if kern.IsString(result) {
fmt.Printf("\"%s\"\n", result)
} else {
fmt.Println(result)
}
}
func isReaderTerminal(r io.Reader) bool {
if fh, ok := r.(*os.File); ok {
return utils.StreamIsTerminal(fh)
}
return false
}
func registerLocalFunctions(ctx kern.ExprContext) {
const (
devParamProp = "prop"
devParamDigits = "digits"
)
aboutFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
result = about()
return
}
ctrlListFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
vars := ctx.EnumVars(func(name string) bool {
return len(name) > 0 && name[0] == '_'
})
result = kern.ListFromStrings(vars)
return
}
ctrlFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
varName, _ := args[devParamProp].(string)
if len(args) == 1 {
result = expr.GlobalCtrlGet(ctx, varName)
} else {
result = expr.GlobalCtrlSet(ctx, varName, args[kern.ParamValue])
}
return
}
envSetFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var varName, value string
var ok bool
if varName, ok = args[kern.ParamName].(string); !ok {
err = kern.ErrExpectedGot(name, kern.TypeString, args[kern.ParamName])
return
}
if value, ok = args[kern.ParamValue].(string); !ok {
err = kern.ErrExpectedGot(name, kern.TypeString, args[kern.ParamValue])
return
}
if err = os.Setenv(varName, value); err == nil {
result = value
}
return
}
envGetFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var varName string
var ok bool
if varName, ok = args[kern.ParamName].(string); !ok {
err = kern.ErrExpectedGot(name, kern.TypeString, args[kern.ParamName])
return
}
if result, ok = os.LookupEnv(varName); !ok {
err = fmt.Errorf("environment variable %q does not exist", varName)
}
return
}
envListFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
env := os.Environ()
vars := make([]string, 0, len(env))
for _, e := range env {
name, _, _ := strings.Cut(e, "=")
vars = append(vars, name)
}
result = kern.ListFromStrings(vars)
return
}
binFunc := func(ctx kern.ExprContext, name string, args map[string]any) (result any, err error) {
var value, digits int64
var ok bool
var sb strings.Builder
if value, ok = args[kern.ParamValue].(int64); !ok {
err = kern.ErrExpectedGot(name, kern.TypeInt, args[kern.ParamValue])
return
}
if digits, ok = args[devParamDigits].(int64); !ok {
err = kern.ErrExpectedGot(name, kern.TypeInt, args[devParamDigits])
return
}
if digits != 64 && digits != 32 && digits != 16 && digits != 8 {
err = fmt.Errorf("%s param allows 8, 16, 32, or 64 values only", devParamDigits)
return
}
mask := uint64(0)
for i := 0; i < int(digits); i++ {
mask |= (1 << i)
}
maskedValue := uint64(value) & mask
// if maskedValue != uint64(value) {
// err = fmt.Errorf("%s param (%d) is not compatible with the value (%d) of %s param", expr.ParamValue, value, digits, devParamDigits)
// return
// }
for i := int(digits) - 1; i >= 0; i-- {
if maskedValue&(1<<i) == 0 {
sb.WriteByte('0')
} else {
sb.WriteByte('1')
}
}
result = sb.String()
return
}
ctx.RegisterFunc("about", kern.NewGolangFunctor(aboutFunc), kern.TypeString, []kern.ExprFuncParam{})
ctx.RegisterFunc("ctrlList", kern.NewGolangFunctor(ctrlListFunc), kern.TypeListOfStrings, []kern.ExprFuncParam{})
ctx.RegisterFunc("ctrl", kern.NewGolangFunctor(ctrlFunc), kern.TypeAny, []kern.ExprFuncParam{
kern.NewFuncParam(devParamProp),
kern.NewFuncParamFlag(kern.ParamValue, kern.PfOptional),
})
ctx.RegisterFunc("envSet", kern.NewGolangFunctor(envSetFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamName),
kern.NewFuncParam(kern.ParamValue),
})
ctx.RegisterFunc("envGet", kern.NewGolangFunctor(envGetFunc), kern.TypeString, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamName),
})
ctx.RegisterFunc("envList", kern.NewGolangFunctor(envListFunc), kern.TypeListOfStrings, []kern.ExprFuncParam{})
ctx.RegisterFunc("bin", kern.NewGolangFunctor(binFunc), kern.TypeInt, []kern.ExprFuncParam{
kern.NewFuncParam(kern.ParamValue),
kern.NewFuncParamFlagDef(devParamDigits, kern.PfOptional|kern.PfDefault, int64(8)),
})
}
func main() {
setupCommands()
opt := NewOptions()
opt.loadRc()
if err := opt.parseArgs(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
ctx := expr.NewSimpleStore()
if err := importBuiltins(ctx, opt); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
registerLocalFunctions(ctx)
if len(opt.expressions) == opt.rcCount || opt.forceInteractive {
opt.expressions = append(opt.expressions, os.Stdin)
}
for _, input := range opt.expressions {
if isReaderTerminal(input) {
goInteractiveReadline(opt, ctx, input)
} else {
_, enableOutput := input.(*strings.Reader)
goBatch(opt, ctx, input, enableOutput)
if f, ok := input.(*os.File); ok {
f.Close()
}
}
}
// TODO: why did I added these lines?
// if opt.output {
// printResult(opt, ctx.GetLast())
// }
}
-49
View File
@@ -1,49 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// match.go
package main
import (
"os"
"path"
"path/filepath"
"strings"
)
func matchFilePattern(dirName string, pattern string, join bool) (fileList []string, err error) {
var entries []os.DirEntry
if entries, err = os.ReadDir(dirName); err == nil {
for _, entry := range entries {
if entry.Type().IsRegular() {
var match bool
if match, err =filepath.Match(pattern, entry.Name()); err != nil {
fileList = nil
break
}
if match {
if fileList == nil {
fileList = make([]string, 0, 1)
}
if join {
fileList = append(fileList, path.Join(dirName, entry.Name()))
} else {
fileList = append(fileList, entry.Name())
}
}
}
}
}
return
}
func matchPathPattern(pathPattern string) (fileList []string, err error) {
dirName := path.Dir(pathPattern)
pattern := path.Base(pathPattern)
return matchFilePattern(dirName, pattern, true)
}
func isPattern(name string) bool{
return strings.ContainsAny(name, "*?[]")
}
-36
View File
@@ -1,36 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// main.go
package main
import (
"path"
"slices"
"testing"
)
func TestIsPattern(t *testing.T) {
target := "non-pattern"
if isPattern(target) {
t.Errorf("%q recognized as a pattern", target)
}
target = "pattern/*.expr"
if !isPattern(target) {
t.Errorf("%q not recognized as a pattern", target)
}
}
func TestMatchFilePattern(t *testing.T) {
target := "./go.*sum"
dirName := path.Dir(target)
pattern := path.Base(target)
if matchedFiles, err := matchFilePattern(dirName, pattern, true); err == nil {
if slices.Compare(matchedFiles, []string{"go.sum", "go.work.sum"}) != 0 {
t.Errorf("Matched file list is not correct: %v", matchedFiles)
}
} else {
t.Errorf("Got error: %v", err)
}
}
-140
View File
@@ -1,140 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// options.go
package main
import (
"fmt"
"io"
"os"
"strings"
"git.portale-stac.it/go-pkg/expr"
"git.portale-stac.it/go-pkg/expr/kern"
"git.portale-stac.it/go-pkg/utils"
)
type Options struct {
printTree bool
printPrefix bool
forceInteractive bool
builtin []any
expressions []io.Reader
formOpt kern.FmtOpt
baseVerb string
base int
output bool
rcCount int
}
func NewOptions() *Options {
return &Options{
expressions: make([]io.Reader, 0),
builtin: make([]any, 0),
formOpt: kern.Base10,
baseVerb: "%d",
base: 10,
output: true,
rcCount: 0,
}
}
func (opt *Options) loadRc() {
var rcPath string
var fh *os.File
var err error
rcList := []string{
".ecli.rc",
"~/.ecli.rc",
"~/.config/expr/ecli.rc",
"~/.dev-expr.rc", // OBSOLETE, to be removed in future releases
}
for _, rcFile := range rcList {
if rcPath, err = utils.ExpandPath(rcFile); err != nil {
return
}
if fh, err = os.Open(rcPath); err == nil {
opt.expressions = append(opt.expressions, fh) // rc should be the first source to be read
opt.rcCount++
break
}
}
}
func listBuiltins() {
expr.IterateBuiltinModules(func(name, description string, imported bool) bool {
if imported {
name = "*" + name
}
fmt.Printf("%20q: %s\n", name, description)
return true
})
}
func (opt *Options) parseArgs() (err error) {
for i := 1; i < len(os.Args) && err == nil; i++ {
arg := os.Args[i]
switch arg {
case "-i":
opt.forceInteractive = true
case "-t":
opt.printTree = true
case "-p":
opt.printPrefix = true
case "-e":
if i+1 < len(os.Args) {
i++
spec := os.Args[i]
if strings.HasPrefix(spec, "@") {
var f *os.File
if f, err = os.Open(spec[1:]); err == nil {
opt.expressions = append(opt.expressions, f)
} else {
return
}
} else {
if len(spec) > 0 && spec[len(spec)-1] != '\n' {
spec += "\n"
}
opt.expressions = append(opt.expressions, strings.NewReader(spec))
}
} else {
err = errOptValueRequired(arg)
}
case "-b":
if i+1 < len(os.Args) {
i++
specs := strings.Split(os.Args[i], ",")
if len(specs) == 1 {
opt.builtin = append(opt.builtin, specs[0])
} else {
opt.builtin = append(opt.builtin, specs)
}
} else {
err = errOptValueRequired(arg)
}
case "-B", "--list-builtins":
listBuiltins()
os.Exit(0)
case "-m", "--modules":
expr.IterateBuiltinModules(func(name, description string, _ bool) bool {
fmt.Printf("%20q: %s\n", name, description)
return true
})
os.Exit(0)
case "--noout":
opt.output = false
case "-h", "--help", "help":
cmdHandler.help()
os.Exit(0)
case "-v", "--version", "version", "about":
fmt.Println(about())
os.Exit(0)
default:
err = fmt.Errorf("invalid option nr %d %q", i+1, arg)
}
}
return
}
-79
View File
@@ -1,79 +0,0 @@
#!/usr/bin/env bash
# Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
# All rights reserved.
RESOURCE_FILE=".build.rc"
BUILD_REGISTER=".build_register"
PASSWORD=
GITEA_USER="camoroso"
GITEA_PASSWORD_FILE="${HOME}/.gitea_password"
GITEA_OWNER="go-pkg"
GITEA_HOST="https://git.portale-stac.it"
GITEA_BASE_PATH="api/packages"
GITEA_PKG_TYPE="generic"
function exitMsg() {
echo >&2 "${1}"
exit 1
}
function readBuildCount() {
local reg ver count
if [ -r "${BUILD_REGISTER}" ]; then
reg=$(<"${BUILD_REGISTER}")
else
reg="${PROGRAM_VERSION} 0"
fi
read ver count <<<"${reg}"
if [ "${ver}" != "${PROGRAM_VERSION}" ]; then
count=0
fi
echo ${count}
}
if [ -r "${GITEA_PASSWORD_FILE}" ]; then
if ! PASSWORD=$(<"${GITEA_PASSWORD_FILE}"); then
exitMsg "Can're password file '${GITEA_PASSWORD_FILE}'"
fi
else
exitMsg "Password file '${GITEA_PASSWORD_FILE}' not found"
fi
if [ -z "${PASSWORD}" ]; then
exitMsg "Empty password. Please, check file '${GITEA_PASSWORD_FILE}'"
fi
if ! ./build.bash; then
exitMsg "Build program failed"
fi
if ! source "${RESOURCE_FILE}"; then
exitMsg "Loading resource file failed"
fi
if ! exeList=$(echo 2>/dev/null ${PROGRAM_NAME}_v${PROGRAM_VERSION}_*); then
exitMsg "No executable found"
fi
buildCount=$(readBuildCount)
fileCount=0
for exe in ${exeList}; do
if [ "${exe/tar.gz/}" != "${exe}" ]; then
continue
fi
((fileCount++))
dir="${exe}_${buildCount}"
dist="${dir}.tar.gz"
rm -f "${dist}"
printf "%2d: %-30s --> %s\n" "${fileCount}" "${exe}" "${dist}"
mkdir "${dir}"
cp "${exe}" "${dir}/${PROGRAM_NAME}"
tar czf "${dist}" "${dir}"
rm -fR "${dir}"
url="${GITEA_HOST}/${GITEA_BASE_PATH}/${GITEA_OWNER}/${GITEA_PKG_TYPE}/${PROGRAM_NAME}/${PROGRAM_VERSION}/${dist}"
# echo "${url}"
curl --user "${USER}:${PASSWORD}" --upload-file "${dist}" "${url}"
rm -f "${dist}"
done
-23
View File
@@ -1,23 +0,0 @@
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.
// util-string.go
package main
import (
"fmt"
)
func checkStringLiteral(literal string) (value string, err error) {
length := len(literal)
if length >= 2 {
if (literal[0] == '"' && literal[length-1] == '"') || literal[0] == '\'' && literal[length-1] == '\'' {
value = literal[1 : length-1]
} else {
err = fmt.Errorf("unquoted or partially quoted string literal: `%s`", literal)
}
} else {
err = fmt.Errorf("invalid string literal: `%s`", literal)
}
return
}
-1
View File
@@ -1 +0,0 @@
1.17.0
+23 -21
View File
@@ -47,18 +47,18 @@ _Expr_ is a GO package that can analyze, interpret and calculate expressions.
Expressions are texts containing sequences of operations represented by a syntax very similar to that of most programming languages. _Expr_ package provides these macro functions: Expressions are texts containing sequences of operations represented by a syntax very similar to that of most programming languages. _Expr_ package provides these macro functions:
* *_Scanner_* -- Its input is a text. It scans expression text characters to produce a flow of logical symbol and related attributes, aka tokens. * *_Scanner_* -- Its input is a text. It scans expression text characters to produce a flow of logical symbol and related attributes, aka tokens.
* *_Parser_* -- Parser input is the token flow coming from the scanner. It analyses the token flow verifyng if it complies with the _Expr_ syntax. If that is the case, the Parser generates the Abstract Syntax Tree (AST). This is tree data structure that represents the components of an expressions and how they are related one each other. * *_Parser_* -- Parser input is the token flow coming from the scanner. It analyses the token flow verifying if it complies with the _Expr_ syntax. If that is the case, the Parser generates the Abstract Syntax Tree (AST). This is tree data structure that represents the components of an expression and how they are related to each other.
* *_Calculator_*. Its input is the AST. It computes the parsed expression contained in the AST and returns the result or an error. * *_Calculator_*. Its input is the AST. It computes the parsed expression contained in the AST and returns the result or an error.
image::expression-diagram.png[] image::expression-diagram.png[]
==== Variables ==== Variables
_Expr_ supports variables. The result of an expression can be stored in a variable and reused in other espressions by simply specifying the name of the variable as an operand. _Expr_ supports variables. The result of an expression can be stored in a variable and reused in other expressions by simply specifying the name of the variable as an operand.
==== Multi-expression ==== Multi-expression
An input text valid for _Expr_ can contain more than an expression. Expressions are separated by [blue]`;` (semicolon). When an input contains two or more expressions it is called _multi-expression_. An input text valid for _Expr_ can contain more than an expression. Expressions are separated by [blue]`;` (semicolon). When an input contains two or more expressions it is called _multi-expression_.
_Expr_ parses and computes each expression of a multi-espression, from the left to the right. If all expressions are computed without errors, it only returns the value of the last, the right most. _Expr_ parses and computes each expression of a multi-expression, from the left to the right. If all expressions are computed without errors, it only returns the value of the last, the right most.
The result of each expression of a multi-expression is stored in an automatic variable named _last_. In this way, each expression can refer to the result of the previous one without the need to assign that value to a new dedicated variable. The result of each expression of a multi-expression is stored in an automatic variable named _last_. In this way, each expression can refer to the result of the previous one without the need to assign that value to a new dedicated variable.
@@ -76,7 +76,7 @@ Imported functions are registered in the _global context_. When an expression fi
===== Inspecting contexts ===== Inspecting contexts
_Expr_ provides the operator [blue]_$$_ that returns the current context. This can be used to inspect the content of the context, for example to check the value of a variable or to see which functions are currently linked to the context. This operator is primarily intended for debugging purposes. _Expr_ provides the operator [blue]_$$_ that returns the current context. This can be used to inspect the content of the context, for example to check the value of a variable or to see which functions are currently linked to the context. This operator is primarily intended for debugging purposes.
An interactive tool could like `ecli` (see <<_ecli_test_tool>>) can be used to inspect contexts interactively. An interactive tool like `ecli` (see <<_ecli_test_tool>>) can be used to inspect contexts interactively.
.Example: inspecting contexts .Example: inspecting contexts
@@ -105,7 +105,7 @@ An interactive tool could like `ecli` (see <<_ecli_test_tool>>) can be used to i
In order to inspect the global context issue the [blue]`$$ global` operation. In order to inspect the global context issue the [blue]`$$ global` operation.
//// ////
.Example: list all functions whose name starts with "str" .Example: list all functions whose name starts with "str"
`>>>` [blue]`builtin "string` [gray]__// most function in the builtin module *string* have names starting with "str".__ + `>>>` [blue]`builtin "string` [gray]__// most functions in the builtin module *string* have names starting with "str".__ +
[green]`1` [green]`1`
:dollar: $ :dollar: $
@@ -129,7 +129,7 @@ In order to inspect the global context issue the [blue]`$$ global` operation.
=== `ecli` Expression Calculator Interactive Tool === `ecli` Expression Calculator Interactive Tool
Before we begin to describe the syntax of _Expr_, it is worth introducing _ecli_, former _dev-expr_, because it will be used to show many examples of expressions. Before we begin to describe the syntax of _Expr_, it is worth introducing _ecli_, former _dev-expr_, because it will be used to show many examples of expressions.
`ecli` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in additon to the automatic verification test suite based on the Go test framework, `ecli` provided an important aid for quickly testing of new features during their development. `ecli` is a simple program that can be used to evaluate expressions interactively. As its name suggests, it was created for testing purpose. In fact, in addition to the automatic verification test suite based on the Go test framework, `ecli` provided an important aid for quickly testing of new features during their development.
`ecli` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input. `ecli` can work as a _REPL_, _**R**ead-**E**xecute-**P**rint-**L**oop_, or it can process expression acquired from files or standard input.
@@ -257,7 +257,7 @@ Value range: *-9223372036854775808* to *9223372036854775807*
^(1)^ The sum operator [blue]`+` also supports adding an integer number to a string. In this case, the number is converted to a string and prependend or appended to the string, e.g. `"x" + 48` results in `"x48"`. ^(1)^ The sum operator [blue]`+` also supports adding an integer number to a string. In this case, the number is converted to a string and prependend or appended to the string, e.g. `"x" + 48` results in `"x48"`.
[[note_string_repl]] [[note_string_repl]]
^(2)^ The product operator also supports multiplying a string by an integer. In this case, the number represents homw may times the string has to be repeated in the result, e.g. `"foo" * 3` returnsn `"foofoofoo"`. ^(2)^ The product operator also supports multiplying a string by an integer. In this case, the number represents how many times the string has to be repeated in the result, e.g. `"foo" * 3` returns `"foofoofoo"`.
[[note_float_division]] [[note_float_division]]
^(3)^ See also the _float division_ [blue]`./` below. ^(3)^ See also the _float division_ [blue]`./` below.
@@ -396,7 +396,7 @@ Some arithmetic operators also apply to strings.
| [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_ | [blue]`*` | _repeat_ | Make _n_ copy of a string | [blue]`"one" * 2` -> _"oneone"_
|=== |===
The charanters in a string can be accessed using the square `[]` operator. The characters in a string can be accessed using the square `[]` operator.
.Item access syntax .Item access syntax
==== ====
@@ -419,7 +419,7 @@ The charanters in a string can be accessed using the square `[]` operator.
[green]`"d"` [green]`"d"`
`>>>` [blue]`#s` [gray]_// number of chars_ + `>>>` [blue]`#s` [gray]_// number of chars_ +
[gren]`4` [green]`4`
`>>>` [blue]`#"abc"` [gray]_// number of chars_ + `>>>` [blue]`#"abc"` [gray]_// number of chars_ +
[green]`3` [green]`3`
@@ -476,7 +476,7 @@ Currently, boolean operations are evaluated using _short cut evaluation_. This m
---- ----
2 > (a=1) or (a=8) > 0; a // <1> 2 > (a=1) or (a=8) > 0; a // <1>
---- ----
<1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and, as a conseguence, its right value is not computed. Therefore the _a_ variable only receives the integer _1_. <1> This multi-expression returns _1_ because in the first expression the left value of [blue]`or` is _true_ and, as a consequence, its right value is not computed. Therefore the _a_ variable only receives the integer _1_.
TIP: `ecli` provides the _ctrl()_ function that allows to change this behaviour. TIP: `ecli` provides the _ctrl()_ function that allows to change this behaviour.
@@ -724,7 +724,7 @@ The value of each sub-expression is stored in the automatic variable _last_.
[green]`2` [green]`2`
`>>>` [blue]`x=2*3 but x-1` + `>>>` [blue]`x=2*3 but x-1` +
[green]`5`. [green]`5`
[blue]`but` behavior is very similar to [blue]`;`. The only difference is that [blue]`;` is not a true operator and can't be used inside parenthesis [blue]`(` and [blue]`)`. [blue]`but` behavior is very similar to [blue]`;`. The only difference is that [blue]`;` is not a true operator and can't be used inside parenthesis [blue]`(` and [blue]`)`.
@@ -804,7 +804,7 @@ The triple special case of the selector operator is very useful, but it only wor
==== ====
=== Variable default value [blue]`??`, [blue]`?=`, and [blue]`?!` === Variable default value [blue]`??`, [blue]`?=`, and [blue]`?!`
The left operand of the first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operatand can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression. The left operand of the first two operators, [blue]`??` and [blue]`?=`, must be a variable. The right operand can be any expression. They return the value of the variable if this is defined; otherwise they return the value of the right expression.
IMPORTANT: If the left variable is defined, the right expression is not evaluated at all. IMPORTANT: If the left variable is defined, the right expression is not evaluated at all.
@@ -812,7 +812,7 @@ The [blue]`??` operator do not change the status of the left variable.
The [blue]`?=` assigns the calculated value of the right expression to the variable on the left side. The [blue]`?=` assigns the calculated value of the right expression to the variable on the left side.
The third one, [blue]`?!`, is the alternate operator. If the variable on the left size is not defined, it returns [blue]_nil_. Otherwise it returns the result of the expressione on the right side. The third one, [blue]`?!`, is the alternate operator. If the variable on the left side is not defined, it returns [blue]_nil_. Otherwise it returns the result of the expression on the right side.
IMPORTANT: If the variable [blue]`?!` is NOT defined, the expression is not evaluated at all. IMPORTANT: If the variable [blue]`?!` is NOT defined, the expression is not evaluated at all.
@@ -914,7 +914,7 @@ The table below shows all supported operators by decreasing priorities.
//^1^ Experimental //^1^ Experimental
.Special assignment perators .Special assignment operators
[cols="^2,^2,^4,^6"] [cols="^2,^2,^4,^6"]
|=== |===
| Priority | Operator | Operation |Equivalent operation | Priority | Operator | Operation |Equivalent operation
@@ -997,7 +997,7 @@ _param-name_ = _identifier_
=== _Golang_ function definition === _Golang_ function definition
Description of how to define Golang functions and how to bind them to _Expr_ are topics covered in another documents that I'll write, one day, maybe. Description of how to define Golang functions and how to bind them to _Expr_ are topics covered in other documents.
=== Function calls === Function calls
To call a function, either Expr or Golang type, it is necessary to specify its name and, at least, its required parameters. To call a function, either Expr or Golang type, it is necessary to specify its name and, at least, its required parameters.
@@ -1263,7 +1263,7 @@ Returns _true_ if the value type of _<expr>_ is string, false otherwise.
===== bool() ===== bool()
Syntax: `bool(<expr>) -> bool` + Syntax: `bool(<expr>) -> bool` +
Returns a _boolean_ value consisent with the value of the expression. Returns a _boolean_ value consistent with the value of the expression.
.Examples .Examples
`>>>` [blue]`bool(1)` + `>>>` [blue]`bool(1)` +
@@ -1842,7 +1842,7 @@ After the first use of the [blue]`{plusplus}` operator, the prefixed operator [b
[green]`"one"` [green]`"one"`
==== [blue]*`$$()`* -- Expansion special function for iterators ==== [blue]*`$$()`* -- Expansion special function for iterators
The [blue]`$$()` operator is a special function already seen applied to contexts. It can also be applied that can be used with iterators. When applied to an iterator, it returns a _linked list_ of all the remaining elements of the collection. The state of the iterator is updated to the end of the collection. If there are no more elements to iterate over, it returns an empty list. The [blue]`$$()` operator is a special function already seen applied to contexts. It can also be applied to iterators. When applied to an iterator, it returns a _linked list_ of all the remaining elements of the collection. The state of the iterator is updated to the end of the collection. If there are no more elements to iterate over, it returns an empty list.
#todo: examples# #todo: examples#
@@ -1851,7 +1851,7 @@ Named operators are operators that are identified by a name instead of a symbol.
.Available named operators .Available named operators
* *_.next_*: same as [blue]`{plusplus}`. * *_.next_*: same as [blue]`{plusplus}`.
* *_.current_*: same as [blue]`{star}`. * *_.current_*: same as [blue]`{star}.`
* *_.reset_*: resets the state of the iterator to the initial state. * *_.reset_*: resets the state of the iterator to the initial state.
* *_.count_*: returns the number of elements in the iterator already visited. * *_.count_*: returns the number of elements in the iterator already visited.
* *_.index_*: returns the index of the current element in the iterator. Before the first use of the [blue]`{plusplus}` operator, it returns the error [red]_-1_. * *_.index_*: returns the index of the current element in the iterator. Before the first use of the [blue]`{plusplus}` operator, it returns the error [red]_-1_.
@@ -1920,18 +1920,20 @@ Syntax: +
The left side of [blue]`groupby` operator is a list ofdictionaries or an iterator over a list of dictionaries. It takes a key and returns a dictionary where the keys are the unique values of the specified key in the dictionaries and the values are lists of dictionaries that have that key value. In other words, it groups the dictionaries by the specified key value. The left side of [blue]`groupby` operator is a list ofdictionaries or an iterator over a list of dictionaries. It takes a key and returns a dictionary where the keys are the unique values of the specified key in the dictionaries and the values are lists of dictionaries that have that key value. In other words, it groups the dictionaries by the specified key value.
NOTE: Currently, keys of group are always strings. In the future, it will be possible to specify a key function to compute the keys of the groups.
.Examples .Examples
`>>>` [blue]`[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}, {"name": "Charlie", "age": 30}] groupby "age"` + `>>>` [blue]`[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}, {"name": "Charlie", "age": 30}] groupby "age"` +
[source,json] [source,yaml]
---- ----
{ {
25: [ "25": [
{ {
"name": "Bob", "name": "Bob",
"age": 25 "age": 25
} }
], ],
30: [ "30": [
{ {
"name": "Alice", "name": "Alice",
"age": 30 "age": 30
+2
View File
@@ -1,2 +1,4 @@
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe h1:bWYrKmmfv37uNgXTdwkLSKYiYPJ1yfWmjBnvtMyAYzk=
github.com/yqylovy/goimportdot v0.0.0-20170519021755-eb181a7eeabe/go.mod h1:alTKUpAJ/zbp17qvZwcFNwzufrb5DljMDY4mgJlIHao=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d h1:0olWaB5pg3+oychR51GUVCEsGkeCU/2JxjBgIo4f3M0=
golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/exp v0.0.0-20241204233417-43b7b7cde48d/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
+6 -7
View File
@@ -89,17 +89,16 @@ func searchAmongPath(filename string, dirList []string) (filePath string) {
if dir, err = util.ExpandPath(dir); err != nil { if dir, err = util.ExpandPath(dir); err != nil {
continue continue
} }
fullPath := path.Join(dir, filename) if fullPath := path.Join(dir, filename); isFile(fullPath) {
if isFile(fullPath) {
filePath = fullPath filePath = fullPath
break break
} }
// subdir := strings.TrimSuffix(filename, suffix) subdir := strings.TrimSuffix(filename, suffix)
// if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) { if fullPath := path.Join(dir, subdir, filename); isFile(fullPath) {
// filePath = fullPath filePath = fullPath
// break break
// } }
} }
return return
} }
+2 -2
View File
@@ -33,9 +33,9 @@ func pluginExists(name string) (exists bool) {
func makePluginName(name string) (decorated string) { func makePluginName(name string) (decorated string) {
var template string var template string
if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") { if execName, err := os.Executable(); err != nil || !strings.Contains(execName, "debug") {
template = "expr-%s%s" template = "expr-%s-plugin%s"
} else { } else {
template = "expr-%s%s.debug" template = "expr-%s-plugin%s.debug"
} }
decorated = fmt.Sprintf(template, name, SHAREDLIBRARY_EXTENSION) decorated = fmt.Sprintf(template, name, SHAREDLIBRARY_EXTENSION)
return return