O jeito difícil
Você quer um analisador descendente recursivo .
Para obter precedência, você precisa pensar recursivamente, por exemplo, usando sua string de amostra,
1+11*5
para fazer isso manualmente, você teria que ler o 1
, ver o sinal de mais e iniciar uma nova "sessão" de análise recursiva começando com 11
... e certifique-se de analisar 11 * 5
em seu próprio fator, gerando uma árvore de análise com1 + (11 * 5)
.
Tudo isso parece tão doloroso até mesmo para tentar explicar, especialmente com a impotência adicional de C. Veja, depois de analisar o 11, se o * fosse realmente um + em vez disso, você teria que abandonar a tentativa de criar um termo e, em vez disso, analisar o 11
se como um fator. Minha cabeça já está explodindo. É possível com a estratégia decente recursiva, mas existe uma maneira melhor ...
A maneira fácil (certa)
Se você usa uma ferramenta GPL como o Bison, provavelmente não precisa se preocupar com problemas de licenciamento, já que o código C gerado pelo bison não é coberto pela GPL (IANAL, mas tenho certeza de que as ferramentas GPL não forçam a GPL código / binários gerados; por exemplo, a Apple compila códigos como, digamos, Aperture com GCC e os vende sem ter que colocar o código em GPL).
Baixar Bison (ou algo equivalente, ANTLR, etc.).
Normalmente, há algum código de amostra em que você pode simplesmente executar o bison e obter o código C desejado que demonstra esta calculadora de quatro funções:
http://www.gnu.org/software/bison/manual/html_node/Infix-Calc.html
Observe o código gerado e veja que isso não é tão fácil quanto parece. Além disso, as vantagens de usar uma ferramenta como o Bison são: 1) você aprende algo (especialmente se você ler o livro do Dragão e aprender sobre gramáticas), 2) você evita o NIH tentando reinventar a roda. Com uma ferramenta real de gerador de analisador, você realmente tem esperança de aumentar a escala mais tarde, mostrando a outras pessoas que você conhece que analisadores são o domínio das ferramentas de análise.
Atualizar:
As pessoas aqui ofereceram muitos bons conselhos. Meu único aviso contra pular as ferramentas de análise ou apenas usar o algoritmo Shunting Yard ou um analisador decente recursivo rolado à mão é que pequenas linguagens de brinquedo 1 podem algum dia se transformar em grandes linguagens reais com funções (sin, cos, log) e variáveis, condições e para rotações.
Flex / Bison pode muito bem ser um exagero para um intérprete pequeno e simples, mas um analisador + avaliador único pode causar problemas no futuro quando mudanças precisam ser feitas ou recursos precisam ser adicionados. Sua situação irá variar e você precisará usar seu bom senso; apenas não puna outras pessoas por seus pecados [2] e construa uma ferramenta menos do que adequada.
Minha ferramenta favorita para análise
A melhor ferramenta do mundo para esse trabalho é a biblioteca Parsec (para analisadores decentes recursivos) que vem com a linguagem de programação Haskell. Parece muito com o BNF , ou como alguma ferramenta especializada ou linguagem específica de domínio para análise (código de amostra [3]), mas é na verdade apenas uma biblioteca regular em Haskell, o que significa que compila na mesma etapa de construção que o resto de seu código Haskell, e você pode escrever código Haskell arbitrário e chamá-lo dentro de seu analisador, e você pode misturar e combinar outras bibliotecas no mesmo código . (Incorporar uma linguagem de análise como esta em uma linguagem diferente de Haskell resulta em muitos problemas sintáticos, a propósito. Eu fiz isso em C # e funciona muito bem, mas não é tão bonito e sucinto.)
Notas:
1 Richard Stallman diz, em Por que você não deve usar o Tcl
A principal lição do Emacs é que uma linguagem para extensões não deve ser uma mera "linguagem de extensão". Deve ser uma linguagem de programação real, projetada para escrever e manter programas substanciais. Porque as pessoas vão querer fazer isso!
[2] Sim, estou para sempre marcado por usar essa "linguagem".
Observe também que quando eu enviei esta entrada, a visualização estava correta, mas o analisador menos do que adequado comeu minha tag de fechar âncora no primeiro parágrafo , provando que os analisadores não são algo para se brincar porque se você usar regexes e um deles te hackear provavelmente errará algo sutil e pequeno .
[3] Snippet de um parser Haskell usando Parsec: uma calculadora de quatro funções ampliada com expoentes, parênteses, espaços em branco para multiplicação e constantes (como pi e e).
aexpr = expr `chainl1` toOp
expr = optChainl1 term addop (toScalar 0)
term = factor `chainl1` mulop
factor = sexpr `chainr1` powop
sexpr = parens aexpr
<|> scalar
<|> ident
powop = sym "^" >>= return . (B Pow)
<|> sym "^-" >>= return . (\x y -> B Pow x (B Sub (toScalar 0) y))
toOp = sym "->" >>= return . (B To)
mulop = sym "*" >>= return . (B Mul)
<|> sym "/" >>= return . (B Div)
<|> sym "%" >>= return . (B Mod)
<|> return . (B Mul)
addop = sym "+" >>= return . (B Add)
<|> sym "-" >>= return . (B Sub)
scalar = number >>= return . toScalar
ident = literal >>= return . Lit
parens p = do
lparen
result <- p
rparen
return result