Programming Language Parser (in Java) - Quale sarebbe un'alternativa di design migliore per un caso speciale?

5

Sfondo

Attualmente sto progettando il mio linguaggio di programmazione come progetto di ricerca. Ho fatto la maggior parte della grammatica e scritto come grammatica context-free, e dovrebbe funzionare così com'è. - Ora sto lavorando al compilatore attuale che dovrebbe tradurre la lingua in x86 binary assembly code , in particolare, sto lavorando su parser (il front-end).

La sintassi del linguaggio è, per la maggior parte, molto simile a Java / C / C ++. Il parser, che costruisce una rappresentazione intermedia fuori dal codice sorgente, funziona come segue:

La grammatica è costruita come un grande albero in cui il vero codice sorgente determina solo le foglie; Ogni variabile sintattica (o non terminale) ha una propria classe e ognuna di queste classi ha un metodo static get(byte[] source, int offset) che restituisce una nuova foglia (nodo) o null se la sintassi del codice sorgente non si adatta a questa struttura non terminale.

Sto utilizzando una variazione di predictive parsing , a proposito.

Per il non terminale DataType , ho scelto la seguente struttura grammaticale:

DataType:
    PrimitiveDataType
 |  ArrayDataType
 |  ComplexDataType

ArrayDataType:
    DataType []

Ho detto che questo linguaggio è orientato agli oggetti?

Quindi il problema qui è che quando viene chiamato il metodo DataType di get , prima controlla se il seguente è un tipo di dati primitivo, viene chiamato il metodo PrimitiveDataType get . Supponendo di disporre di un array, restituire null , quindi continua a verificare se è un ArrayDataType , chiamando il metodo get .

Problema

Gli array possono essere creati con qualsiasi tipo di dati, inclusi gli array stessi (che apparirebbero come Type[][] ). Quindi, cosa farebbe il metodo ArrayDataType di get è di nuovo chiamare il metodo DataType get per capire di che tipo è la matrice. Sfortunatamente, è qui che la mia implementazione del parser fallirebbe perché questo comportamento si traduce in un loop!

Domanda

Ci sarebbero alternative di progettazione buone / migliori a questo?

    
posta Kierrow 23.07.2012 - 17:56
fonte

4 risposte

2

Il parser predittivo selezionato (LL (k)) significa che dovrai risolvere i problemi di ricorsione a sinistra. Algoritmo per la risoluzione delle ricorsioni dirette e indirette è chiaramente descritto su wikipedia:

link

Alcune informazioni possono essere trovate nei post qui su StackOverflow:

link link link

In linguaggio umano (non scientifico :) "problema di ricorsione a sinistra" significa che non puoi ricorrere all'infinito in ricorsione con non-terminale (A - > Ab) ancora e ancora. Ad un certo punto devi alimentare l'algoritmo del parser con un terminale per interrompere un loop.

In BNF questo potrebbe essere:

Problema di ricorsione:

NT: NT T
NT: T

Una soluzione:

NT: T NT2
NT2: T NT2
NT2: 

Per la tua grammatica potrebbe essere:

DataType:
    PrimitiveDataType ArrayDimensions
 |  ComplexDataType ArrayDimensions

ArrayDimensions:
    [] ArrayDimensions
 |

Se il tuo generatore di parser non consente produzioni vuote e / o se desideri elaborare separatamente i tipi di array, prova qualcosa del tipo:

DataType:
    DataTypeName
 |  ArrayDataType

ArrayDataType:
    DataTypeName ArrayDimensions

DataTypeName:
    PrimitiveDataType
 |  ComplexDataType

ArrayDimensions:
    []
 |  [] ArrayDimensions
    
risposta data 23.07.2012 - 20:52
fonte
2

Il problema è che l'utilizzo di un LL (1) parser può portare alla ricorsione a sinistra . Puoi evitarlo passando a un parser LR o usando questa grammatica per evitare la ricorsione:

DataType:
    PrimitiveDataType ArrayDataType
 |  ComplexDataType ArrayDataType

ArrayDataType:
    [] ArrayDataType
 |  (empty)

Se l'analisi non fa parte del tuo progetto, cerca solo una libreria / generatore di analisi, probabilmente ce ne sono molti là fuori.

    
risposta data 23.07.2012 - 19:31
fonte
0

La chiave qui è semplicemente usare un parser LR piuttosto che LL. C'è una ragione per cui virtualmente nessuna implementazione usa effettivamente LL come è stato detto, ed è perché in realtà non riesce a riconoscere molte grammatiche particolarmente utili.

    
risposta data 23.07.2012 - 20:38
fonte
0

Questo problema che stai affrontando sembra con qualcosa che ho già affrontato. In tal caso, dovrebbe essere implementato nella classe Syntatic Analyzer e sì, genererà un albero, sarebbe effettivamente ricorsivo.

E.g.: nel mio caso, ho dovuto implementare un modo in cui la funzione avrebbe ricevuto un elenco di argomenti, quindi la mia regola di grammatica per l'elenco di argomenti sarebbe simile a questa:

<ArgLi> ::= <Arg> <ArgLi> |

dove:

<Arg> ::= '(' <Type> Identifier ')'

<ArgLi> è chiamato recursivelly

<Type> ::= 'int' | 'real' | 'str'

Identifier = {ID Head}{ID Tail}*

Quindi, per implementare questa funzione, il comando ha dei delimitatori, quindi, anche se entra in un ciclo, controllerà la regola e determinerà l'azione in base al token corrente che sto cercando.

Ad esempio, supponiamo di voler scrivere questo:

( def main [ ( int a1 ) ( str a2 )  ]

...

)

Il codice che segue la mia lingua per capire questo è:

/**
 * <Def> ::= '(' 'def' Identifier '[' <ArgLi> ']' <CommandLi> ')'
 */
public Node Def() {

    Node node = new Node(NodeIdentifier.DEF);
    if (token.getImage().equals("(")) {
        node.addSon(new Node(NodeIdentifier.TOKEN, token));
        token = readToken();
        if (token.getImage().equals("def")) {
            node.addSon(new Node(NodeIdentifier.TOKEN, token));
            token = readToken();
            if (token._getClass().equals("ID")
                    || token.getImage().equals("main")) {
                node.addSon(new Node(NodeIdentifier.TOKEN, token));
                token = readToken();
                if (token.getImage().equals("[")) {
                    node.addSon(new Node(NodeIdentifier.TOKEN, token));
                    token = readToken();
                    node.addSon(argLi());
                    if (token.getImage().equals("]")) {
                        node.addSon(new Node(NodeIdentifier.TOKEN, token));
                        token = readToken();
                        node.addSon(commandLi());
                        if (token.getImage().equals(")")) {
                            node.addSon(new Node(NodeIdentifier.TOKEN,
                                    token));
                            token = readToken();
                        } else {
                            errors.add("Error: token ')' not recognized. line: "
                                    + token.getLine());
                        }
                    } else {
                        errors.add("Error: token ']' not recognized. line: "
                                + token.getLine());
                    }
                } else {
                    errors.add("Error: token '[' not recognized. line: "
                            + token.getLine());
                }
            } else {
                errors.add("Error: token 'ID' not recognized. line: "
                        + token.getLine());
            }
        } else {
            errors.add("Error: token 'def' not recognized. line: "
                    + token.getLine());
        }
    } else {
        errors.add("Error: token '(' not recognized. line: "
                + token.getLine());
    }
    return node;
}

/**
 * <ArgLi> ::= <Arg> <ArgLi> |
 */
public Node argLi() {

    Node node = new Node(NodeIdentifier.ARGLI);
    if (token.getImage().equals("(")) {
        node.addSon(arg());
        node.addSon(argLi());
    }
    return node;
}

/**
 * <Arg> ::= '(' <Type> Identifier ')'
 */
public Node arg() {
    Node node = new Node(NodeIdentifier.ARG);
    if (token.getImage().equals("(")) {
        node.addSon(new Node(NodeIdentifier.TOKEN, token));
        token = readToken();
        node.addSon(type());
        if (token._getClass().equals("ID")) {
            node.addSon(new Node(NodeIdentifier.TOKEN, token));
            token = readToken();
            if (token.getImage().equals(")")) {
                node.addSon(new Node(NodeIdentifier.TOKEN, token));
                token = readToken();
            } else {
                errors.add("Error: token ')' not recognized. line: "
                        + token.getLine());
            }
        } else {
            errors.add("Error: token 'ID' not recognized. line: "
                    + token.getLine());
        }
    } else {
        errors.add("Error: token '(' not recognized. line: "
                + token.getLine());
    }

    return node;
}

Come puoi vedere i metodi arg() e argLi() sono chiamati recursivelly, ed è così che l'ho implementato per evitare loop infiniti in modo efficiente.

Sono abbastanza sicuro che questa tecnica ti possa aiutare con il tuo problema.

    
risposta data 23.07.2012 - 18:08
fonte

Leggi altre domande sui tag