Scheme Lisp - Using eval for arbitrary arithmetic expressions

gm123

I'm trying to evaluate arbitrary and nested arithmetic expressions in scheme with eval. The expressions are build up from numbers, operators, constant bindings and parentheses. For example, (eval (+ 1 1)) or (eval (+ (* 1 2) 3))

The problem is, the expressions could also have unbalanced parentheses, like (eval ((+ 1 1)), and the reader in scheme would just wait when the expression is entered till all parentheses are closed and somehow matched.

I looked into quotation in scheme and by using a try-catch-mechanism in scheme (How to implement a try-catch block in scheme?) - but that didn't work. I'm also thinking about implementing in scheme my own version of an evaluator for arithmetic expressions, maybe with brackets as delimiters.

I search for a way to evaluate arbitrary expressions, and if the expression is not valid, to get an error message like 'not valid expression' from eval, and not have the reader waiting for closing parentheses.

tfb

If, as you say, the expressions you are trying to parse may have unbalanced parens, then those expressions clearly live in some unstructured data which is, almost certainly a sequence of characters of some kind. Since the syntax of the expressions you want to read is a subset of the syntax of Scheme, then you can simply use the tool that the language provides for you: read: you definitely do not want to write your own parser for this unless you like spending a lot of time getting corner cases right.

Note that any parser which turns a sequence of characters into some object must at some point ask for the next character from the sequence. There are two things that can happen in response to that:

  • it can get a character, possibly after waiting for something on the other end of that sequence – a human perhaps – to generate the character;
  • it can get an indication that there are no more characters – an end-of-file indicator of some kind.

No magic you can do can avoid that. So the problem you describe of the reader waiting for the close-paren is not real: any parser you write will have the same problem, when reading from an interactive stream: it simply has to wait for the human on the other end of that stream to either type something, or to indicate that the stream is complete, at which point it can decide whether what it saw was well-formed or not.

So the answer to the turning-sequences-of-characters-into-object part of your problem is to use the read provided by the language. Here is one approach to doing that – since you have specified that you are using Racket, this uses Racket, rather than RnRS Scheme for any n. Almost all of this code would not be needed if you did not care about restricting the reader somewhat (and I am not sure I have restricted it enough). The rest of it deals with handling exceptions from the reader, and turning them into multiple values.

(define (read-thing source)
  ;; Read some object from a source port.
  ;; Return two values: either the object read and #t,
  ;; or some exception object and #f if something went wrong.
  ;;
  ;; This tries to defang the reader but I am not sure it does enough
  (with-handlers ([exn? (λ (e) (values e #f))])
    (call-with-default-reading-parameterization
     (thunk
      (parameterize ([read-accept-lang #f]
                     [read-accept-reader #f])
        (values (read source) #t))))))

(define (read-thing-from-file f)
  ;; read a thing from a file
  (call-with-input-file f read-thing))

(define (read-thing-from-string s)
  ;; read a thing from a string
  (call-with-input-string s read-thing))

So now we can try this:

> (read-thing-from-string "foo")
'foo
#t
> (read-thing-from-string "(foo")
(exn:fail:read:eof
 "read: expected a `)` to close `(`"
 #<continuation-mark-set>
 (list (srcloc 'string #f #f 1 1)))
#f

As you can see the second value tells you whether read raised an exception or not.

Now you can use this thing-reader to feed an evaluator. For instance you can use a function like this:

(define (read-and-evaluate reader source
                           evaluator environment)
  ;; Read something with a read-thing compatible reader from source
  ;; and hand it to evaluator with environment as a second argument
  ;; If read-thing indicates an error then simply raise it again.
  (call-with-values
   (thunk (reader source))
   (λ (thing good)
     (when (not good)
       (raise thing))
     (evaluator thing environment))))

So now, the problem is reduced to writing evaluator, which I'm not going to do.

Here is an example of this function being used with a trivial evaluator which simply returns the form it has been given.

> (read-and-evaluate
   read-thing-from-string "(foo)"
   (λ (form env) form) #f)
'(foo)
> (read-and-evaluate
   read-thing-from-string "(foo"
   (λ (form env) form) #f)
; string::1: read: expected a `)` to close `(` [,bt for context]

Of course, the whole handling-the-exception-in-the-reader thing is not needed here, since all I end up doing it reraising it, but it does show you how you can handle such an error.


A note on writing an evaluator. It is tempting to say that, well, (+ 1 2) is a valid Scheme expression, and Scheme has eval so just use that. This is like using a thermonuclear device to demolish a house: it does a good job of demolishing the house but it has undesirable consequences such as killing hundreds of thousands of people. Consider, for instance, that (system "rm -rf $HOME") is also a valid Racket expression, but one you may not want to run.

To avoid this kind of code-injection attack you want to restrict the evaluator as much as you possibly can, so it will evaluate only the language you are interested in. The same applies to the reader: above the full Racket reader can, I am almost sure, evaluate arbitrary Racket code at read-time: I've tried (but probably failed) to defang it above.

Racket has a significant set of tools which let you define a more restricted language, for which eval would be safe. However for a really simple language such as evaluating arithmetic expressions it is almost certainly simpler to simply write your own evaluator. It certainly is more educational to do so.

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

How to eval twice in lisp (without using eval)

From Dev

How to eval twice in lisp (without using eval)

From Dev

Arithmetic in Lisp

From Dev

equivalence of arithmetic expressions using algebra_simps

From Dev

equivalence of arithmetic expressions using algebra_simps

From Dev

Evaluate object expressions without using eval

From Dev

Lisp error: (error "Lisp nesting exceeds `max-lisp-eval-depth'") when using cl- functions

From Dev

Suspending arithmetic operation in lisp

From Dev

Suspending arithmetic operation in lisp

From Dev

Removing unnecessary/duplicates parentheses from arithmetic expressions using stack(s)

From Java

first order logic creating terms for arithmetic expressions using prolog

From Dev

Understand two examples using indirect expansion for variable expansion in arithmetic expressions

From Dev

Lisp / Scheme : Progn vs And

From Dev

Seeking clarification on Scheme eval

From Dev

What's the reason for using lambda expressions to define functions in Scheme?

From Dev

Functions in R - using eval() and parse() to plot expressions in rgl

From Dev

Arithmetic expressions in for-loop

From Dev

"Multiplication of Arbitrary Precision Numbers" in Scheme

From Dev

Inserting at arbitrary position in list in Scheme

From Dev

Finding the difference in an arithmetic progression in Lisp

From Dev

Understanding expressions in Scheme

From Dev

Understanding expressions in Scheme

From Dev

Iterative range function in Scheme/Lisp

From Dev

Simulate scheme define in common lisp

From Dev

Simulate scheme define in common lisp

From Dev

Eval in Scheme accessing lexical variables

From Dev

Backquote in Common Lisp: read and eval

From Dev

EVAL: undefined function NIL in Lisp

From Dev

EVAL: undefined function in Common LISP

Related Related

  1. 1

    How to eval twice in lisp (without using eval)

  2. 2

    How to eval twice in lisp (without using eval)

  3. 3

    Arithmetic in Lisp

  4. 4

    equivalence of arithmetic expressions using algebra_simps

  5. 5

    equivalence of arithmetic expressions using algebra_simps

  6. 6

    Evaluate object expressions without using eval

  7. 7

    Lisp error: (error "Lisp nesting exceeds `max-lisp-eval-depth'") when using cl- functions

  8. 8

    Suspending arithmetic operation in lisp

  9. 9

    Suspending arithmetic operation in lisp

  10. 10

    Removing unnecessary/duplicates parentheses from arithmetic expressions using stack(s)

  11. 11

    first order logic creating terms for arithmetic expressions using prolog

  12. 12

    Understand two examples using indirect expansion for variable expansion in arithmetic expressions

  13. 13

    Lisp / Scheme : Progn vs And

  14. 14

    Seeking clarification on Scheme eval

  15. 15

    What's the reason for using lambda expressions to define functions in Scheme?

  16. 16

    Functions in R - using eval() and parse() to plot expressions in rgl

  17. 17

    Arithmetic expressions in for-loop

  18. 18

    "Multiplication of Arbitrary Precision Numbers" in Scheme

  19. 19

    Inserting at arbitrary position in list in Scheme

  20. 20

    Finding the difference in an arithmetic progression in Lisp

  21. 21

    Understanding expressions in Scheme

  22. 22

    Understanding expressions in Scheme

  23. 23

    Iterative range function in Scheme/Lisp

  24. 24

    Simulate scheme define in common lisp

  25. 25

    Simulate scheme define in common lisp

  26. 26

    Eval in Scheme accessing lexical variables

  27. 27

    Backquote in Common Lisp: read and eval

  28. 28

    EVAL: undefined function NIL in Lisp

  29. 29

    EVAL: undefined function in Common LISP

HotTag

Archive