Recursion 为什么Prolog S-expression标记器在其基本情况下失败?

Recursion 为什么Prolog S-expression标记器在其基本情况下失败?,recursion,prolog,tokenize,logic-programming,s-expression,Recursion,Prolog,Tokenize,Logic Programming,S Expression,为了学习一些Prolog,我正在使用gnuprolog并探索它的解析能力,我从编写一个Lisp或S表达式开始,如果我是精确的标记器,它给定一组标记,比如[,'f','o','o',]应该产生[,'foo',]。它没有按预期工作,这就是为什么我在这里!我认为我的思维过程在我的伪代码中清晰可见: tokenize([current | rest], buffer, tokens): if current is '(' or ')', Tokenize the rest,

为了学习一些Prolog,我正在使用gnuprolog并探索它的解析能力,我从编写一个Lisp或S表达式开始,如果我是精确的标记器,它给定一组标记,比如[,'f','o','o',]应该产生[,'foo',]。它没有按预期工作,这就是为什么我在这里!我认为我的思维过程在我的伪代码中清晰可见:

tokenize([current | rest], buffer, tokens):
    if current is '(' or ')',
        Tokenize the rest,
        And the output will be the current token buffer,
        Plus the parenthesis and the rest.

    if current is ' ',
        Tokenize the rest with a clean buffer,
        And the output will be the buffer plus the rest.
    
    if the tail is empty,
        The output will be a one-element list containing the buffer.
    
    otherwise,
        Add the current character to the buffer,
        And the output will be the rest tokenized, with a bigger buffer.
我将其翻译成序言如下:

tokenize([Char | Chars], Buffer, Tokens) :-
    ((Char = '(' ; Char = ')') ->
        tokenize(Chars, '', Tail_Tokens),
        Tokens is [Buffer, Char | Tail_Tokens];
    Char = ' ' ->
        tokenize(Chars, '', Tail_Tokens),
        Tokens is [Buffer | Tail_Tokens];

    Chars = [] -> Tokens is [Buffer];

    atom_concat(Buffer, Char, New_Buffer),
    tokenize(Chars, New_Buffer, Tokens)).

print_tokens([]) :- write('.').
print_tokens([T | N]) :- write(T), write(', '), print_tokens(N).

main :-
    % tokenize(['(', 'f', 'o', 'o', '(', 'b', 'a', 'r', ')', 'b', 'a', 'z', ')'], '', Tokens),
    tokenize(['(', 'f', 'o', 'o', ')'], '', Tokens),
    print_tokens(Tokens).
当运行结果时,如下所示:gprolog-consult file lisp_parser.pl它只告诉我否。我跟踪了main,它给了我下面的堆栈跟踪。我不明白为什么tokenize对于空案例失败。我看到缓冲区是空的,因为它是用上一次清除的,但是即使令牌在那个时间点是空的,令牌不是会递归地累积更大的结果吗?有人谁是与Prolog好给我一些提示在这里

| ?- main.

no
| ?- trace.
The debugger will first creep -- showing everything (trace)

(1 ms) yes
{trace}
| ?- main.
      1    1  Call: main ? 
      2    2  Call: tokenize(['(',f,o,o,')'],'',_353) ? 
      3    3  Call: tokenize([f,o,o,')'],'',_378) ? 
      4    4  Call: atom_concat('',f,_403) ? 
      4    4  Exit: atom_concat('',f,f) ? 
      5    4  Call: tokenize([o,o,')'],f,_429) ? 
      6    5  Call: atom_concat(f,o,_454) ? 
      6    5  Exit: atom_concat(f,o,fo) ? 
      7    5  Call: tokenize([o,')'],fo,_480) ? 
      8    6  Call: atom_concat(fo,o,_505) ? 
      8    6  Exit: atom_concat(fo,o,foo) ? 
      9    6  Call: tokenize([')'],foo,_531) ? 
     10    7  Call: tokenize([],'',_556) ? 
     10    7  Fail: tokenize([],'',_544) ? 
      9    6  Fail: tokenize([')'],foo,_519) ? 
      7    5  Fail: tokenize([o,')'],fo,_468) ? 
      5    4  Fail: tokenize([o,o,')'],f,_417) ? 
      3    3  Fail: tokenize([f,o,o,')'],'',_366) ? 
      2    2  Fail: tokenize(['(',f,o,o,')'],'',_341) ? 
      1    1  Fail: main ? 

(1 ms) no
{trace}
| ?- 

这个怎么样。我想这就是你想要做的,但是让我们使用Definite子句语法,它只是horn子句,带有:-替换为->和两个省略的参数,其中包含输入字符列表和剩余字符列表。DCG规则示例:

rule(X) --> [c], another_rule(X), {predicate(X)}.
列表处理规则//1说:当您在输入列表中找到字符c时,然后使用另一个_规则//1继续列表处理,当这个规则生效时,正常调用谓词

然后:

因此:

?- tokenize([h,e,l,l,o],R).
R = [hello].

?- tokenize([h,e,l,'(',l,')',o],R).
R = [hel,(,l,),o].

?- tokenize([h,e,l,'(',l,l,')',o],R).
R = [hel,(,ll,),o].

我认为在GNU Prolog中,“hello”符号直接生成[h,e,l,l,o]。

这个怎么样。我想这就是你想要做的,但是让我们使用Definite子句语法,它只是horn子句,带有:-替换为->和两个省略的参数,其中包含输入字符列表和剩余字符列表。DCG规则示例:

rule(X) --> [c], another_rule(X), {predicate(X)}.
列表处理规则//1说:当您在输入列表中找到字符c时,然后使用另一个_规则//1继续列表处理,当这个规则生效时,正常调用谓词

然后:

因此:

?- tokenize([h,e,l,l,o],R).
R = [hello].

?- tokenize([h,e,l,'(',l,')',o],R).
R = [hel,(,l,),o].

?- tokenize([h,e,l,'(',l,l,')',o],R).
R = [hel,(,ll,),o].
我认为在GNU Prolog中,“hello”符号直接生成[h,e,l,l,o]

我不明白为什么tokenize对于空案例失败

Prolog中任何东西失败的原因都是因为没有使它成为真的子句。如果tokenize的唯一子句的形式为tokenize[Char | Chars],…,则不需要调用tokenize[]。。。将永远无法匹配此子句,并且由于没有其他子句,因此调用将失败

所以你需要增加这样一个条款。但首先:

:- set_prolog_flag(double_quotes, chars).
这允许您将[,f,o,o,]写为foo

此外,您还必须计划输入完全为空的情况,或者您可能必须为缓冲区发出令牌的其他情况,但前提是没有,因为不应该有令牌乱丢结果

finish_buffer(Tokens, Buffer, TokensMaybeWithBuffer) :-
    (   Buffer = ''
    ->  TokensMaybeWithBuffer = Tokens
    ;   TokensMaybeWithBuffer = [Buffer | Tokens] ).
例如:

?- finish_buffer(MyTokens, '', TokensMaybeWithBuffer).
MyTokens = TokensMaybeWithBuffer.

?- finish_buffer(MyTokens, 'foo', TokensMaybeWithBuffer).
TokensMaybeWithBuffer = [foo|MyTokens].
?- tokenize([], '', Tokens).
Tokens = [].

?- tokenize([], 'foo', Tokens).
Tokens = [foo].
注意,您可以将缓冲区前置到令牌列表,即使您还不知道令牌列表是什么!这就是逻辑变量的威力。代码的其余部分也使用这种技术

因此,空输入的情况如下:

tokenize([], Buffer, Tokens) :-
    finish_buffer([], Buffer, Tokens).
例如:

?- finish_buffer(MyTokens, '', TokensMaybeWithBuffer).
MyTokens = TokensMaybeWithBuffer.

?- finish_buffer(MyTokens, 'foo', TokensMaybeWithBuffer).
TokensMaybeWithBuffer = [foo|MyTokens].
?- tokenize([], '', Tokens).
Tokens = [].

?- tokenize([], 'foo', Tokens).
Tokens = [foo].
其余个案:

tokenize([Parenthesis | Chars], Buffer, TokensWithParenthesis) :-
    (   Parenthesis = '('
    ;   Parenthesis = ')' ),
    finish_buffer([Parenthesis | Tokens], Buffer, TokensWithParenthesis),
    tokenize(Chars, '', Tokens).
tokenize([' ' | Chars], Buffer, TokensWithBuffer) :-
    finish_buffer(Tokens, Buffer, TokensWithBuffer),
    tokenize(Chars, '', Tokens).
tokenize([Char | Chars], Buffer, Tokens) :-
    Char \= '(',
    Char \= ')',
    Char \= ' ',
    atom_concat(Buffer, Char, NewBuffer),
    tokenize(Chars, NewBuffer, Tokens).
请注意,我是如何为单独的案例使用单独的子句的。这使代码更具可读性,但与…->…;相比,它确实有缺点。。。最后一个子句必须排除前一个子句处理的字符。一旦你有了这个形状的代码,并且你很高兴它能工作,你就可以使用…->。。。如果你真的想

示例:

?- tokenize("(foo)", '', Tokens).
Tokens = ['(', foo, ')'] ;
false.

?- tokenize(" (foo)", '', Tokens).
Tokens = ['(', foo, ')'] ;
false.

?- tokenize("(foo(bar)baz)", '', Tokens).
Tokens = ['(', foo, '(', bar, ')', baz, ')'] ;
false.
最后,也是非常重要的一点,is运算符仅用于计算算术表达式。当您将异常应用于任何非算术的对象时,它将抛出异常。统一不同于算术表达式的求值。统一写为=

我不明白为什么tokenize对于空案例失败

Prolog中任何东西失败的原因都是因为没有使它成为真的子句。如果tokenize的唯一子句的形式为tokenize[Char | Chars],…,则不需要调用tokenize[]。。。将永远无法匹配此子句,并且由于没有其他子句,因此调用将失败

所以你需要增加这样一个条款。但首先:

:- set_prolog_flag(double_quotes, chars).
这允许您将[,f,o,o,]写为foo

此外,您还必须计划输入完全为空的情况,或者您可能必须为缓冲区发出令牌的其他情况,但前提是没有,因为不应该有令牌乱丢结果

finish_buffer(Tokens, Buffer, TokensMaybeWithBuffer) :-
    (   Buffer = ''
    ->  TokensMaybeWithBuffer = Tokens
    ;   TokensMaybeWithBuffer = [Buffer | Tokens] ).
例如:

?- finish_buffer(MyTokens, '', TokensMaybeWithBuffer).
MyTokens = TokensMaybeWithBuffer.

?- finish_buffer(MyTokens, 'foo', TokensMaybeWithBuffer).
TokensMaybeWithBuffer = [foo|MyTokens].
?- tokenize([], '', Tokens).
Tokens = [].

?- tokenize([], 'foo', Tokens).
Tokens = [foo].
注意,您可以将缓冲区前置到令牌列表,即使您还不知道令牌列表是什么!这就是逻辑变量的威力。代码的其余部分也使用这种技术

因此,空输入的情况如下:

tokenize([], Buffer, Tokens) :-
    finish_buffer([], Buffer, Tokens).
例如:

?- finish_buffer(MyTokens, '', TokensMaybeWithBuffer).
MyTokens = TokensMaybeWithBuffer.

?- finish_buffer(MyTokens, 'foo', TokensMaybeWithBuffer).
TokensMaybeWithBuffer = [foo|MyTokens].
?- tokenize([], '', Tokens).
Tokens = [].

?- tokenize([], 'foo', Tokens).
Tokens = [foo].
其余个案:

tokenize([Parenthesis | Chars], Buffer, TokensWithParenthesis) :-
    (   Parenthesis = '('
    ;   Parenthesis = ')' ),
    finish_buffer([Parenthesis | Tokens], Buffer, TokensWithParenthesis),
    tokenize(Chars, '', Tokens).
tokenize([' ' | Chars], Buffer, TokensWithBuffer) :-
    finish_buffer(Tokens, Buffer, TokensWithBuffer),
    tokenize(Chars, '', Tokens).
tokenize([Char | Chars], Buffer, Tokens) :-
    Char \= '(',
    Char \= ')',
    Char \= ' ',
    atom_concat(Buffer, Char, NewBuffer),
    tokenize(Chars, NewBuffer, Tokens).
请注意,我是如何为单独的案例使用单独的子句的。这使代码更具可读性,但与…->…;相比,它确实有缺点。。。最后一个子句必须排除已处理的字符 根据以前的条款。一旦你有了这个形状的代码,并且你很高兴它能工作,你就可以使用…->。。。如果你真的想

示例:

?- tokenize("(foo)", '', Tokens).
Tokens = ['(', foo, ')'] ;
false.

?- tokenize(" (foo)", '', Tokens).
Tokens = ['(', foo, ')'] ;
false.

?- tokenize("(foo(bar)baz)", '', Tokens).
Tokens = ['(', foo, '(', bar, ')', baz, ')'] ;
false.
最后,也是非常重要的一点,is运算符仅用于计算算术表达式。当您将异常应用于任何非算术的对象时,它将抛出异常。统一不同于算术表达式的求值。统一写为=


为此,您可能需要使用Definite子句语法,该语法用于处理字符列表或其他内容。但除此之外,这很奇怪:代币是[Buffer,Char | Tail_代币]。。。这是算术评估,这可能不是你想要的。你想要atom|u concatBuffer,Char,Token,Tokens=[Token | Tail_Tokens]?@DavidTonhofer我在想,如果有一个括号,我会这样做:'a','b','c'对于这个片段,一旦标记器点击括号,它就会知道缓冲区ab是一个完整的标记,并且可以清除标记缓冲区。所以代币部分是['ab',…]。你能解释一下为什么我会把括号和ab放在一起吗?这里的算术评估是什么?我是个新手,所以你可能知道得更清楚。如果你叫is,那么is右边的一定是算术表达式。在X中是4+5。但你说代币是[缓冲区,Char | Tail_代币]。可能应该是Tokens=[Buffer,Char | Tail_Tokens],只要统一就可以了。除了替换is by=,还必须为递归定义添加一个基本大小写:tokenize[]、124;、[]。您可能需要为此使用Definite子句语法,该语法用于处理字符列表或其他内容。但除此之外,这很奇怪:代币是[Buffer,Char | Tail_代币]。。。这是算术评估,这可能不是你想要的。你想要atom|u concatBuffer,Char,Token,Tokens=[Token | Tail_Tokens]?@DavidTonhofer我在想,如果有一个括号,我会这样做:'a','b','c'对于这个片段,一旦标记器点击括号,它就会知道缓冲区ab是一个完整的标记,并且可以清除标记缓冲区。所以代币部分是['ab',…]。你能解释一下为什么我会把括号和ab放在一起吗?这里的算术评估是什么?我是个新手,所以你可能知道得更清楚。如果你叫is,那么is右边的一定是算术表达式。在X中是4+5。但你说代币是[缓冲区,Char | Tail_代币]。可能应该是Tokens=[Buffer,Char | Tail_Tokens],只是统一而已。除了替换is by=,还必须为递归定义添加一个基本情况:tokenize[],[uu,[]。您的代码可以工作,但我不能完全理解。我想这只是我第三天的Prolog。在许多标记化之前,有一个空原子——为什么?还有,这是什么意思!在这里指明?这让我有点困惑。顺便说一句,谢谢你详尽的回答。@CaspianAhlberg这需要一点时间来适应。这个表示Prolog应该提交到当前选择的子句。如果左侧的谓词出现任何故障,从而发生了对其他解决方案的重做搜索,那么执行将不会从右向左!或具有相同名称/arity的其他子句将不会被尝试。相反,整个谓词将被视为失败,并将选择另一个谓词。我试图在Byrd Box模型上写一个not来解释Prolog的执行,也许这会有所帮助:。它可能有一些错误。标记化//2中的空原子是缓冲区状态。在“->”的左边,这是告诉打电话的人的。这就是为什么当我们遇到时,左侧缓冲区是空的。但是,如果我们遇到一个“Ch”,那么要告诉调用方的缓冲区状态就是递归调用告诉我们的缓冲区状态,左边是“Ch”。你的代码可以工作,但我不能完全理解。我想这只是我第三天的Prolog。在许多标记化之前,有一个空原子——为什么?还有,这是什么意思!在这里指明?这让我有点困惑。顺便说一句,谢谢你详尽的回答。@CaspianAhlberg这需要一点时间来适应。这个表示Prolog应该提交到当前选择的子句。如果左侧的谓词出现任何故障,从而发生了对其他解决方案的重做搜索,那么执行将不会从右向左!或具有相同名称/arity的其他子句将不会被尝试。相反,整个谓词将被视为失败,并将选择另一个谓词。我试图在Byrd Box模型上写一个not来解释Prolog的执行,也许这会有所帮助:。它可能有一些错误。标记化//2中的空原子是缓冲区状态。在“->”的左边,这是告诉打电话的人的。这就是为什么当我们遇到时,左侧缓冲区是空的。但是,如果我们遇到“Ch”, 然后告诉调用者的缓冲区状态是我们通过递归调用得到的缓冲区状态,左边是'Ch'。