Original Article: Monadic Parsing using JavaScript
Author: cs.rit.edu
Axel T. Schreiner, Department of Computer Science, Rochester Institute of Technology
Resumo

Monad é uma classe em Haskell, que é fundamental para encapsular os efeitos colaterais. Um tipo monádico pode ser usado, por exemplo, para manter e manipular o estado juntamente com outra computação ou para ignorar a execução seqüencial e recuperar da falha. Um domínio de problema significativo é a análise: o suporte para analisadores monádicos existe para Haskell, Python e outras linguagens.

Esta página da Web descreve a análise monadic LL (n) com JavaScript, completa com uma classe base para classes monádicas que envolvem funções de estado, uma notação para incorporar cálculos monádicos em JavaScript (ou seja, um equivalente ao do notação em Haskell), um pré-processador para traduzir a notação em JavaScript, um gerador de scanner baseado em expressões regulares, uma fábrica para classes para representar árvores de análise e a implementação de um pequeno idioma com manipulação de exceções como outro exemplo de uma computação monádica. O pré-processador é implementado usando o analisador monádico que ele suporta.

Todo o código-fonte nesta página da Web é ao vivo e pode ser editado e executado de forma interativa. Uma série de exemplos foram configurados para execução nesta página da web. Eles geralmente são seguidos por sugestões para novas investigações que envolvem a edição e a reexecução dos exemplos. A página foi testada com inúmeros navegadores no Mac OS X e Windows XP - O Firefox lida com a página bem, o Internet Explorer teve problemas com o pré-processamento dos exemplos. (Detalhes são registrados nos comentários nesta página.)

O código-fonte é organizado em namespaces, ou seja, coleções globais; cada área editável é rotulada com o namespace ao qual pertence e tem um botão de reinicialização para restaurar o conteúdo original. Mais duas páginas são abertas por trás dessa página que contém o código restante de dois namespaces (JSM, o pré-processador para a notação monádica, e Mini, a implementação de uma linguagem de programação pequena), dos quais apenas excertos são apresentados nesta página. Usando os links em Download Namespaces abaixo do conteúdo original de cada espaço para nome pode ser exibido em uma página da Web separada.

A notação monádica precisa ser traduzida para o Javascript antes que possa ser executada; Isso é realizado usando o botão de pré-processamento seguindo cada área editável com notação monádica. Qualquer botão de pré-processamento pode ser (ab) usado para traduzir o código arbitrário inserido na área editável anterior; O botão de reinicialização restaurará o conteúdo original.

Uma área de somente leitura (pré-processamento ou saída) pode ser expandida ou contratada clicando na área. Os seguintes botões podem ser usados pré-processar todo o código (exceto onde a fonte pré-processada faz parte da página da Web para começar), execute todos os exemplos de JavaScript, interprete todos os pequenos exemplos de linguagem e expanda o tamanho de todas as áreas somente leitura.

Em Haskell [1] um tipo de dados é um conjunto de valores. Um tipo de dados pode ser definido como uma instância de uma ou mais classes; uma classe declara operadores e métodos polimórficos que uma instância de tipo deve implementar. Monad é uma classe com operações que controlam a execução seqüencial; a notação de doação é uma maneira muito conveniente de expressar cálculos em um estilo aparentemente imperativo enquanto envolvem valores de tipos que são instâncias de Mônada. A implementação das operações da Mônada permite abandonar a execução seqüencial, por exemplo, pode haver uma reação à falha, e o estado pode ser mantido separadamente da execução seqüencial, por exemplo, para rastrear operações, suportar uma forma de atribuição ou executar entrada e saída. Especificamente, Haskell usa o monad do IO para separar a entrada / saída estadual da programação funcional "pura" sem estado.

Muitas linguagens imperativas como C# [2], Groovy [3], Java [4], JavaScript [5], Python [6], Ruby [7], e, claro, todas as variantes de Lisp [8], funções de suporte como valores de primeira ordem. Alguns desses idiomas, por exemplo, JavaScript e Lisp, são dinamicamente digitados, outros, como Haskell ou C #, suportam alguma forma de inferência de tipo; qualquer variante de digitação simplifica muito o uso de funções. No entanto, mas para Haskell, a maioria dos idiomas não integra tipos monádicos com construções linguísticas especiais.

Se uma linguagem de programação suportar a atribuição, entrada / saída e algum tipo de armazenamento global, não há necessidade urgente de mônadas porque o estado pode ser mantido de forma bastante explícita. No entanto, esta página da Web mostra que as mônadas podem ser criadas com apenas funções como valores de primeira ordem. A página da Web ilustra que - pelo menos na área de análise - as mônadas são uma maneira muito útil de estruturar a computação. Especificamente, esta página da Web mostra como programar analisadores de descida recursiva diretamente das gramáticas LL (n) baseadas em uma infra-estrutura que requer apenas expressões e funções regulares como valores de primeira ordem. Ele também estende o JavaScript com uma notação semelhante à notação de Haskell que pode ser vista como o idioma de entrada para um gerador de analisador, mas que também pode ser usado para outros cálculos monádicos. Existe uma breve discussão sobre a forma como a notação é utilizada para converter-se em JavaScript, ou seja, como o gerador do analisador é implementado. Finalmente, existe uma implementação de um intérprete para uma linguagem de programação convencional muito pequena que serve de exemplo para outros cálculos monádicos.

A página da Web fornece ferramentas e exemplos para aqueles que precisam implementar pequenos idiomas usando o JavaScript, e sugere, por exemplo, como as ferramentas podem ser implementadas rapidamente em outras linguas que suportam expressões e funções regulares como valores de primeira ordem.

Esta seção descreve Monad, uma classe base para classes monádicas em JavaScript. Para o sistema descrito aqui, um valor monádico é um objeto que contém uma função de estado:

Monad

Dado um valor monádico, sua função de estado pode ser aplicada a um valor de estado:

Monad

Ao contrário de Haskell, o JavaScript é uma linguagem digitada dinamicamente, ou seja, nem o tipo de state nem o tipo de retorno da função de estado deve ser especificado; assim sendo, Monad pode fornecer a funcionalidade de todas as classes monádicas baseadas em função estatal. Por convenção neste sistema, no sucesso, uma função estatal retornará uma coleção com duas propriedades, a nova state e o value encapsulado pelo valor monádico que contém a função de estado; Na falha, uma função de estado retornará uma coleção com um fail propriedade, por exemplo, com uma mensagem de erro. Com esta convenção Monad combina as capacidades da Haskell's Either e mônadas baseadas em função estatal.

Dado dois valores monádicos, orElse cria um novo valor monádico na mesma classe que o receptor. O novo valor contém uma função de estado que aplicará a função de estado do receptor ou - somente em caso de falha - a função de estado contida no valor do argumento para orElse:

Monad

Falando vagamente, os valores monádicos "são" funções de estado e orElse combina-os para a execução alternativa. Deve notar-se que qualquer função de estado é aplicada ao mesmo estado de entrada. Na terminologia de Haskell, orElse é o mplus operação do MonadPlus classe.

Intuitivamente, o bind operação, denotada como >>= em Haskell's Monad classe, combina dois valores monádicos, isto é, funções de estado, para execução sequencial (e cria um escopo para um valor de resultado). No entanto, a execução seqüencial tem que ser controlável: deve ser garantido que a primeira função de estado seja executada primeiro e que seja possível suprimir a execução da segunda função de estado se a primeira falhar. Além disso, e ao contrário orElse, Se as duas funções de estado tiverem sucesso, o estado final eo valor devem depender de ambas as funções.

Portanto, o método andThen não combina dois valores monádicos diretamente. Em vez disso, aceita como argumento uma função que se espera que aceite uma value propriedade produzida pela função de estado do receptor e retornar o valor monádico que andThen é combinar com o receptor:

Monad

O método andThen retorna um valor monádico na mesma classe que o receptor que contém uma função de estado que primeiro aplica a função de estado do receptor. Se for bem sucedido, o result.value é usado para produzir o segundo valor monádico e sua função de estado é aplicada ao result.state. Falando os resultados do valor final da composição de ambas as funções de estado, o estado de entrada é enviado apenas para a primeira função de estado e seu estado de resultado é enviado para a segunda função de estado.

Monad.subclass é uma função de conveniência para criar construtores para novas classes monádicas que herdam os métodos descritos até agora. (Falando estritamente, não há herança entre as classes em JavaScript, no entanto, esta página da Web usa a terminologia de Java. Nessa linguagem Monad.subclass é um método de classe - que não é herdado pelas novas classes monádicas.)

Monad
Monad

Monad é a superclasse de todas as classes monádicas. Portanto, o novo construtor result está acorrentado ao Monad construtor e um Monad objeto é configurado como o prototype da nova classe. O objetivo do prototype é herdar andThen, apply e orElse; Portanto, o stateFunction é excluído do prototype. Finalmente result é adicionado como constructor ao prototype e voltou.

Uma classe monádica deve conter alguns valores monádicos. Assim sendo, subclass cria alguns métodos de classe para a nova classe que criam valores monádicos. succeed cria uma instância da nova classe para o seu valor de argumento, ou seja, retorna um valor monádico contendo uma função de estado que retornará uma coleção com o valor de argumento de succeed e o estado de entrada:

Monad

dump serializa uma coleção e está conectado como toString método para o resultado da função de estado definida para succeed, mas também pode ser usado como um método de classe para exibir seu argumento:

Monad

O método da classe fail cria um valor monádico com uma função de estado que retornará uma coleção com o argumento de fail:

Monad

succeed e fail são as return e fail operações exigidas pela Haskell's Monad classe. fail também é a operação mzero de MonadPlus.

Finalmente, mais dois tipos de valores monádicos tornar-se-ão úteis e são definidos como valor de uma variável de classe e resultados de um método de classe, respectivamente. get é um valor monádico que contém uma função de estado que retorna o estado de entrada como ambos, value e state; é usado para expor o estado atual dentro de uma computação monádica:

Monad

put retorna um valor monádico contendo uma função de estado que ignora o estado de entrada e retorna os argumentos de put como o novo value e state; é usado para definir o estado atual dentro de uma computação monádica:

Monad

Para simplificar a depuração, todos os valores de resultado estão conectados a dump.

Operations of Monad and MonadPlus should satisfy certain axioms which can now be tested using Monad. The tests are cumulative and can be edited and executed below. With a stand-alone implementation of JavaScript such as Rhino [9] or SpiderMonkey [10] the examples can be executed interactively once the Monad definition from the previous section is loaded.

Axioms é uma classe monádica e m é um valor monádico para o qual os axiomas serão testados; Axioms também é usado como um namespace para evitar a confusão global:

Axioms

Usado como um método de classe acima, dump mostra que m contém uma função de estado que retornará o argumento 'hello' costumava criar m e o estado de entrada. O JavaScript não pode mostrar que a variável livre value da função do estado, de fato, fechou o argumento dado a succeed quando m foi criado.

Implicitamente usado para converter para uma string abaixo, dump mostra que o resultado da aplicação da função estatal contida em m combina 'hello' e o estado de entrada 's'.

Axioms

Tente isso: fail('message'), get, e put('value', 'state') pode ser usado para obter outros valores monádicos. Mude m acima, ou introduzir variáveis adicionais, e examinar os resultados acima e ao testar os axiomas abaixo. ▢

Falando vagamente, se considerarmos valores monádicos como um conjunto com uma operação andThen a função succeed tem que agir como uma unidade certa:

Axioms

Isso mostra que os dois valores monádicos m e m.andThen(succeed) exibem o mesmo comportamento. No entanto, eles contêm funções de estado muito diferentes:

Axioms

Tente isso: Observe que os resultados das duas invocações de apply igualam-se, independentemente do valor monádico ao qual você se liga m porque succeed age como uma unidade certa para andThen. Observe também que a função de estado imediatamente acima não muda porque é o resultado de andThen em si. ▢

Da mesma forma, um valor monádico construído com succeed age como uma unidade esquerda. Dada alguma função f que retorna um valor monádico e algum argumento 'hello':

Axioms

O valor monádico f('hello') exibe o mesmo comportamento acima como o valor monádico resultante da combinação succeed('hello').andThen(f) abaixo:

Axioms

Tente isso: Novamente, o valor monádico na definição de f acima pode ser alterado para verificar o comportamento de outra função. Defina f de modo que o resultado envolva hello world!. ▢

O terceiro axioma requer andThen para ser uma operação associativa. Continuar o exemplo aqui é uma ilustração que envolve um valor monádico construído com succeed:

Axioms

A definição original de f usa get que copia o estado de entrada 'hello' como valor e g usa succeed para anexar ' world!' para isso. A cadeia acima é executada da esquerda para a direita, mas as funções podem ser combinadas para mudar isso:

Axioms

O exemplo mostra que (pelo menos para esses valores e funções monádicas específicas) o comportamento não depende se m é combinado pela primeira vez com g e o resultado é combinado com f (associação a partir da esquerda), ou m é combinado com o resultado da combinação g com f (associação da direita).

Tente isso: Mude as definições de m, f, e g para investigar outros casos para o terceiro axioma. ▢

MonadPlus As operações também devem satisfazer certos axiomas: mzero deve atuar como um zero direito e esquerdo em combinações com >>= e mplus, ou seja, fail, andThen e orElse deve agir como falso combinado com preemptivo e e ou operações em uma linguagem de programação.

x e falso é falso:

Axioms

x or false is x:

Axioms

falso e x é falso:

Axioms

falso ou x é x:

Axioms

Tente isso: Introduza uma variável x em cada axioma e vincular valores monádicos diferentes para x para confirmar os axiomas para outros valores. Mude a função g e verifique novamente. ▢

Deve salientar-se que os exemplos não prove que os métodos e valores herdados de Monad satisfazer os axiomas. Os exemplos apenas ilustram que os axiomas são observados para um valor monádico específico e para a função g definido anteriormente; Muitos valores e funções podem ser inseridos para verificar novamente.

Idealmente, um analisador examina uma string de entrada e produz um valor, por exemplo, a cadeia de entrada pode conter uma expressão aritmética e o valor é uma árvore que representa a expressão ou mesmo o valor da expressão. Um analisador geralmente não consegue completar a tarefa - pode entender apenas uma parte inicial da entrada ou até mesmo falhar completamente. Portanto, faz sentido exigir que uma função de analisador aceite a entrada e devolva um valor e a entrada restante, se for bem-sucedida, e uma seqüência de caracteres com uma mensagem de erro se não. Em outras palavras, uma função de analisador é uma função de estado e o estado é a entrada a ser examinada.

Esta seção descreve uma classe Parser com valores monádicos que contêm funções de analisador:

Parser

Esta seção deve muito ao excelente livro de Hutton [11]. Ele começa com analisadores que aceitam um único personagem ou mesmo nada e se acumulam em analisadores que aceitam números ou identificadores, opcionalmente, rodeados por espaço em branco. No entanto, o JavaScript suporta expressões regulares que simplificam a descrição de blocos de construção de baixo nível para analisadores complexos. Por exemplo, a coleção a seguir é suficiente para descrever as peças que podem ser combinadas para formar expressões aritméticas:

arithmetic

Uma coleção com mais algumas propriedades já pode descrever uma pequena linguagem de programação:

language

A propriedade skip tem significado especial como discutido abaixo; Todos os outros nomes de propriedade podem ser escolhidos à vontade. Cada propriedade tem uma expressão regular como valor e todos, exceto skip eles próprios podem ter uma propriedade reserved com uma lista de exceções para indicar correspondências com um significado especial. Um analisador sempre examina a parte inicial da entrada; portanto, as expressões regulares são mais eficientes se estiverem ancoradas com ^.

reserved permite criar analisadores para símbolos específicos e para todos os outros símbolos que correspondem a um padrão. Por exemplo, muitas linguagens de programação usam as mesmas convenções para identificadores definidos pelo usuário e palavras reservadas específicas do idioma. Portanto, a expressão regular especificada para uma propriedade como word é suposto combinar ambos os tipos de entrada. Se uma partida de word está contido em word.reserved é aceito apenas por um analisador gerado para uma palavra reservada, e não pelo analisador que aceita qualquer outra coisa correspondente à expressão regular especificada para word.

Parser.Factory é uma classe de objetos que podem produzir monadicais Parser valores. Um objeto de fábrica é construído com uma coleção de propriedades conforme descrito acima. Opcionalmente, um sinalizador de rastreamento pode ser especificado:

Parser
Parser

A análise real da entrada, ou seja, o corpo da função analisador, pode ser delegada a um método comum scan que aceita entrada e uma expressão regular e retorna uma coleção com o texto correspondente como value e a entrada restante como state:

Parser

A entrada original pode ser uma string, mas a entrada restante será uma coleção com a string restante como text e o número da linha onde esta corda começa como lno. Se não houver correspondência, ou se a partida não estiver no início da entrada, o resultado é uma mensagem de erro que inclui o número da linha atual na entrada:

Parser

Um dos resultados está conectado a dump para simplificar a depuração, por exemplo:

Scanners

Tente isso: O padrão arithmetic.skip corresponde a um ou mais caracteres de espaço em branco. Remova o espaço em branco principal da entrada para ver uma mensagem de erro quando skip falha. Observe os diferentes números de linha.

Tente isso: Mude arithmetic de modo que um personagem de nova linha seja reconhecido como um symbol em vez de espaço em branco, ou seja, o exemplo acima deve falhar logo que o único espaço de liderança seja removido da entrada. Aplique symbol() para reconhecer a nova linha líder. ▢

A variável da instância de fábrica skip faz referência a um analisador. Para cada outra propriedade da coleção, um método é adicionado à fábrica, que criará um analisador. Se houver um skip propriedade, esses analisadores descartarão uma partida antes de considerar o resto de sua entrada:

Parser

Por exemplo, a fábrica de analisadores criada a partir do arithmetic A coleta é suficiente para lidar com expressões aritméticas:

Scanners

Tente isso: Cada analisador escolhe um item da entrada; invocar print para exibir os valores sucessivos acima. ▢

Se o scan método encontra uma correspondência, o valor do resultado pode ser filtrado com um argumento especificado no método do gerador.

Tente isso: Mude o argumento de number para ver o terceiro analisador falhar. Em seguida, altere a entrada original para que o analisador seja bem-sucedido novamente. ▢

Se houver um reserved lista para uma propriedade o método correspondente cria um analisador somente se o argumento, se houver, estiver na lista. O language coleção inclui palavras reservadas:

Scanners

Tente isso: Again, invoke print to display how the successive items in the original input are accepted; i.e., print a, b, c, d, and e. ▢

Parsers são objetos que podem ser aplicados para aceitar a entrada. Os analisadores podem ser criados por métodos geradores de uma fábrica de analisadores. O exemplo mostra que os analisadores usados com freqüência podem ser salvos, por exemplo, como variáveis de instância da fábrica do analisador; a variável de instância pode até substituir o método do gerador.

Se não há reserved lista para uma propriedade, um analisador pode ser gerado sem um argumento para aceitar qualquer entrada que corresponda à expressão regular correspondente, ou pode ser gerado com um argumento para aceitar apenas a entrada que deve corresponder à expressão regular e deve ser igual ao argumento. Se houver um reserved lista, um analisador pode ser gerado com um argumento da lista para aceitar entrada igual ao argumento; se for gerado sem um argumento, ele aceita qualquer correspondência da expressão regular que não está na reserved Lista. Em qualquer caso, se houver skip expressão, correspondência de entrada skip é primeiro descartado.

Em resumo, as expressões regulares são usadas para particionar a entrada, skip indica a entrada a ser descartada, os argumentos do gerador restringem o que a entrada particionada é aceitável, e cada reserved lista descreve casos especiais a serem considerados quando a entrada foi particionada.

Esta seção discute como os analisadores simples podem ser combinados para criar analisadores que aceitam entrada com uma estrutura mais complicada. Considere uma gramática para expressões aritméticas:

      term:    number | '(' sum ')';
      product: term '*' product | term '/' product | term;
      sum:     product '+' sum | product '-' sum | product;

Um Parser.Factory com base no arithmetic coleção apresentada na seção anterior produz analisadores para number e os símbolos do operador literal utilizados nesta gramática. Os analisadores são valores monádicos e podem ser combinados com andThen para execução sequencial e orElse Para alternativas, isto é, essas duas operações são suficientes para construir analisadores para os não-terminais term, product, e sum nesta gramática, simplesmente traduzindo a gramática:

Expr

Tente isso: Use um diferente number como entrada para term. Em seguida, tente entrar (10). ▢

Cada símbolo terminal da gramática como number ou '(' é traduzido em um analisador gerado a partir de uma coleção de padrões por Parser.Factory.

Cada símbolo não terminal como term é traduzido para um analisador combinado de outros analisadores: uma seqüência de itens na gramática é combinada usando andThen, alternativas são combinadas usando orElse. O exemplo acima mostra que os analisadores geralmente podem ser aplicados antes de todos os outros analisadores terem sido definidos.

Expr

Tente isso: Insira algumas outras expressões aritméticas. Além disso, especifique uma expressão inválida, como -23. Mude a definição de term para permitir um único sinal (ou mesmo mais) um menos. ▢

Com uma notação suportada pelo préprocessador conforme descrito abaixo, será muito mais fácil especificar esse ninho de chamadas de função. No entanto, o resultado é um analisador funcional para expressões aritméticas - produz um value e a entrada restante para uma frase correta como mostrado acima e uma mensagem de erro em caso de falha:

Expr

expr espera que a string de entrada contenha somente números, operadores e parênteses na ordem correta e, opcionalmente, espaço em branco, nada mais. No exemplo acima, a expressão é seguida por um ponto-e-vírgula em uma segunda linha para demonstrar que uma mensagem de erro contém o número da linha atual.

Tente isso: Remova o ponto-e-vírgula da entrada para ver expr tenha sucesso. Insira outras expressões. ▢

Deve-se notar que a ordem das alternativas é importante. orElse não implementa backtracking, ele só ativa o segundo analisador se o primeiro falhar. Por exemplo, as alternativas de sum poderia ser reordenado:

      sum:     product | product '+' sum | product '-' sum;

which translates into

Expr_badSum

Tente isso: Quanto da entrada acima badSum aceitar? O que acontece se a entrada estiver entre parênteses? ▢

O error O método é (temporariamente) substituído acima para que ele produza saída assim que haja alguma falha. A execução com a entrada original mostra que apenas os operadores de product são pesquisados. As duas falhas ocorrem quando product tenta encontrar um operador de multiplicação antes de se instalar para um simples term. Enquanto badSum sucede, não aceita toda a expressão porque a primeira alternativa é bem sucedida com um simples product. Portanto, quando uma gramática é convertida em analisadores, as alternativas mais longas devem ser especificadas primeiro.

O exemplo sugere que é ineficaz quando ocorre uma falha no meio de uma alternativa: orElse começa com a entrada original, isto é, neste caso 1 é analisado por number e term um total de três vezes antes product finalmente é bem sucedido. A gramática pode ser alterada para evitar isso:

      betterProduct: term mulDivs;
      mulDivs:       '*' betterProduct | '/' betterProduct | /* empty */;

An empty alternative succeeds without accepting input, i.e., without changing state:

Expr

with (Parser) is needed in this code because Parser provides the method succeed.

Tente isso: Insira badSum acima e combiná-lo com betterProduct. Isso melhora o desempenho? Reduz o número de falhas se a entrada estiver entre parênteses? ▢

Extended BNF, pioneira de Nicklaus Wirth para Pascal [12], apresenta construções para descrever uma gramática e evitar a recursão. Um estilo popular para as construções geralmente é usado em RFCs de Internet [13]: os itens podem ser agrupados com parênteses, os itens opcionais são marcados com um sufixo ?, e os sufixos * e + indicar que um item pode ser repetido zero e uma ou mais vezes, respectivamente. A gramática para expressões aritméticas e a tradução para analisadores podem ser alteradas da seguinte forma:

      betterSum: product summands?;
      summands:  ('+' product | '-' product)+;

This translates into:

Expr

Tente isso: Adicione mais somas à entrada e observe o valor resultante. ▢

Muitas vezes, os parênteses não precisam ser traduzidos explicitamente; Aqui estão implicados pelo fato de que o aplicativo do método é associativo à esquerda. A implementação usa dois dos Parser métodos optional, some, e many correspondente aos sufixos ?, +, and *, respectivamente, que podem ser aplicados a qualquer Parser valor:

Parser

Dado um analisador, optional cria um novo analisador com uma função de analisador que executará a função de analisador do receptor e retornará o resultado se for bem-sucedido; caso contrário, ele retornará o argumento value (ou uma string vazia) e a entrada original como state.

Dado um analisador, some cria um novo analisador com uma função de analisador que executará a função de analisador do receptor uma ou mais vezes e retornará uma lista com os resultados como value e a entrada restante como state; a função de analisador falha se a função do receptor não for bem-sucedida pelo menos uma vez.

Dado um analisador, many cria um novo analisador com uma função de analisador que tenta executar a função de analisador do receptor uma ou mais vezes e retorna uma lista com os resultados como value e a entrada restante como state; a função analisador sempre terá sucesso, mas a resultante value A lista está vazia ea entrada state é inalterado se a função de analisador do receptor não for bem sucedida pelo menos uma vez.

A implementação de some usa o fato de que andThen combina um analisador e uma função e passa o value resultando da execução bem sucedida da função de analisador do receptor como argumento para a função. Na cadeia de andThen acima das definições de função estão aninhadas de modo que todos os escopos de parâmetros se estendam até a extremidade da função mais interna, ou seja, qualquer parâmetro pode ser usado a partir do ponto onde é introduzido e em todas as funções aninhadas.

As implementações dos métodos acima sugerem que uma função de analisador não precisa sempre retornar uma string como value. De fato, some e many providenciar para retornar listas e optional pode providenciar para retornar um valor arbitrário. Isso pode ser explorado, por exemplo, para interpretar uma expressão aritmética conforme ela é analisada. Aqui está uma gramática expressa com o BNF estendido:

      term:    number | '(' sum ')';
      product: term ('*' term | '/' term)*;
      sum:     product ('+' product | '-' product)*;
      expr:    sum eof;

This translates into the following interpreter:

Eval

Quando apropriado, as funções passaram para andThen foi dado um parâmetro para ligar o valor produzido pelo analisador precedente e outro andThen foi adicionado a cada sequência (exceto para a primeira alternativa de term) com uma função que usa succeed para retornar um valor para a frase de expressão reconhecida.

Os analisadores criados usando many listas de retorno acima das funções de curry, por exemplo, a frase - 10 vai retornar

      function (x) { return Number(x) - 10; }

e many cria uma lista dessas funções. foldl é um método de classe geralmente útil Parser que leva um valor e uma lista (possivelmente vazia) de funções de curry e as aplica da esquerda para a direita para acumular o valor do resultado, isto é, foldl interpreta sequências de operação associativa esquerda:

Parser

Tente isso: Input some other expressions.

Tente isso: Remova Number de todas as funções e avaliar a entrada original, bem como 1+2 e 3*4. Adicione código para processar o resultado de af.number() de modo que as expressões sejam novamente avaliadas corretamente. ▢

O interpretador para expressões aritméticas mostrado na seção anterior funciona, mas a implementação é bastante propensa a erros devido ao uso excessivo de funções aninhadas conforme exigido por andThen. Haskell faz a notação simplifica enormemente a especificação de cálculos com valores monádicos e esta seção discute algo semelhante que é necessário para tornar os valores monádicos palatáveis em JavaScript. A gramática para expressões aritméticas

      term:    number | '(' sum ')';
      product: term ('*' term | '/' term)*;
      sum:     product ('+' product | '-' product)*;
      expr:    sum eof;

can be translated into an interpreter much more literally using a notation for monadic values:

EvalM

{{{ e }}} envie uma computação que retornará um valor monádico. A computação consiste em uma ou mais alternativas, separadas por |||. Uma alternativa consiste em uma ou mais peças de código JavaScript. Cada peça deve entregar um valor monádico e deve ser terminada com um ponto e vírgula. (Dependendo do contexto, o JavaScript permite que uma nova linha atue como um terminador de instruções, mas isso não é suportado aqui.)

Cada parte do código JavaScript pode ser precedida por um identificador e <-. Nesse caso, o valor embrulhado pelo valor monádico produzido pelo código JavaScript está ligado ao identificador, por exemplo,

      x <- succeed(y);

irá vincular o valor y para o identificador x. O escopo do identificador se estende desde o próximo valor monádico até o final da alternativa (isto é, x acima não pode ser usado no lugar de y mas pode ser usado além succeed); o identificador pode ser somado dentro de seu escopo.

A notação pode ser aninhada e sufixos como optional, many, e some pode ser aplicado. Isso ajuda a traduzir o resto da gramática para a notação monádica:

EvalM

O pré-processador descrito em uma seção subseqüente converte essa notação no código desenvolvido na seção anterior.

Tente isso: O valor numérico resultante não está correto. Use as entradas 1+2 e 3*4, preprocess, executar e, em seguida, alterar (e pré-processar) a primeira alternativa de term acima para produzir o resultado correto.

Tente isso: Adicione % como um operador que retorna o restante após a divisão. Adicione uma operação de sinal de menos.

Tente isso: O que acontece se houver uma divisão em zero? O que acontece se uma divisão por zero retorna fail('zero')? ▢

Alguns navegadores (mais notavelmente o Safari da Apple) e o intérprete de JavaScript SpiderMonkey [10] parece restringir a profundidade da pilha de chamadas JavaScript e não pode pré-processar exemplos muito maiores. Felizmente, Firefox [14] e o intérprete de JavaScript Rhino [9] não parecem estar restritos.

Muitas vezes, é conveniente representar a entrada como uma árvore para processamento posterior. Por exemplo, o código da seção anterior pode ser alterado ligeiramente para produzir uma árvore para uma expressão aritmética:

Tree

term permanece quase inalterado: um number é representado como um Leaf nó e qualquer coisa sum calcula é o resultado de term.

Tree

As funções criadas em product e sum são alterados para criar nós de árvore e conectar seus descendentes ao invés de avaliar imediatamente as várias operações. (A área de préprocessamento mostra todo o construtor de árvores, não apenas a tradução de expr, sum, e product.)

Tente isso: Adicione ou remova parênteses e altere a expressão aritmética para ver como a árvore muda, por exemplo, para refletir a precedência. Não se esqueça de pré-processar sempre que a expressão aritmética é alterada. ▢

Os nós de árvore são tão simples que um método de fábrica estática Parser.makeTreeClasses pode organizar cada propriedade de uma coleção, como Tree acima para ser um construtor de objetos JavaScript; por convenção, apenas essas propriedades são modificadas para serem classes de árvore onde o nome começa com uma letra maiúscula:

Parser

makeTreeClasses instala um construtor que simplesmente salva sua arguments como content. Se makeTreeClasses é chamado com um segundo argumento que avalia true, o construtor exibirá imediatamente o novo objeto; Isso é bastante útil para depurar um construtor de árvores.

makeTreeClasses armazena o className como uma variável de instância compartilhada. Isso possibilita a implementação de uma estática dumpTree função que está conectada como toString para cada uma das classes geradas:

Parser

dumpTree retorna o nome da classe seguido por uma lista recuada dos valores em content, caso existam. A lista é responsável por matrizes aninhadas e objetos aninhados, mas espera-se que os objetos aninhados implementem toString de forma compatível.

Tente isso: Adicione % como um operador que retorna o restante após a divisão. Adicione uma operação de sinal de menos. ▢

A notação monádica é incorporada em JavaScript e um interpretador de JavaScript pode ser estendido para lidar com a notação diretamente. No entanto, especialmente para fins de prototipagem, é muito mais simples converter a notação em JavaScript antes da interpretação. Esta seção discute as partes significativas de uma implementação de pré-processador; O código completo do pré-processador está disponível para edição (e autoprocessamento) em uma página de edição separada.

A notação monádica pode ser descrita por uma gramática que se concentra nos cálculos monádicos e obscurece a maioria dos JavaScript:

      jsm:      term+ eof;
      term:     monad | blanks | word | quoted
          |     '(' term* ')' | '[' term* ']' | '{' term* '}' | symbol;
      monad:    '{{{' mvalues ('|||' mvalues)* '}}}';
      mvalues:  mvalue+;
      mvalue:   blanks? (word blanks? '<-')? term+ ';' blanks?;

A gramática mostra quais analisadores (como blanks) tem que ser criado com Parser.Factory e quais símbolos literais devem ser aceitos por um ou mais desses analisadores. Descrever a fábrica do analisador é o primeiro passo para implementar o pré-processador.

jsm é código JavaScript e pode especificar valores monádicos usando monad frases. O pré-processador normaliza o espaço em branco, ou seja, substitui comentários e múltiplos espaços por espaços em branco simples e preserva todos os caracteres da nova linha em benefício do JavaScript para que haja uma correspondência trivial entre as linhas de entrada e saída. Portanto, a descrição da fábrica do analisador não contém uma skip propriedade:

      JSM.scanner = {
        blanks:   /^(\s|\/\/.*|\/\*([^*]|\*+[^\/*])*\*+\/)+/,
        word:     /^[a-zA-Z_][a-zA-Z_0-9]*/,
        quoted:   /^("([^"\\\n]|\\.)*"|'([^'\\\n]|\\.)*'|\(\/([^\/\\\n]|\\.)*\/\))/,
        symbol:   /^(\{\{\{|\|\|\||\}\}\}|<-|[^a-zA-Z_])/,
        eof:      /^$/
      };

blanks descreve o espaço em branco e os comentários que devem ser normalizados para a saída. word descreve os identificadores aos quais a notação monádica pode vincular os valores envolvidos. quoted descreve cordas e expressões regulares para que seu conteúdo não possa ser confundido com símbolos. Finalmente, symbol descreve os significantes símbolos multi-personagens e todos os caracteres únicos que não podem iniciar um identificador - isso inclui dígitos únicos que compõem números.

Tente isso: Adicione seqüências de dígitos como number ao scanner descrição e term função discutida abaixo. ▢

Uma lista reservada é especificada para distinguir símbolos que são significativos para a gramática dos outros símbolos que o padrão corresponde:

      JSM.scanner.symbol.reserved =
        [ '{{{', '|||', '}}}', '<-', ';', '(', ')', '[', ']', '{', '}' ];

Infelizmente, como cordas, expressões regulares podem conter outros caracteres que, em seguida, perdem seu significado sintático, mas as expressões regulares são mais difíceis de detectar do que as cadeias de caracteres, porque a barra principal também pode aparecer sozinha como operador de divisão. Para simplificar o problema, o pré-processador exige que uma expressão regular deve ser entre parênteses sem partes intermináveis.

A gramática agora pode ser traduzida para a notação que descreve usando métodos como many, etc., para os sufixos. Por exemplo:

      with (Parser) with (JSM)
        // mvalue: blanks? (word blanks? '<-')? term+ ';' blanks?
        JSM.mvalue =
          {{{
              pf.blanks().optional();
              {{{
                  pf.word();
                  pf.blanks().optional();
                  pf.symbol('<-');
              }}}.optional();
              term(true).some();
              pf.symbol(';');
              pf.blanks().optional();
          }}};

Uma vez que um pré-processador está disponível, este código pode ser convertido em JavaScript e executado para verificar se a entrada está em conformidade com a gramática. Infelizmente, para a versão inicial do pré-processador, este código teve que ser traduzido manualmente usando andThen, etc., no estilo mostrado anteriormente.

O fragmento de código sugere uma complicação: term é usado três vezes na gramática: no nível do código JavaScript em jsm, ao nível do código monádico em mvalue como mostrado acima, e recursivamente, entre vários parênteses term em si:

      term: monad | blanks | word | quoted
          | '(' term* ')' | '[' term* ']' | '{' term* '}' | symbol;

No mvalue um ponto-e-vírgula é significativo, mas em outros lugares não é, ou seja, o curinga symbol nem sempre pode combinar um ponto-e-vírgula. Felizmente, term é codificado como um valor monádico, isto é, como um item de dados que pode ser criado por uma função com um parâmetro que controla se um ponto e vírgula deve ser reconhecido quando o item de dados é aplicado:

      with (Parser) with (JSM)
        JSM.term = function (noSemicolon) {
          return (
            {{{
                monad;
            |||
                pf.blanks();
            |||
                // ...
            |||
                pf.symbol(';');
                noSemicolon ? fail('semicolon not expected')
                            : succeed(';');
            |||
                pf.symbol('(');
                term(false).many(); 
                pf.symbol(')');
            |||
                // ...
            |||
                pf.symbol(); // all non-reserved symbols
            }}});
        };

O manuseio de JavaScript de novas linhas pode resultar em ambiguidades - é por isso que blanks são explícitos nesta gramática para que as linhas novas possam ser passadas de notação monadic para JavaScript pré-processado. Especificamente, se return e um argumento é separado por uma nova linha, o JavaScript tratará return sozinho como uma declaração! Como o código acima mostra isso é facilmente contornado, encerrando um return argumento entre parênteses e inserir uma nova linha, se houver, apenas após os principais parênteses.

Uma vez que uma gramática foi traduzida em notação monádica - e executada para reconhecer alguma entrada, se possível - precisa ser estendido com o código que irá representar ou interpretar a entrada. O pré-processador usa o método de fábrica makeTreeClasses discutido na seção anterior; o problema de traduzir a entrada é assim delegado à adição de métodos apropriados para essas classes mais tarde. As classes necessárias podem ser descobertas de cima para baixo, simplesmente adicionando identificadores para vincular valores interessantes retornados pelos analisadores e adicionando chamadas de construtor em succeed chamadas para representar os valores interessantes. A notação monádica é ideal para esta abordagem:

      with (Parser) with (JSM)
        JSM.term = function (noSemicolon) {
          return (
            {{{
                monad;
            |||
                b <- pf.blanks();
                succeed(new Blank(b));
            |||
                // ...
            |||
                pf.symbol(';');
                noSemicolon ? fail('semicolon not expected')
                            : succeed(new Text(';'));
            |||
                      pf.symbol('(');
                ts <- term(false).many(); 
                      pf.symbol(')');
                succeed(new Paren('(', ts, ')'));
            |||
                // ...
            |||
                s <- pf.symbol(); // all non-reserved symbols
                succeed(new Text(s));
            }}});
        };

Paren e Text pode ser implementado usando makeTreeClasses mas Blank é melhor codificado manualmente porque o espaço em branco é normalizado antes de ser passado:

      JSM.Blank = function (value) {
        this.value = value.replace(/./g, '');  // remove all but newlines
        if (!this.value) this.value = ' ';     // if no newlines: single blank
        if (trace) print(this);
      };

      JSM.Blank.prototype.toString = function () {
        switch (this.value) {
        case ' ':  return 'blank';
        case '\n': return 'newline';
        default:   return this.value.length+' newlines';
        }
      };

A gramática e a notação monádica podem ser alteradas para realizar uma reescrita ou validação de entrada que não pode ser convenientemente expressa na própria gramática. Por exemplo, a regra original

      mvalue:  blanks? (word blanks? '<-')? term+ ';' blanks?;

pode ser reescrito como

      mvalue:  ( blanks word blanks? '<-'
               |        word blanks? '<-'
               | blanks?                 )  term+ ';' blanks?;

porque isso torna simples combinar as duas primeiras peças opcionais de espaço em branco antes de entregá-las ao construtor. Além disso, term podem ser blanks e isso é razoável sempre que term é usado nesta gramática - parênteses vazios ou chaves, nenhum código JavaScript, etc. - exceto na situação mostrada acima: um valor monádico não pode estar vazio. Isso precisa ser verificado antes mvalue é aceito:

      with (Parser) with (JSM)
        JSM.mvalue =
          {{{
              bw <- {{{
                        b1 <- pf.blanks();
                        w  <- pf.word();
                        b2 <- pf.blanks().optional(null);
                        pf.symbol('<-');
                        succeed([new Blank(b1 + (b2 ? b2 : '')), w]);
                    |||
                        w  <- pf.word();
                        b2 <- pf.blanks().optional(null);
                        pf.symbol('<-');
                        succeed([b2 ? new Blank(b2) : null, w]);
                    |||
                        b1 <- pf.blanks().optional(null);
                        succeed([b1 ? new Blank(b1) : null, null]);
                    }}};
              ts <- term(true).some();
              pf.symbol(';');
              b3 <- pf.blanks().optional(null);
              (function () {
                for (var n = 0; n < ts.length; ++ n)
                  if (!(ts[n] instanceof Blank))
                    return succeed(new Mvalue(bw[0], bw[1], ts,
                                     b3 ? new Blank(b3) : null));
                return fail('expecting monadic value');
              })();
          }}};

bw está vinculado a uma lista contendo null ou Blank para as duas primeiras peças de espaço em branco e null ou uma string para o identificador; ts está vinculado a uma lista de termos que não incluirão pontos e vírgulas; finalmente, b3 é obrigado a null ou uma corda com o último espaço em branco, se houver. Tudo o que resta é um loop sobre o term lista para ver se ele contém um não-Blank e, em caso afirmativo, o mvalue o analisador é bem sucedido e o Mvalue construtor pode ser chamado com uma representação canônica dos possíveis descendentes.

As linguagens de programação muitas vezes fazem a diferença entre declarações e expressões. A notação monádica exige que cada computação produza um valor monádico para que possa ser desembrulhado e vinculado a um identificador se um for especificado. Isso significa que uma computação deve ser uma expressão de JavaScript, ou seja, pode usar a avaliação condicional com ? : mas não pode usar um loop. No entanto, o código acima mostra que sempre é possível inserir uma função anônima sem parâmetros contendo instruções e chamar a função imediatamente após a definição. Entre as declarações devem ser pelo menos um return para criar o valor necessário.

Finalmente, dada uma árvore que representa um programa de JavaScript com incorporado monad frases, a geração de código é implementada como um método gen em cada uma das classes a partir das quais a árvore é construída:

      JSM.Blank.prototype.gen = function () {
        return this.value;
      };

      JSM.Paren.prototype.gen = function () {
        var content = this.content[1],
          result = '';
        for (var n = 0; n < content.length; ++ n)
          result += content[n].gen();
        return this.content[0] + result + this.content[2];
      };

Text e Blank simplesmente emitem texto ou espaço branco normalizado, respectivamente. Paren emite os delimitadores e usa um loop para gerar código para o content entre os delimitadores. A parte mais difícil da conversão é realizada por Monad, Mvalues, e Mvalue. Uma Monad o nó da árvore contém uma lista não vazia de Mvalues e usa um loop para conectar seu código com orElse se necessario:

      // Monad: Mvalues+
      //   Mvalues .orElse( Mvalues ) ...
      JSM.Monad.prototype.gen = function () {
        var content = this.content[0], 
          result = content[0].gen();
        for (var n = 1; n < content.length; ++ n)
          result += '.orElse(' + content[n].gen() + ')';
        return result;
      };

Mvalues contém uma lista não vazia de Mvalue objetos e inicia um loop implementado por recursão para que eles possam implementar o nome apropriado do parâmetro da função e conectar seu código com andThen se necessario:

      // Mvalues: Mvalue+
      JSM.Mvalues.prototype.gen = function () {
        var content = this.content[0];
        return content[0].gen('', content, 1);
      };

      // Mvalue: Blank? word? (Blank|Monad|Paren|Text)+ Blank?
      //   Blank? term+ Blank? .andThen(function (word?) { return ... })?
      Mvalue.prototype.gen = function (head, content, next) {
        if (this.content[0]) head += this.content[0].gen();
        for (var n = 0; n < this.content[2].length; ++ n)
          head += this.content[2][n].gen();
        if (this.content[3]) head += this.content[3].gen();
        if (next < content.length) {
          head += '.andThen(function (';
          if (this.content[1]) head += this.content[1];
          head += ') { return (';
          head = content[next].gen(head, content, next+1);
          head += '); })'
        }
        return head;
      };

Normalmente, as chamadas do construtor são emitidas pelo succeed cláusulas na notação monádica; no entanto, para fins de teste, uma árvore pode ser criada, exibida e convertida de forma interativa:

JSM_Tree

Tente isso: Aplique gen como indicado para ver o código gerado. ▢

Mas para espaços em branco, este exemplo gera o mesmo código que a notação monádica:

JSM_Tree

A exibição da árvore é sempre muito útil para projetar métodos de geração de código. É instrutivo levar a saída da árvore acima e caminhar através da gen métodos para as várias classes de árvores de pré-processador.

Esta seção discute a implementação de uma pequena linguagem de programação imperativa. A implementação usa classes monádicas e consiste em um analisador para construir uma árvore para representar o programa de destino e de um intérprete que avalia a árvore. O código completo do sistema está disponível para edição e pré-processamento em uma página de edição separada.

O programa a seguir implementa o algoritmo de Euclides para calcular o maior divisor comum de dois números naturais. Ele usa características típicas de uma linguagem de programação tão pequena e imperativa:

Mini.interpret()

A seguinte função controla o processamento de uma string de origem escrita no pequeno idioma:

Mini

Tente isso: Mude o pequeno exemplo de idioma acima para obter o maior divisor comum de outros pares de números naturais.

Tente isso: Introduza um erro, por exemplo, adicione um ponto-e-vírgula após 36.

Tente isso: Estenda o exemplo para calcular o múltiplo menos comum dos dois números. ▢

O analisador e o construtor de árvores para o pequeno idioma são implementados exatamente como o préprocessador discutido na seção anterior. A gramática é uma extensão da gramática para expressões aritméticas apresentadas anteriormente:

      term:        number | word | '(' sum ')';
      product:     term ('*' term | '/' term)*;
      sum:         product ('+' product | '-' product)*;
      comparison:  sum ('<' sum | '<=' sum
                |  '>' sum | '>=' sum | '<>' sum | '=' sum);
      stmt:        sum | word '=' sum | 'print' sum
          |        'if' comparison 'then' stmt ('else' stmt)?
          |        'while' comparison 'do' stmt
          |        '{' stmt* '}'
          |        'try' stmt 'catch' (word ':')? stmt
          |        'raise' quoted;
      prog:        stmt eof;

A gramática pode ser traduzida em notação monádica assim que a coleção de expressões regulares para a Parser.Factory foi definido:

      Mini.scanner = {
        skip:    /^(\s|\/\/.*|\/\*([^*]|\*+[^\/*])*\*+\/)+/,  // Java comments
        word:    /^[a-zA-Z_][a-zA-Z_0-9]*/,                   // identifiers
        number:  /^[0-9]+/,                                   // integers
        quoted:  /^'[^'\n]*'/,                                // simple strings
        symbol:  /^(<=|>=|<>|.)/,
        eof:     /^$/
      };
      Mini.scanner.word.reserved = [
        'catch', 'do', 'else', 'if', 'print', 'raise', 'then', 'try', 'while'
      ];

Uma vez que a gramática é traduzida, é fácil ver quais classes são necessárias para representar um programa. Todos podem ser gerados usando makeTreeClasses:

      var Mini = {
        // arithmetic
        Add: 1, Div: 1, Mul: 1, Sub: 1,            // left right
        Leaf: 1,                                   // number
        Name: 1,                                   // string
        // comparisons
        Eq: 1, Ge: 1, Gt: 1, Le: 1, Lt: 1, Ne: 1,  // left right
        // statements
        Assign: 1,                                 // string sum
        Block: 1,                                  // stmt+
        Expr: 1,                                   // sum
        If: 1,                                     // comparison stmt stmt?
        Print: 1,                                  // sum
        While: 1,                                  // comparison stmt
        // exception handling
        Raise: 1,                                  // string
        Try: 1                                     // stmt name? stmt
      };

Tente isso: Mude interpret acima para exibir a árvore que representa o algoritmo de Euclides. Execute o exemplo e compare a árvore com o Mini coleção. Desligue o intérprete.

Tente isso: Na página de edição, mude scanner e stmt de modo que um bloco deve ser incluído com palavras como begin e end. Mude o exemplo e teste.

Tente isso: Teste aninhado if afirmações. O que acontece com um "pendurado"" else cláusula? Use a página de edição para Mini e fazer else obrigatório. ▢

O intérprete para o pequeno idioma pode ser implementado com o visitante padrão de design ou adicionando eval métodos para as classes de árvores. Um interpretador mapeia os recursos do idioma de destino para o idioma do host e tem que simular características faltantes; por exemplo, Haskell não suporta tarefas de atribuição e de exceção, ou seja, se um intérprete estiver escrito em Haskell, esses recursos devem ser simulados usando valores monádicos.

O JavaScript suporta tanto a atribuição global quanto o tratamento de exceções, e não há nenhuma necessidade óbvia de simulação. No entanto, os valores monádicos permitem separar o comportamento do host e do alvo. O intérprete aqui discutido usa uma classe monádica Memory que mantém um ambiente de nomes de variáveis atuais e valores acessíveis através de fetch e store métodos à medida que a avaliação prossegue. O ambiente pode ser implementado como uma coleção:

      // collection-based environment.
      Mini.Hash = function () { };

      Mini.Hash.prototype.fetch = function (name) {
        return this[name];
      };

      Mini.Hash.prototype.store = function (name, value) {
        this[name] = value;
        return this;
      };

Todos os métodos de avaliação devem retornar valores monádicos, por exemplo:

      // monad with an environment
      Mini.Memory = Monad.subclass();
      
      with (Mini) with (Memory)
        Leaf.prototype.eval = function () {
          var value = this.content[0];  // number
          return succeed(Number(value));
        };

Os métodos de avaliação para operadores binários podem ser implementados com uma função de fábrica bin que recebe a operação real como parâmetro e cria o método de avaliação com notação monádica, por exemplo:

      Mini.bin = function (op) {       // operation as a function
        return function () {
          var left = this.content[0],  // left operand tree
            right = this.content[1];   // right operand tree
          return (
            {{{
                l <- left.eval();
                r <- right.eval();
                succeed(op(l, r));
            }}});
        };
      };
      
      with (Mini) with (Memory)
        Add.prototype.eval = bin(function (l, r) { return l + r; });

Memory mantém um ambiente com fetch e store operações como estado. O acesso ao estado é combinado com essas operações para implementar referência e atribuição variável:

      with (Mini) with (Memory) {
        Name.prototype.eval = function () {
          var name = this.content[0];  // variable name
          return (
            {{{
                env <- get;
                succeed(env.fetch(name));
            }}});
        };
        
        Assign.prototype.eval = function () {
          var name = this.content[0],  // variable name
            sum = this.content[1];     // expression tree
          return (
            {{{
                value <- sum.eval();
                env <- get;
                put(undefined, env.store(name, value));
            }}});
        };
      }

Uma vez que os valores monádicos são empregados, andThen deve ser usado para a execução de seqüência no nível da instrução. if pode ser mapeado para avaliação condicional; uma falta else parte tem que ser fudged:

      with (Mini) with (Memory)
        If.prototype.eval = function () {
          var c = this.content[0],  // condition tree
            t = this.content[1],    // then tree
            e = this.content[2];    // else tree
          return (
            {{{
                cond <- c.eval();
                cond ? t.eval() : (e ? e.eval() : succeed(undefined));        
            }}});
        };

Uma seqüência de instruções requer um loop que, por sua vez, requer uma implementação recursiva:

      with (Mini) with (Memory)
        Block.prototype.eval = function (_n) {
          var self = this,              // for closure
            n = _n ? _n : 0,            // for tail recursion
            content = this.content[0];  // the statement list
          if (n >= content.length)
            return succeed(undefined);  // completed block has no value
          else
            return (
              {{{
                  content[n].eval();    // sequencing must be by andThen
                  self.eval(n+1);
              }}});
        };

similarmente, enquanto também deve basear-se na recursão:

      with (Mini) with (Memory)
        While.prototype.eval = function () {
          var self = this,              // for closure
            c = this.content[0],        // condition tree
            body = this.content[1];     // body tree
          return (
            {{{
                cc <- c.eval();
                (function () {
                  if (!cc) return succeed(undefined);
                  return (
                    {{{
                        body.eval();
                        self.eval();
                    }}});
                })();      
            }}});
        };

Para um idioma de host como o JavaScript, que possui todos os recursos do idioma alvo, essa abordagem parece muito complicada. No entanto, uma classe monádica pode ser usada para implementar manipulação de exceções de forma muito elegante e com uma escolha de semântica:

Mini.interpret()

Tente isso: Desagrado try e a primeira catch.

Tente isso: Desagrado try e o segundo catch. ▢

O manuseio de exceções é baseado em fail e orElse, ou seja, uma operação como a divisão pode usar fail e causar uma exceção que um pequeno programa de idiomas pode capturar:

      with (Mini) with (Memory)
        Div.prototype.eval = function () {
          var left = this.content[0],  // left operand tree
            right = this.content[1];   // right operand tree
          return (
            {{{
                l <- left.eval();
                r <- right.eval();
                r ? succeed(l / r) : fail('division by zero');
            }}});
          };

Uma falha pode ser capturada com orElse, isto é, com ||| em notação monádica:

      with (Mini) with (Memory)
        Try.prototype.eval = function () {
          var a = this.content[0],  // try body tree
            n = this.content[1],    // catch variable name if any
            b = this.content[2];    // catch body tree
          if (!n)  // no catch variable name
            return (
              {{{
                  a.eval();
              |||
                  b.eval();
              }}});

O try body a é avaliado. Se houver uma falha, o catch body b é avaliado. Ambos usam o mesmo estado inicial (ambiente).

É mesmo possível fazer a mensagem de erro de fail disponível como um valor variável no corpo de captura. Isso é realizado pela re-implementação orElse com um argumento de função ao estilo de andThen:

          function onError (a, b) { // re-implement orElse with a parameter
            return new Memory(
              function (state) {
                var result = a.apply(state);
                return 'fail' in result ? b(result.fail).apply(state) : result;
              }
            );
          }

A alternativa b deve ser embrulhado como uma função para fornecer o alcance do parâmetro para qualquer valor que deve ser passado para a alternativa. Com onError, O tratamento de exceções pode ser implementado para que o manipulador de exceção tenha acesso à mensagem de falha:

          return onError(a.eval(),
            function (value) {
              return (
                {{{
                    env <- get;
                    put(undefined, env.store(n, value));
                    b.eval();
                }}});
            });
        };

O try body a é avaliado incondicionalmente. Se ele falhar, a mensagem de falha é vinculada ao parâmetro value e daí para o nome da variável n no ambiente antes do catch body b é avaliado, isto é, b tem acesso à mensagem de falha com o nome da variável n.

Talvez o aspecto mais interessante desta implementação do tratamento de exceção seja que a interação entre atribuição e controle de exceção pode ser influenciada pela implementação do ambiente.

Mini.interpret()

Este exemplo produz um ambiente final que contém valores para foo, bar, e foobar, ou seja, catch parece ter operado o estado no ponto de raise e não - como deveria ser o caso de uma implementação baseada em mônadas - no estado em try. Esse comportamento é causado por Hash, a implementação do meio ambiente: conforme mostrado acima, catch é implementado usando orElse, ou seja, try e catch Comece com o mesmo ambiente. Contudo, Hash é uma coleção de JavaScript simples, ou seja, todas as alterações são persistentes. A persistência pode ser removida, por exemplo, clonando o ambiente sempre que for alterado (há formas menos dispendiosas):

      Mini.ClonedHash = function () { };

      Mini.ClonedHash.prototype.fetch = function (name) {
        return this[name];
      };

      Mini.ClonedHash.prototype.store = function (name, value) {
        var result = new Mini.ClonedHash();
        for (var n in this)
          if (this.hasOwnProperty(n))
            result[n] = this[n];
        result[name] = value;
        return result;
      };

Tente isso: Mude interpret perto do início desta seção para usar um ClonedHash v ▢

Se o ambiente não for persistente, as atribuições no try O bloco é desfeito se houver uma exceção. Embora a implementação seja cara, o fato de que isso ocorra invisivelmente pode tornar essa variante de uma try declaração bastante atraente, por exemplo, em um contexto de backtracking.

Esta página da Web discute um estilo de programação funcional sugerido pelo Monad e MonadPlus classes de Haskell e introduziu uma notação para facilitar a codificação em JavaScript. O domínio do problema significativo é a análise: o suporte para analisadores monádicos existe para Haskell [15], Python [16], e outras línguas [17].

Esta página da Web descreve a análise monadic LL (n) com JavaScript. Contém a implementação de Monad, uma classe base para classes monadicais de JavaScript que envolvem funções de estado. Subclasses podem ser criadas com o método de classe subclass. Uma subclasse tem o valor monádico compartilhado get para obter o estado atual; os métodos de classe put para criar um valor monádico para armazenar um novo valor e estado, succeed e fail para criar valores monádicos relatando sucesso e falha, e dump para serializar coleções; e os métodos apply para aplicar um valor monádico, e andThen e orElse para criar valores monádicos que aplicam valores monádicos sequencialmente ou como alternativas.

Esta página contém a implementação de Parser, uma Monad subclasse de analisadores monádicos que podem ser combinados para representar LL (n) gramáticas. Parser fornece os combinadores adicionais optional, some, e many para apoiar a representação de gramáticas EBNF. Parser tem uma série de métodos de classe. foldl suporta a montagem associativa à esquerda de uma lista de funções e makeTreeClasses converte certos nomes de propriedade em uma coleção em classes para representar árvores que estão conectadas a dumpTree para serialização. Parser.Factory é uma classe que é construída com uma coleção de expressões regulares e possui métodos para construir analisadores que aceitam entradas que correspondem às expressões regulares.

Esta página contém um pré-processador Converting monadic notation que estende o JavaScript com um notation o que simplifica a especificação de cálculos que envolvem valores monádicos. Em particular, a notação facilita a tradução das gramáticas LL (n) em combinações de Parser valores. Nesta perspectiva, JSM é um gerador de analisador para JavaScript e a notação monádica estende o JavaScript como idioma de entrada para o gerador do analisador. A página da Web contém três exemplos maiores que usam o préprocessador: um construtor de árvore para uma pequena linguagem de programação, um intérprete para as árvores que implementa a recuperação de erros e uma abordagem funcional para atribuição e, finalmente, o próprio préprocessador.

Monad requer apenas funções como valores de primeira ordem, Parser Além disso, beneficia de expressões regulares. A digitação dinâmica é conveniente, mas não é essencial. Portanto, esta página da Web pode servir como um modelo para a implementação rápida de geradores de analisador em outras linguagens de programação onde esses recursos estão disponíveis ou podem ser simulados.

Finalmente, todo o código nesta página da Web pode ser editado e usado de forma interativa. Portanto, os arquivos de suporte desta página da web podem ser usados como uma estrutura para programação interativa e alfabetizada em JavaScript.

code/getStdin.js uma tentativa de ler a fonte da entrada padrão.
code/jsm.js programa de linha de comando para executar o pré-processador
code/mini.js programa de linha de comando para compilar e executar o pequeno idioma
paper/ infrastructure for interactive, literate JavaScript tutorials.
aritmética padrões para expressões aritméticas.
Axiomas exemplos que ilustram os axiomas de mônadas.
Eval avaliar expressões aritméticas.
EvalM avaliar expressões aritméticas (notação monádica).
Expr reconhecer expressões aritméticas.
Expr_badSum reconheça as expressões aritméticas (defeituosas).
JSM pré-processador para converter a notação monádica para JavaScript.
JSM_Tree ilustrar a árvore para a notação obrigatória.
language padrões para uma pequena linguagem de programação.
Mini intérprete para uma linguagem de linguagem de programação pequena.
Monad classe básica abstrata para aulas monádicas.
Parser analisador monádico.
Scanners exemplos que ilustram a análise.
Tree representam expressões aritméticas como árvores.
change crie uma página com todas as alterações atuais ao código original.
[1] Haskell - HaskellWiki, http://haskell.org/ retrieved on June 30, 2008.
[2] The C# Language, http://msdn.microsoft.com/en-us/vcsharp/aa336809.aspx retrieved on June 30, 2008.
[3] Groovy - Home, http://groovy.codehaus.org/ retrieved on June 30, 2008.
[4] Closures for the Java Programming Language, http://javac.info/ retrieved on June 30, 2008.
[5] ECMAScript Language Specification, Edition 3, http://www.mozilla.org/js/language/E262-3.pdf retrieved on June 30, 2008.
[6] Guido van Rossum, Python Reference Manual, http://docs.python.org/ref/ retrieved on June 30, 2008.
[7] Ruby Home Page, http://www2.ruby-lang.org/en/ retrieved on June 30, 2008.
[8] The Scheme Programming Language, http://www-swiss.ai.mit.edu/projects/scheme/ retrieved on June 30, 2008.
[9] Rhino - JavaScript for Java, http://www.mozilla.org/rhino retrieved on June 30, 2008.
[10] SpiderMonkey (JavaScript-C) Engine, http://www.mozilla.org/js/spidermonkey retrieved on June 30, 2008.
[11] Graham Hutton, Programming in Haskell, Cambridge University Press, 2007.
[12] Niklaus Wirth, The Programming Language Pascal (Revised Report), http://www.standardpascal.org/The_Programming_Language_Pascal_1973.pdf retrieved on June 30, 2008.
[13] RFC-Editor Webpage, http://www.rfc-editor.org/ retrieved on June 30, 2008.
[14] Firefox web browser, http://www.mozilla.com/firefox/ retrieved on June 30, 2008.
[15] Daan Leijen, Parsec, a fast combinator parser, http://research.microsoft.com/users/daan/download/parsec/parsec.pdf retrieved on June 30, 2008.
[16] Pysec: Monadic Combinatoric Parsing in Python, http://www.valuedlessons.com/2008/02/pysec-monadic-combinatoric-parsing-in.html retrieved on June 30, 2008. April 1, 2008
[17] Monad (functional programming), Wikipedia, http://en.wikipedia.org/wiki/Monads_in_functional_programming#External_links retrieved on June 30, 2008.