Trudna droga
Chcesz rekurencyjnego parsera zstępującego .
Aby uzyskać pierwszeństwo, musisz myśleć rekurencyjnie, na przykład używając przykładowego ciągu,
1+11*5
aby zrobić to ręcznie, musiałbyś przeczytać 1
, a następnie zobaczyć plus i rozpocząć zupełnie nową rekurencyjną "sesję" parsowania zaczynając od 11
... i upewnić się, że przeanalizowałeś 11 * 5
swój własny czynnik, uzyskując drzewo parsowania z 1 + (11 * 5)
.
To wszystko wydaje się tak bolesne, że nawet próba wyjaśnienia, szczególnie z dodatkową bezsilnością C. Widzisz, po przeanalizowaniu 11, gdyby * było faktycznie + zamiast tego, musiałbyś porzucić próbę stworzenia terminu i zamiast tego przeanalizować 11
jako czynnik. Moja głowa już eksploduje. Jest to możliwe dzięki rekurencyjnej przyzwoitej strategii, ale jest lepszy sposób ...
Prosty (właściwy) sposób
Jeśli używasz narzędzia GPL, takiego jak Bison, prawdopodobnie nie musisz się martwić problemami licencyjnymi, ponieważ kod C wygenerowany przez bison nie jest objęty GPL (IANAL, ale jestem prawie pewien, że narzędzia GPL nie wymuszają GPL wygenerowany kod / pliki binarne; na przykład Apple kompiluje kod, na przykład Aperture z GCC i sprzedaje go bez konieczności GPL wspomnianego kodu).
Pobierz Bison (lub coś równoważnego, ANTLR itp.).
Zwykle istnieje przykładowy kod, na którym można po prostu uruchomić bison i uzyskać żądany kod C, który demonstruje ten czterofunkcyjny kalkulator:
http://www.gnu.org/software/bison/manual/html_node/Infix-Calc.html
Spójrz na wygenerowany kod i przekonaj się, że nie jest to takie proste, jak się wydaje. Ponadto korzyści wynikające z używania narzędzia takiego jak Bison to: 1) nauczysz się czegoś (zwłaszcza jeśli czytasz książkę o smokach i uczysz się o gramatyce), 2) unikasz próby odkrycia koła przez NIH . Dzięki prawdziwemu narzędziu do generowania parserów masz nadzieję na późniejsze skalowanie, pokazując innym osobom, które znasz, że parsery są domeną narzędzi parsujących.
Aktualizacja:
Ludzie tutaj udzielili wielu rozsądnych rad. Moim jedynym ostrzeżeniem przed pomijaniem narzędzi parsujących lub po prostu korzystaniem z algorytmu Shunting Yard lub ręcznie obracanego rekurencyjnego, przyzwoitego parsera jest to, że małe języki zabawkowe 1 mogą kiedyś przekształcić się w duże, rzeczywiste języki z funkcjami (sin, cos, log) i zmiennymi, warunkami i pętle.
Flex / Bison może być przesadą dla małego, prostego tłumacza, ale jednorazowy parser + ewaluator może powodować problemy, gdy trzeba wprowadzić zmiany lub dodać funkcje. Twoja sytuacja będzie się różnić i będziesz musiał kierować się własnym osądem; po prostu nie karz innych ludzi za swoje grzechy [2] i buduj mniej niż odpowiednie narzędzie.
Moje ulubione narzędzie do parsowania
Najlepszym narzędziem na świecie do tego zadania jest biblioteka Parsec (dla rekurencyjnych porządnych parserów), która jest dostarczana z językiem programowania Haskell. Wygląda bardzo podobnie do BNF lub jakiegoś wyspecjalizowanego narzędzia lub języka specyficznego dla domeny do analizowania (przykładowy kod [3]), ale w rzeczywistości jest to zwykła biblioteka w Haskell, co oznacza, że kompiluje się w tym samym kroku co reszta Twojego kodu Haskell i możesz napisać dowolny kod Haskell i wywołać go w swoim parserze, a także możesz mieszać i dopasowywać inne biblioteki w tym samym kodzie . (Nawiasem mówiąc, osadzenie takiego języka parsującego w języku innym niż Haskell skutkuje mnóstwem syntaktycznego cruft. Zrobiłem to w C # i działa całkiem dobrze, ale nie jest tak ładne i zwięzłe.)
Uwagi:
1 Richard Stallman mówi, w Dlaczego nie powinieneś używać Tcl
Główna lekcja Emacsa jest taka, że język rozszerzeń nie powinien być zwykłym „językiem rozszerzeń”. Powinien to być prawdziwy język programowania, przeznaczony do pisania i utrzymywania istotnych programów. Ponieważ ludzie będą chcieli to zrobić!
[2] Tak, jestem na zawsze przerażony używaniem tego „języka”.
Należy również pamiętać, że kiedy złożyłam tego wpisu, podgląd było poprawne, ale tak to mniej niż odpowiednie parser zjadł moją bliską znacznik zakotwiczenia w akapicie pierwszym , udowadniając, że parser nie są czymś, co należy lekceważyć, ponieważ jeśli używasz regexes i jednorazowe hacki ty prawdopodobnie dostanie coś subtelnego i małego nie tak .
[3] Fragment parsera Haskella używającego Parseka: czterofunkcyjny kalkulator rozszerzony o wykładniki, nawiasy, białe znaki do mnożenia i stałe (jak pi i 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