Fastnat i tankarna

C, C++, Pascal, Assembly, Raspberry, Java, Matlab, Python, BASIC, SQL, PHP, etc.
Användarvisningsbild
Icecap
Inlägg: 26151
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Fastnat i tankarna

Inlägg av Icecap »

Jag har ju en funktion som gör att jag programledes i en µC kan "känna igen" ord och förstå kommandon i klar text.

Det fungerar skitbra - förutom idag då jag krånglade med det och mina uträkningar sket sig.

Såhär gör jag: (litet exempel)
const BYTE Comm_Text_Get[] = "Get";
const BYTE Comm_Text_Set[] = "Set";
const BYTE Comm_Text_Time[] = "Time";
const BYTE Comm_Text_Date[] = "Date";

Detta är de ord som kommandon kan styckas ihop av.

Sedan ska jag kunde indexera i dessa så att rutinen kan scanna igenom kommandosträngen och jämföra med dessa kommandoord. Alltså måste jag göra en tabell och för att få den till att bli en förutsägbar storlek blir den en tabell med BYTE*:
const BYTE* Comm_Texts[] = {Comm_Text_Get, Comm_Text_Set, Comm_Text_Time, Comm_Text_Date};

Jag behöver veta storleken på tabellen:
#define Comm_Texts_Size (sizeof(Comm_Texts) / sizeof(BYTE*))

Och nu kommer kruxet som ställer till det i mina tankar. Kommandon som ska kännas igen ska ju definieras. Och då kommer problemet:
enum {Index_Get, Index_Set, Index_Time, Index_Date};

#define Command_Get_Time ((Index_Get * Comm_Texts_Size) + Index_Time)
#define Command_Set_Time ((Index_Set * Comm_Texts_Size) + Index_Time)
#define Command_Get_Date ((Index_Get * Comm_Texts_Size) + Index_Date)
#define Command_Set_Date ((Index_Set * Comm_Texts_Size) + Index_Date)

Min rutin som känner igen kommandoorden tar dom steg för steg och räknar på samma sätt vilket betyder att när jag har de värden som motsvarar giltiga kommandon kommer en switch() att ta hand om dom.

Men problemet som kan uppstå när man börjar få lite olika ord är att enum{Index_???} och Comm_Texts[] kommer i otakt och då passar väldigt få värden.

Så är det någon som har en bra idé om hur jag kan "bygga upp" Index_??? utan att ha en separat tabell som ska vara synkroniserat med en enum-listan ville jag bli glad.

Denna gången löste jag det vid att ta data i Comm_Texts[] och kopiera det in i enum{} och göra en snabb search-&-replace - men detta sätt är ju FEL! (Läs "En man som heter Ove" för att förstå).
Användarvisningsbild
baron3d
EF Sponsor
Inlägg: 1339
Blev medlem: 1 oktober 2005, 23:58:43
Ort: Torestorp

Re: Fastnat i tankarna

Inlägg av baron3d »

Så här skulle jag göra:

Kod: Markera allt

struct { 
   const BYTE *s;
   const BYTE  tag;
} kom_struct;

kom_struct tabell[] = {
   "Get", 1,
   "Set", 2,

};
Reserverar mig för syntaxfel;
Användarvisningsbild
Icecap
Inlägg: 26151
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Re: Fastnat i tankarna

Inlägg av Icecap »

baron3d: Definitivt ett sätt - men om man manuellt klantar sig med fortlöpande numrering är man ju lika klippt för det. Men jag kan hålla med om att det är enklare att överskåda!
Användarvisningsbild
Jan Almqvist
Inlägg: 1581
Blev medlem: 1 oktober 2013, 20:48:26
Ort: Orust

Re: Fastnat i tankarna

Inlägg av Jan Almqvist »

Jag skulle föreslå en riktig lexikalanalysator som tolkar dina "Get", "Set" och övriga indata plus en enklare parser.

Lätt att göra och mer generellt.
Användarvisningsbild
Icecap
Inlägg: 26151
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Re: Fastnat i tankarna

Inlägg av Icecap »

Jan Almqvist: Nu är jag osäker på vad du menar med "lexikalanalysator" men jag HAR en rutin som hittar kommandon ord för ord så att jag kan stycka ihop kommandon som jag vill. I detta fall har jag 29 olika ord som jag kombinerar till 52 olika kommandosträngar (och det blir nog fler eftersom).

En googling ger att en "lexikalanalysator" bara kontrollerar att texten uppfyller en viss struktur, typ XML eller liknande och jag ser faktisk inte vad det skulle ge av funktion. Kommandon ger jag via ett terminalprogram och tangentbordet och att börja med en struktur där är bara att glömma.

Om jag skriver "set intensity low xxx" (där xxx är ett värde) dekoder min rutin "Set", "Intensity" och "Low". När den kombination uppnår rätt värde exekveras <case Command_Set_Intensity_Low:> och värdet "xxx" kollas för tillåtna värden innan det används.

Och det fungerar perfekt - fram till det blir osynkroniserat mellan index och faktisk räkföljd.

Så det svåra är att se till att autonumreringen passar med verkligheten.
Användarvisningsbild
Jan Almqvist
Inlägg: 1581
Blev medlem: 1 oktober 2013, 20:48:26
Ort: Orust

Re: Fastnat i tankarna

Inlägg av Jan Almqvist »

om du bygger upp det med en lexikalanalysator och en parser så kommer lexikalanalysatorn att returnera olika "lexeme" eller "token" när den anropas från parsern, lämpligen ett lexeme eller token i taget.

för dina 29 ord behövs lika många lexemes/tokens samt ett lexeme/token som betyder numeriskt värde. kanske också ett lexeme eller token för radslut. ditt lexeme/token för värde måste innehålla värdet också.
zealotry
Inlägg: 918
Blev medlem: 9 oktober 2004, 22:28:40
Ort: Västerås

Re: Fastnat i tankarna

Inlägg av zealotry »

Jag hade nog byggt ett länkat träd. Fyll trädet med alla giltiga kombinationer. Sen när du ska parsea är det bara att vandra ner i trädet, är det en giltig kombination så hittar du ett löv. Det kan peka på den funktion/case som ska köras för det kommandot.
johano
Inlägg: 1943
Blev medlem: 22 januari 2008, 10:07:45
Ort: Stockholm

Re: Fastnat i tankarna

Inlägg av johano »

Kolla in MAP-makrot, https://github.com/swansontec/map-macro

Med det skulle du kunna skriva nåt sånthär:

Kod: Markera allt

#include "map.h"

#define STRING(x) #x,
#define INDEX(x) Index_##x,

#define COMMANDS(...) \
	enum { MAP(INDEX, __VA_ARGS__) }; \
	const char* Comm_Texts[]={ MAP(STRING, __VA_ARGS__) };

COMMANDS(Get,Set,Date,Time,Interval,Poll,Status,Machine,Effort)

int main(int argc, char**argv)
{
	return 0;
}
Och, kompilerat med GCC -E får jag följande preprocessade .c-fil:

Kod: Markera allt

enum { Index_Get, Index_Set, Index_Date, Index_Time, Index_Interval, Index_Poll, Index_Status, Index_Machine, Index_Effort, };
const char* Comm_Texts[]={ "Get", "Set", "Date", "Time", "Interval", "Poll", "Status", "Machine", "Effort", };

int main(int argc, char**argv)
{
 return 0;
}
/johan
Användarvisningsbild
baron3d
EF Sponsor
Inlägg: 1339
Blev medlem: 1 oktober 2005, 23:58:43
Ort: Torestorp

Re: Fastnat i tankarna

Inlägg av baron3d »

En liknande variant som jag använder för flerspråksstöd:

Kod: Markera allt

#define TEXT_SKVENS 	\
TEXTSELECT( TEXT_PA,	"på"	,	"on")\
TEXTSELECT( TEXT_AV,	"av"	,	"off")\
TEXTSELECT( TEXT_AUTO,	"auto",	"auto")\


#undef  TEXTSELECT
#define TEXTSELECT(symbol, svstr, enstr, ...) #svstr,
const BYTE *texter_se_arr[] = { TEXT_SKVENS };

#undef  TEXTSELECT
#define TEXTSELECT(symbol, svstr, enstr, ...) #enstr,
const BYTE *texter_en_arr[] = { TEXT_SKVENS };
johano
Inlägg: 1943
Blev medlem: 22 januari 2008, 10:07:45
Ort: Stockholm

Re: Fastnat i tankarna

Inlägg av johano »

Och ytterligare en variant på baron3d's variant :)

Kod: Markera allt

#define STRING(x) #x,
#define INDEX(x) Index_##x,

#define ITEMS(f) \
	f(Get) \
	f(Set) \
	f(Date) \
	f(Time) \

enum { ITEMS(INDEX) };
const char* Comm_Texts[]={ ITEMS(STRING) };
/johan
Användarvisningsbild
Icecap
Inlägg: 26151
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Re: Fastnat i tankarna

Inlägg av Icecap »

johano: sedär - det var något att gå vidare med! Den ska jag suga lite på när jag får tid.

Det verkar vara en allmän uppfattning att det svåra är att dekoda vilka kommandoord som finns - men det är totalt avklarat redan! Att prata om kommandosyntax, parser osv är att missa målet!

Det finns ett par sätt man kan göra detta på. Om ett kommando kan bestå av ett ord är det bara att ha en index så att resultatet av funktioner i grunden är: Det finns en tabell över orden och är det ord #3 ska funktion #3 anropas.

Men om det finns fler ord i ett kommando (t.ex. "set intensity", "get intensity") kan man välja att antingen ha en tabell över indexen. Alltså:
Ord #1 har index X.
Ord #2 har index Y.
osv.

Sedan kan man sålla ner med t.ex. switch() efter switch() - eller göra som jag gör: Räkna ihop till en enda variabel som man sedan kör igenom en switch(). Jag anser att mitt sätt är enklast att läsa i koden sedan utan en massa kommentarer i koden.
zealotry
Inlägg: 918
Blev medlem: 9 oktober 2004, 22:28:40
Ort: Västerås

Re: Fastnat i tankarna

Inlägg av zealotry »

Exakt, men bygger du ett länkat träd istället för tabell så behöver du inte hålla koll på nån tabell själv som kan spåra ur. Bara mata in i trädet med en add-funktion. Alla giltiga kombinationer läggs in i init av programmet.

Add(Set intensity, funcX)
Add(Get intensity) funcY)


Sen en get function som letar i trädet och returnerar/kör functionen den hittar.
Användarvisningsbild
Jan Almqvist
Inlägg: 1581
Blev medlem: 1 oktober 2013, 20:48:26
Ort: Orust

Re: Fastnat i tankarna

Inlägg av Jan Almqvist »

hur ska du ge dina ord index så att summan blir "rätt"?

29 ord kan kombineras på ett oändligt antal sätt.

( 29 + 29 * 29 + 29 * 29 * 29 + 29 * 29 * 29 * 29 etc )

även vi begränsar oss till vad som kan tänkas rymmas i en inmatningsbuffert t.ex. 80 eller 256 tecken så blir det miljarders miljarder olika kombinationer.
Användarvisningsbild
Icecap
Inlägg: 26151
Blev medlem: 10 januari 2005, 14:52:15
Ort: Aabenraa, Danmark

Re: Fastnat i tankarna

Inlägg av Icecap »

Orden har ett index i en tabell. I detta fall går de mellan 0 och 28.

Om ett giltigt kommando består av index 3 följd av index 6 blir uträkningen:
(3 * 29) + 6 = 93.

Så om jag "fångar" värdet 93 kan jag fastställa att de två ord har kommit i följd.

Självklart betyder det att om jag har två olika kommandon där det ena är på två ord och det nästa är på tre ord och de två första ord är de samma i båda fall blir det fel, då fångar rutinen de första två ord. Men det kan jag överleva.

Jag har funderat på att kunde "gå tillbaka" så att den finner alla 3 ord i ovanstående exempel och först när de 3 ord är hittat kan rutinen avgöra att kommandot rent faktisk är på 3 ord.
mounte
Inlägg: 204
Blev medlem: 14 november 2010, 13:15:00
Ort: Sandviken

Re: Fastnat i tankarna

Inlägg av mounte »

För skojs skull så gjorde jag en högst hypotetisk implementation i Python bara för att labba lite kvickt. Tog ca 30 minuter och helt utan tester *fy skäms*.
Försökt att hålla mig till c-liknande struktur om man nu skull vilja porta.
Jag har bara täckt lite generik i denna implementation men tanken/idéen jag hade var att det man ska behöva deklarera är endast följande:

Kod: Markera allt

commands = ["GET", "SET", "TIME", "DATE"]
Som listar vilka kommandon som ska tolkas.

Kod: Markera allt

actions = [(gettime, 0), (getdate, 0), (settime, 1), (setdate, 1), (setdatetime, 2), (settimedate, 2)] 
som listar vilka funktioner som ska kallas samt hur många parametrar som behövs som indata

Kod: Markera allt

commandsequences = [(0, 2), (0, 3), (1, 2), (1, 3), (1, 3, 2), (1, 2, 3)]
commandactions = [0,1,2,3,4,5]
som listar vilka sekvenser av tolkade kommandon som betyder något och vilken action som den mappas mot.
dvs. (0,3) ==> GET DATE, mappas till action 1 ==> (getdate, 0) som följdaktligen kallar på funktionen getdate utan parametrar.

jag har kapslat datat som funktionerna opererar på i en "struct" ... men går lika bra med globala variabler etc...

Som sagt, endast för skojs skull en koncept-lösning. Kanske kul för någon att leka med.

Kod: Markera allt

class operator:
	def __init__(self):
		self.time = 0
		self.date = 0


def gettime(obj):
	print("** GET TIME: ", obj.time)
	return obj.time
def getdate(obj):
	print("** GET DATE: ", obj.date)
	return obj.date
def settime(obj, time):
	print("** SET TIME: ", obj.time, "to", time)
	obj.time = time
def setdate(obj, date):
	print("** SET DATE: ", obj.date, "to", date)
	obj.date = date
def setdatetime(obj, date, time):
	print("** SET DATE and TIME:", obj.date, obj.time, "to:", date, time)
def settimedate(obj, time, date):
	print("** SET TIME and DATE:", obj.time, obj.date, "to:", time, date)
# list of commands
commands = ["GET", "SET", "TIME", "DATE"]
# list of function pointers and how many parameters are needed
actions = [(gettime, 0), (getdate, 0), (settime, 1), (setdate, 1), (setdatetime, 2), (settimedate, 2)] 
# list of valid sequences of commands
commandsequences = [(0, 2),
					(0, 3),
					(1, 2),
					(1, 3),
					(1, 3, 2),
					(1, 2, 3)]
commandactions = [0,1,2,3,4,5]

# will be used as upper bound for our search
longest_cmd = 0
for c in commandsequences:
	if len(c) > longest_cmd:
		longest_cmd = len(c)
					
# instance of object to operate on
obj = operator()
# sequence of commands that we get from the user
inputsequence = "UNKNOWN 112 SET GET GET SET GET DATE GET TIME SET TIME SET TIME 123 GET TIME SET DATE 312 GET DATE SET TIME DATE 1337 31337 GET TIME GET DATE SET DATE TIME 123 321 GET TIME GET DATE"

command_seq = inputsequence.split()
cmd_parsed = []
is_param = []
for c in command_seq:
	if c in commands:
		is_param.append(False)
		cmd_parsed.append(commands.index(c))
	else:
		is_param.append(True)
		cmd_parsed.append(c)
cmd_parsed = tuple(cmd_parsed)

# rude parsing without parameter validation
cpos = 0
while cpos < len(cmd_parsed):
	action_called=False
	print("Trying to parse commands from", cpos)
	while is_param[cpos]:
		print("Can not start with argument, skip")
		cpos += 1
	testlen = longest_cmd
	while testlen > 0:
		if action_called:
			cpos -= 1
			break
			
		print("Testing commands of length:", testlen, "from pos:", cpos, "RAW:", command_seq[cpos:cpos+testlen])
		inseq = cmd_parsed[cpos:cpos+testlen]
		if inseq in commandsequences:
			print("Found Command...")
			cpos += testlen
			idx = commandsequences.index(inseq)
			action, nparams = actions[commandactions[idx]]
			if nparams > 0:
				print("...with parameters expected...")
				pfound = 0
				params = []
				while pfound < nparams:
					print("Checking if:", cmd_parsed[cpos], "at:", cpos ,"is a parameter")
					if is_param[cpos]:
						params.append(cmd_parsed[cpos])
						cpos += 1
						pfound += 1
					else:
						# not enough parameters to call command
						# break out of this nicely
						print("Not enough parameters for this command")
						break
				if pfound < nparams:
					continue
				print("Calling command with parameters")
				action(obj, *params)
				action_called = True
				continue
			else:
				print("Calling command without parameters")
				action(obj)
				action_called = True
				continue
		testlen -= 1
	cpos += 1
Senast redigerad av mounte 19 mars 2015, 22:30:32, redigerad totalt 1 gång.
Skriv svar