例外:Invalid_argument "String.sub / Bytes.sub"

Exception: Invalid_argument "String.sub / Bytes.sub"

我为 OCaml 中的基本算术表达式编写了尾递归扫描器

语法

Exp ::= n | Exp Op Exp | (Exp)

Op ::= + | - | * | /
type token =
| Tkn_NUM of int
| Tkn_OP of string
| Tkn_LPAR
| Tkn_RPAR
| Tkn_END

exception ParseError of string * string

let tail_tokenize s =
  let rec tokenize_rec s pos lt =
    if pos < 0 then lt
    else
      let c = String.sub s pos 1 in
      match c with
      | " " -> tokenize_rec s (pos-1) lt
      | "(" -> tokenize_rec s (pos-1) (Tkn_LPAR::lt)
      | ")" -> tokenize_rec s (pos-1) (Tkn_RPAR::lt)
      | "+" | "-" | "*" | "/" -> tokenize_rec s (pos-1) ((Tkn_OP c)::lt)
      | "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ->
        (match lt with
         | (Tkn_NUM n)::lt' -> 
           (let lta = Tkn_NUM(int_of_string (c^(string_of_int n)))::lt' in
            tokenize_rec s (pos-1) lta)
         | _ -> tokenize_rec s (pos-1) (Tkn_NUM (int_of_string c)::lt) 
         )
      |_ -> raise (ParseError ("Tokenizer","unknown symbol: "^c))
  in
  tokenize_rec s (String.length s) [Tkn_END]

在执行期间我得到

tail_tokenize "3+4";;
Exception: Invalid_argument "String.sub / Bytes.sub".

您的示例是这样的:

tail_tokenize "3+4"

第一个调用如下所示:

tokenize_rec "3+4" 3 Tkn_END

由于 3 不小于 0,因此 tokenize_rec 中的第一个调用将如下所示:

String.sub "3+4" 3 1

如果您自己尝试,您会发现它无效:

# String.sub "3+4" 3 1;;
Exception: Invalid_argument "String.sub / Bytes.sub".

向后遍历字符串似乎有点奇怪,但要做到这一点,您需要从 String.length s - 1 开始。

从错误消息中可以清楚地看出 String.sub 是问题所在。它的参数是 spos1,最后一个是常量,另外两个直接来自函数参数。 运行 这可能是一个好主意,将其与替换实际值的参数隔离开来:

let s = "3+4" in
String.sub s (String.length s) 1

这样做我们再次遇到同样的错误,希望现在清楚原因了:您正试图从最后一个字符获取长度为 1 的子字符串,这意味着它会尝试越过字符串的末尾,当然不能。

从逻辑上讲,您可能会尝试从 pos 中减去 1,以便从最后一个字符之前开始获取长度为 1 的子字符串。但是你又得到了同样的错误。那是因为你的终止条件是 pos < 0,这意味着你将尝试 运行 String sub s (0 - 1) 1。因此,您也需要调整终止条件。但是一旦你做到了,你就应该是个好人!