Parser poleceń

VD
  • Rejestracja:ponad 10 lat
  • Ostatnio:10 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:prawie 2 lata
  • 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:10 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:prawie 2 lata
  • 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:około 12 lat
  • Ostatnio:5 dni
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:ponad 9 lat
  • Ostatnio:17 dni
  • 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:10 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:ponad 9 lat
  • Ostatnio:17 dni
  • 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:około 12 lat
  • Ostatnio:5 dni
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.

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.