Skip to content

Commit

Permalink
multiple: add error chains and Panic() (#168)
Browse files Browse the repository at this point in the history
* res/grammar: Add error chains.

* scanner: Add error chain operator.

* wdte: Add support for error chains.

* std/io: Add `Panic()`.

* wdte: Remove the backtrace from `Error.Error()`.
  • Loading branch information
DeedleFake committed Dec 5, 2018
1 parent 426d1f9 commit c431233
Show file tree
Hide file tree
Showing 8 changed files with 180 additions and 56 deletions.
83 changes: 44 additions & 39 deletions ast/internal/pgen/table.go

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions res/grammar.ebnf
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
| ε
<chain> -> -> <expr>
| -- <expr>
| -| <expr>
| ε
<slot> -> : <slotassign>
| ε
Expand Down
3 changes: 2 additions & 1 deletion scanner/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import "strings"
var (
symbols = map[string]struct{}{
".": {},
"->": {},
"{": {},
"}": {},
"[": {},
Expand All @@ -15,7 +14,9 @@ var (
"=>": {},
";": {},
":": {},
"->": {},
"--": {},
"-|": {},
"(@": {},
}

Expand Down
61 changes: 61 additions & 0 deletions std/io/io.go
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,65 @@ func Writeln(frame wdte.Frame, args ...wdte.Func) wdte.Func {
}).Call(frame, args...)
}

// Panic is a WDTE function with the following signatures:
//
// panic err
// panic w err
// panic desc err
// panic w desc err
//
// Note that, somewhat unusually, Panic accepts its arguments in any order.
//
// It writes the given error to w, prepending the optional
// description in the form `desc: err` and appending a newline. It
// then returns the error. If an error occurs somewhere internally,
// such as while printing, that error is returned instead.
//
// Panic is primarily intended for use with the error chain operator.
// For example:
//
// + a b -| panic 'Failed to add a and b';
func Panic(frame wdte.Frame, args ...wdte.Func) wdte.Func {
frame = frame.Sub("panic")

var w writer
var desc wdte.String
var e error

set := func(f wdte.Func) {
switch f := f.(type) {
case writer:
w = f
case wdte.String:
desc = f + ": "
case error:
e = f

default:
panic(fmt.Errorf("Unexpected argument type: %T", f))
}
}

n := 3
if len(args) < 3 {
n = len(args)
}

for i, arg := range args[:n] {
args[i] = arg.Call(frame)
set(args[i])
}
if e == nil {
return auto.SaveArgs(wdte.GoFunc(Panic), args...)
}

_, err := fmt.Fprintf(w, "%v%v\n", desc, e)
if err != nil {
return wdte.Error{Err: err, Frame: frame}
}
return e.(wdte.Func)
}

// Scope is a scope containing the functions in this package.
var Scope = wdte.S().Map(map[wdte.ID]wdte.Func{
"stdin": stdin{},
Expand All @@ -566,6 +625,8 @@ var Scope = wdte.S().Map(map[wdte.ID]wdte.Func{

"write": wdte.GoFunc(Write),
"writeln": wdte.GoFunc(Writeln),
"panic": wdte.GoFunc(Panic),
//"panicln": wdte.GoFunc(Panicln),
})

func init() {
Expand Down
27 changes: 18 additions & 9 deletions translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (m *translator) fromArgDecls(argdecls *ast.NTerm, ids []ID) []ID {
}
}

func (m *translator) fromExpr(expr *ast.NTerm, ignored bool, chain Chain) (r Func) {
func (m *translator) fromExpr(expr *ast.NTerm, flags uint, chain Chain) (r Func) {
first := m.fromSingle(expr.Children()[0].(*ast.NTerm))
in := m.fromArgs(expr.Children()[1].(*ast.NTerm), nil)
slots, assignFunc := m.fromSlot(expr.Children()[3].(*ast.NTerm))
Expand All @@ -82,7 +82,7 @@ func (m *translator) fromExpr(expr *ast.NTerm, ignored bool, chain Chain) (r Fun
piece := &ChainPiece{
Expr: r,

Ignored: ignored,
Flags: flags,
Slots: slots,
AssignFunc: assignFunc,
}
Expand All @@ -102,7 +102,7 @@ func (m *translator) fromLetExpr(expr *ast.NTerm) Func {
mods := m.fromFuncMods(first)
id := ID(assign.Children()[1].(*ast.Term).Tok().Val.(string))
args := m.fromArgDecls(assign.Children()[2].(*ast.NTerm), nil)
inner := m.fromExpr(assign.Children()[4].(*ast.NTerm), false, nil)
inner := m.fromExpr(assign.Children()[4].(*ast.NTerm), 0, nil)

if mods&funcModMemo != 0 {
inner = &Memo{
Expand Down Expand Up @@ -139,7 +139,7 @@ func (m *translator) fromLetExpr(expr *ast.NTerm) Func {
assign.Children()[2].(*ast.NTerm),
[]ID{ID(assign.Children()[1].(*ast.Term).Tok().Val.(string))},
),
Expr: m.fromExpr(assign.Children()[6].(*ast.NTerm), false, nil),
Expr: m.fromExpr(assign.Children()[6].(*ast.NTerm), 0, nil),
}
}

Expand Down Expand Up @@ -256,8 +256,8 @@ func (m *translator) fromSwitches(switches *ast.NTerm, cases [][2]Func) [][2]Fun
switch sw := switches.Children()[0].(type) {
case *ast.NTerm:
cases = append(cases, [...]Func{
m.fromExpr(sw, false, nil),
m.fromExpr(switches.Children()[2].(*ast.NTerm), false, nil),
m.fromExpr(sw, 0, nil),
m.fromExpr(switches.Children()[2].(*ast.NTerm), 0, nil),
})
return m.fromSwitches(switches.Children()[4].(*ast.NTerm), cases)

Expand Down Expand Up @@ -321,7 +321,7 @@ func (m *translator) fromExprs(exprs *ast.NTerm, funcs []Func) []Func {
case *ast.NTerm:
switch expr.Name() {
case "expr":
funcs = append(funcs, m.fromExpr(expr, false, nil))
funcs = append(funcs, m.fromExpr(expr, 0, nil))
case "letexpr":
let := m.fromLetExpr(expr)
funcs = append(funcs, let)
Expand Down Expand Up @@ -355,8 +355,17 @@ func (m *translator) fromChain(chain *ast.NTerm, acc Chain) Func {
return acc
}

ignored := chain.Children()[0].(*ast.Term).Tok().Val == "--"
return m.fromExpr(chain.Children()[1].(*ast.NTerm), ignored, acc)
oper := chain.Children()[0].(*ast.Term).Tok().Val

var flags uint
if oper == "--" {
flags |= IgnoredChain
}
if oper == "-|" {
flags |= ErrorChain
}

return m.fromExpr(chain.Children()[1].(*ast.NTerm), flags, acc)
}

type funcMod uint
Expand Down
6 changes: 1 addition & 5 deletions types.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package wdte

import (
"bytes"
"fmt"
"math/big"
"reflect"
Expand Down Expand Up @@ -206,10 +205,7 @@ func (e Error) Call(frame Frame, args ...Func) Func { // nolint
}

func (e Error) Error() string {
var buf bytes.Buffer
_ = e.Frame.Backtrace(&buf)

return fmt.Sprintf("WDTE Error: %v\n%s", e.Err, buf.Bytes())
return e.Err.Error()
}

func (e Error) Reflect(name string) bool { // nolint
Expand Down
45 changes: 43 additions & 2 deletions wdte.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,17 +462,31 @@ func (f FuncCall) String() string { // nolint
return fmt.Sprint(f.Func)
}

const (
NormalChain = 0
IgnoredChain = 1 << (iota - 1)
ErrorChain
)

// A ChainPiece is, as you can probably guess from the name, a piece
// of a Chain. It stores the underlying expression as well as some
// extra information necessary for properly evaluating the Chain.
type ChainPiece struct {
Expr Func

Ignored bool
Flags uint
Slots []ID
AssignFunc AssignFunc
}

func (p ChainPiece) String() string { // nolint
if inner, ok := p.Expr.(fmt.Stringer); ok {
return inner.String()
}

return fmt.Sprint(p.Expr)
}

// Chain is an unevaluated chain expression.
type Chain []*ChainPiece

Expand All @@ -484,20 +498,47 @@ func (f Chain) Call(frame Frame, args ...Func) Func { // nolint
var slotScope *Scope
var prev Func
for _, cur := range f {
if _, ok := prev.(error); ok != (cur.Flags&ErrorChain != 0) {
continue
}

tmp := cur.Call(frame.WithScope(frame.Scope().Sub(slotScope)))
if prev != nil {
tmp = tmp.Call(frame.WithScope(frame.Scope().Sub(slotScope)), prev)
}

slotScope, tmp = cur.AssignFunc(frame, slotScope, cur.Slots, tmp)

if !cur.Ignored {
if cur.Flags&IgnoredChain == 0 {
prev = tmp
}
}
return prev
}

func (f Chain) String() string {
if len(f) == 0 {
return "<empty chain>"
}

var sb strings.Builder

fmt.Fprint(&sb, f[0])
for _, p := range f[1:] {
m := "->"
if p.Flags&IgnoredChain != 0 {
m = "--"
}
if p.Flags&ErrorChain != 0 {
m = "-|"
}

fmt.Fprintf(&sb, " %v %v", m, p)
}

return sb.String()
}

// A Sub is a function that is in a subscope. This is most commonly an
// imported function.
type Sub []Func
Expand Down
10 changes: 10 additions & 0 deletions wdte_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,11 @@ func TestBasics(t *testing.T) {
script: `1 -> + 2 -- + 5 -> - 1;`,
ret: wdte.Number(2),
},
{
name: "Chain/Error",
script: `1 -| 'Not broken yet.' -> a -> + 3 -| 'It broke.' -| 'Or did it?';`,
ret: wdte.String("It broke."),
},
{
name: "Fib",
script: `let main n => n { <= 1 => n; true => + (main (- n 2)) (main (- n 1)); }; main 12;`,
Expand Down Expand Up @@ -578,6 +583,11 @@ func TestIO(t *testing.T) {
script: `let io => import 'io'; let main => 'test' -> io.writeln io.stdout;`,
out: "test\n",
},
{
name: "Panic",
script: `let io => import 'io'; + a b -| io.panic io.stderr 'Failed to add a and b' -| 3;`,
err: `Failed to add a and b: "a" is not in scope` + "\n",
},
{
name: "Lines",
script: `let io => import 'io'; let s => import 'stream'; let str => import 'strings'; let main v => str.read v -> io.lines -> s.collect;`,
Expand Down

0 comments on commit c431233

Please sign in to comment.