21/MAR/2006 Foi corrigido um erro na expressão regular usada nos primeiros exemplos, nesta página. 10/MAR/2006 Primeira versão deste documento.
Interactua-se com o sistema através a linha de comandos do Prolog ou usando scripts escritos em Prolog.
As entidades suportadas pelo programa são as seguintes:
Há três níveis de utilização do P-FLAT:
Efectuemos algumas experiências simples usando a expressão regular (0 + 1 0* 1)*, que descreve a linguagem das sequências binárias onde o símbolo "1" ocorre um número par de vezes. No que se segue, o input do utilizador aparece a vermelho. Repare também que a expressão regular teve de ser escrita usando sintaxe modificada, para poder ser lida pelo Prolog.
> pl -s pflat.pl
% pflat.pl compiled 0.06 sec, 124,668 bytes
Welcome to SWI-Prolog (Multi-threaded, Version 5.6.7)
Copyright (c) 1990-2006 University of Amsterdam.
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
Please visit http://www.swi-prolog.org for details.
For help, use ?- help(Topic). or ?- apropos(Word).
?- show((0 + 1 * 0^* * 1)^*). % Mostra a expressão regular
REGULAR EXPRESSION:
Definition: re((0+1*0^* *1)^*)
Yes
?- alpha((0 + 1 * 0^* * 1)^*, X). % Pede o alfabeto da expressão regular
X = {1, 0}
Yes
?- accept((0 + 1 * 0^* * 1)^*, [1,1]). % Pergunta se a palavra "11" pertence à linguagem definida pela expressão regular
Yes
?- accept((0 + 1 * 0^* * 1)^*, [1,0]). % Pergunta se a palavra "10" pertence à linguagem definida pela expressão regular
No
?- word((0 + 1 * 0^* * 1)^*, X). % Gera algumas palavras para verificar empiricamente se a expressão define mesmo a linguagem pretendida
X = [] ;
X = [0] ;
X = [0, 0] ;
X = [1, 1] ;
X = [0, 0, 0] ;
X = [1, 1, 0] ;
X = [1, 0, 1] ;
X = [0, 1, 1] ;
X = [0, 0, 0, 0] ;
X = [1, 1, 0, 0] ;
X = [1, 0, 1, 0] ;
X = [0, 1, 1, 0] ;
X = [1, 0, 0, 1] ;
X = [0, 1, 0, 1] ;
X = [0, 0, 1, 1] ;
X = [1, 1, 1, 1] ;
X = [0, 0, 0, 0, 0] ;
X = [1, 1, 0, 0, 0]
Yes
?- tracing((0 + 1 * 0^* * 1)^*, [1,1]). % Pede a prova detalhada de que a palavra "11" pode ser obtida a partir da expressão regular
TRACING REGULAR EXPRESSION:
Traced word: [1, 1]
At least one execution path stops at an acceptance state. Word accepted.
Traced steps:
>1 1 ((0+1*0^* *1)^*)
>1 1 (0+1*0^* *1) ((0+1*0^* *1)^*)
>1 1 (1*0^* *1) ((0+1*0^* *1)^*)
>1 1 (1*0^*) (1) ((0+1*0^* *1)^*)
>1 1 (1) (0^*) (1) ((0+1*0^* *1)^*)
1>1 1 (0^*) (1) ((0+1*0^* *1)^*)
1>1 1 (1) ((0+1*0^* *1)^*)
1 1> 1 1 ((0+1*0^* *1)^*)
1 1> 1 1
Yes
?-
Eis a documentação dos predicados usados nesta secção, copiada directamente do ficheiro fonte do P-FLAT:
/* alpha(?E,-A) The alphabet of entity E is A show(?E) Display entity E accept(?E, +W) E accepts W accept(?E, +W, -Path) E accepts W with execution path Path word(?E, -W) E generates W (results are ordered) tracing(+E, +W) Trace E accepting W */Para ter uma ideia das outras habilidades do P-FLAT, veja a secção "Predicados disponíveis", perto do final deste documento.Escrita de scripts
Apesar de ser possível usar o P-FLAT da forma exemplificada na secção anterior, trabalhando directamente sobre a linha de comando, muitas vezes é melhor registar em ficheiro as declarações e os comandos que nos interessam e depois consultar esse ficheiro. Cria-se assim um script prático de usar e que pode ser guardado para o futuro.A escrita de um script pode revelar-se um pouco trabalhosa se se pretender produzir output elaborado. Para ajudar, o P-FLAT disponibiliza os predicados execute/1 e execute/3 que sabem executar comandos, gerando automaticamente output razoavelmente expressivo.
Para exemplificar, eis um script contendo uma declaração e diversos comandos:
re(evenRE, (0 + 1 * 0^* * 1)^*). % Declara expressão regular :- execute(show(evenRE)). % Mostra a expressão regular :- execute(alpha(evenRE,A), A, true). % Pede o alfabeto da expressão regular :- execute(accept(evenRE, [1,1])). % Pergunta se a palavra "11" pertence à linguagem definida pela expressão regular :- execute(accept(evenRE, [0,1])). % Pergunta se a palavra "10" pertence à linguagem definida pela expressão regular :- execute(word(evenRE,A), A, length(A,4)). % Gera palavras até aparecer a primeira com comprimento 4 :- execute(tracing(evenRE, [1,1])). % Pede a prova detalhada de que a palavra "11" pode ser obtida a partir da expressão regularPara consultar o ficheiro "fich.pl", você faz:
[fich].Neste caso, o output produzido é quase igual ao da secção anterior.Os ficheiros "tutorial.pl" e "examples.pl" fazem parte da distribuição do P-FLAT. São scripts que ilustram diversos aspectos da utilização do P-FLAT.
Eis a documentação dos predicados execute/1 e execute/3, copiada directamente do ficheiro fonte do P-FLAT:
/* execute(+G, ?R, +S) Call goal G and write result R until G fails or stop condition S succeeds execute(+G) Call goal G and print 'Yes' ou 'No' */Declarações
O P-FLAT permite declarar entidades em ficheiro, associando um nome a cada uma delas. Por exemplo, é desta forma que se declara uma expressão regular chamada evenRE.
re(evenRE, (0 + 1 * 0^* * 1)^*).Apresentam-se agora diversas declarações para ilustrar todo o tipo de entidades declaráveis em P-FLAT (existem nove tipos ao todo):
% alfabeto
alphabet(bitsA, {0,1}).
% relação de ordem total
alphabet_order(upO, bitsA, [0,1]). % relação de ordem total sobre o alfabeto bitsA
alphabet_order(upO, {0,1}, [0,1]). % também se pode declarar assim.
alphabet_order(downO, bitsA, [1,0]). % outra relação de ordem total sobre o alfabeto bitsA
% linguagem
language(evenL, bitsA). % linguagem sobre o alfabeto bitsA
language(evenL, {0,1}). % também se pode declarar assim...
language(evenL, {0}+{1}). % ...ou assim.
% predicado
predicate(evenP, bitsA). % define a linguagem {w: evenP(w)}
% expressão regular
re(evenRE, (0 + 1 * 0^* * 1)^*).
% autómato finito
fa(abFA,
1, % estado inicial
{1/a/1, 1/a/2, 1/b/2, % transições
2/b/2, 2/b/1
},
{2} % estados finais
).
% gramática independente do contexto
cfg(arithCFG,
'E', % símbolo de partida
{('E'->['E',+,'T']), % produções
('E'->['T']),
('T'->['T',*,'F']),
('T'->['F']),
('F'->['(','E',')']),
('F'->[a])
}
).
% autómato de pilha
pda(mirrorPDA,
i, % estado inicial
z, % símbolo inicial da pilha
{i/z/[]/i/[z], i/z/a/p/[a,z], i/z/b/p/[b,z], % transições
p/a/a/p/[a,a], p/a/b/p/[b,a],
p/b/a/p/[a,b], p/b/b/p/[b,b],
p/a/a/q/[], p/b/b/q/[],
q/a/a/q/[], q/b/b/q/[],
q/z/[]/t/[z]
},
{i,t} % estados finais
).
% máquina de Turing
tm(swapabTM,
q0, % estado inicial
{q0/'B'/'B'/'R'/q0, q0/a/a/'L'/q0, q0/'B'/'B'/'R'/q1, % transições
q1/a/b/'R'/q1, q1/b/a/'R'/q1, q1/'B'/'B'/'L'/q2,
q2/a/a/'L'/q2, q2/b/b/'L'/q2, q2/'B'/'B'/'R'/q3
},
{q3} % estados finais
).
Declaração de linguagens e de predicados
No P-FLAT, quase todos os tipos de declarações são auto-suficientes, no sentido em que a própria declaração já inclui toda a informação necessária para caracterizar a entidade em causa. Há duas excepções: as declarações de linguagens e as declarações de predicados.
Declaração de linguagens
Deve complementar-se a declaração duma linguagem com um conjunto de factos Prolog que exemplificam palavras que pertencem à linguagem e palavras que não pertencem à linguagem. Chama-se a esse factos testes unitários. Os testes unitários têm a forma
in_language(Word, Name, Boolean)e afirmam que a palavra Word pertence ou não à linguagem Name, consoante o valor de Boolean.Para exemplificar, eis uma declaração mais completa da linguagem evenL:
language(evenL, {0,1}).
in_language([], evenL, true).
in_language([0,0,0], evenL, true).
in_language([1,1], evenL, true).
in_language([0,1,1], evenL, true).
in_language([1,1,0,0], evenL, true).
in_language([1,0,0,0,1,1,0,1], evenL, true).
in_language([1], evenL, false).
in_language([1,1,1], evenL, false).
in_language([1,0,0], evenL, false).
in_language([0,0,1], evenL, false).
in_language([1,0,0,1,1,0,0], evenL, false).
Recomendamos que você defina sempre pelos menos 5 casos positivos e cinco casos negativos, tendo ainda o cuidado de identificar as situações limite mais importantes.
A importância dos testes unitários
Imagine que você tem em mente uma determinada linguagem e pretende representá-la usando uma das técnicas estudadas em LFA, digamos usando um autómato finito. Nesse caso, deve declarar primeiro a linguagem e associar-lhe um conjunto de testes unitários. Só depois é que deve tentar escrever o autómato. Vale a pena o primeiro passo pelas seguintes razões:
Para exemplificar, eis a declaração completa do predicado evenP:
predicate(evenP, bitsA).
evenP(W) :- occurs(1, W, N), 0 is N mod 2. % occurs/3 é um dos predicados sobre palavras disponível no P-FLAT
Os predicados constituem a forma mais directa de definir linguagens no P-FLAT. Um predicado P define directamente a linguagem {w: P(w)}).
Validações
Validação de declarações
Para validar uma declaração, usa-se o comando
:- execute(check_declaration(Name)).onde Name representa o nome da entidade a validar.Use sempre o comando check_declaration/1 após cada declaração. Assim, como neste exemplo:
re(evenRE, (0 + 1 * 0^* * 1)^*). :- execute(check_declaration(evenRE)).Vale a pena validar todas as declarações, pois são dezenas as situações inválidas e as situações suspeitas que o P-FLAT consegue detectar. As primeiras dão origem a mensagens de erro e as segundas dão origem a warnings.
só para dar uma ideia, eis apenas alguns dos muitos problemas que o P-FLAT detecta:
- O mesmo nome está usado em duas declarações distintas.
- Alfabeto contém símbolos proibidos.
- Alfabeto está vazio.
- Ordenação não é uma permutação de todos os símbolos do alfabeto indicado.
- Linguagem declarada não tem qualquer testes unitário associado.
- Teste unitário correspondente à palavra vazia não aparece.
- Teste unitário refere uma palavra com símbolos fora do alfabeto indicado.
- Expressão regular mal-formada.
- Autómato finito sintacticamente errado.
- Autómato finito sem estados finais.
- Autómato finito sem transição inicial definida.
- Autómato finito com estados inalcançáveis.
- Gramática usa símbolos não-terminais para os quais não há produções.
Validação de definições
Para validar uma definição face a um conjunto de testes unitários, usa-se o comando
:- execute(test_definition(Def, Lang)).onde Def representa uma entidade que supostamente caracteriza a linguagem Lang. A definição Def é validada face ao conjunto de testes unitários para Lang.Use sempre o comando test_definition/2 após cada declaração, para validar a entidade declarada. Isto aplica-se a predicados, expressões regulares, autómatos finitos, gramáticas independentes do contexto, autómatos de pilha e máquinas de Turing.
Como para cada declaração é importante validar, não só a declaração, como também a entidade declarada, torna-se quase inevitável que cada declaração acabe por conduzir à escrita de três linhas. Assim:
re(evenRE, (0 + 1 * 0^* * 1)^*). :- execute(check_declaration(evenRE)). :- execute(test_definition(evenRE, evenL)).Juntando tudo
Juntando tudo o que aprendemos até agora, eis um exemplo realista de script, cujo estilo você poderá imitar.Neste script primeiro declara-se a linguagem evalL, escrevem-se diversos testes unitários para ela e valida-se a declaração. Seguidamente, declara-se a expressão regular evenRE, valida-se a correcção dessa declaração e valida-se a definição evenRE face à linguagem evenL. Só depois de ter tudo validado, é que se começam a fazer experiências com a expressão regular.
language(evenL, {0,1}). % Declara linguagem
in_language([], evenL, true). % Testes unitários
in_language([0,0,0], evenL, true).
in_language([1,1], evenL, true).
in_language([0,1,1], evenL, true).
in_language([1,1,0,0], evenL, true).
in_language([1,0,0,0,1,1,01], evenL, true).
in_language([1], evenL, false).
in_language([1,1,1], evenL, false).
in_language([1,0,0], evenL, false).
in_language([0,01], evenL, false).
in_language([1,0,0,1,1,0,0], evenL, false).
:- execute(check_declaration(evenL)). % Valida declaração da linguagem (tem de ficar depois dos testes unitários)
re(evenRE, (0 + 1 * 0^* * 1)^*). % Declara expressão regular
:- execute(check_declaration(evenRE)). % Valida declaração da expressão regular
:- execute(test_definition(evenRE, evenL)). % Valida expressão regular face a linguagem
:- execute(show(evenRE)). % Mostra a expressão regular
:- execute(alpha(evenRE,A), A, true). % Pede o alfabeto da expressão regular
:- execute(accept(evenRE, [1,1])). % Pergunta se a palavra "11" pertence à linguagem definida pela expressão regular
:- execute(accept(evenRE, [0,1])). % Pergunta se a palavra "10" pertence à linguagem definida pela expressão regular
:- execute(word(evenRE,A), A, length(A,4)). % Gera palavras até aparecer a primeira com comprimento 4
:- execute(tracing(evenRE, [1,1])). % Pede uma prova detalhada de que a palavra "11" pode ser obtida a partir da expressão regular
Predicados disponíveis
Predicados universais
Alguns dos predicados do P-FLAT aplicam-se a quase todas as entidades e por isso dizem-se predicados universais. Eis a documentação dos predicados universais, copiada directamente do ficheiro fonte do P-FLAT:
/* alpha(?E,-A) The alphabet of entity E is A show(?E) Display entity E accept(?E, +W) E accepts W accept(?E, +W, -Path) E accepts W with execution path Path word(?E, -W) E generates W tracing(+E, +W) Trace E accepting W */Predicados particulares
Outros predicados do P-FLAT só se aplicam a a entidades particulares, pelo que se dizem predicados particulares. Em muitos casos, o nome de cada predicado particular tem um prefixo que indica o tipo de entidade a que se aplica. Eis a documentação dos predicados particulares mais importantes, copiada directamente do ficheiro fonte do P-FLAT:
/* prefix(?W1,+W2) Word W1 is a prefix of W2 suffix(?W1,+W2) Word W1 is a suffix of W2 subword(?W1,+W2) Word W1 is contained in W2 occurs(+S,+W,?N) Symbol S occurs N times in word W lexically_ordered(?/+W1, +/?W2, ?O) W1 < W2 according to the lexical order mixed_ordered(?W1, +W2, ?O) W1 < W2 according to the "mixed" order word_language(?W, +L) W belongs to language expression L re_fa(?RE, -FAE) Translates RE into expression FAE over finite automata re_simplify(?RE1, -RE2) RE2 is a simpler equivalent of RE1 fa_deterministic(?FA, ?Bool) Bool says whether FA is deterministic of not fa_re(+FAD, -RE) Translates deterministic FA into RE fa_determine(?FA1, -FA2) Transform non-deterministic FA1 into deterministic FA2 fa_rename(+FA1, -FA2) FA2 is as FA1, but with all states renamed with new ids fa_minimise(?FA1, -FA2) The minimisation of FA1 is FA2 fa_compute(+FAE, -FA) Evaluate expression FAE over finite automata, producing FA cfg_normalize(?CFG, -CFG1) CFG1 is a quasi-lambda-free and chain-free equivalent grammar pda_deterministic(?PDA, ?Bool) Bool says whether PDA is deterministic or not pda_change_criterion(?PDA1, -PDA2) Convert between the two acceptance criteria tm_deterministic(?TM, ?Bool) Bool says whether TM is deterministic or not */Aprender mais sobre o P-FLAT
Se desejar ficar um perito no P-FLAT, então consulte o script "tutorial.pl" e observe o output a ser gerado ao mesmo tempo que estuda o código Prolog que lhe deu origem. É muito instrutivo.Corra também o script "examples.pl".
Finalmente, a fonte de informação mais detalhada para o P-FLAT é o seu próprio código fonte. O código segue fielmente as definições apresentadas nas aulas teóricas e encontra-se profusamente comentado.