Parser poleceń

VD
  • Rejestracja:ponad 10 lat
  • Ostatnio:9 miesięcy
  • Postów:72
0

Cześć,

mój problem jest dość prosty, jednak jego implementacja wydaje mi się bardzo złożona lub brakuje mi wiedzy o jakimś istotnym elemencie. Mianowicie chciałem zrobić parser, który zamieniałby polecenie wpisane w konsoli, np:

Kopiuj
test Ala 3

na uruchomienie fukcji z parametrami

Kopiuj
test("Ala", 3);

Wcześniej chciałem rejestrować komendy w mniej więcej taki sposób:

Kopiuj
register_cmd(parser, "test", 2, test);

Czy takie coś jest możliwe i ewentualnie w którym kierunku poszukiwać rozwiązania?

Dzięki za pomoc!
Pozdrawiam

Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
0

Jak najbardziej jest to możliwe; w absolutnie najprostszej wersji:
http://ideone.com/25zAZM ;-) (oczywiście to podejście nie ma żadnego sensu na dłuższą metę)

Do poczytania: mapy, wskaźniki na funkcje, tokenizacja.


0

W bardziej ograniczony sposób to _Generic mógłby rozwiązać kolegi problem. (C11)

Albo jeszcze coś w tym stylu `void job( void(*fun)(), int typ_funkcji, *void data );
zależnie od typu castujemy funkcję na prawidłową i date na prawidłową np. stukture.
tam w nawiasach nie ma voida bo to pointer do funkcji która przyjmuje jakies tam argumenty może ich nie być mogą być.

Przeszło mi też przez myśl va_list + ptr do funkcji.

Same dzikie haki. ;-)

wujnia
nie da się poprawić a zjadełm tylde. void job( void(*fun)(), int typ_funkcji, void *data );
VD
  • Rejestracja:ponad 10 lat
  • Ostatnio:9 miesięcy
  • Postów:72
0

Dzięki za odpowiedzi, ale właśnie szukam takich dzikich haków, a nie trywialnych rozwiązań :D Marzy mi się, żeby możliwe było zdefiniowanie nowej komendy z pliku tekstowego, która będzie uruchamiała istniejącą funkcję i pobierała odpowiednie argumenty. Też myślałem, żeby kombinować coś z va_list, ale jak potem argumenty przekazać odpowiednio do docelowej funkcji (np: int test(int, char*, float))?

wujnia
w stylu printf np. "i s f"
VD
wiem, że takie funkcje jak printf tworzy się przez va_list. Chciałbym przerobić np łancuch tekstowy "test 3 2 ala" na wywołanie funkcji
wujnia
Jak kolega napisze to się do tego odniosę. Pozdrawiam. Wczytujesz zmienne i sprawdzasz co i jak.
0

Może zrób obiekt kompilatora, który w locie sparsuje polecenie na bajtkod, który wyegzekujesz.

W pythonie jest taki mechanizm jak getattr, który po podaniu obiektu i "nazwyfunkcji", przeszukuje przestrzeń nazw obiektu podanego i zwraca tą metodę, którą potem egzekujesz np. z jednym argumentem **args, z kolei tylko dana funkcja wie ile parametrów potrzebuje, a jak zabraknie to wywalasz wyjątek z komunikatem, będzie to w miarę polimorficzne.

Patryk27
Moderator
  • Rejestracja:ponad 17 lat
  • Ostatnio:ponad rok
  • Lokalizacja:Wrocław
  • Postów:13042
0

Tak, bajtkod i od razu jitowanie na x86 w locie zróbmy. Autorowi chodzi (póki co) wyłącznie o przetwarzanie prostych poleceń - czas na parsery i kompilatory przyjdzie potem :P


edytowany 1x, ostatnio: Patryk27
spartanPAGE
  • Rejestracja:prawie 12 lat
  • Ostatnio:około 5 godzin
4

Spróbuj coś takiego:

Kopiuj
#include <iostream>
#include <functional>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
#include <unordered_map>
#include <iterator>
using namespace std;

using arguments = vector<string>;
using command = function<void(arguments const &)>;
using commands = unordered_map<string, command>;

vector<string> str_to_tokens(string const &src) {
	istringstream stream(src);
	return {
		istream_iterator<string>{stream},
		istream_iterator<string>{}
	};
}

void print(arguments const &args) {
	for(auto &&arg: args) {
		cout << arg << " ";
	}
	cout << endl;
}


int main() {
	commands cmds = {
		{"print", print}
	};
	
	string input;
	while(getline(cin, input)) {
		if(input.empty()) {
			continue;
		}
		auto tokens = str_to_tokens(input);
		auto cmd = tokens[0];
		cmds.at(cmd)(arguments{begin(tokens)+1, end(tokens)});
	}
	return 0;
}

Proste jak budowa cepa, a powinno wystarczyć na twoje potrzeby.
http://ideone.com/bOrLAL
**input: **
print Ala ma dziwnego kota.
**output: **
Ala ma dziwnego kota.

Z takim niewydajnym podejściem możesz szybko stać się dość kreatywny, patrz np.

Kopiuj
#include <iostream>
#include <functional>
#include <vector>
#include <string>
#include <sstream>
#include <algorithm>
#include <unordered_map>
#include <iterator>
#include <cassert>
using namespace std;

//boring helper functions
inline std::string trim(const std::string &s) {
   auto  wsfront=std::find_if_not(s.begin(),s.end(),[](int c){return std::isspace(c);});
   return std::string(wsfront,std::find_if_not(s.rbegin(),std::string::const_reverse_iterator(wsfront),[](int c){return std::isspace(c);}).base());
}

vector<string> str_to_tokens(string const &src) {
	istringstream stream(src);
	return {
		istream_iterator<string>{stream},
		istream_iterator<string>{}
	};
}

string join(vector<string> const &vec, string const &delim) {
    stringstream res;
    copy(vec.begin(), vec.end(), ostream_iterator<string>(res, delim.c_str()));
    return res.str();
}

//scaffolding
using arguments = vector<string>;
using command = function<void(arguments const &)>;
using commands = unordered_map<string, command>;
using middleware = function<void(arguments &)>;
using middlewares = vector<middleware>;

//bullshit that matters
void print(arguments const &args) {
	for(auto &&arg: args) {
		cout << arg << " ";
	}
	cout << endl;
}

struct data_store {
	using vname = string;
	using vval = string;
	unordered_map<vname, vval> vars;
	
	void store(vname const &name, vval const &val) {
		vars[name] = val;
	}
	
	void store_cmd(arguments const &args) {
		enum { VNAME, VEQSIGN, VVAL_BEG };
		assert(args[VEQSIGN] == "=");
		auto val = join({begin(args)+VVAL_BEG, end(args)}, " ");
		store(args[VNAME], trim(val));
	}
	
	vval get(vname const &name) const {
		return vars.at(name);
	}
};

void replace_vars_with_values(data_store const &store, string const &var_prefix, arguments &args) {
	for(auto &arg: args) {
		if(arg.rfind(var_prefix, 0) == 0) {
			arg = store.get(string{begin(arg)+var_prefix.size(), end(arg)});
		}
	}
}

void apply_middlewares(middlewares const &mdws, arguments &args) {
	for(auto &&mdw: mdws) {
		mdw(args);
	}
}

int main() {
	string var_prefix = "$";
	data_store dstore;
	
	middlewares mdws = {
		[&](arguments &args) { return replace_vars_with_values(dstore, var_prefix, args); }
	};
	
	commands cmds = {
		{"print", print},
		{"var", [&dstore](arguments const &args) { dstore.store_cmd(args); }}
	};
	
	string input;
	while(getline(cin, input)) {
		if(input.empty()) {
			continue;
		}
		auto tokens = str_to_tokens(input);
		apply_middlewares(mdws, tokens);
		
		enum { FUNC_POS, ARGS_START };
		
		cmds.at(tokens[FUNC_POS])(arguments{begin(tokens)+ARGS_START, end(tokens)});
	}
	return 0;
}

https://ideone.com/kdaGqy
input:

Kopiuj
var person = Ala
var state = ma dziwnego kota.
var f = print

$f $person $state

output:
Ala ma dziwnego kota.

edytowany 4x, ostatnio: spartanPAGE
Azarien
boring helper czy nie boring, od wcinania kodu to dyspensy nie daje :-)
Azarien
a tak przy okazji, czy nie wystarczyłoby samo isspace zamiast lambdy? (jako wskaźnik na funkcję)
spartanPAGE
@Azarien wziąłem gotowca z http://stackoverflow.com/questions/216823/whats-the-best-way-to-trim-stdstring ;P a tak przy okazji, czy nie wystarczyłoby samo isspace zamiast lambdy? dałoby radę z ::isspace
nalik
  • Rejestracja:około 9 lat
  • Ostatnio:prawie 2 lata
  • Postów:1039
0

Dodam tylko, że jeżeli chcesz mapować polecenia na funkcje już istniejące w programie podczas wykonania tegoż programu, bez predefiniowania wszystkich dostępnych funkcji już podczas kompilacji, to powinieneś skorzystać z biblioteki systemowej do obsługi kontenerów kodu lub do ładowania dynamicznego. Tzn. możesz użyć biblioteki libelf (zakładając, że korzystasz z linuxa), otworzyć samego siebie, przeiterować się przez wszystkie sekcje i tablice symboli wewnątrz tej sekcji i znaleźć odpowiednią funkcję. Łatwiejszym sposobem jest użycie dlopen aby dostać wskaźnik na dowolną funkcję w bibliotece współdzielonej albo programie aktualnie wykonywanym ( http://man7.org/linux/man-pages/man3/dlopen.3.html ). Pamiętaj jednak, że w przypadku c++ nazwy będą zamanglowane (extern "C" pomoże Ci uzyskać nazwy funkcji w stylu C).

Pozostaje problem przekazania parametrów do funkcji i przeżutowania wskaźnika na odpowiedni typ (zakładam, że własnego kodu asm do wołania funkcji nie zamierzasz pisać). Nie wiem na ile rozumiesz ABI i wygenerowany kod, ale upraszczając, funkcje napisane w C/C++ mają przypisane przejmowane i zwracane typy podczas kompilacji. Podczas uruchomienia programu, o ile to nie jest wersja debugowa, tej informacji już nie ma. Ewentualnym rozwiązaniem jest założenie, że wszystkie funkcje będą przyjmowały taki sam "opisowy" typ parametru, z którym bedą w stanie sobie poradzić. Takie dynamiczne typowanie dla ubogich ;).

Oczywiście można opisać wszystkie funkcje, zdefiniować sposoby ich wołania z poziomu parsera już podczas pisania programu. Ale w takim wypadku w ogóle nie potrzebujesz tego dynamicznego pobierania wskaźników na funckje. Zależy, czy chcesz mieć dynamiczne wołanie funkcji "natywnych" czy interpreter predefiniowanych poleceń.

P.S.
Inne pojęcie, które ewentualnie może Cię zainteresować to FFI ( https://en.wikipedia.org/wiki/Foreign_function_interface ).

edytowany 9x, ostatnio: nalik
Azarien
ja myślę że próba zrobienia „refleksji” na binarkach z C++ to trochę overkill.
nalik
Też tak myślę.
VD
  • Rejestracja:ponad 10 lat
  • Ostatnio:9 miesięcy
  • Postów:72
0

Widzę, że pojawiło się kilka pytań. Korzystam z linuxa i nie zależy mi jakoś specjalnie, żeby program działał na Windowsie. Dodatkowo zapomniałem zaznaczyć, że jestem zmuszony do używania ANSI C89, także mechanizmy wprowadzone później lub w C++ odpadają.

Wielki Pomidor napisał(a):

Może zrób obiekt kompilatora, który w locie sparsuje polecenie na bajtkod, który wyegzekujesz.

Hah

spartanPAGE napisał(a):

Spróbuj coś takiego

Wygląda naprawdę fajnie, widzę tylko, że co druga konstrukcja nie jest dostępna w moim "ukochanym" C89 :D Jeśli mogę się spytać - czy warto studiować ten kod i spróbować go przełożyć na C?

nalik napisał(a):

Pozostaje problem przekazania parametrów do funkcji i przeżutowania wskaźnika na odpowiedni typ

Można przyjać, że wszystkie funkcje zwracają typ int, jednak parametry to już jest problem

Oczywiście można opisać wszystkie funkcje, zdefiniować sposoby ich wołania z poziomu parsera już podczas pisania programu.

I chyba wgłębiając się w temat tak zrobię. Miałem nadzieję, że problem jest dosyć powszechny i istnieją gotowe rozwiązania. Jak w takim razie są pisane programy, które pozwalają na wpisywanie poleceń? (dla przykładu gnuplot) Porównują pierwszy łańcuch, a potem mają zakodowane ileś scanf, żeby na koniec przekazać argumenty do funkcji? I tak dla każdego polecenia?

Wielkie dzięki za pomoc!

edytowany 1x, ostatnio: VeloxDigitis
nalik
  • Rejestracja:około 9 lat
  • Ostatnio:prawie 2 lata
  • Postów:1039
0

Nie wiem jak działa gnuplot, ale podejrzewam, że jednak parsuje kod, tworzy AST i to intepretuje albo generuje z AST bajtkod czy też inną formę pośrednią. Parser można napisać z ręki, ale można i go w większości wygenerować (choćby najbardziej znane narzędzia jak flex, bison, yacc, ...).

edytowany 5x, ostatnio: nalik
spartanPAGE
  • Rejestracja:prawie 12 lat
  • Ostatnio:około 5 godzin
0

Wygląda naprawdę fajnie, widzę tylko, że co druga konstrukcja nie jest dostępna w moim "ukochanym" C89 :D Jeśli mogę się spytać - czy warto studiować ten kod i spróbować go przełożyć na C?

Przełożenie tego na C nie powinno być problemem. Tutaj krótko jak działa pierwszy kod:

Kopiuj
Trzymamy zbiór funkcji o ściśle określonej sygnaturze - dzięki temu możemy wrzucić je do kontenera i doczepić do nich nazwę.
W C wystarczy Ci coś takiego: 

typedef int (*func_t)(int argc, const char * const argv[]);
typedef struct { const char *name; func_pointer func; } named_func_t;

Później zostaje Ci wczytać linię, rozdzielić je, znaleźć funkcję o nazwie pierwszego argumentu i przekazać resztę jako faktyczne argumenty.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)