例外: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
是问题所在。它的参数是 s
、pos
和 1
,最后一个是常量,另外两个直接来自函数参数。 运行 这可能是一个好主意,将其与替换实际值的参数隔离开来:
let s = "3+4" in
String.sub s (String.length s) 1
这样做我们再次遇到同样的错误,希望现在清楚原因了:您正试图从最后一个字符获取长度为 1 的子字符串,这意味着它会尝试越过字符串的末尾,当然不能。
从逻辑上讲,您可能会尝试从 pos
中减去 1,以便从最后一个字符之前开始获取长度为 1 的子字符串。但是你又得到了同样的错误。那是因为你的终止条件是 pos < 0
,这意味着你将尝试 运行 String sub s (0 - 1) 1
。因此,您也需要调整终止条件。但是一旦你做到了,你就应该是个好人!
我为 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
是问题所在。它的参数是 s
、pos
和 1
,最后一个是常量,另外两个直接来自函数参数。 运行 这可能是一个好主意,将其与替换实际值的参数隔离开来:
let s = "3+4" in
String.sub s (String.length s) 1
这样做我们再次遇到同样的错误,希望现在清楚原因了:您正试图从最后一个字符获取长度为 1 的子字符串,这意味着它会尝试越过字符串的末尾,当然不能。
从逻辑上讲,您可能会尝试从 pos
中减去 1,以便从最后一个字符之前开始获取长度为 1 的子字符串。但是你又得到了同样的错误。那是因为你的终止条件是 pos < 0
,这意味着你将尝试 运行 String sub s (0 - 1) 1
。因此,您也需要调整终止条件。但是一旦你做到了,你就应该是个好人!