2016-04-24 9 views
8

Ich versuche eine adaptive parser in Prolog schreiben: Mit anderen Worten, ein Parser, der seine eigenen Parsing-Regeln zur Laufzeit ändern kann.Ist adaptives Parsen in Prolog möglich?

Um dies zu tun, muss ich neue Prädikate zur Laufzeit generieren, aber ich bin mir nicht sicher, ob dies möglich ist. Wäre es möglich, ein Prädikat zu schreiben, die eine Liste wie diese nimmt:

generate_dcg_rule([A," is greater than ",B]). 

... und erzeugt dann ein neues Prädikat wie diese?

expr(A," is greater than ",B) --> 
    symbol(A)," is greater than ",symbol(B). 

Antwort

6

Ja, ist das problemlos möglich.

Prolog ist eine sehr dynamische Sprache, und Sie können zur Laufzeit beliebige Klauseln verwenden, z. B. assertz/1.

Sie können DCG-Regeln auf normale Prolog-Regeln erweitern, indem Sie den Prolog-Termausdehnungsmechanismus verwenden, der normalerweise dafür verwendet wird.

Zum Beispiel mit expand_term/2:

 
?- expand_term((expr(A," is greater than ",B) --> 
    symbol(A)," is greater than ",symbol(B)), Clause). 
Clause = (expr(A, [' ', i, s, ' ', g, r, e|...], B, _G242, _G253):-symbol(A, _G242, _G264), _G264=[' ', i, s, ' ', g|...], symbol(B, _G275, _G253)). 

können Sie solche Klauseln behaupten mit assertz/1:

 
?- expand_term((Head --> Body), Clause), assertz(Clause). 

Bitte beachte, dass ich double_quotes Satz chars in diesem Beispiel verwenden, dh Nutzung:

 
:- set_prolog_flag(double_quotes, chars). 

in Ihrer Quelle Dateien. Dies ist in jedem Fall eine gute Idee.

Beachten Sie auch, dass ich angenommen habe, dass Sie bereits einen Weg gefunden haben, die Listen, die Sie in Ihrem generate_dcg_rule/1 Beispiel geben, zu tatsächlichen   DCGs zu übersetzen. Für diese Übersetzung empfehle ich eher ein Prädikat wie list_dcg/2, das die Beziehung zwischen solchen Listen und DCG   Regeln deklarativ beschreibt. Der Vorteil ist klar: Sie können zum Beispiel Test solche Beziehungen interaktiv und mit Testfällen etc. Für Ihr konkretes Beispiel, eine Klausel, die diese Beziehung definiert ähnlich sein könnte:

 
list_dcg([A,Ls,B], DCG) :- 
     Ls = "is greater than ", 
     DCG = (expr(A, Ls, B) --> symbol(A), Ls, symbol(B)). 

Ich lasse verallgemeinern diese zu Ihrem andere Anwendungsfälle als Übung. Insgesamt ist eine Möglichkeit, solche Klauseln dynamisch zu behaupten also:

 
?- list_dcg(List, DCG), expand_term(DCG, Clause), assertz(Clause). 

Hinweis, wie wir profitieren von Prolog homoiconic   Natur in solchen Beispielen: Prolog   Regeln und DCG   Regeln eine natürliche Darstellung als Prolog haben   Begriffe, und wir können sie einfach aufschreiben und über sie wie alle anderen Begriffe auch innerhalb Ziele sprechen.

+0

Mit 'set_prolog_flag/2' sein Sowohl eine * directive * als auch ein * integriertes Prädikat *, warum verwenden Sie die 'initialization/1'-Anweisung, die das Setzen des Flags auf * verzögert. nach * die Quelldatei, die sie enthält, wird kompiliert und geladen? –

+0

Sehr guter Punkt, danke! Ich habe das geändert, um die Richtlinie sofort zu verwenden. – mat

2

Ich habe einen adaptiven Parser geschrieben, der englische Phrasen in mathematische Ausdrücke umwandelt. Sie können es problemlos mit Ihren eigenen Übersetzungsregeln erweitern, sodass es zum Erstellen erweiterbarer Benutzeroberflächen in natürlicher Sprache verwendet werden kann.

Dies ist eine mögliche Eingabe:

(A squared) times (b squared) equals c to the power of 3 times the product of 5 and 6 

und dies ist seine Ausgabe:

(A * A) * (b * b) = c^3 * 5 * 6 

Die Durchführung des Programms wird hier gezeigt:

:- initialization(main). 
:- set_prolog_flag(double_quotes, chars). % This is for SWI 7+ to revert to the prior interpretation of quoted strings. 

%This is an adaptive parser for SWI-Prolog. 

main :- 
    %Type any kind of input here to see the output! The input must be compatible with the grammar that is defined below.   
    Input = "(A squared) times (b squared) equals c to the power of 3 times the product of 5 and 6", 

    iterated_translate(Input,Output), writeln(Input), writeln(Output), writeln('\n'), writeln('\n'). 

%The grammar is defined here. The variables must be uppercase letters. 
%The input in each translation rule is followed by its output. 
theList(TheList) :- 
      %You can easily extend this parser by adding more rules to this list. 
      TheList = 
      [['A to the power of B', 
       'A^B'], 
      %The next transformation is the final output of 'A to the power of B'. 
      ['A^B', 
       'A^B'], 
      ['A * B', 
       'A * B'], 
      ['the product of A and B', 
       'A times B'], 
      ['A squared', 
       'the product of A and A'], 
      ['A times B', 
       'A * B'], 
      ['A = B', 
       'A = B'], 
      ['A equals B', 'A = B']]. 
%This is the end of the grammar. The rest of the translator is implemented below. 

output_expr(Lang,[Input,[A,B]]) --> 
     { 
     theList(TheList), 
     list_to_output__(TheList,TheList1,[A,B]), 
     member([Input,Output], 
      TheList1) 
     }, 
     input_to_output_(Lang,Input,Output). 

atom_is_upper(N) :- 
    atom_chars(N, [L]), 
    char_type(L, upper). 

atom_is_var(Names_to_vars,Atom,Var) :- 
    atom(Atom),atom_is_upper(Atom),member([Atom,Var],Names_to_vars). 

list_to_output__([],[],_) :- true. 
list_to_output__([Start1|Rest1],[Start2|Rest2],Vars) :- 
    list_to_output_(Start1,Start2,Vars),list_to_output__(Rest1,Rest2,Vars). 

list_to_output_([A1_,B1_],[A2,B2],Vars) :- atomic_list_concat(A1,' ',A1_),atomic_list_concat(B1,' ',B1_),list_to_output(A1,A2,Vars),list_to_output(B1,B2,Vars). 

list_to_output([],[],_) :- true. 
list_to_output([Start1|Rest1],[Start2|Rest2],[A,B]) :- 
    (Start1='A'->Start2=A;Start1='B'-> Start2=B;Start1=Start2),list_to_output(Rest1,Rest2,[A,B]). 

list_to_grammar_(Lang,Start,Rest) --> 
    {(Start = [A])->(Rest = []->Start1 = expr(Lang,A);Start1 = parentheses_expr(Lang,A));atom_chars(Start,Start1)},Start1. 

list_to_grammar(Lang,[Start]) --> 
    list_to_grammar_(Lang,Start,[]). 

list_to_grammar(Lang,[Start|Rest]) --> 
    list_to_grammar_(Lang,Start,Rest),ws_,list_to_grammar(Lang,Rest). 

a_number([A,B]) --> 
    (a__number(A), ".", a__number(B)). 

a_number(A) --> 
    a__number(A). 

a__number([L|Ls]) --> digit(L), a__number_r(Ls). 
a__number_r([L|Ls]) --> digit(L), a__number_r(Ls). 
a__number_r([])  --> []. 
digit(Let)  --> [Let], { code_type(Let, digit) }. 

symbol([L|Ls]) --> letter(L), symbol_r(Ls). 
symbol_r([L|Ls]) --> letter(L), symbol_r(Ls). 
symbol_r([])  --> []. 
letter(Let)  --> [Let], { code_type(Let, alpha) }. 

ws --> "";((" ";"\n"),ws). 
ws_ --> (" ";"\n"),ws. 

input_to_output(Lang,A,B) --> 
    {Lang = input} -> 
     A; 
    {Lang=output} -> 
     B. 

input_to_output_(Lang,A,B) --> 
    {A_=list_to_grammar(Lang,A),B_=list_to_grammar(Lang,B)},input_to_output(Lang,A_,B_). 

parentheses_expr(Lang,["(",A,")"]) --> 
    ("(",(expr(Lang,A)),")"). 

parentheses_expr(_,symbol(A)) --> 
    symbol(A). 

parentheses_expr(_,a_number(A)) --> 
    a_number(A). 

expr(Lang,A) --> 
    parentheses_expr(Lang,A);output_expr(Lang,A). 

translate(Input1,Output1) :- 
    phrase(output_expr(input,Ls),Input1), 
    phrase(output_expr(output,Ls),Output1). 

iterated_translate(Input1, Output2) :- 
    %Keep translating until the input is the same as the output. 
    translate(Input1,Output1), 
    (Input1=Output1, Output1 = Output2;iterated_translate(Output1,Output2)). 
+0

Oh ja das ist schön! In 'input_to_output // 3' können Sie die Vereinheitlichung mit zwei Klauseln wie' input_to_output (input, A, _) -> A.' und 'input_to_output (output, _, B) -> in den DCG-Kopf ziehen B.' Vorteil: Allgemeiner (kann auch verwendet werden, wenn das erste Argument uninstantiiert ist)! In 'list_to_output/3' können Sie einfach Fakten anstelle von' Head: - true' verwenden, d. H. Einfach 'Head.' schreiben. Außerdem scheint es, dass Sie von "maplist/3" profitieren können, wenn Sie die Argumente neu ordnen, um zuerst 'Vars' zu platzieren. Sie können dann 'maplist (list_to_output (Vars), Ls0, Ls)' sagen und den Code verkürzen. Danke für das Teilen! – mat

+0

@mat Ich habe versucht, diesen Parser kompatibel mit Links-rekursive Grammatik auch zu machen. Wäre dies in ISO Prolog möglich? –

+0

Ja, das ist natürlich möglich. Eine Möglichkeit, dies zu tun, ist eine Technik namens Links-Faktorisierung. Es ist eine nette Übung, eine existierende Grammatik auf diese Weise neu zu schreiben. Ein anderer Ansatz besteht darin, eine Liste von Token zu übergeben, die * höchstens * konsumiert werden können, und jede DCG-Regel im Voraus die Anzahl der Tokens angeben zu lassen, die sie schließlich selbst verbrauchen wird. Mithilfe eines einfachen Fixpunktalgorithmus können Sie diese Zahlen dann "vor" jeder Regel ziehen und im Voraus prüfen, ob genügend Token für die Anwendung der Regel vorhanden sind. Du bist definitiv auf dem richtigen Weg, es ist sehr schön, das alles in Prolog zu machen! – mat