commit f0bc5f2273dc410871ac827aa80a754d9f6afd92 Author: Benjamin VAUDOUR Date: Wed Feb 21 11:42:56 2024 +0100 Commit initial diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7a3094a --- /dev/null +++ b/LICENSE @@ -0,0 +1,11 @@ +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +Version 2, December 2004 + +Copyright (C) 2004 Sam Hocevar + +Everyone is permitted to copy and distribute verbatim or modified copies of this license document, and changing it is allowed as long as the name is changed. + +DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b13e3ba --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# calc + +Calc est une calculatrice en ligne de commande écrite en Go et fonctionnant en notation polonaise inverse. + +## Installation + +Vous devez avoir go installé sur votre système. Pour installer calc : + +``` +go install gitea.zaclys.com/bvaudour/calc +``` + +## Usage + +Saisissez simplement `calc` dans votre terminal. Pour afficher l’aide, saisissez `h` puis appuyez sur la touche `Entrée`. + +Exemple : + +``` +1 1 + p +``` + +(effectue l’opération `1+1` puis affiche le résultat) + +## Opérateurs supportés + +### Opérateurs mathématiques de base + +- Addition +- Soustraction +- Multiplication +- Division entière +- Division exacte +- Reste +- Incrémentation +- Décrémentation +- Valeur absolue +- Inverse +- Puissance au carré +- Puissance quelconque +- Racine carrée +- Racine quelconque +- Factorielle +- Valeur absolue + +### Comparaison de nombres + +- Comparaison +- Égalité +- Inégalité +- Supériorité +- Supériorité stricte +- Infériorité +- Infériorité stricte + +### Opération sur les bits + +- Déplacement à gauche +- Déplacement à droite + +### Statistiques + +- Minimum +- Maximum +- Moyenne +- Médiane +- Écart-type +- Mode +- Variance + +## Format des nombres + +Calc supporte 4 types de nombres : + +- Entiers +- Décimaux +- Fractions +- Nombres scientifiques + +Les nombres peuvent être spécifiés en n’importe quelle base, de 2 à 36. + +## Registre + +Le registre permet de stocker en mémoire des nombres ou piles de nombres, et les réinjecter dans la pile. + +## Macro + +Les macros permettent d’enregistrer des suites de commandes complexes. diff --git a/calc/calc.go b/calc/calc.go new file mode 100644 index 0000000..423e537 --- /dev/null +++ b/calc/calc.go @@ -0,0 +1,182 @@ +package calc + +import ( + "gitea.zaclys.com/bvaudour/gob/number" + "gitea.zaclys.com/bvaudour/gob/option" +) + +type Calc struct { + stack Stack + registry Registry + macro Macro +} + +type CalcFunc func(*Calc) option.Option[error] +type IntCalcFunc[N integer] func(...N) CalcFunc +type WordCalcFunc func(string) CalcFunc +type CalcErrFunc func(*Calc) []error + +func New() *Calc { + return &Calc{ + registry: make(Registry), + macro: make(Macro), + } +} + +func (c *Calc) SetPrecision(precision option.Option[uint64], fix option.Option[bool]) { + if p, ok := precision.Get(); ok { + number.FloatingPrecision = p + } + + if f, ok := fix.Get(); ok { + number.FixedPrecision = f + } +} + +func (c *Calc) Precision() (precision uint64, fix bool) { + return number.FloatingPrecision, number.FixedPrecision +} + +func (c *Calc) Push(numbers ...number.Number) { c.stack.Push(numbers...) } +func (c *Calc) Stack() Stack { return c.stack.Clone() } +func (c *Calc) GetStack() (result option.Result[Stack]) { + if c.stack.IsEmpty() { + result = option.Err[Stack](ErrIsEmpty(errStack)) + } else { + result = option.Ok(c.Stack()) + } + + return +} +func (c *Calc) Len() { c.Push(number.IntOf(c.stack.Len())) } + +func (c *Calc) Last() option.Result[number.Number] { return c.stack.Last() } + +func (c *Calc) Pop() (err option.Option[error]) { + if e, ok := c.stack.Pop().Err(); ok { + err = option.Some(e) + } + + return +} + +func (c *Calc) Reset() option.Option[error] { return c.stack.Reset() } +func (c *Calc) Duplicate() option.Option[error] { return c.stack.Duplicate() } +func (c *Calc) DuplicateStack() option.Option[error] { return c.stack.DuplicateAll() } +func (c *Calc) Reverse() option.Option[error] { return c.stack.Reverse() } +func (c *Calc) ReverseStack() option.Option[error] { return c.stack.ReverseAll() } +func (c *Calc) Sort() option.Option[error] { return c.stack.Sort() } +func (c *Calc) SortDesc() option.Option[error] { return c.stack.SortDesc() } + +func (c *Calc) Registries() Registry { return c.registry.Clone() } +func (c *Calc) GetRegistries() (result option.Result[Registry]) { + if len(c.registry) == 0 { + result = option.Err[Registry](ErrIsEmpty(errMemory)) + } else { + result = option.Ok(c.Registries()) + } + + return +} + +func (c *Calc) Registry(k string) option.Result[Stack] { return c.registry.Get(k) } +func (c *Calc) ResetRegistry(k string) option.Option[error] { return c.registry.Reset(k) } +func (c *Calc) ResetRegistries() option.Option[error] { return c.registry.ResetAll() } + +func (c *Calc) Store(k string) (err option.Option[error]) { + pop := c.stack.Pop() + if n, ok := pop.Ok(); ok { + c.registry.Set(k, n) + } else if e, ok := pop.Err(); ok { + err = option.Some(e) + } + + return +} + +func (c *Calc) StoreStack(k string) (err option.Option[error]) { + if c.stack.IsEmpty() { + err = option.Some(ErrIsEmpty(errStack)) + } else { + c.registry.Set(k, c.Stack()...) + } + + return +} + +func (c *Calc) Load(k string) (err option.Option[error]) { + result := c.registry.Get(k) + if s, ok := result.Ok(); ok { + c.Push(s...) + } else if e, ok := result.Err(); ok { + err = option.Some(e) + } + + return +} + +func (c *Calc) Macros() Macro { return c.macro.Clone() } +func (c *Calc) GetMacros() (result option.Result[Macro]) { + if len(c.macro) == 0 { + result = option.Err[Macro](ErrIsEmpty(errMemory)) + } else { + result = option.Ok(c.Macros()) + } + + return +} + +func (c *Calc) Macro(k string) (result option.Result[MacroStack]) { return c.macro.Get(k) } +func (c *Calc) StoreMacro(k string, args ...MacroElement) { + c.macro.Set(k, args) +} + +func (c *Calc) Exec(s MacroStack) (errors []error) { + for _, e := range s { + if n, ok := e.Left(); ok { + c.Push(n) + } else if f, ok := e.Right(); ok { + if err, ok := f(c).Get(); ok { + errors = append(errors, err) + } + } + } + + return +} + +func (c *Calc) ExecMacro(k string) (errors []error) { + result := c.macro.Get(k) + if s, ok := result.Ok(); ok { + errors = c.Exec(s) + } else if err, ok := result.Err(); ok { + errors = append(errors, err) + } + + return +} + +func (c *Calc) ResetMacro(k string) option.Option[error] { return c.macro.Reset(k) } +func (c *Calc) ResetMacros() option.Option[error] { return c.macro.ResetAll() } +func (c *Calc) NoAction() (error option.Option[error]) { return } + +func (c *Calc) If(e1, e2 MacroElement) (err option.Option[error]) { + result := c.stack.Pop() + if n, ok := result.Ok(); ok { + if n.IsZero() || n.IsNan() { + if n2, ok := e2.Left(); ok { + c.Push(n2) + } else if c2, ok := e2.Right(); ok { + c2(c) + } + } else if n1, ok := e1.Left(); ok { + c.Push(n1) + } else if c1, ok := e1.Right(); ok { + c1(c) + } + } else if e, ok := result.Err(); ok { + err = option.Some(e) + } + + return +} diff --git a/calc/cli/cli.go b/calc/cli/cli.go new file mode 100644 index 0000000..b0ad8aa --- /dev/null +++ b/calc/cli/cli.go @@ -0,0 +1,205 @@ +package cli + +import ( + "errors" + "fmt" + "regexp" + "strings" + + "gitea.zaclys.com/bvaudour/gob/number" + "gitea.zaclys.com/bvaudour/gob/option" + "gitea.zaclys.net/bvaudour/calc/calc" +) + +type Cli struct { + c *calc.Calc + macros map[string]string + needMacro bool + macro []string + needAlt bool + alt []string +} + +func New() *Cli { + c := Cli{ + c: calc.New(), + macros: make(map[string]string), + } + return &c +} + +func (c *Cli) beginMacro() (err option.Option[error]) { + if c.needMacro { + return option.Some(errors.New("Les macros imbriquées ne sont pas supportées")) + } + c.needMacro = true + + return +} +func (c *Cli) addMacro(arg string) (err option.Option[error]) { + if !c.needMacro { + return option.Some(errors.New("La macro n’a pas commencé…")) + } + c.macro = append(c.macro, arg) + + return +} +func (c *Cli) endMacro(k string) (err option.Option[error]) { + macro, fields, needMacro := "[ ]", make([]string, len(c.macro)), c.needMacro + copy(fields, c.macro) + c.resetMacro() + + if len(fields) > 0 { + macro = fmt.Sprintf("[ %s ]", strings.Join(fields, " ")) + } + + if !needMacro { + return option.Some(errors.New("La macro n’a pas commencé…")) + } else if c.needAlt { + return c.addAlt(fmt.Sprintf("%s%s", macro, k)) + } else if len(fields) == 0 { + c.macros[k] = macro + c.c.StoreMacro(k, calc.MacroCommand(calc.NoAction)) + return + } + + cmds := make([]calc.MacroElement, len(fields)) + for i, f := range fields { + result := c.parse(f) + if me, ok := result.Ok(); ok { + cmds[i] = me + } else if e, ok := result.Err(); ok { + return option.Some(e) + } + } + + c.macros[k] = macro + c.c.StoreMacro(k, cmds...) + + return +} +func (c *Cli) removeMacro(k string) (err option.Option[error]) { + if err = c.c.ResetMacro(k); !err.IsDefined() { + delete(c.macros, k) + } + + return +} +func (c *Cli) removeMacros() (err option.Option[error]) { + if err = c.c.ResetMacros(); !err.IsDefined() { + c.macros = make(map[string]string) + } + + return +} + +func (c *Cli) beginAlt() (err option.Option[error]) { + if c.needAlt { + err = option.Some(errors.New("Les alternatives imbriquées ne sont pas supportées")) + } else { + c.needAlt = true + } + + return +} +func (c *Cli) addAlt(arg string) (err option.Option[error]) { + if !c.needAlt { + return option.Some(errors.New("L’alternative n’est pas commencée…")) + } else if len(c.alt) == 2 { + return option.Some(errors.New("L’alternative est pleine…")) + } + + c.alt = append(c.alt, arg) + return +} +func (c *Cli) endAlt() (err option.Option[error]) { + if !c.needAlt || len(c.alt) != 2 { + return option.Some(errors.New("L’alternative n’est pas pleine ou n’est pas commencée…")) + } + + r1, r2 := c.parse(c.alt[0]), c.parse(c.alt[1]) + if me1, ok := r1.Ok(); ok { + if me2, ok := r2.Ok(); ok { + return c.c.If(me1, me2) + } + } else if e1, ok := r1.Err(); ok { + return option.Some(e1) + } else if e2, ok := r2.Err(); ok { + return option.Some(e2) + } + + return +} + +func (c *Cli) resetAlt() { c.needAlt, c.alt = false, c.alt[:0] } +func (c *Cli) resetMacro() { c.needMacro, c.macro = false, c.macro[:0] } +func (c *Cli) resetAll() { + c.resetAlt() + c.resetMacro() + c.c.Reset() +} + +func (c *Cli) parse(arg string) option.Result[calc.MacroElement] { + var me calc.MacroElement + + if arg == "[" { + me = calc.MacroCommand(cliToCalcFunc(c, beginMacro)) + } else if regexp.MustCompile(`^\]\w+$`).MatchString(arg) { + me = calc.MacroCommand(cliToCalcFunc(c, storeMacro(arg[1:]))) + } else if c.needMacro { + me = calc.MacroCommand(cliToCalcFunc(c, addMacro(arg))) + } else if arg == "?" { + me = calc.MacroCommand(cliToCalcFunc(c, conditional)) + } else if c.needAlt { + me = calc.MacroCommand(cliToCalcFunc(c, addConditional(arg))) + } else if cb, exists := otherFunctions[arg]; exists { + me = calc.MacroCommand(otherToCalcFunc(cb)) + } else if cb, exists := searchCalcFunction(arg); exists { + me = calc.MacroCommand(cb) + } else if cb, exists := searchCliFunction(arg); exists { + me = calc.MacroCommand(cliToCalcFunc(c, cb)) + } else { + n, ok := number.Parse(arg).Get() + if !ok { + return option.Err[calc.MacroElement](fmt.Errorf("Commande inconnue: %s", arg)) + } + me = calc.MacroNumber(n) + } + + return option.Ok(me) +} + +func (c *Cli) Exec(arg string) { + result := c.parse(arg) + + if err, ok := result.Err(); ok { + printError(err) + c.resetAll() + return + } + + if element, ok := result.Ok(); ok { + if n, ok := element.Left(); ok { + c.c.Push(n) + } else if cb, ok := element.Right(); ok { + if err, ok := cb(c.c).Get(); ok { + printError(err) + c.resetAll() + return + } + } + } + + if c.needAlt && len(c.alt) == 2 { + if err, ok := c.endAlt().Get(); ok { + printError(err) + c.resetAll() + } + } +} + +func (c *Cli) ExecAll(args string) { + for _, arg := range strings.Fields(args) { + c.Exec(arg) + } +} diff --git a/calc/cli/functions.go b/calc/cli/functions.go new file mode 100644 index 0000000..d49f315 --- /dev/null +++ b/calc/cli/functions.go @@ -0,0 +1,229 @@ +package cli + +import ( + "errors" + "fmt" + "os" + "strings" + + "gitea.zaclys.com/bvaudour/gob/option" + "gitea.zaclys.net/bvaudour/calc/calc" +) + +type integer interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 +} + +type CliFunc func(*Cli) option.Option[error] +type WordCliFunc func(string) CliFunc + +func stack2Str(numbers calc.Stack) string { + sl := make([]string, len(numbers)) + for i, n := range numbers { + sl[i] = n.String() + } + return strings.Join(sl, " ") +} + +func printResult(arg any) (result option.Option[error]) { + if _, e := fmt.Printf("\033[1;33m%s\033[m\n", arg); e != nil { + result = option.Some(e) + } + + return +} + +func printError(err error) (result option.Option[error]) { + if _, e := fmt.Printf("\033[1;31m%s\033[m\n", err); e != nil { + result = option.Some(e) + } + + return +} + +func printLine(k string, arg any) (result option.Option[error]) { + if _, e := fmt.Printf("\033[1;32m%s\033[m → \033[1;33m%s\033[m\n", k, arg); e != nil { + result = option.Some(e) + } + + return +} + +func printHelp() (result option.Option[error]) { + if _, e := fmt.Print(help); e != nil { + result = option.Some(e) + } + + return +} + +func printOption(c *calc.Calc) option.Option[error] { + p, f := c.Precision() + t := "auto" + if f { + t = "fixe" + } + + return printResult(fmt.Sprintf("%d (%s)", p, t)) +} + +func printLast(c *calc.Calc) (err option.Option[error]) { + result := c.Last() + if n, ok := result.Ok(); ok { + err = printResult(n) + } else if e, ok := result.Err(); ok { + err = option.Some(e) + } + + return +} + +func printStack(c *calc.Calc) (err option.Option[error]) { + result := c.GetStack() + if s, ok := result.Ok(); ok { + err = printResult(stack2Str(s)) + } else if e, ok := result.Err(); ok { + err = option.Some(e) + } + + return +} + +func printRegistries(c *calc.Calc) (err option.Option[error]) { + result := c.GetRegistries() + if r, ok := result.Ok(); ok { + for k, s := range r { + printLine(k, stack2Str(s)) + } + } else if e, ok := result.Err(); ok { + err = option.Some(e) + } + + return +} + +func printRegistry(k string) calc.CalcFunc { + return func(c *calc.Calc) (err option.Option[error]) { + result := c.Registry(k) + if s, ok := result.Ok(); ok { + err = printResult(stack2Str(s)) + } else if e, ok := result.Err(); ok { + err = option.Some(e) + } + + return + } +} + +func printMacros(c *Cli) (err option.Option[error]) { + result := c.c.GetMacros() + if e, ok := result.Err(); ok { + err = option.Some(e) + } else { + for k, m := range c.macros { + printLine(k, m) + } + } + + return +} + +func printMacro(k string) CliFunc { + return func(c *Cli) (err option.Option[error]) { + result := c.c.Macro(k) + if e, ok := result.Err(); ok { + err = option.Some(e) + } else { + err = printResult(c.macros[k]) + } + + return + } +} + +func resetMacro(k string) CliFunc { + return func(c *Cli) option.Option[error] { + return c.removeMacro(k) + } +} + +func storeMacro(k string) CliFunc { + return func(c *Cli) option.Option[error] { + return c.endMacro(k) + } +} + +func addMacro(v string) CliFunc { + return func(c *Cli) option.Option[error] { + return c.addMacro(v) + } +} + +func setPrecision(c *calc.Calc, precision option.Option[uint64], fix bool) (err option.Option[error]) { + c.SetPrecision(precision, option.Some(fix)) + return +} + +func fixPrecision(fix bool) calc.CalcFunc { + return func(c *calc.Calc) (err option.Option[error]) { + return setPrecision(c, option.None[uint64](), fix) + } +} + +func precision[N integer](fix bool) calc.IntCalcFunc[N] { + return func(args ...N) calc.CalcFunc { + return func(c *calc.Calc) (err option.Option[error]) { + var p option.Option[uint64] + if len(args) > 1 { + p = option.Some(uint64(args[0])) + } + return setPrecision(c, p, fix) + } + } +} + +func beginMacro(c *Cli) option.Option[error] { + return c.beginMacro() +} + +func conditional(c *Cli) option.Option[error] { + return c.beginAlt() +} + +func addConditional(v string) CliFunc { + return func(c *Cli) option.Option[error] { + return c.addAlt(v) + } +} + +func quit() (err option.Option[error]) { + os.Exit(0) + return +} + +func execMacro(k string) calc.CalcFunc { + return func(c *calc.Calc) (err option.Option[error]) { + errs := c.ExecMacro(k) + if len(errs) > 0 { + sErrors := make([]string, len(errs)) + for i, e := range errs { + sErrors[i] = e.Error() + } + err = option.Some(errors.New(strings.Join(sErrors, "\n"))) + } + + return + } +} + +func cliToCalcFunc(c *Cli, cb CliFunc) calc.CalcFunc { + return func(_ *calc.Calc) option.Option[error] { + return cb(c) + } +} + +func otherToCalcFunc(cb func() option.Option[error]) calc.CalcFunc { + return func(_ *calc.Calc) option.Option[error] { + return cb() + } +} diff --git a/calc/cli/init.go b/calc/cli/init.go new file mode 100644 index 0000000..d7e5226 --- /dev/null +++ b/calc/cli/init.go @@ -0,0 +1,18 @@ +package cli + +import ( + _ "embed" + "strings" +) + +//go:embed resources/help.txt +var embedHelp []byte + +var help string + +func init() { + help = string(embedHelp) + help = strings.ReplaceAll(help, "{T}", "\033[1;33m") + help = strings.ReplaceAll(help, "{0}", "\033[m") + help = strings.ReplaceAll(help, "{B}", "\033[33m") +} diff --git a/calc/cli/mapping.go b/calc/cli/mapping.go new file mode 100644 index 0000000..baea4c7 --- /dev/null +++ b/calc/cli/mapping.go @@ -0,0 +1,180 @@ +package cli + +import ( + "regexp" + "strconv" + + "gitea.zaclys.com/bvaudour/gob/option" + "gitea.zaclys.net/bvaudour/calc/calc" +) + +var ( + otherFunctions = map[string]func() option.Option[error]{ + "h": printHelp, + "q": quit, + } + + calcFunctions = map[string]calc.CalcFunc{ + "op": printOption, + "of": fixPrecision(true), + "oa": fixPrecision(false), + "p": printLast, + "P": printStack, + "c": calc.Pop, + "C": calc.Reset, + "d": calc.Duplicate, + "D": calc.DuplicateStack, + "r": calc.Reverse, + "R": calc.ReverseStack, + "s": calc.Sort, + "S": calc.SortDesc, + ":P": printRegistries, + ":C": calc.ResetRegistries, + ";C": calc.ResetMacros, + "|": calc.Abs, + "++": calc.Inc, + "--": calc.Dec, + "inv": calc.Inv, + "+": calc.Add, + "-": calc.Sub, + "*": calc.Mul, + "×": calc.Mul, + "/": calc.Div, + "÷": calc.Div, + "//": calc.Quo, + "%": calc.Rem, + "²": calc.Square, + "^": calc.Pow, + "v": calc.Sqrt, + "√": calc.Sqrt, + "vn": calc.Sqrtn, + "!": calc.Fact, + "<=>": calc.Cmp, + "=": calc.Eq, + "<>": calc.Ne, + "≠": calc.Ne, + ">": calc.Gt, + ">=": calc.Ge, + "≥": calc.Ge, + "<=": calc.Le, + "≤": calc.Le, + "cb": calc.ToBase(2), + "co": calc.ToBase(8), + "cx": calc.ToBase(16), + "ci": calc.ToInteger[uint](), + "cd": calc.ToDecimal[uint](), + "cs": calc.ToScientific[uint](), + "cf": calc.ToFraction[uint](), + ">>": calc.Rsh, + "<<": calc.Lsh, + "min": calc.Min, + "max": calc.Max, + "sum": calc.Sum, + "moy": calc.Mean, + "med": calc.Median, + "mod": calc.Mode, + "var": calc.Variance, + "dev": calc.StdDeviation, + "l": func(c *calc.Calc) (err option.Option[error]) { + c.Len() + return + }, + "n": calc.NoAction, + } + + wordCalcFunctions = map[string]calc.WordCalcFunc{ + `^:p\w+$`: printRegistry, + `^:c\w+$`: calc.ResetRegistry, + `^:l\w+$`: calc.Load, + `^:s\w+$`: calc.Store, + `^:S\w+$`: calc.StoreStack, + `^;x\w+$`: execMacro, + } + + intCalcFunctions = map[string]calc.IntCalcFunc[uint64]{ + `^c\d+$`: func(bases ...uint64) calc.CalcFunc { + if len(bases) > 0 { + return calc.ToBase(bases[0]) + } + return func(c *calc.Calc) (err option.Option[error]) { return } + }, + `^ci\d+$`: calc.ToInteger[uint64], + `^cd\d+$`: calc.ToDecimal[uint64], + `^cs\d+$`: calc.ToScientific[uint64], + `^cf\d+$`: calc.ToFraction[uint64], + `^of\d+$`: precision[uint64](true), + `^oa\d+$`: precision[uint64](false), + } + + cliFunctions = map[string]CliFunc{ + ";P": printMacros, + "[": beginMacro, + "?": conditional, + } + + wordCliFunctions = map[string]WordCliFunc{ + `^;p\w+$`: printMacro, + `^;c\w+$`: resetMacro, + `^\]\w+$`: storeMacro, + } +) + +func searchWordCalcFunction(arg string) (cb calc.CalcFunc, ok bool) { + for k, f := range wordCalcFunctions { + if ok = regexp.MustCompile(k).MatchString(arg); ok { + cb = f(arg[2:]) + break + } + } + + return +} + +func searchIntCalcFunction(arg string) (cb calc.CalcFunc, ok bool) { + for k, f := range intCalcFunctions { + if ok = regexp.MustCompile(k).MatchString(arg); ok { + var d uint64 + var err error + if d, err = strconv.ParseUint(arg[1:], 10, 64); err != nil { + d, _ = strconv.ParseUint(arg[2:], 10, 64) + } + cb = f(d) + break + } + } + + return +} + +func searchWordCliFunction(arg string) (cb CliFunc, ok bool) { + for k, f := range wordCliFunctions { + if ok = regexp.MustCompile(k).MatchString(arg); ok { + if arg[0] == ']' { + cb = f(arg[1:]) + } else { + cb = f(arg[2:]) + } + break + } + } + + return +} + +func searchCalcFunction(arg string) (cb calc.CalcFunc, ok bool) { + if cb, ok = calcFunctions[arg]; !ok { + if cb, ok = searchWordCalcFunction(arg); !ok { + cb, ok = searchIntCalcFunction(arg) + } + } + + return +} + +func searchCliFunction(arg string) (cb CliFunc, ok bool) { + if cb, ok = cliFunctions[arg]; !ok { + cb, ok = searchWordCliFunction(arg) + } + + return +} diff --git a/calc/cli/resources/help.txt b/calc/cli/resources/help.txt new file mode 100644 index 0000000..064d40e --- /dev/null +++ b/calc/cli/resources/help.txt @@ -0,0 +1,110 @@ +{T}Affichage de l’aide{0} + {B}h{0} Affiche l’aide + +{T}Format des nombres{0} + {B}[+|-]{0} Nombre entier + {B}[+|-].{0} Nombre décimal + {B}E{0} Nombre décimal en notation scientifique (base 10) + {B}×^[+|-]{0} + Nombre décimal en notation scientifique (base quelconque) + {B}/{0} Nombre rationnel + {B}0b{0} Nombre en base 2 + {B}1b{0} Nombre en base 2 négatif + {B}0o{0} Nombre en base 8 + {B}1o{0} Nombre en base 8 négatif + {B}0x{0} Nombre en base 16 + {B}1x{0} Nombre en base 16 négatif + {B}()[+|-]{0} Nombre en base b + +{T}Options d’impression{0} + {B}po{0} Imprime les options d’affichage + {B}of{0} Affichage des nombres à virgules avec une précision fixe de n décimales + {B}oa{0} Affichage des nombres à virgules avec une précision auto (max n décimales) + Les zéros non significatifs sont supprimés. + +{T}Gestion de la pile{0} + {B}p{0} Impression du dernier élément de la pile + {B}P{0} Impression de toute la pile + {B}c{0} Suppression du dernier élément + {B}C{0} Réinitialisation de toute la pile + {B}d{0} Duplication du dernier élément + {B}D{0} Duplication de toute la pile + {B}r{0} Inversion de l’ordre des deux derniers éléments + {B}R{0} Inversion de l’ordre de tous les éléments de la pile + {B}s{0} Tri des éléments de la pile par ordre croissant + {B}S{0} Tri des éléments de la pile par ordre décroissant + +{T}Gestion des registres{0} + {B}:P{0} Impression des registres + {B}:p{0} Impression du registre r + {B}:C{0} Efface tous les registres + {B}:c{0} Efface le registre r + {B}:l{0} Charge le registre r dans la pile + {B}:s{0} Stocke le dernier élément de la pile dans le registre r et le supprime + {B}:S{0} Stocke le contenu de la pile dans le registre r et la réinitialise + +{T}Gestion des macros{0} + {B};P{0} Impression des macros + {B};p{0} Impression de la macro m + {B};C{0} Efface toutes les macros + {B};c{0} Efface La macro m + {B}[ ]{0} Stocke la chaîne en tant que macro dans m + {B};x{0} Exécute la macro m + +{T}Arithmétique élémentaire{0} + {B}|{0} Valeur absolue + {B}++{0} Ajoute 1 + {B}--{0} Soustrait 1 + {B}inv{0} inverse le nombre + {B}+{0} Addition + {B}-{0} Soustraction + {B}*, ×{0} Multiplication + {B}/, ÷{0} Division + {B}//{0} Division entière + {B}%{0} Modulo + {B}²{0} Puissance carrée + {B}^{0} Puissance + {B}v, √{0} Racine carrée + {B}vn{0} Racine n-ième (deux nombres sont consommés) + {B}!{0} Factorielle + +{T}Fonctions de comparaison{0} + {B}<=>{0} Comparaison + {B}={0} Égalité + {B}<>, ≠{0} Différence + {B}>{0} Supériorité stricte + {B}<{0} Infériorité stricte + {B}>=, ≥{0} Supériorité + {B}<=, ≤{0} Infériorité + +{T}Conversions{0} + {B}cb{0} Conversion en base 2 + {B}co{0} Conversion en base 8 + {B}cx{0} Conversion en base 16 + {B}c{0} Conversion en base n + {B}ci[]{0} Conversion en entier. Si la base est définie, force la conversion de la base. + {B}cd[]{0} Conversion en nombre décimal. Si la base est définie, force la conversion de la base. + {B}cf[]{0} Conversion en nombre scientifique. Si la base est définie, force la conversion de la base. + {B}cs[]{0} Conversion en fraction. Si la base est définie, force la conversion de la base. + +{T}Arithmétique sur les bits{0} + {B}>>{0} Déplace les bits vers la droite + {B}<<{0} Déplace les bits vers la gauche + +{T}Fonctions statistiques{0} + {B}min{0} Minimum + {B}max{0} Maximum + {B}sum{0} Somme + {B}moy{0} Moyenne + {B}med{0} Médiane + {B}mod{0} Mode + {B}var{0} Variance + {B}dev{0} Écart-type + +{T}Actions spéciales{0} + {B}l{0} Ajoute la taille de la pile dans la pile + {B}?{0} Dépile le dernier élément de la pile pour sélectionner le prochain argument à utiliser + - Si la pile est vide ou que l’élément dépilé vaut 0 ou , saute le prochain argument et exécute le suivant + - Sinon, exécute le prochain arguement et saute le suivant + {B}n{0} Ne fait aucune action + {B}q{0} Termine le mode interactif diff --git a/calc/errors.go b/calc/errors.go new file mode 100644 index 0000000..79a510a --- /dev/null +++ b/calc/errors.go @@ -0,0 +1,26 @@ +package calc + +import ( + "fmt" +) + +const ( + errEmpty = "%s est vide" + errNotExist = "%s n’existe pas" + errNeed2Nbs = "%s contient moins de 2 éléments" + errUnknown = "commande inconnue : '%s'" + errStack = "La pile" + errReg = "Le registre '%s'" + errMemory = "La mémoire" + errMacro = "La macro '%s'" + errStayArgs = "la liste des arguments restants à parser" + errCallback = "La fonction de callback n’est pas définie" + errParser = "La fonction de parsage n’est pas définie" +) + +func ErrIsEmpty(s string) error { return fmt.Errorf(errEmpty, s) } +func ErrNotExist(s string) error { return fmt.Errorf(errNotExist, s) } +func ErrLenLt2(s string) error { return fmt.Errorf(errNeed2Nbs, s) } +func ErrIsUnknow(s string) error { return fmt.Errorf(errUnknown, s) } +func reg(s string) string { return fmt.Sprintf(errReg, s) } +func mac(s string) string { return fmt.Sprintf(errMacro, s) } diff --git a/calc/functions.go b/calc/functions.go new file mode 100644 index 0000000..7b9fa81 --- /dev/null +++ b/calc/functions.go @@ -0,0 +1,130 @@ +package calc + +import ( + "gitea.zaclys.com/bvaudour/gob/number" + "gitea.zaclys.com/bvaudour/gob/option" +) + +func Push(numbers ...number.Number) CalcFunc { + return func(c *Calc) (err option.Option[error]) { + c.Push(numbers...) + return + } +} + +// Gestion de la pile +func Pop(c *Calc) option.Option[error] { return c.Pop() } +func Reset(c *Calc) option.Option[error] { return c.Reset() } +func Duplicate(c *Calc) option.Option[error] { return c.Duplicate() } +func DuplicateStack(c *Calc) option.Option[error] { return c.DuplicateStack() } +func Reverse(c *Calc) option.Option[error] { return c.Reverse() } +func ReverseStack(c *Calc) option.Option[error] { return c.ReverseStack() } +func Sort(c *Calc) option.Option[error] { return c.Sort() } +func SortDesc(c *Calc) option.Option[error] { return c.SortDesc() } + +// Gestion du registre +func ResetRegistry(k string) CalcFunc { + return func(c *Calc) option.Option[error] { + return c.ResetRegistry(k) + } +} +func ResetRegistries(c *Calc) option.Option[error] { return c.ResetRegistries() } +func Store(k string) CalcFunc { + return func(c *Calc) option.Option[error] { + return c.Store(k) + } +} +func StoreStack(k string) CalcFunc { + return func(c *Calc) option.Option[error] { + return c.StoreStack(k) + } +} +func Load(k string) CalcFunc { + return func(c *Calc) option.Option[error] { + return c.Load(k) + } +} + +// Gestion des macros +func ResetMacro(k string) CalcFunc { + return func(c *Calc) option.Option[error] { + return c.ResetMacro(k) + } +} +func ResetMacros(c *Calc) option.Option[error] { return c.ResetMacros() } +func StoreMacro(k string, args ...MacroElement) CalcFunc { + return func(c *Calc) (err option.Option[error]) { + c.StoreMacro(k, args...) + return + } +} +func Exec(s MacroStack) CalcErrFunc { + return func(c *Calc) []error { + return c.Exec(s) + } +} +func ExecMacro(k string) CalcErrFunc { + return func(c *Calc) []error { + return c.ExecMacro(k) + } +} +func If(e1, e2 MacroElement) CalcFunc { + return func(c *Calc) option.Option[error] { + return c.If(e1, e2) + } +} + +// Aucune action +func NoAction(c *Calc) option.Option[error] { return c.NoAction() } + +var ( + // Opérations mathématiques + Abs = Op1(number.Abs) + Neg = Op1(number.Neg) + Inc = Op1(number.Inc) + Dec = Op1(number.Dec) + Inv = Op1(number.Inv) + Add = Op2(number.Add) + Sub = Op2(number.Sub) + Mul = Op2(number.Mul) + Div = Op2(number.Div) + Quo = Op2(number.Quo) + Rem = Op2(number.Rem) + Square = Op1(number.Square) + Pow = Op2(number.Pow) + Sqrt = Op1(number.Sqrt) + Sqrtn = Op2(number.Sqrtn) + Fact = Op1(number.Fact) + + // Opérations de comparaison + Cmp = Op2(number.Cmp) + Eq = Op2(number.Eq) + Ne = Op2(number.Ne) + Gt = Op2(number.Gt) + Lt = Op2(number.Lt) + Ge = Op2(number.Ge) + Le = Op2(number.Le) + + // Opérations sur les bits + Lsh = Op2(number.Lsh) + Rsh = Op2(number.Rsh) + Bit = Op2(number.Bit) + + // Fonctions statistiques + Sum = Reduce(number.Sum) + Min = Reduce(number.Min) + Max = Reduce(number.Max) + Mean = Reduce(number.Min) + Median = Reduce(number.Median) + Mode = Reduce(number.Mode) + Variance = Reduce(number.Variance) + StdDeviation = Reduce(number.StdDeviation) +) + +// Conversions +func ToBase[N integer](base N) CalcFunc { return Op1(ConvertToBase(base)) } +func ToInteger[N integer](base ...N) CalcFunc { return Op1(ConvertToInteger(base...)) } +func ToDecimal[N integer](base ...N) CalcFunc { return Op1(ConvertToDecimal(base...)) } +func ToFraction[N integer](base ...N) CalcFunc { return Op1(ConvertToFraction(base...)) } +func ToScientific[N integer](base ...N) CalcFunc { return Op1(ConvertToScientific(base...)) } +func Round(precision uint64, base ...uint) CalcFunc { return Op1(ConvertRound(precision, base...)) } diff --git a/calc/macro.go b/calc/macro.go new file mode 100644 index 0000000..bfc66f2 --- /dev/null +++ b/calc/macro.go @@ -0,0 +1,101 @@ +package calc + +import ( + "fmt" + + "gitea.zaclys.com/bvaudour/gob/number" + "gitea.zaclys.com/bvaudour/gob/option" +) + +type MacroElement = option.Choice[number.Number, CalcFunc] + +type MacroStack []MacroElement + +type Macro map[string]MacroStack + +func MacroNumber(n number.Number) MacroElement { return option.Left[number.Number, CalcFunc](n) } + +func MacroCommand(c CalcFunc) MacroElement { return option.Right[number.Number, CalcFunc](c) } + +func (s MacroStack) Clone() MacroStack { + out := make(MacroStack, len(s)) + copy(out, s) + + return out +} + +func NewMacroStack(args ...any) option.Result[MacroStack] { + var s MacroStack + for _, e := range args { + if n, ok := e.(number.Number); ok { + s = append(s, MacroNumber(n)) + } else if c, ok := e.(CalcFunc); ok { + s = append(s, MacroCommand(c)) + } else { + return option.Err[MacroStack](ErrIsUnknow(fmt.Sprint(e))) + } + } + + return option.Ok(s) +} + +func (m Macro) Exists(k string) (ok bool) { + _, ok = m[k] + return +} + +func (m Macro) Set(k string, s MacroStack) { + m[k] = s +} + +func (m Macro) SetStack(k string, args ...any) (err option.Option[error]) { + result := NewMacroStack(args...) + if s, ok := result.Ok(); ok { + m.Set(k, s) + } else if e, ok := result.Err(); ok { + err = option.Some(e) + } + + return +} + +func (m Macro) Get(k string) (result option.Result[MacroStack]) { + if s, ok := m[k]; ok { + result = option.Ok(s.Clone()) + } else { + result = option.Err[MacroStack](ErrNotExist(mac(k))) + } + + return +} + +func (m Macro) Reset(k string) (err option.Option[error]) { + if m.Exists(k) { + delete(m, k) + } else { + err = option.Some(ErrNotExist(mac(k))) + } + + return +} + +func (m Macro) ResetAll() (err option.Option[error]) { + if len(m) > 0 { + for k := range m { + delete(m, k) + } + } else { + err = option.Some(ErrIsEmpty(errMemory)) + } + + return +} + +func (m Macro) Clone() Macro { + out := make(Macro) + for k, s := range m { + out[k] = s.Clone() + } + + return out +} diff --git a/calc/operations.go b/calc/operations.go new file mode 100644 index 0000000..a0c23d6 --- /dev/null +++ b/calc/operations.go @@ -0,0 +1,88 @@ +package calc + +import ( + "gitea.zaclys.com/bvaudour/gob/number" + "gitea.zaclys.com/bvaudour/gob/option" +) + +type integer interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 +} + +func Op1(cb number.Op1Func) CalcFunc { + return func(c *Calc) (err option.Option[error]) { + result := c.stack.Pop() + if n, ok := result.Ok(); ok { + c.Push(cb(n)) + } else if e, ok := result.Err(); ok { + c.Reset() + err = option.Some(e) + } + + return + } +} + +func Op2(cb number.Op2Func) CalcFunc { + return func(c *Calc) (err option.Option[error]) { + result := c.stack.Pop2() + if n, ok := result.Ok(); ok { + c.Push(cb(n.N1, n.N2)) + } else if e, ok := result.Err(); ok { + c.Reset() + err = option.Some(e) + } + + return + } +} + +func Reduce(cb number.ReduceFunc) CalcFunc { + return func(c *Calc) (err option.Option[error]) { + if c.stack.IsEmpty() { + err = option.Some(ErrIsEmpty(errStack)) + } else { + n := cb(c.Stack()...) + c.Reset() + c.Push(n) + } + + return + } +} + +func ConvertToBase[N integer](base N) number.Op1Func { + return func(n number.Number) number.Number { + return number.ToBase(n, base) + } +} + +func ConvertToInteger[N integer](base ...N) number.Op1Func { + return func(n number.Number) number.Number { + return number.ToInteger(n, base...) + } +} + +func ConvertToDecimal[N integer](base ...N) number.Op1Func { + return func(n number.Number) number.Number { + return number.ToDecimal(n, base...) + } +} + +func ConvertToFraction[N integer](base ...N) number.Op1Func { + return func(n number.Number) number.Number { + return number.ToFraction(n, base...) + } +} + +func ConvertToScientific[N integer](base ...N) number.Op1Func { + return func(n number.Number) number.Number { + return number.ToScientific(n, base...) + } +} + +func ConvertRound(precision uint64, base ...uint) number.Op1Func { + return func(n number.Number) number.Number { + return number.Round(n, precision, base...) + } +} diff --git a/calc/registry.go b/calc/registry.go new file mode 100644 index 0000000..7a1b339 --- /dev/null +++ b/calc/registry.go @@ -0,0 +1,58 @@ +package calc + +import ( + "gitea.zaclys.com/bvaudour/gob/number" + "gitea.zaclys.com/bvaudour/gob/option" +) + +type Registry map[string]Stack + +func (r Registry) Exists(k string) (ok bool) { + _, ok = r[k] + return +} + +func (r Registry) Set(k string, numbers ...number.Number) { + r[k] = numbers +} + +func (r Registry) Get(k string) (result option.Result[Stack]) { + if s, ok := r[k]; ok { + result = option.Ok(s.Clone()) + } else { + result = option.Err[Stack](ErrNotExist(reg(k))) + } + + return +} + +func (r Registry) Reset(k string) (err option.Option[error]) { + if r.Exists(k) { + delete(r, k) + } else { + err = option.Some(ErrNotExist(reg(k))) + } + + return +} + +func (r Registry) ResetAll() (err option.Option[error]) { + if len(r) > 0 { + for k := range r { + delete(r, k) + } + } else { + err = option.Some(ErrIsEmpty(errMemory)) + } + + return +} + +func (r Registry) Clone() Registry { + out := make(Registry) + for k, s := range r { + out[k] = s.Clone() + } + + return out +} diff --git a/calc/stack.go b/calc/stack.go new file mode 100644 index 0000000..7eab6f6 --- /dev/null +++ b/calc/stack.go @@ -0,0 +1,136 @@ +package calc + +import ( + "gitea.zaclys.com/bvaudour/gob/number" + "gitea.zaclys.com/bvaudour/gob/option" +) + +type Number2 struct { + N1, N2 number.Number +} + +type Stack []number.Number + +func (s *Stack) Len() int { return len(*s) } + +func (s *Stack) IsEmpty() bool { return s.Len() == 0 } + +func (s *Stack) Clone() (out Stack) { + out = make(Stack, s.Len()) + copy(out, *s) + + return +} + +func (s *Stack) Last() option.Result[number.Number] { + i := s.Len() - 1 + + if i >= 0 { + return option.Ok((*s)[i]) + } + return option.Err[number.Number](ErrIsEmpty(errStack)) +} + +func (s *Stack) Last2() option.Result[Number2] { + i := s.Len() - 2 + + if i >= 0 { + return option.Ok(Number2{ + N1: (*s)[i], + N2: (*s)[i+1], + }) + } + return option.Err[Number2](ErrLenLt2(errStack)) +} + +func (s *Stack) Pop() (n option.Result[number.Number]) { + if n = s.Last(); n.IsOk() { + *s = (*s)[:s.Len()-1] + } + + return +} + +func (s *Stack) Pop2() (n option.Result[Number2]) { + if n = s.Last2(); n.IsOk() { + *s = (*s)[:s.Len()-2] + } + + return +} + +func (s *Stack) Reset() (err option.Option[error]) { + if s.Len() > 0 { + *s = (*s)[:0] + } else { + err = option.Some(ErrIsEmpty(errStack)) + } + + return +} + +func (s *Stack) Push(numbers ...number.Number) { *s = append(*s, numbers...) } + +func (s *Stack) Duplicate() (err option.Option[error]) { + result := s.Last() + if n, ok := result.Ok(); ok { + s.Push(n) + } else if e, ok := result.Err(); ok { + err = option.Some(e) + } + + return +} + +func (s *Stack) DuplicateAll() (err option.Option[error]) { + if s.IsEmpty() { + err = option.Some(ErrIsEmpty(errStack)) + } else { + s.Push((*s)...) + } + + return +} + +func (s *Stack) Swap(i, j int) { (*s)[i], (*s)[j] = (*s)[j], (*s)[i] } + +func (s *Stack) Reverse() (err option.Option[error]) { + i := s.Len() - 2 + if i >= 0 { + s.Swap(i, i+1) + } else { + err = option.Some(ErrLenLt2(errStack)) + } + + return +} + +func (s *Stack) ReverseAll() (err option.Option[error]) { + if s.IsEmpty() { + err = option.Some(ErrIsEmpty(errStack)) + } else { + *s = number.Reverse((*s)...) + } + + return +} + +func (s *Stack) Sort() (err option.Option[error]) { + if s.IsEmpty() { + err = option.Some(ErrIsEmpty(errStack)) + } else { + *s = number.Sort((*s)...) + } + + return +} + +func (s *Stack) SortDesc() (err option.Option[error]) { + if s.IsEmpty() { + err = option.Some(ErrIsEmpty(errStack)) + } else { + *s = number.SortDesc((*s)...) + } + + return +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6a71a35 --- /dev/null +++ b/go.mod @@ -0,0 +1,8 @@ +module gitea.zaclys.net/bvaudour/calc + +go 1.22.0 + +require ( + gitea.zaclys.com/bvaudour/gob v0.0.0-20240221091259-5b437644bd67 // indirect + gitea.zaclys.com/bvaudour/readline v0.0.0-20240221095017-721dfd1e4fe2 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..03aa86e --- /dev/null +++ b/go.sum @@ -0,0 +1,4 @@ +gitea.zaclys.com/bvaudour/gob v0.0.0-20240221091259-5b437644bd67 h1:Fe9g2grCWVsKCZktydlUNff4ihjRIWO2U7EBVHosdOw= +gitea.zaclys.com/bvaudour/gob v0.0.0-20240221091259-5b437644bd67/go.mod h1:Gw6x0KNKoXv6AMtRkaI+iWM2enVzwHikUSskuEzWQz4= +gitea.zaclys.com/bvaudour/readline v0.0.0-20240221095017-721dfd1e4fe2 h1:ImUUypyhZwtwYNRytCve7Os4ADayq8pgI9iVi7FkrZM= +gitea.zaclys.com/bvaudour/readline v0.0.0-20240221095017-721dfd1e4fe2/go.mod h1:KQcGQpPnklyZYVgjOj8KqiBUqEOoBj2L0Bp9VdIdmPI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..f3718df --- /dev/null +++ b/main.go @@ -0,0 +1,22 @@ +package main + +import ( + "fmt" + "os" + + "gitea.zaclys.com/bvaudour/readline" + "gitea.zaclys.net/bvaudour/calc/calc/cli" +) + +func main() { + calc, rl := cli.New(), readline.New() + for { + result := rl.Prompt("> ") + if args, ok := result.Ok(); ok { + calc.ExecAll(args) + } else if err, ok := result.Err(); ok { + fmt.Println(result) + fmt.Fprintf(os.Stderr, "Erreur d’entrée: %s\n", err) + } + } +}