Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 10 additions & 17 deletions core/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,8 @@ import (
// Error is returned by all slurp operations. Cause indicates the underlying
// error type. Use errors.Is() with Cause to check for specific errors.
type Error struct {
Message string
Cause error
Form string
Begin, End Position
Cause error
Message string
}

// With returns a clone of the error with message set to given value.
Expand All @@ -30,22 +28,17 @@ func (e Error) Unwrap() error { return e.Cause }

func (e Error) Error() string {
if e.Cause != nil {
return fmt.Sprintf("%v: %s", e.Cause, e.Message)
return fmt.Sprintf("EvalError: %v: %s", e.Cause, e.Message)
}
return e.Message
}

// Position represents the positional information about a value read
// by reader.
type Position struct {
File string
Ln int
Col int
return fmt.Sprintf("EvalError: %s", e.Message)
}

func (p Position) String() string {
if p.File == "" {
p.File = "<unknown>"
func (e Error) Format(s fmt.State, verb rune) {
if !s.Flag('#') {
fmt.Fprint(s, e.Error())
}
return fmt.Sprintf("%s:%d:%d", p.File, p.Ln, p.Col)

// TODO: render the offending form.
fmt.Fprint(s, e.Error())
}
33 changes: 21 additions & 12 deletions reader/errors.go → reader/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ var (
// that more data is needed to complete the current form.
ErrEOF = errors.New("unexpected EOF while parsing")

// ErrUnmatchedDelimiter is returned when a reader macro encounters a closing
// container- delimiter without a corresponding opening delimiter (e.g. ']'
// but no '[').
ErrUnmatchedDelimiter = errors.New("unmatched delimiter")

// ErrNumberFormat is returned when a reader macro encounters a illegally
// formatted numerical form.
ErrNumberFormat = errors.New("invalid number format")
Expand All @@ -28,9 +23,7 @@ var (
// some issue. Use errors.Is() with Cause to check for specific underlying
// errors.
type Error struct {
Form string
Cause error
Rune rune
Begin, End Position
}

Expand All @@ -40,11 +33,27 @@ func (e Error) Is(other error) bool { return errors.Is(e.Cause, other) }
// Unwrap returns the underlying cause of the error.
func (e Error) Unwrap() error { return e.Cause }

func (e Error) Error() string {
cause := e.Cause
if errors.Is(cause, ErrUnmatchedDelimiter) {
cause = fmt.Errorf("unmatched delimiter '%c'", e.Rune)
func (e Error) Error() string { return fmt.Sprintf("ReaderError: %v", e.Cause) }

func (e Error) Format(s fmt.State, verb rune) {
if !s.Flag('#') {
fmt.Fprint(s, e.Error())
return
}

return fmt.Sprintf("error while reading: %v", cause)
/*
* File "<REPL>" line 1, column 2
* ReaderError: unmatched delimiter ']'
*/
fmt.Fprintf(s, "File \"%s\" line %d, column %d\n",
e.Begin.File,
e.Begin.Ln,
e.Begin.Col)
fmt.Fprintf(s, "ReaderError: %s", e.Error())
}

type unmatchedDelimiterError rune

func (r unmatchedDelimiterError) Error() string {
return fmt.Sprintf("unmatched delimiter '%c'", r)
}
30 changes: 14 additions & 16 deletions reader/macros.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ type Macro func(rd *Reader, init rune) (core.Any, error)
// unmatched delimiters such as closing parenthesis etc.
func UnmatchedDelimiter() Macro {
return func(rd *Reader, initRune rune) (core.Any, error) {
e := rd.annotateErr(ErrUnmatchedDelimiter, rd.Position(), "").(Error)
e.Rune = initRune
return nil, e
return nil, rd.annotateErr(unmatchedDelimiterError(initRune), rd.Position())
}
}

Expand All @@ -73,7 +71,7 @@ func symbolReader(symTable map[string]core.Any) Macro {

s, err := rd.Token(init)
if err != nil {
return nil, rd.annotateErr(err, beginPos, s)
return nil, rd.annotateErr(err, beginPos)
}

if predefVal, found := symTable[s]; found {
Expand All @@ -98,33 +96,33 @@ func readNumber(rd *Reader, init rune) (core.Any, error) {

switch {
case isRadix && (decimalPoint || isScientific):
return nil, rd.annotateErr(ErrNumberFormat, beginPos, numStr)
return nil, rd.annotateErr(ErrNumberFormat, beginPos)

case isScientific:
v, err := parseScientific(numStr)
if err != nil {
return nil, rd.annotateErr(err, beginPos, numStr)
return nil, rd.annotateErr(err, beginPos)
}
return v, nil

case decimalPoint:
v, err := strconv.ParseFloat(numStr, 64)
if err != nil {
return nil, rd.annotateErr(ErrNumberFormat, beginPos, numStr)
return nil, rd.annotateErr(ErrNumberFormat, beginPos)
}
return builtin.Float64(v), nil

case isRadix:
v, err := parseRadix(numStr)
if err != nil {
return nil, rd.annotateErr(err, beginPos, numStr)
return nil, rd.annotateErr(err, beginPos)
}
return v, nil

default:
v, err := strconv.ParseInt(numStr, 0, 64)
if err != nil {
return nil, rd.annotateErr(ErrNumberFormat, beginPos, numStr)
return nil, rd.annotateErr(ErrNumberFormat, beginPos)
}

return builtin.Int64(v), nil
Expand All @@ -141,7 +139,7 @@ func readString(rd *Reader, init rune) (core.Any, error) {
if errors.Is(err, io.EOF) {
err = ErrEOF
}
return nil, rd.annotateErr(err, beginPos, string(init)+b.String())
return nil, rd.annotateErr(err, beginPos)
}

if r == '\\' {
Expand All @@ -151,7 +149,7 @@ func readString(rd *Reader, init rune) (core.Any, error) {
err = ErrEOF
}

return nil, rd.annotateErr(err, beginPos, string(init)+b.String())
return nil, rd.annotateErr(err, beginPos)
}

// TODO: Support for Unicode escape \uNN format.
Expand Down Expand Up @@ -191,7 +189,7 @@ func readKeyword(rd *Reader, init rune) (core.Any, error) {

token, err := rd.Token(-1)
if err != nil {
return nil, rd.annotateErr(err, beginPos, token)
return nil, rd.annotateErr(err, beginPos)
}

return builtin.Keyword(token), nil
Expand All @@ -202,7 +200,7 @@ func readCharacter(rd *Reader, _ rune) (core.Any, error) {

r, err := rd.NextRune()
if err != nil {
return nil, rd.annotateErr(err, beginPos, "")
return nil, rd.annotateErr(err, beginPos)
}

token, err := rd.Token(r)
Expand Down Expand Up @@ -237,7 +235,7 @@ func readList(rd *Reader, _ rune) (core.Any, error) {
forms = append(forms, val)
return nil
}); err != nil {
return nil, rd.annotateErr(err, beginPos, "")
return nil, rd.annotateErr(err, beginPos)
}

return builtin.NewList(forms...), nil
Expand All @@ -249,12 +247,12 @@ func quoteFormReader(expandFunc string) Macro {
if err != nil {
if err == io.EOF {
return nil, Error{
Form: expandFunc,
// Form: expandFunc,
Cause: ErrEOF,
}
} else if err == ErrSkip {
return nil, Error{
Form: expandFunc,
// Form: expandFunc,
Cause: errors.New("cannot quote a no-op form"),
}
}
Expand Down
12 changes: 5 additions & 7 deletions reader/reader.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,19 +381,17 @@ func (rd *Reader) execDispatch() (core.Any, error) {
return form, nil
}

func (rd *Reader) annotateErr(err error, beginPos Position, form string) error {
func (rd *Reader) annotateErr(err error, beginPos Position /*, form string */) error {
if err == io.EOF || err == ErrSkip {
return err
}

readErr := Error{}
if e, ok := err.(Error); ok {
readErr = e
} else {
readErr = Error{Cause: err}
readErr, ok := err.(Error)
if !ok {
readErr.Cause = err
}

readErr.Form = form
// readErr.Form = form
readErr.Begin = beginPos
readErr.End = rd.Position()
return readErr
Expand Down
54 changes: 12 additions & 42 deletions repl/options.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package repl

import (
"bufio"
"io"
"os"

Expand Down Expand Up @@ -35,10 +34,7 @@ type ErrMapper func(err error) error
// backed by os.Stdin
func WithInput(in Input, mapErr ErrMapper) Option {
if in == nil {
in = &lineReader{
scanner: bufio.NewScanner(os.Stdin),
out: os.Stdout,
}
in = NewPrompt(NewLineReader(os.Stdin), os.Stdout)
}

if mapErr == nil {
Expand All @@ -51,16 +47,16 @@ func WithInput(in Input, mapErr ErrMapper) Option {
}
}

// WithOutput sets the REPL's output stream.`nil` defaults to stdout.
func WithOutput(w io.Writer) Option {
if w == nil {
w = os.Stdout
}
// // WithOutput sets the REPL's output stream.`nil` defaults to stdout.
// func WithOutput(w io.Writer) Option {
// if w == nil {
// w = os.Stdout
// }

return func(repl *REPL) {
repl.output = w
}
}
// return func(repl *REPL) {
// repl.output = w
// }
// }

// WithBanner sets the REPL's banner which is displayed once when the REPL
// starts.
Expand Down Expand Up @@ -98,44 +94,18 @@ func WithReaderFactory(factory ReaderFactory) Option {
// A `nil` value for p defaults to `Renderer`.
func WithPrinter(p Printer) Option {
if p == nil {
p = Renderer{}
p = &Renderer{}
}

return func(repl *REPL) {
repl.printer = p
repl.output = p
}
}

func withDefaults(opts []Option) []Option {
return append([]Option{
WithInput(nil, nil),
WithOutput(nil),
WithReaderFactory(nil),
WithPrinter(nil),
}, opts...)
}

type lineReader struct {
scanner *bufio.Scanner
out io.Writer
prompt string
}

func (lr *lineReader) Readline() (string, error) {
lr.out.Write([]byte(lr.prompt))

if !lr.scanner.Scan() {
if lr.scanner.Err() == nil { // scanner swallows EOF
return lr.scanner.Text(), io.EOF
}

return "", lr.scanner.Err()
}

return lr.scanner.Text(), nil
}

// no-op
func (lr *lineReader) SetPrompt(p string) {
lr.prompt = p
}
Loading