first ecli commit
This commit is contained in:
@@ -0,0 +1,415 @@
|
||||
// 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())
|
||||
// }
|
||||
}
|
||||
Reference in New Issue
Block a user