diff --git a/dump/main.go b/cmd/mtcldebug/main.go similarity index 51% rename from dump/main.go rename to cmd/mtcldebug/main.go index 6a31d0f..008b61e 100644 --- a/dump/main.go +++ b/cmd/mtcldebug/main.go @@ -25,14 +25,6 @@ func main() { parseOnly := flag.Bool("p", false, "parse without eval") flag.Parse() - lexer := lexer.NewLexer(os.Stdin) - lexer.Name = "stdin" - - parser := mtcl.NewParser(lexer) - if *logged { - parser.LogFunc = log.Print - } - interp := mtcl.NewInterp() interp.SetPrelude(mtcl.Prelude()) @@ -58,34 +50,63 @@ func main() { return results, err })) - for { - cmd, err := parser.ParseCommand() - if err == io.EOF { - return - } else if err != nil { - log.Printf("PARSE ERR: %+v", err) - } + inputs := []string{"-"} + if flag.NArg() > 0 { + inputs = flag.Args() + } - if *dump { - enc := json.NewEncoder(os.Stdout) - enc.SetIndent("", " ") - _ = enc.Encode(cmd) + for _, input := range inputs { + r, err := file(input) + if err != nil { + log.Fatalf("Error reading input %v: %v", input, err) } - if *parseOnly && *dump { - return - } + lexer := lexer.NewLexer(r) + lexer.Name = "stdin" - line := cmd.Token().Start.Line - fmt.Printf("%03d: %v\n", line, cmd) - if *parseOnly { - continue + parser := mtcl.NewParser(lexer) + if *logged { + parser.LogFunc = log.Print } - vals, err := interp.Do(cmd) - if err != nil { - fmt.Printf("ERR: %v\n", err) - } else { - fmt.Printf("=> %v\n", vals) + + for { + cmd, err := parser.ParseCommand() + if err == io.EOF { + break + } else if err != nil { + log.Printf("PARSE ERR: %+v", err) + } + + if *dump { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + _ = enc.Encode(cmd) + } + + if *parseOnly && *dump { + break + } + + line := cmd.Token().Start.Line + fmt.Printf("%03d: %v\n", line, cmd) + if *parseOnly { + continue + } + vals, err := interp.Do(cmd) + if err != nil { + fmt.Printf("ERR: %v\n", err) + } else { + fmt.Printf("=> %v\n", vals) + } } + + r.Close() + } +} + +func file(name string) (io.ReadCloser, error) { + if name == "-" { + return io.NopCloser(os.Stdin), nil } + return os.Open(name) } diff --git a/dump/in.tcl b/dump/in.tcl deleted file mode 100644 index 416d325..0000000 --- a/dump/in.tcl +++ /dev/null @@ -1,2 +0,0 @@ -foo {bar baz} -quux[wub] $x+$y "foo[bar baz wub; foo "[bar "baz [eh; wha; what]"]"; what] " diff --git a/interp.go b/interp.go index 8fa293d..1d559d7 100644 --- a/interp.go +++ b/interp.go @@ -7,246 +7,11 @@ import ( "strings" "go.spiff.io/mtcl/lexer" - "golang.org/x/exp/maps" "golang.org/x/exp/slices" ) const UnknownCmd = `*unknown*` -func IsEmpty(val Value) bool { - switch val := val.(type) { - case String: - return len(val) == 0 - case Values: - return len(val) == 0 - case Map: - return len(val) == 0 - case nil: - return true - } - return false -} - -func Empty() Values { - return nil -} - -func EmptyIterator() Iterator { - return func(Values) (bool, error) { - return false, nil - } -} - -type OnceIterable struct { - Value -} - -func (o OnceIterable) Iterator() Iterator { - if IsEmpty(o.Value) { - return EmptyIterator() - } - return OnceIterator(o.Value) -} - -func OnceIterator(val Value) Iterator { - var iter Iterator - iter = func(vals Values) (bool, error) { - if len(vals) == 0 { - return true, nil - } - iter = func(vals Values) (bool, error) { return false, nil } - if len(vals) != 1 { - return false, nil - } - vals[0] = val - return true, nil - } - return func(vals Values) (bool, error) { - return iter(vals) - } -} - -type String string - -func (String) value() {} -func (s String) String() string { return string(s) } - -func (String) Type() string { - return "string" -} - -func (s String) Expand() Values { - if s == "" { - return nil - } - return Values{s} -} - -type Error struct { - Err error -} - -func (*Error) value() {} - -func (e *Error) String() string { - return e.Err.Error() -} - -func (e *Error) Error() string { - return e.Err.Error() -} - -func (e *Error) Unwrap() error { - return e.Err -} - -func (e *Error) Expand() Values { - return Values{String(e.Error())} -} - -func (e *Error) Type() string { - return "error" -} - -type Values []Value - -func (Values) value() {} -func (vs Values) String() string { - switch len(vs) { - case 0: - return "" - case 1: - return vs[0].String() - default: - var out strings.Builder - for i, v := range vs { - str := v.String() - if str == "" { - continue - } - out.Grow(len(str) + 1) - if i > 0 && out.Len() > 0 { - out.WriteByte(' ') - } - out.WriteString(str) - } - return out.String() - } -} - -func (vs Values) Expand() Values { - return vs -} - -func (vs Values) Type() string { - return "vec" -} - -func (vs Values) Iterator() Iterator { - return func(vals Values) (bool, error) { - if len(vals) == 0 { - return len(vs) > 0, nil - } - if len(vs) < len(vals) { - return false, nil - } - copy(vals, vs) - vs = vs[len(vals):] - return true, nil - } -} - -type Map map[String]Value - -func (m Map) value() {} - -func (m Map) Type() string { - return "dict" -} - -func (m Map) Expand() Values { - keys := maps.Keys(m) - slices.Sort(keys) - values := make(Values, len(keys)) - for i, key := range keys { - ki := i * 2 - vi := ki + 1 - values[ki], values[vi] = String(key), m[key] - } - return values -} - -func (m Map) String() string { - items := make([]string, 0, len(m)*2) - for k, v := range m { - items = append(items, string(k), v.String()) - } - return strings.Join(items, " ") -} - -func (m Map) Iterator() Iterator { - keys := maps.Keys(m) - slices.Sort(keys) - return func(vals Values) (bool, error) { - switch len(vals) { - case 0: - return len(keys) > 0, nil - case 1, 2: - default: - return false, errors.New("map iterator only accepts one or two iterator parameters") - } - for i, key := range keys { - val, ok := m[key] - if !ok { - continue - } - keys = keys[i+1:] - switch len(vals) { - case 2: - vals[0], vals[1] = String(key), val - case 1: - vals[0] = String(key) - } - return true, nil - } - keys = nil - return false, nil - } -} - -type Func struct { - Fn Cmd - Binds Values -} - -func (*Func) value() {} -func (*Func) String() string { return "func" } -func (*Func) Type() string { return "func" } - -func (fn *Func) Expand() Values { return Values{fn} } - -func (fn *Func) Call(tcl *Interp, args Values) (Values, error) { - if len(fn.Binds) > 0 { - args = append(append(make(Values, 0, len(args)+len(fn.Binds)), fn.Binds...), args...) - } - return fn.Fn.Call(tcl, args) -} - -type Value interface { - value() - - String() string - Type() string - Expand() Values -} - -type Iterable interface { - Value - Iterator() Iterator -} - -type Iterator func(vals Values) (bool, error) - type varTable map[string]*Values func (vt varTable) set(name string, vals Values) { @@ -330,24 +95,6 @@ type cmdExpr interface { callExpr(interp *Interp, args []Expr) (Values, error) } -func doInContext[Exprs ~[]E, E Expr](exprScope, evalScope *Interp, args Exprs) (last Values, err error) { - var cmds []*Command - for _, arg := range args { - cmds, err = commandsFor(exprScope, arg) - if err != nil { - return last, err - } - - for _, cmd := range cmds { - last, err = evalScope.Do(cmd) - if err != nil { - return last, err - } - } - } - return last, err -} - func commandsFor(tcl *Interp, e Expr) ([]*Command, error) { // Use pre-parsed commands. if rs, ok := e.(*RawString); ok && rs.Cmds != nil { @@ -392,6 +139,34 @@ func (tcl *Interp) BindCmds(cmds map[string]Cmd) { } } +type EvalErrorFunc func(Values, error) (values Values, err error, exit bool) + +func (tcl *Interp) Eval(e Expr, lookup *Interp, handleErr EvalErrorFunc) (result Values, err error) { + if lookup == nil { + lookup = tcl + } + cmds, err := commandsFor(lookup, e) + if err != nil { + return nil, fmt.Errorf("eval: cannot parse expression as command(s): %w", err) + } + if handleErr == nil { + handleErr = func(vals Values, err error) (_ Values, _ error, exit bool) { + return vals, err, true + } + } + var exit bool + for _, cmd := range cmds { + result, err = tcl.Do(cmd) + if err != nil { + result, err, exit = handleErr(result, err) + if err != nil || exit { + return result, err + } + } + } + return result, err +} + func (tcl *Interp) Do(e Expr) (result Values, err error) { switch e := e.(type) { case *Access: @@ -414,6 +189,20 @@ func (tcl *Interp) Do(e Expr) (result Values, err error) { return tcl.expandWord(e) case *Literal: + switch e.Literal { + case "true": + return Values{True}, nil + case "false": + return Values{False}, nil + case "": + return Values{String("")}, nil + } + if f := e.Literal[0]; len(e.Literal) > 0 && (f == '-' || f == '+' || (f >= '0' && f <= '9')) { + var n Int + if _, ok := n.SetString(e.Literal, 0); ok { + return Values{&n}, nil + } + } return Values{String(e.Literal)}, nil default: diff --git a/parser.go b/parser.go index 3ebfd6e..04e531c 100644 --- a/parser.go +++ b/parser.go @@ -55,7 +55,13 @@ func (p *Parser) inc(sect string, args ...any) func() { } type Expr interface { + // Token returns the token from the first part of the Expr. The + // concrete type of the Expr may contain additional tokens identifying + // the remaining tokens making up the Expr. Token() token.Token + // String returns the Expr as a string that is equivalent in value + // to the source tokens (with differences allowed for comments + // and whitespace). String() string } @@ -255,8 +261,7 @@ func (p *Parser) parseExpr() Expr { loop: for { - tok := p.tok - switch tok.Kind { + switch p.tok.Kind { case token.BracketOpen: parts = append(parts, p.parseBlock()) case token.Word: diff --git a/prelude.go b/prelude.go index 7a40ebf..4254def 100644 --- a/prelude.go +++ b/prelude.go @@ -13,17 +13,36 @@ import ( ) var baseCmds = map[string]Cmd{ - "set": CmdFunc(PreludeSet), - "...": CmdFunc(PreludeExpand), - "&": CmdFunc(PreludeBind), - "type": CmdFunc(PreludeType), - "puts": CmdFunc(PreludePuts), - "concat": CmdFunc(PreludeConcat), - "dict": CmdFunc(PreludeDict), - "list": CmdFunc(PreludeList), - ".": CmdFunc(PreludeIndex), - "empty?": CmdFunc(PreludeIsEmpty), - "error?": CmdFunc(PreludeIsError), + "set": CmdFunc(PreludeSet), + "...": CmdFunc(PreludeExpand), + "&": CmdFunc(PreludeBind), + "puts": CmdFunc(PreludePuts), + "concat": CmdFunc(PreludeConcat), + "dict": CmdFunc(PreludeDict), + "list": CmdFunc(PreludeList), + ".": CmdFunc(PreludeIndex), + "empty?": CmdFunc(PreludeIsEmpty), + "error?": CmdFunc(PreludeIsError), + "len": CmdFunc(PreludeLen), + + // Types + "type": CmdFunc(PreludeType), + "int": CmdFunc(PreludeInt), + "str": CmdFunc(PreludeStr), + "seq": CmdFunc(PreludeSeq), + + // Control structures + "catch": CmdExprFunc(PreludeCatch), + "foreach": CmdExprFunc(PreludeForeach), + "if": CmdExprFunc(PreludeIf), + "and": CmdExprFunc(PreludeAnd), + "or": CmdExprFunc(PreludeOr), + "not": CmdFunc(PreludeNot), + "true?": CmdFunc(PreludeIsTrue), + "upvar": CmdFunc(PreludeUpvar), + "uplevel": CmdExprFunc(PreludeUplevel), + + // Control flow "break?": isErrKindFunc(ReturnBreak), "break": CmdFunc(PreludeBreak), "continue?": isErrKindFunc(ReturnContinue), @@ -31,11 +50,14 @@ var baseCmds = map[string]Cmd{ "return?": isErrKindFunc(ReturnOK), "return-error?": isErrKindFunc(ReturnError), "return": CmdFunc(PreludeReturn), - "catch": CmdExprFunc(PreludeCatch), - "foreach": CmdExprFunc(PreludeForeach), - "upvar": CmdFunc(PreludeUpvar), - "uplevel": CmdExprFunc(PreludeUplevel), - "fn": CmdExprFunc(PreludeFn), + + // Function definition + "fn": CmdExprFunc(PreludeFn), + + // Pass-through + "true": CmdFunc(PreludeTrue), + "false": CmdFunc(PreludeFalse), + "tap": CmdFunc(PreludeTap), } func Prelude() map[string]Cmd { @@ -88,14 +110,6 @@ func PreludeBind(tcl *Interp, args Values) (Values, error) { }}, nil } -func PreludeType(tcl *Interp, args Values) (Values, error) { - types := make(Values, len(args)) - for i, arg := range args { - types[i] = String(arg.Type()) - } - return types, nil -} - func PreludePuts(tr *Interp, args Values) (Values, error) { _, err := fmt.Println(strings.Join(Strings(args), "\t")) return nil, err @@ -148,7 +162,7 @@ func PreludeIndex(tr *Interp, args Values) (Values, error) { } else if index < 0 { index = len(conc) - index } else { - return nil, fmt.Errorf(".: index %d must be a positive [1..) or negative integer (..-1]") + return nil, fmt.Errorf(".: index %d must be a positive [1..) or negative integer (..-1]", rawIndex) } if index < 0 || index >= len(conc) { return nil, fmt.Errorf(".: index %d out of bounds for list", rawIndex) @@ -172,9 +186,12 @@ func PreludeIndex(tr *Interp, args Values) (Values, error) { } func PreludeIsEmpty(tcl *Interp, args Values) (Values, error) { + if IsEmpty(args) { + return Values{String("true")}, nil + } out := make(Values, len(args)) for i, arg := range args { - if !IsEmpty(arg) { + if IsEmpty(arg) { out[i] = String("true") } else { out[i] = Empty() @@ -195,16 +212,89 @@ func PreludeIsError(tcl *Interp, args Values) (Values, error) { return out, nil } -func PreludeBreak(tcl *Interp, args Values) (Values, error) { - return slices.Clone(args), ReturnBreak +func PreludeLen(tcl *Interp, args Values) (Values, error) { + lens := make(Values, len(args)) + for i, arg := range args { + if arg == nil { + lens[i] = NewInt(0) + } else { + lens[i] = NewInt(arg.Len()) + } + } + return lens, nil } -func PreludeContinue(tcl *Interp, args Values) (Values, error) { - return slices.Clone(args), ReturnContinue +func PreludeType(tcl *Interp, args Values) (Values, error) { + types := make(Values, len(args)) + for i, arg := range args { + types[i] = String(arg.Type()) + } + return types, nil } -func PreludeReturn(tcl *Interp, args Values) (Values, error) { - return slices.Clone(args), ReturnOK +func PreludeInt(tcl *Interp, args Values) (Values, error) { + out := make(Values, len(args)) + for i, arg := range args { + var n Int + str := arg.String() + if _, ok := n.SetString(str, 0); !ok { + return nil, fmt.Errorf("cannot parse %q as integer", str) + } + out[i] = &n + } + return out, nil +} + +func PreludeStr(tcl *Interp, args Values) (Values, error) { + out := make(Values, len(args)) + for i, arg := range args { + out[i] = String(arg.String()) + } + return out, nil +} + +func PreludeSeq(tcl *Interp, args Values) (Values, error) { + args, err := PreludeInt(tcl, args) + if err != nil { + return nil, err + } + var seq *Seq + switch len(args) { + case 1: // seq start=0 end step=1|-1 + seq = &Seq{ + Start: new(Int), + End: args[0].(*Int), + Step: new(Int), + } + switch seq.End.Sign() { + case -1: + seq.Start.SetInt64(-1) + case 1: + seq.Start.SetInt64(1) + } + + case 2: // seq start end step=1|-1 + seq = &Seq{ + Start: args[0].(*Int), + End: args[1].(*Int), + Step: new(Int), + } + case 3: // seq start end step + seq = &Seq{ + Start: args[0].(*Int), + End: args[1].(*Int), + Step: args[2].(*Int), + } + return Values{seq}, nil + default: + return nil, fmt.Errorf("seq: expected 1..3 arguments, got %d", len(args)) + } + if seq.End.Cmp(&seq.Start.Int) < 0 { + seq.Step.SetInt64(-1) + } else { + seq.Step.SetInt64(1) + } + return Values{seq}, nil } func PreludeCatch(tcl *Interp, args []Expr) (last Values, err error) { @@ -322,6 +412,126 @@ foreach: } } +func PreludeIf(tcl *Interp, args []Expr) (results Values, err error) { + if len(args) < 2 { + return nil, errors.New("if: must pass at least one condition and script pair") + } + + // Scan structure for validity before evaluating. + for args, condNum := args[2:], 1; len(args) > 0; condNum++ { + if len(args) < 2 { + return nil, errors.New("if: condition missing script") + } + cond := args[0] + args = args[2:] + if cond.String() == "else" { + if len(args) != 0 { + return nil, errors.New("if: else condition must be the last case") + } + } else if cond.String() == "elseif" { + if len(args) == 0 { + return nil, errors.New("if: condition missing script") + } + args = args[1:] + } else { + return nil, fmt.Errorf("if: unexpected word %q, need elseif or else", cond.String()) + } + } + + cond := func(condNum int) (results Values, err error, next bool) { + cond, body := args[0], args[1] + args = args[2:] + condEval, err := tcl.Eval(cond, tcl, nil) + if rc, ok := returnCode(err); ok && rc != 0 { + return nil, nil, true + } else if err != nil { + return nil, fmt.Errorf("if: error evaluating condition %d: %w", condNum, err), false + } else if !Truthy(condEval) { + return nil, nil, true + } + results, err = tcl.Eval(body, tcl, nil) + return results, err, false + } + + condElse := func(condNum int) (results Values, err error, next bool) { + var cond, body Expr + switch args[0].String() { + case "elseif": + cond, body, args = args[1], args[2], args[3:] + condEval, err := tcl.Eval(cond, tcl, nil) + if rc, ok := returnCode(err); ok && rc != 0 { + return nil, nil, true + } else if err != nil { + return nil, fmt.Errorf("if: error evaluating condition %d: %w", condNum, err), false + } else if !Truthy(condEval) { + return nil, nil, true + } + + case "else": + body, args = args[1], args[2:] + + default: + panic("unreachable") + } + + results, err = tcl.Eval(body, tcl, nil) + return results, err, false + } + + for condNum := 1; len(args) > 0; condNum++ { + var next bool + results, err, next = cond(condNum) + if next == false { + return results, err + } + cond = condElse + } + return results, nil +} + +func PreludeAnd(tcl *Interp, args []Expr) (last Values, err error) { + for _, arg := range args { + last, err = tcl.Do(arg) + if rc, ok := returnCode(err); ok && rc != 0 { + return nil, nil + } else if err != nil || !Truthy(last) { + return nil, err + } + } + return last, err +} + +func PreludeOr(tcl *Interp, args []Expr) (last Values, err error) { + for _, arg := range args { + last, err = tcl.Do(arg) + if rc, ok := returnCode(err); ok && rc != 0 { + last = nil + continue + } else if err != nil || Truthy(last) { + return last, err + } else if Truthy(last) { + return last, nil + } + } + return nil, nil +} + +func PreludeNot(tcl *Interp, args Values) (Values, error) { + out := make(Values, len(args)) + for i, arg := range args { + out[i] = Bool(!Truthy(arg)) + } + return out, nil +} + +func PreludeIsTrue(tcl *Interp, args Values) (Values, error) { + out := make(Values, len(args)) + for i, arg := range args { + out[i] = Bool(Truthy(arg)) + } + return out, nil +} + func PreludeUpvar(tcl *Interp, args Values) (Values, error) { if len(args) == 0 { return nil, errors.New("upvar requires at least 1 argument, got 0") @@ -336,7 +546,7 @@ func PreludeUpvar(tcl *Interp, args Values) (Values, error) { return nil, nil } -func PreludeUplevel(tcl *Interp, args []Expr) (Values, error) { +func PreludeUplevel(tcl *Interp, args []Expr) (results Values, err error) { var depth uint = 1 var exprs = args switch len(args) { @@ -361,7 +571,25 @@ func PreludeUplevel(tcl *Interp, args []Expr) (Values, error) { if err != nil { return nil, fmt.Errorf("unable to get scope at depth %d: %w", depth, err) } - return doInContext(tcl, evalScope, exprs) + for _, expr := range exprs { + results, err = evalScope.Eval(expr, evalScope, nil) + if err != nil { + break + } + } + return results, err +} + +func PreludeBreak(tcl *Interp, args Values) (Values, error) { + return slices.Clone(args), ReturnBreak +} + +func PreludeContinue(tcl *Interp, args Values) (Values, error) { + return slices.Clone(args), ReturnContinue +} + +func PreludeReturn(tcl *Interp, args Values) (Values, error) { + return slices.Clone(args), ReturnOK } func PreludeFn(tcl *Interp, args []Expr) (Values, error) { @@ -464,6 +692,18 @@ leader: return Values{&Func{Fn: CmdFunc(fn)}}, nil } +func PreludeTrue(tcl *Interp, args Values) (Values, error) { + return Values{True}, nil +} + +func PreludeFalse(tcl *Interp, args Values) (Values, error) { + return Values{False}, nil +} + +func PreludeTap(tcl *Interp, args Values) (Values, error) { + return args, nil +} + func isErrKindFunc(target error) CmdFunc { return func(tcl *Interp, args Values) (Values, error) { out := make(Values, len(args)) diff --git a/type_bool.go b/type_bool.go new file mode 100644 index 0000000..13fa385 --- /dev/null +++ b/type_bool.go @@ -0,0 +1,27 @@ +package mtcl + +type Bool bool + +const True Bool = true +const False Bool = false + +func (Bool) value() {} + +func (b Bool) String() string { + if b { + return "true" + } + return "false" +} + +func (b Bool) Expand() Values { + return Values{b} +} + +func (Bool) Len() int { + return 1 +} + +func (b Bool) Type() string { + return "bool" +} diff --git a/type_error.go b/type_error.go new file mode 100644 index 0000000..06531c6 --- /dev/null +++ b/type_error.go @@ -0,0 +1,31 @@ +package mtcl + +type Error struct { + Err error +} + +func (*Error) value() {} + +func (e *Error) String() string { + return e.Err.Error() +} + +func (e *Error) Error() string { + return e.Err.Error() +} + +func (e *Error) Unwrap() error { + return e.Err +} + +func (e *Error) Expand() Values { + return Values{String(e.Error())} +} + +func (*Error) Len() int { + return 1 +} + +func (e *Error) Type() string { + return "error" +} diff --git a/type_func.go b/type_func.go new file mode 100644 index 0000000..43dc60e --- /dev/null +++ b/type_func.go @@ -0,0 +1,25 @@ +package mtcl + +type Func struct { + Fn Cmd + Binds Values +} + +func (*Func) value() {} +func (*Func) String() string { return "func" } +func (*Func) Type() string { return "func" } + +func (fn *Func) Expand() Values { + return Values{fn} +} + +func (*Func) Len() int { + return 1 +} + +func (fn *Func) Call(tcl *Interp, args Values) (Values, error) { + if len(fn.Binds) > 0 { + args = append(append(make(Values, 0, len(args)+len(fn.Binds)), fn.Binds...), args...) + } + return fn.Fn.Call(tcl, args) +} diff --git a/type_int.go b/type_int.go new file mode 100644 index 0000000..aec8b18 --- /dev/null +++ b/type_int.go @@ -0,0 +1,29 @@ +package mtcl + +import "math/big" + +type Int struct { + big.Int +} + +func NewInt(x int) *Int { + var n Int + n.SetInt64(int64(x)) + return &n +} + +var _ Value = (*Int)(nil) + +func (*Int) value() {} + +func (n *Int) Expand() Values { + return Values{n} +} + +func (*Int) Len() int { + return 1 +} + +func (n *Int) Type() string { + return "int" +} diff --git a/type_map.go b/type_map.go new file mode 100644 index 0000000..79e46a4 --- /dev/null +++ b/type_map.go @@ -0,0 +1,71 @@ +package mtcl + +import ( + "errors" + "strings" + + "golang.org/x/exp/maps" + "golang.org/x/exp/slices" +) + +type Map map[String]Value + +func (m Map) value() {} + +func (m Map) Type() string { + return "dict" +} + +func (m Map) Expand() Values { + keys := maps.Keys(m) + slices.Sort(keys) + values := make(Values, len(keys)) + for i, key := range keys { + ki := i * 2 + vi := ki + 1 + values[ki], values[vi] = String(key), m[key] + } + return values +} + +func (m Map) Len() int { + return len(m) +} + +func (m Map) String() string { + items := make([]string, 0, len(m)*2) + for k, v := range m { + items = append(items, string(k), v.String()) + } + return strings.Join(items, " ") +} + +func (m Map) Iterator() Iterator { + keys := maps.Keys(m) + slices.Sort(keys) + return func(vals Values) (bool, error) { + switch len(vals) { + case 0: + return len(keys) > 0, nil + case 1, 2: + default: + return false, errors.New("map iterator only accepts one or two iterator parameters") + } + for i, key := range keys { + val, ok := m[key] + if !ok { + continue + } + keys = keys[i+1:] + switch len(vals) { + case 2: + vals[0], vals[1] = String(key), val + case 1: + vals[0] = String(key) + } + return true, nil + } + keys = nil + return false, nil + } +} diff --git a/type_seq.go b/type_seq.go new file mode 100644 index 0000000..384798a --- /dev/null +++ b/type_seq.go @@ -0,0 +1,104 @@ +package mtcl + +import ( + "fmt" + "math" + "math/big" +) + +type Seq struct { + Start *Int + End *Int + Step *Int +} + +func (s *Seq) String() string { + return fmt.Sprintf("%v:%v:%v", s.Start, s.End, s.Step) +} + +func (*Seq) value() {} + +func (*Seq) Type() string { return "seq" } + +func (s *Seq) Expand() (values Values) { + iter := s.Iterator() + values = make(Values, 0, s.Len()) + var temp [1]Value + for { + ok, err := iter(Values(temp[:])) + if err != nil { + return nil + } else if !ok { + break + } + values = append(values, temp[0]) + } + return values +} + +func (s *Seq) Len() int { + pos := new(big.Int).Set(&s.Start.Int) + end := &s.End.Int + dir := end.Cmp(pos) + if dir == 0 { + return 1 + } + + step := &s.Step.Int + if step.Sign() != dir { + return 0 // Cannot compute. + } + + delta := new(big.Int).Sub(end, pos) + delta, mod := delta.DivMod(delta, step, new(big.Int)) + if mod.Sign() == 0 { + delta = delta.Add(delta, big.NewInt(1)) + } + if !delta.IsInt64() { + return math.MaxInt64 + } + estimate := delta.Int64() + if estimate < 0 { + estimate = -estimate + } + if estimate > math.MaxInt32 { + return math.MaxInt32 + } + return int(estimate) +} + +func (s *Seq) Iterator() Iterator { + pos := new(big.Int).Set(&s.Start.Int) + end := &s.End.Int + dir := end.Cmp(pos) + if dir == 0 { + return EmptyIterator() + } + + step := &s.Step.Int + if step.Sign() != dir { + return EmptyIterator() + } + + next := func() *Int { + if end.Cmp(pos) == -dir { + return nil + } + + n := &Int{} + n.Set(pos) + pos = pos.Add(pos, step) + return n + } + + return func(out Values) (ok bool, err error) { + for i := range out { + n := next() + if n == nil { + return false, nil + } + out[i] = n + } + return true, nil + } +} diff --git a/type_string.go b/type_string.go new file mode 100644 index 0000000..2f989a5 --- /dev/null +++ b/type_string.go @@ -0,0 +1,21 @@ +package mtcl + +type String string + +func (String) value() {} +func (s String) String() string { return string(s) } + +func (String) Type() string { + return "string" +} + +func (s String) Expand() Values { + if s == "" { + return nil + } + return Values{s} +} + +func (s String) Len() int { + return len(s) +} diff --git a/type_value.go b/type_value.go new file mode 100644 index 0000000..1674006 --- /dev/null +++ b/type_value.go @@ -0,0 +1,154 @@ +package mtcl + +import ( + "strconv" + "strings" +) + +type Value interface { + value() + + Len() int + String() string + Type() string + Expand() Values +} + +type Values []Value + +func Empty() Values { + return nil +} + +func (Values) value() {} + +func (vs Values) String() string { + switch len(vs) { + case 0: + return "" + case 1: + return vs[0].String() + default: + var out strings.Builder + for i, v := range vs { + str := v.String() + if str == "" { + continue + } + out.Grow(len(str) + 1) + if i > 0 && out.Len() > 0 { + out.WriteByte(' ') + } + out.WriteString(str) + } + return out.String() + } +} + +func (vs Values) Expand() Values { + return vs +} + +func (vs Values) Len() int { + return len(vs) +} + +func (vs Values) Type() string { + return "vec" +} + +func (vs Values) Iterator() Iterator { + return func(vals Values) (bool, error) { + if len(vals) == 0 { + return len(vs) > 0, nil + } + if len(vs) < len(vals) { + return false, nil + } + copy(vals, vs) + vs = vs[len(vals):] + return true, nil + } +} + +type Iterable interface { + Value + Iterator() Iterator +} + +type Iterator func(vals Values) (bool, error) + +func EmptyIterator() Iterator { + return func(Values) (bool, error) { + return false, nil + } +} + +type OnceIterable struct { + Value +} + +func (o OnceIterable) Iterator() Iterator { + if IsEmpty(o.Value) { + return EmptyIterator() + } + return OnceIterator(o.Value) +} + +func OnceIterator(val Value) Iterator { + var iter Iterator + iter = func(vals Values) (bool, error) { + if len(vals) == 0 { + return true, nil + } + iter = func(vals Values) (bool, error) { return false, nil } + if len(vals) != 1 { + return false, nil + } + vals[0] = val + return true, nil + } + return func(vals Values) (bool, error) { + return iter(vals) + } +} + +func Truthy(val Value) bool { + if IsEmpty(val) { + return false + } + switch val := val.(type) { + case *Int: + return val.Sign() != 0 + case Bool: + return bool(val) + case String: + b, err := strconv.ParseBool(string(val)) + return b || err != nil + case Values: + for _, sub := range val { + if Truthy(sub) { + return true + } + } + } + return false +} + +func IsEmpty(val Value) bool { + switch val := val.(type) { + case *Int: + return false + case String: + return len(val) == 0 + case Values: + return len(val) == 0 + case Map: + return len(val) == 0 + case Bool: + return !bool(val) + case nil: + return true + } + return false +}