Chcę stworzyć prostą, sprawdzoną koncepcję aplikacji (REPL), która pobiera liczbę, a następnie przetwarza polecenia na tym numerze.
Przykład: zaczynam od 1. Następnie piszę „ add 2
”, daje mi 3. Następnie piszę „ multiply 7
”, daje mi 21. Następnie chcę wiedzieć, czy jest liczbą pierwszą, więc piszę „ is prime
” (na bieżącym numerze - 21), daje mi to fałsz. „ is odd
” dałoby mi prawdę. I tak dalej.
Teraz, w przypadku prostej aplikacji z kilkoma poleceniami, nawet proste switch
byłoby w stanie przetworzyć polecenia. Ale jeśli chcę rozszerzalności, jak powinienem wdrożyć tę funkcjonalność? Czy używam wzorca polecenia? Czy zbuduję prosty parser / interpreter dla języka? Co jeśli chcę bardziej złożone polecenia, takie jak „ multiply 5 until >200
”? Jaki byłby łatwy sposób na rozszerzenie go (dodanie nowych poleceń) bez ponownej kompilacji?
Edycja: aby wyjaśnić kilka rzeczy, moim ostatecznym celem nie byłoby zrobienie czegoś podobnego do WolframAlpha, ale raczej procesor listy (liczb). Ale najpierw chcę zacząć powoli (od pojedynczych liczb).
Mam na myśli coś podobnego do tego, w jaki sposób Haskell mógłby przetwarzać listy, ale bardzo prosta wersja. Zastanawiam się, czy wystarczyłoby coś takiego jak wzorzec poleceń (lub odpowiednik), czy też muszę stworzyć nowy mini-język i parser, aby osiągnąć moje cele?
Edit2: Dzięki za wszystkie odpowiedzi, wszystkie były dla mnie bardzo pomocne, ale Emmad Kareem pomógł mi najbardziej, więc wybiorę to jako odpowiedź. Dzięki jeszcze raz!
źródło
Odpowiedzi:
To brzmi jak tłumacz. Wygląda na to, że martwisz się bardziej implementacją niż szczegółową funkcjonalnością (tutaj tylko zgaduję). Przedłużony projekt nie jest trywialnym zadaniem. Upewnij się, że dokładnie przestudiowałeś zakres, ponieważ wymaga to podejścia inżynieryjnego, a nie podejścia doraźnego w celu uzyskania niezawodnego produktu, a nie produktu z 1000 poprawkami, który działa tylko czasami.
Wybierz składnię i przygotuj się do jej przeanalizowania i wykonania niezbędnych kontroli składni. Ten link może ci w tym pomóc: Utwórz własny parser .
Spójrz na: ten temat, ponieważ dotyczy różnych aspektów pracy, są też dobre linki, które mogą ci pomóc (szczególnie odpowiedź RMK) .: Tworzenie tłumacza języka . Możesz zobaczyć przykład ładnie wyglądającego projektu, który jest nieco podobny w: Ultimate Programmable Scientific Calculator . Możesz znaleźć kod źródłowy i działający program dla interpretera wiersza poleceń C # tutaj Command-Line-Implementation-of-C # -Made-for-Teaching . Używanie kompilatora do wykonywania skomplikowanych zadań, takich jak parsowanie i pisanie zmiennych itp., Może być sprytnym sposobem na uniknięcie złożoności pisania tego wszystkiego samemu. Istnieje również opcja Mono, która zapewnia funkcję powłoki charp, którą możesz rzucić okiem: CsharpRepl .
źródło
Jeśli nie jesteś specjalnie zainteresowany napisaniem samego parsera dla siebie, proponuję rzucić okiem na jedną z ram generatora parsera. W przypadku C masz YACC lub Bison , ale powinny istnieć inne alternatywy dla innych języków, jeśli wolisz.
Eliminuje to złożoność analizy złożonych gramatyk i pozwala skoncentrować się na zadaniu, które chcesz wykonać. Oczywiście może to być przesada w gramatyce, którą sugerujesz w pytaniu, ale ponieważ wspominasz o możliwości rozszerzenia na bardziej złożoną gramatykę, warto przynajmniej zainspirować się tymi ramami.
źródło
To, co opisujesz, jest bardzo zbliżone do języka stosu .
Na przykład w Factor to, co opisujesz , byłoby zrobione
Lub możesz zdefiniować własne słowa, a następnie użyć ich, na przykład
Dzięki tym definicjom staje się powyższy przykład
Zwykle języki stosu są łatwe do przeanalizowania, ponieważ używają pojedynczych słów, które są oddzielone spacjami. Sugeruję, abyś spojrzał na Factor - może być dokładnie taki, jak chcesz. Powinno być łatwo zdefiniować słowa, które wykonują potrzebne przetwarzanie.
EDYCJA : Jeśli naprawdę chcesz zaprojektować podobny język, sugeruję, aby i tak grać z jednym z nich. Analiza języka stosu jest trywialna - dzielisz się na puste miejsca, a naiwna implementacja przetwarzania jest łatwa: wystarczy zadbać o to, co dzieje się na stosie.
źródło
Nie powinieneś Rozszerzalność tworzy całą złożoność przy bardzo niewielkim zysku. To powiedziawszy, musisz zapewnić hak do istniejącego stanu. Sposób na sprawdzenie stanu, zmodyfikowanie stanu i zapewnienie mechanizmu zwracania innych wyników (drukowanie na ekran). Będziesz potrzebował sposobu, aby kod podstawowy odkrywał moduły, ładował je i wysyłał do nich polecenia.
Możesz, ale prawdopodobnie nie jest to właściwe.
Nie zamierzasz wziąć całego wejścia i wysłać go do przetworzenia, ale zamiast tego przeanalizować dane wejściowe, wysłać do właściwego modułu obsługi i pozwolić mu wykonać swoją pracę. Polecenie nie różni się w tej komunikacji; więc nie ma wzorca poleceń.
Będziesz potrzebował czegoś, aby poradzić sobie z dzieleniem danych wejściowych na tokeny. W przypadku rozwiązania rozszerzalnego prawdopodobnie nie zrobisz nic więcej. W przypadku dobrze zdefiniowanego rozwiązania posiadanie pełnego drzewa analizy zapewni lepszą wydajność, obsługę błędów i możliwość debugowania.
Być może powinieneś zajrzeć do języka LISt Processing . Zestawienie kodu i danych powinno dobrze pasować do tego, co opisujesz.
źródło