如何解决Scheme Lisp-将eval用于任意算术表达式
我正在尝试使用eval评估方案中的任意和嵌套算术表达式。
表达式由数字,运算符,常量绑定和括号组成。
例如,(eval (+ 1 1))
或(eval (+ (* 1 2) 3))
问题是,表达式也可能带有不平衡的括号,例如
(eval ((+ 1 1))
,方案中的读者将在输入表达式后等待
直到所有括号都闭合并以某种方式匹配。
我研究了方案中的报价,并在方案中使用了try-catch机制 (How to implement a try-catch block in scheme?)- 但这没用。我还考虑在方案中实现自己的算术表达式评估器版本, 也许用方括号作为分隔符。
我正在寻找一种评估任意表达式的方法,如果表达式是 无效,是为了从eval中获取类似“无效表达式”之类的错误消息,并且读者无法等待右括号。
编辑: 有关解决方案的实现,请参见下文。
解决方法
您的策略无效。 Scheme具有“ try-catch”,但就像大多数编程语言中的“ try-catch”一样,它仅在运行时错误(程序运行时发生的错误)上起作用。相反,括号不平衡问题是语法错误(读取程序时发生的错误)。
例如,在Python中,您可以编写:
try:
1 / 0
except:
pass
将捕获错误,但不会捕获语法错误,例如:
try:
if
except:
pass
如果要允许输入的括号不平衡,则需要将输入作为字符串而不是语言本身的表达式。例如:
(define my-input "(+ 1 1")
很好,但是:
(define my-input (+ 1 1)
不是。
但是,对输入字符串进行计算很烦人。因此,下一步应该做的是标记化输入字符串并将其解析为树。您也可以在这里检测到不平衡的括号并报告错误:
(define (parse input-string)
;; TODO
)
(parse "(+ 1 2)")
;; expect '(+ 1 2)
(parse "(+ 1 2")
;; expect an error: "not valid expression"
假设parse
没有错误,那么您将有一个像'(+ 1 2)
的树作为输出。下一步是编写一个消耗一棵树并产生答案的函数
(define (interp input-tree)
;; TODO
)
(interp '(+ 1 2))
;; expect 3
(interp '(+ (+ 5 6) 7))
;; expect 18
您的eval
就是parse
和interp
的组成:
(define (eval input-string)
(interp (parse input-string)))
(eval "(+ 1 2)")
;; expect 3
(eval "(+ 1 2")
;; expect an error: "not valid expression"
,
如果您要解析的表达式可能具有不平衡的解析度,那么这些表达式显然存在于一些非结构化数据中,这些数据几乎可以肯定是某种字符序列。由于要读取的表达式的语法是Scheme语法的子集,因此您可以简单地使用该语言为您提供的工具:read
:您绝对不会要为此编写自己的解析器,除非您喜欢花费大量时间正确处理极端情况。
请注意, any 解析器将字符序列转换为某个对象,必须在某个时候要求序列中的下一个字符。对此可能发生两件事:
- 它可能会得到一个角色,可能是在等待序列另一端的东西(也许是人类)之后才能生成角色;
- 它可以指示没有更多字符-一种文件结尾指示符。
没有法术可以避免。因此,您所描述的等待阅读器关闭的问题并不是真实的:您编写的任何解析器在从交互式流中读取时都会遇到相同的问题:它只需要等待人类在该流的另一端输入某些内容或表明该流已完成,这时它可以决定所看到的内容是否格式正确。
因此,解决您的问题中将字符转换为对象的部分的答案是使用该语言提供的read
。这是一种执行此操作的方法-由于您指定使用的是Racket,因此对任何n都使用Racket,而不是RnRS Scheme。如果您不关心限制读者,那么几乎不需要所有这些代码(而且我不确定我是否已经充分限制了它)。其余内容处理来自读取器的异常,并将其转换为多个值。
(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))
现在我们可以尝试一下:
> (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
您会看到第二个值告诉您read
是否引发了异常。
现在,您可以使用此事物读取器为评估人员提供数据。例如,您可以使用如下功能:
(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))))
所以现在,问题减少到了写evaluator
上,我将不打算这样做。
以下是此函数与简单评估程序一起使用的示例,该评估程序仅返回已给出的形式。
> (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]
当然,这里不需要阅读器中的所有异常处理,因为我最终会重新处理它,但这确实向您显示了如何处理此类错误。
有关编写评估者的说明。很容易说(+ 1 2)
是有效的Scheme表达式,而Scheme具有eval
,所以只需使用它即可。这就像使用热核设备拆除房屋:拆除房屋的工作很出色,但是却带来了不良后果,例如杀死数十万人。例如,考虑(system "rm -rf $HOME")
也是 一个有效的Racket表达式,但是您可能不想运行它。
为避免这种代码注入攻击,您希望尽可能地限制评估程序,因此它将只评估您所感兴趣的语言。 :我几乎可以肯定,完整的Racket阅读器可以在读取时评估任意的Racket代码:我已经尝试过(但很可能失败了)在上面进行了分层。
球拍有很多重要的工具,可让您定义更受限制的语言,对于eval
来说,这是安全的。但是,对于真正的 简单语言(如评估算术表达式),几乎可以肯定,简单地编写自己的评估器会更简单。这样做当然更具教育意义。
在@tfb和@ sorawee-porncharoenwase的帮助下,我现在有了一个可行的解决方案。 我认为在执行arbitray Scheme / Racket表达式时可以节省一些费用, 其中一些可能无济于事。
让我们首先使用@tfb的过程定义。
(+ 1 b)
是有效的Scheme / Rack表达式,因此read-thing-from-string
应该将其声明为有效。
(let-values ([(expr is-valid-expr) (read-thing-from-string "(+ 1 b)")])
(if is-valid-expr "Valid Scheme/Racket expression" "Not valid Scheme/Racket expression"))
;; -> "Valid Scheme/Racket expression"
正如(( 1 b)
所认识的, read-thing-from-string
不是有效的Scheme / Rack表达式。
(let-values ([(expr is-valid-expr) (read-thing-from-string "(( 1 b)")])
(if is-valid-expr "Valid Scheme/Racket expression" "Not valid Scheme/Racket expression"))
; -> "Not valid Scheme/Racket expression"
我只想能够评估任意算术表达式,
像(+ 1 1)
一样,但是像(a b 1)
这样的表达式是有效的Scheme / Racket表达式,应该这样识别。
(let-values ([(expr is-valid-expr) (read-thing-from-string "(a b 1)")])
(if is-valid-expr "Valid Scheme/Racket expression" "Not valid Scheme/Racket expression"))
;; -> "Valid Scheme/Racket expression"
现在我们可以识别有效的Scheme / Racket表达式,我们可以使用自己的表达式进行评估
评估程序。我们本可以使用Scheme的/ Racket的eval
,但这通常是
危险的。
为了编写自定义评估程序,我从书中汲取了很多启发
“计算机程序的结构和解释”,“金属语言抽象”一章。
在本书中,关于环境操纵的设置还有很多, 因为那里有评估程序代码的评估程序-我只是想能够 解释算术表达式,从而可以保持对环境的更多处理 简单。
(define envr (hash 'a 1 'b 2 '+ + '- - '* * '/ /))
(define (eval-expr expr envr)
(cond ((self-evaluating? expr) expr)
((variable? expr) (lookup-variable-value expr envr))
((application? expr)
(apply-proc (eval-expr (operator expr) envr)
(list-of-values (operands expr) envr)))
(else (error "Not valid arithmetic expression: EVAL-EXPR" expr))))
(define (self-evaluating? expr)
(if (number? expr) true false))
(define (variable? expr)
(symbol? expr))
(define (lookup-variable-value var envr)
(with-handlers ([exn:fail? (lambda (exn)
(error "Unbound identifier: LOOKUP-VARIABLE-VALUE" var))])
(hash-ref envr var)))
;; Page 505: "A procedure application is any compound expression that is not
;; one of the above expression types. The car of the expression is
;; the operator,and the cdr is the list of operands:"
(define (application? expr) (pair? expr))
(define (operator expr) (car expr))
(define (operands expr) (cdr expr))
(define (no-operands? ops) (null? ops))
(define (first-operand ops) (car ops))
(define (rest-operands ops) (cdr ops))
(define (apply-proc procedure arguments)
(if (primitive-procedure? procedure)
(apply procedure arguments)
(error "Unknown procedure type: APPLY-PROC" procedure)))
(define (list-of-values exps envr)
(if (no-operands? exps)
'()
(cons (eval-expr (first-operand exps) envr)
(list-of-values (rest-operands exps) envr))))
(define primitive-procedures
(list (list '+ +)
(list '- -)
(list '* *)
(list '/ /)))
(define primitive-procedure-objects
(map (lambda (proc) (cadr proc))
primitive-procedures))
(define (primitive-procedure? proc)
(member proc primitive-procedure-objects))
我们现在拥有了可以评估表达式的所有内容:
;; Example evaluations ;;
(with-handlers ([exn:fail? (lambda (exn)
(exn-message exn))])
(eval-expr 1 envr))
; -> 1
(with-handlers ([exn:fail? (lambda (exn)
(exn-message exn))])
(eval-expr 'b envr))
; -> 2
(with-handlers ([exn:fail? (lambda (exn)
(exn-message exn))])
(eval-expr '(+ 1 b) envr))
; -> 3
(with-handlers ([exn:fail? (lambda (exn)
(exn-message exn))])
(eval-expr '(> 1 b) envr))
; -> "Unbound identifier: LOOKUP-VARIABLE-VALUE >"
(let-values ([(expr is-valid-expr) (read-thing-from-string "(+ 1 b)")])
(if is-valid-expr (eval-expr expr envr) "Not valid Scheme/Racket expression"))
; -> 3
(let-values ([(expr is-valid-expr) (read-thing-from-string "(( 1 b)")])
(if is-valid-expr (eval-expr expr envr) "Not valid Scheme/Racket expression"))
; -> "Not valid Scheme/Racket expression"
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。