mirror of
https://github.com/go-sylixos/elvish.git
synced 2024-12-12 17:27:50 +08:00
489 lines
9.8 KiB
Go
489 lines
9.8 KiB
Go
package eval
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"math/big"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"src.elv.sh/pkg/diag"
|
|
"src.elv.sh/pkg/eval/errs"
|
|
"src.elv.sh/pkg/eval/vals"
|
|
"src.elv.sh/pkg/parse"
|
|
"src.elv.sh/pkg/strutil"
|
|
)
|
|
|
|
// Input and output.
|
|
|
|
func init() {
|
|
addBuiltinFns(map[string]any{
|
|
// Value output
|
|
"put": put,
|
|
"repeat": repeat,
|
|
|
|
// Bytes input
|
|
"read-bytes": readBytes,
|
|
"read-upto": readUpto,
|
|
"read-line": readLine,
|
|
|
|
// Bytes output
|
|
"print": print,
|
|
"echo": echo,
|
|
"pprint": pprint,
|
|
"repr": repr,
|
|
"show": show,
|
|
"printf": printf,
|
|
|
|
// Only bytes or values
|
|
//
|
|
// These are now implemented as commands forwarding one part of input to
|
|
// output and discarding the other. A future optimization the evaler can
|
|
// do is to connect the relevant parts directly together without any
|
|
// kind of forwarding.
|
|
"only-bytes": onlyBytes,
|
|
"only-values": onlyValues,
|
|
|
|
// Bytes to value
|
|
"slurp": slurp,
|
|
"from-lines": fromLines,
|
|
"from-json": fromJSON,
|
|
"from-terminated": fromTerminated,
|
|
|
|
// Value to bytes
|
|
"to-lines": toLines,
|
|
"to-json": toJSON,
|
|
"to-terminated": toTerminated,
|
|
})
|
|
}
|
|
|
|
func put(fm *Frame, args ...any) error {
|
|
out := fm.ValueOutput()
|
|
for _, a := range args {
|
|
err := out.Put(a)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func repeat(fm *Frame, n int, v any) error {
|
|
out := fm.ValueOutput()
|
|
for i := 0; i < n; i++ {
|
|
err := out.Put(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readBytes(fm *Frame, max int) (string, error) {
|
|
in := fm.InputFile()
|
|
buf := make([]byte, max)
|
|
read := 0
|
|
for read < max {
|
|
n, err := in.Read(buf[read:])
|
|
read += n
|
|
if err == io.EOF {
|
|
break
|
|
} else if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
return string(buf[:read]), nil
|
|
}
|
|
|
|
func readUpto(fm *Frame, terminator string) (string, error) {
|
|
if err := checkTerminator(terminator); err != nil {
|
|
return "", err
|
|
}
|
|
in := fm.InputFile()
|
|
var buf []byte
|
|
for {
|
|
var b [1]byte
|
|
_, err := in.Read(b[:])
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
return "", err
|
|
}
|
|
buf = append(buf, b[0])
|
|
if b[0] == terminator[0] {
|
|
break
|
|
}
|
|
}
|
|
return string(buf), nil
|
|
}
|
|
|
|
func checkTerminator(s string) error {
|
|
if len(s) != 1 || s[0] > 127 {
|
|
return errs.BadValue{What: "terminator",
|
|
Valid: "a single ASCII character", Actual: parse.Quote(s)}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readLine(fm *Frame) (string, error) {
|
|
s, err := readUpto(fm, "\n")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return strutil.ChopLineEnding(s), nil
|
|
}
|
|
|
|
type printOpts struct{ Sep string }
|
|
|
|
func (o *printOpts) SetDefaultOptions() { o.Sep = " " }
|
|
|
|
func print(fm *Frame, opts printOpts, args ...any) error {
|
|
out := fm.ByteOutput()
|
|
for i, arg := range args {
|
|
if i > 0 {
|
|
_, err := out.WriteString(opts.Sep)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err := out.WriteString(vals.ToString(arg))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func printf(fm *Frame, template string, args ...any) error {
|
|
wrappedArgs := make([]any, len(args))
|
|
for i, arg := range args {
|
|
wrappedArgs[i] = formatter{arg}
|
|
}
|
|
|
|
_, err := fmt.Fprintf(fm.ByteOutput(), template, wrappedArgs...)
|
|
return err
|
|
}
|
|
|
|
type formatter struct {
|
|
wrapped any
|
|
}
|
|
|
|
func (f formatter) Format(state fmt.State, r rune) {
|
|
wrapped := f.wrapped
|
|
switch r {
|
|
case 's':
|
|
writeFmt(state, 's', vals.ToString(wrapped))
|
|
case 'q':
|
|
// TODO: Support using the precision flag to specify indentation.
|
|
writeFmt(state, 's', vals.ReprPlain(wrapped))
|
|
case 'v':
|
|
var s string
|
|
if state.Flag('#') {
|
|
s = vals.ReprPlain(wrapped)
|
|
} else {
|
|
s = vals.ToString(wrapped)
|
|
}
|
|
writeFmt(state, 's', s)
|
|
case 't':
|
|
writeFmt(state, 't', vals.Bool(wrapped))
|
|
case 'b', 'c', 'd', 'o', 'O', 'x', 'X', 'U':
|
|
var i int
|
|
if err := vals.ScanToGo(wrapped, &i); err != nil {
|
|
fmt.Fprintf(state, "%%!%c(%s)", r, err.Error())
|
|
return
|
|
}
|
|
writeFmt(state, r, i)
|
|
case 'e', 'E', 'f', 'F', 'g', 'G':
|
|
var f float64
|
|
if err := vals.ScanToGo(wrapped, &f); err != nil {
|
|
fmt.Fprintf(state, "%%!%c(%s)", r, err.Error())
|
|
return
|
|
}
|
|
writeFmt(state, r, f)
|
|
default:
|
|
fmt.Fprintf(state, "%%!%c(unsupported formatting verb)", r)
|
|
}
|
|
}
|
|
|
|
// Writes to State using the flag it stores, but with a potentially different
|
|
// verb and value.
|
|
func writeFmt(state fmt.State, v rune, val any) {
|
|
// Reconstruct the verb string.
|
|
var sb strings.Builder
|
|
sb.WriteRune('%')
|
|
for _, f := range "+-# 0" {
|
|
if state.Flag(int(f)) {
|
|
sb.WriteRune(f)
|
|
}
|
|
}
|
|
if w, ok := state.Width(); ok {
|
|
sb.WriteString(strconv.Itoa(w))
|
|
}
|
|
if p, ok := state.Precision(); ok {
|
|
sb.WriteRune('.')
|
|
sb.WriteString(strconv.Itoa(p))
|
|
}
|
|
sb.WriteRune(v)
|
|
|
|
fmt.Fprintf(state, sb.String(), val)
|
|
}
|
|
|
|
func echo(fm *Frame, opts printOpts, args ...any) error {
|
|
err := print(fm, opts, args...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = fm.ByteOutput().WriteString("\n")
|
|
return err
|
|
}
|
|
|
|
func pprint(fm *Frame, args ...any) error {
|
|
out := fm.ByteOutput()
|
|
for _, arg := range args {
|
|
_, err := out.WriteString(vals.Repr(arg, 0))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = out.WriteString("\n")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func repr(fm *Frame, args ...any) error {
|
|
out := fm.ByteOutput()
|
|
for i, arg := range args {
|
|
if i > 0 {
|
|
_, err := out.WriteString(" ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err := out.WriteString(vals.ReprPlain(arg))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
_, err := out.WriteString("\n")
|
|
return err
|
|
}
|
|
|
|
func show(fm *Frame, v diag.Shower) error {
|
|
out := fm.ByteOutput()
|
|
_, err := out.WriteString(v.Show(""))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = out.WriteString("\n")
|
|
return err
|
|
}
|
|
|
|
func onlyBytes(fm *Frame) error {
|
|
// Discard values in a goroutine.
|
|
valuesDone := make(chan struct{})
|
|
go func() {
|
|
for range fm.InputChan() {
|
|
}
|
|
close(valuesDone)
|
|
}()
|
|
// Make sure the goroutine has finished before returning.
|
|
defer func() { <-valuesDone }()
|
|
|
|
_, err := io.Copy(fm.ByteOutput(), fm.InputFile())
|
|
return err
|
|
}
|
|
|
|
func onlyValues(fm *Frame) error {
|
|
// Discard bytes in a goroutine.
|
|
bytesDone := make(chan struct{})
|
|
go func() {
|
|
// Ignore the error
|
|
_, _ = io.Copy(blackholeWriter{}, fm.InputFile())
|
|
close(bytesDone)
|
|
}()
|
|
// Wait for the goroutine to finish before returning.
|
|
defer func() { <-bytesDone }()
|
|
|
|
// Forward values.
|
|
out := fm.ValueOutput()
|
|
for v := range fm.InputChan() {
|
|
err := out.Put(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type blackholeWriter struct{}
|
|
|
|
func (blackholeWriter) Write(p []byte) (int, error) { return len(p), nil }
|
|
|
|
func slurp(fm *Frame) (string, error) {
|
|
b, err := io.ReadAll(fm.InputFile())
|
|
return string(b), err
|
|
}
|
|
|
|
func fromLines(fm *Frame) error {
|
|
filein := bufio.NewReader(fm.InputFile())
|
|
out := fm.ValueOutput()
|
|
for {
|
|
line, err := filein.ReadString('\n')
|
|
if line != "" {
|
|
err := out.Put(strutil.ChopLineEnding(line))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func fromJSON(fm *Frame) error {
|
|
in := fm.InputFile()
|
|
out := fm.ValueOutput()
|
|
|
|
dec := json.NewDecoder(in)
|
|
// See comments below about using json.Number.
|
|
dec.UseNumber()
|
|
for {
|
|
var v any
|
|
err := dec.Decode(&v)
|
|
if err != nil {
|
|
if err == io.EOF {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
converted, err := fromJSONInterface(v)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = out.Put(converted)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Converts a interface{} that results from json.Unmarshal to an Elvish value.
|
|
func fromJSONInterface(v any) (any, error) {
|
|
switch v := v.(type) {
|
|
case nil, bool, string:
|
|
return v, nil
|
|
case json.Number:
|
|
// The JSON syntax doesn't restrict the precision of numbers. Since
|
|
// we called json.Decoder.UseNumber, it preserves the full number
|
|
// literal, and we can try parsing it as a big int.
|
|
if z, ok := new(big.Int).SetString(v.String(), 0); ok {
|
|
// Also normalize to int if the value fits.
|
|
return vals.NormalizeBigInt(z), nil
|
|
}
|
|
// Parse as float64 instead. This can error if the number is not an
|
|
// integer and exceeds the range of float64.
|
|
return strconv.ParseFloat(v.String(), 64)
|
|
case float64:
|
|
return v, nil
|
|
case []any:
|
|
vec := vals.EmptyList
|
|
for _, elem := range v {
|
|
converted, err := fromJSONInterface(elem)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vec = vec.Conj(converted)
|
|
}
|
|
return vec, nil
|
|
case map[string]any:
|
|
m := vals.EmptyMap
|
|
for key, val := range v {
|
|
convertedVal, err := fromJSONInterface(val)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
m = m.Assoc(key, convertedVal)
|
|
}
|
|
return m, nil
|
|
default:
|
|
return nil, fmt.Errorf("unexpected json type: %T", v)
|
|
}
|
|
}
|
|
|
|
func fromTerminated(fm *Frame, terminator string) error {
|
|
if err := checkTerminator(terminator); err != nil {
|
|
return err
|
|
}
|
|
|
|
filein := bufio.NewReader(fm.InputFile())
|
|
out := fm.ValueOutput()
|
|
for {
|
|
line, err := filein.ReadString(terminator[0])
|
|
if line != "" {
|
|
err := out.Put(strutil.ChopTerminator(line, terminator[0]))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if err != nil {
|
|
if err != io.EOF {
|
|
logger.Println("error on reading:", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func toLines(fm *Frame, inputs Inputs) error {
|
|
out := fm.ByteOutput()
|
|
var errOut error
|
|
|
|
inputs(func(v any) {
|
|
if errOut != nil {
|
|
return
|
|
}
|
|
// TODO: Don't ignore the error.
|
|
_, errOut = fmt.Fprintln(out, vals.ToString(v))
|
|
})
|
|
return errOut
|
|
}
|
|
|
|
func toTerminated(fm *Frame, terminator string, inputs Inputs) error {
|
|
if err := checkTerminator(terminator); err != nil {
|
|
return err
|
|
}
|
|
|
|
out := fm.ByteOutput()
|
|
var errOut error
|
|
inputs(func(v any) {
|
|
if errOut != nil {
|
|
return
|
|
}
|
|
_, errOut = fmt.Fprint(out, vals.ToString(v), terminator)
|
|
})
|
|
return errOut
|
|
}
|
|
|
|
func toJSON(fm *Frame, inputs Inputs) error {
|
|
encoder := json.NewEncoder(fm.ByteOutput())
|
|
|
|
var errEncode error
|
|
inputs(func(v any) {
|
|
if errEncode != nil {
|
|
return
|
|
}
|
|
errEncode = encoder.Encode(v)
|
|
})
|
|
return errEncode
|
|
}
|