Commit initial

This commit is contained in:
Benjamin VAUDOUR 2024-02-21 11:42:56 +01:00
commit f0bc5f2273
17 changed files with 1596 additions and 0 deletions

205
calc/cli/cli.go Normal file
View file

@ -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 na 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 na 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("Lalternative nest pas commencée…"))
} else if len(c.alt) == 2 {
return option.Some(errors.New("Lalternative 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("Lalternative nest pas pleine ou nest 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)
}
}

229
calc/cli/functions.go Normal file
View file

@ -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()
}
}

18
calc/cli/init.go Normal file
View file

@ -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")
}

180
calc/cli/mapping.go Normal file
View file

@ -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
}

110
calc/cli/resources/help.txt Normal file
View file

@ -0,0 +1,110 @@
{T}Affichage de laide{0}
{B}h{0} Affiche laide
{T}Format des nombres{0}
{B}[+|-]<n>{0} Nombre entier
{B}[+|-]<n>.<n>{0} Nombre décimal
{B}<decimal|entier>E<entier>{0} Nombre décimal en notation scientifique (base 10)
{B}<decimal|entier en base>×<b>^[+|-]<e>{0}
Nombre décimal en notation scientifique (base quelconque)
{B}<n>/<n>{0} Nombre rationnel
{B}0b<n>{0} Nombre en base 2
{B}1b<n>{0} Nombre en base 2 négatif
{B}0o<n>{0} Nombre en base 8
{B}1o<n>{0} Nombre en base 8 négatif
{B}0x<n>{0} Nombre en base 16
{B}1x<n>{0} Nombre en base 16 négatif
{B}(<b>)[+|-]<n>{0} Nombre en base b
{T}Options dimpression{0}
{B}po{0} Imprime les options daffichage
{B}of<n>{0} Affichage des nombres à virgules avec une précision fixe de n décimales
{B}oa<n>{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 lordre des deux derniers éléments
{B}R{0} Inversion de lordre 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<r>{0} Impression du registre r
{B}:C{0} Efface tous les registres
{B}:c<r>{0} Efface le registre r
{B}:l<r>{0} Charge le registre r dans la pile
{B}:s<r>{0} Stocke le dernier élément de la pile dans le registre r et le supprime
{B}:S<r>{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<m>{0} Impression de la macro m
{B};C{0} Efface toutes les macros
{B};c<m>{0} Efface La macro m
{B}[ <chain> ]<m>{0} Stocke la chaîne en tant que macro dans m
{B};x<m>{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<n>{0} Conversion en base n
{B}ci[<b>]{0} Conversion en entier. Si la base est définie, force la conversion de la base.
{B}cd[<b>]{0} Conversion en nombre décimal. Si la base est définie, force la conversion de la base.
{B}cf[<b>]{0} Conversion en nombre scientifique. Si la base est définie, force la conversion de la base.
{B}cs[<b>]{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 <NaN>, 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