How to efficietly code user selectable logic? - Page 1
Forum section: electronics / general

author: jnz
author_rank: Frequent Contributor
timestamp: August 05, 2015, 06:21:01 pm
content: So I have project coming up that I'd like the user to be able to select some options from a drop down style list, but it gets pretty complex quickly. For example:[Signal#1] [==]  [Send Off Signal]could be replaced with[Signal#1] [>]  [Toggle State]or[Signal#1] [==][Signal#2][==][Send ON Signal]The issue I'm seeing is I would normal just cleanly set the logic with something like like:Code:If (signal1.voltage==5) Transmit(off);But when you start allowing the user to select the logic... I see something more likeCode:if (USER_LOGIC_IS_EQUALS)if (signal.voltage == USER_LOGIC_VOLTAGE_VALUE)if (USER_LOGIC_WANTS_MESSAGE_SENT)Transmit(USER_LOGIC_MESSAGE_STATUS)if (USER_LOGIC_IS_LESS_THAN)if (signal.voltage <= USER_LOGIC_VOLTAGE_VALUE)if (USER_LOGIC_WANTS_MESSAGE_SENT)Transmit(USER_LOGIC_MESSAGE_STATUS)... etcThe issue I have with this, it is seems that with the number of options I'm giving the user for how the system works, it'll get extremely hard to read/debug and complex/spaghetti very quickly.Are there some rules are or way to lay things like this out in a way that keeps readability? I see systems that have large settings and options in XML type formats that seem very complex, so I'm sure where are tricks to this. I had briefly considered dynamically assembling code to run in ram, but that seems like a really bad idea! I had wondered if combining as many options as I could like USER_LOGIC_WANTS_EQUALS_AND_TRANSMIT_A_MESSAGE could simplify the readability but make the code writing/storage a lot higher.

author: piranha32
author_rank: Unknown
timestamp: August 05, 2015, 10:24:45 pm
content: Quote from: jnz on August 05, 2015, 06:21:01 pmSeparate data from the logic. You can encode states of input signals as integer variables, and then describe each condition as a line in an array, together with associated action. The details will depends on your implementation, but code algorithm may look like this (the code has never been tested, most likely is full of bugs, will not compile with any known compiler, but should give you a starting point to write your own implementation):1. Support functions:Code://Function tests if variable state equals required state.//Value -1 of the required state of the variable means that the state of the variable should not be tested//Input:// required - required state// variable - value of the tested variable//Returns:// false - state of the variable does not equal to the requested state// true - value of the variable equals to the requested state, or variable not tested.int testCondition(int required, int variable){if(required<0 || variable<0)return true;return required==variable}//Function translates continuous values of a variable into discrete state identifiers.//Input:// state_info: an array of triplets describing upped and lower limit of the values associated with the state, and the identifier of the state// value - value of the tested variable//Returns// -1 - Input value not within limits// state - value of the "state" field in the array for matching entry////The "state_info" input is an array of value2state_s structs describing state assignments:// value - lower limit of variable values// value - upper limit of variable values// state - state ID//State ID with value <0 indicates end of the arraystruct {float value;int state;} value2state_s;int value2State(struct value2state_s state_info[], float value){state=-1;for(int i=0;state_info.state>0;i++){if(value >= state_info.value && value < state_info.value){state=state_infostate;break;}}return state;}//Function finds in the input array associating variable states and the action, the first entry corresponding to the current state of input variables//Input:// state2action: - pointer to the translation array// variables - an array with states of the variables// variables_nb - length of he array with variables//Returns:// NULL if no matching action found// pointer to the action callback if match found////"state2action" is an array of structures describing variable state and pointer to the callback function.//callback set to NULL indicates the end of the arraystruct {int states[];function *callback;} state2action_s;function* findCallback(struct state2action_s state2action[], int variables[], int variable_nb){for(int i=0;state2action.callback!=NULL;i++){found=true;for(int j=0;j<variable_nb;j++)found=found && testCondition(state2action[.states, variables)if(found)return state2action.callback;}//if we got until here, no action was foundreturn NULL;}2. Assuming that you have 2 input variables, and two actions you ant to invoke under the following conditions:Code:action1: variable1 < 3 and (variable 2 > 0 and variable2 < 1)action2: variable1 >=3your code might look like this:Code:void action1(){Send(MESSAGE1)}void action2(){Send(MESSAGE2)}#define VARIABLE_NB=2float variables=[variable1, variable2];int states;//translate values of variables into variable state IDsstruct value2state_s variables2states[][]=[//Values for variable 1[{values:[-9999,3], state:1}, // variable1 <3{values:[3,9999], state:2}, // variable1 >=3{values:[0,0],state:-1}],//Values for variable 2[{values:[0,1], state:1}, //variable2 >=0 and <1{values:[0,0],state:-1}]];for(int i=0;i<VARIABLE_NB;i++)states=value2State(variables2states,variables)//translation from variable state to callbacks:struct state2action_s actions[]=[{states:[1,1], callback: &action1}, //variable1 <3 and variable2 between 0 and 1{states:[2,-1], callback: &action2} // variable1 >=3, variable 2 not important];//find callbackcallback=findCallback(actions,variables,VARIABLE_NB);//call it if foundif(callback!=NULL)*callback;That's a lot of code, but consider it a tool, which will allow you to build clean and easy to understand programs.You can expand the definition of the state-action structure to add parameters to the callbacks, but it will add more complexity to the code.

author: jnz
author_rank: Frequent Contributor
timestamp: August 05, 2015, 10:28:22 pm
content: whoa.I'm going to need to process that! You have some interesting ideas tho!

author: piranha32
author_rank: Unknown
timestamp: August 05, 2015, 10:30:52 pm
content: Quote from: jnz on August 05, 2015, 10:28:22 pmI know, it's a lot of code, but the point is that the entire spaghetti-code with hard-coded conditions can be reduced to two, fairly simple, and easy to modify arrays.

author: HackedFridgeMagnet
author_rank: Super Contributor
timestamp: August 06, 2015, 12:07:59 am
content: You're right its that type of code that can hide a silly bug. Such as == instead of >=To efficiently code the logic you may to able to simplify the logic first using Boolean Algebra.Also probably use a State Diagram. With transitions and states. Enums for the states.depending on the resultant complexity you could implement as arrays of states or functions or as switch as statements.

author: piranha32
author_rank: Unknown
timestamp: August 06, 2015, 12:15:34 am
content: Quote from: HackedFridgeMagnet on August 06, 2015, 12:07:59 amState machines should also be implemented as transition and output value tables. I stopped hardcoding such things long time ago, even for small automata. The code using state tables is much simpler, easier to test and debug, and designing the table forces you to consider all possible transitions, something what is almost impossible when transitions are implemented as a bunch of ifs and switches. As a bonus you get definition of machine which is very easy to modify, if you have to change its behaviour.

author: Jeroen3
author_rank: Super Contributor
timestamp: August 06, 2015, 10:52:08 am
content: Someone somewhere must have done this before. You could save weeks in programming if you can get/buy a library for this.

author: amyk
author_rank: Super Contributor
timestamp: August 06, 2015, 02:25:19 pm
content: Quote from: jnz on August 05, 2015, 06:21:01 pmThat could be a great way to do it, probably the fastest too. Effectively a little expression compiler.

author: jnz
author_rank: Frequent Contributor
timestamp: August 06, 2015, 04:10:54 pm
content: Quote from: piranha32 on August 05, 2015, 10:30:52 pmYea, its not bad. It's just something I'll need to examine. I've always coded state machines with polling and superloops when given the choice, so I never really messed around with callbacks. I'm in an RTOS now but I think I'm following the basic initial idea you have laid out. Sort of. I'll need to carefully read a few more times!Quote from: amyk on August 06, 2015, 02:25:19 pmOh man, just seems to very dangerous! Plus I'd have the issue of the micro having to sanitize the user's input code. I considered it for like a minute then figured I have other options that seem easier to debug.

author: jnz
author_rank: Frequent Contributor
timestamp: August 06, 2015, 04:24:11 pm
content: Quote from: piranha32 on August 06, 2015, 12:15:34 amQuote from: HackedFridgeMagnet on August 06, 2015, 12:07:59 amCan either of you guys maybe elaborate on state diagram / state tables, in terms of an example like this? Even really rough. I'm just in the information gathering stage right now. Eventually, the configuration tool will be web based, and I'll have to hire out a developer to implement that, I want to be sure of what I want from him before I ever talk to anyone about it...Whatever I come up with will have to be read in over a serial stream. If it's array based, that seems pretty easy to get into rom from serial. But I suppose I'll be on my own to define the data structure of Function / Variables / Conditions / States / Additional Logic... That alone seems complicated enough just in text file to eventual execution! As for security, I think I'd likely just either encrypt the data on the web / decrypt on chip to ensure it's valid, or sign the data and check sig on the chip.Wondering... As to an array of functions, I'd have something like *myFunc(var1, var2) as an element inside myFuncArray, calling with something like myFuncArray(v1, v2) ? I haven't used pointers to functions like that before, but I understand it can be done in a way that's likely not what I just wrote. I know my knowledge is basic, I haven't pushed limits of conventional programming because my job is usually: get-it-done-asap.The one thing I'm super happy about here is I was right to not consider switches and ifs. I had a suspicion but now I'm fairly certain of it!

author: piranha32
author_rank: Unknown
timestamp: August 06, 2015, 04:27:41 pm
content: Quote from: jnz on August 06, 2015, 04:10:54 pmCallback is is just another way to call a function. As long as you don't do dynamic linking, it should not be much different from calling the function explicitly (modulo few compile-time optimizations, and maybe a bit more mess with caching during runtime). If you don't like calling functions through pointers, you can modify findCallback to return an ID of detected action and use switch statement to call hard-coded actions. A bit less flexibility, but should not have huge impact on code complexity.The code as sketched should be pretty deterministic and run in bounded time, but for reliability and safety I would add error checks and change stop conditions in loops to add hard upper limit on number of iterations (instead of relying on specific value in one of the fields).

author: tggzzz
author_rank: Super Contributor
timestamp: August 06, 2015, 09:43:47 pm
content: Quote from: piranha32 on August 06, 2015, 12:15:34 amMost definitely.There can be other advantages: easy transition between logic and software as a system evolves, easy to keep a log of thehistoryof what happened, can have manymanycopies of the same state machine with each its own state vector stored in RAM, etc.

author: HackedFridgeMagnet
author_rank: Super Contributor
timestamp: August 06, 2015, 11:43:43 pm
content: QuoteAccepting that you have done this before, multiple times and it works for you.I think the code can be kept quite sensible and easy to manage just as switch statements for smaller systems.All transitions should be defined in the design of the diagram and the implementation just follows. What you need to consider is all the possible inputs and states, to make sure you don't miss a transition. I think the biggest disadvantage of the diagram is that being a binary file it is hard to use source control to compare with previous versions. Maybe this is what tggzzz means.QuoteI either don't understand what you mean here. Or don't see a huge advantage.I guess what the OP wants is some example code to compare and to work from. I haven't got any easily available.

author: piranha32
author_rank: Unknown
timestamp: August 07, 2015, 02:06:51 am
content: One small request: if you reply to several people in one message, please add attributions to the quotations. Otherwise it's difficult to follow the discussion.Quote from: HackedFridgeMagnet on August 06, 2015, 11:43:43 pmImplementation of sequential machine as a maze of ifs and switches will be maintainable only for trivial cases. Cyclomatic complexity of such code can be as high as (# of states)*(# of possible inputs). For reference: complexity of code modules should be kept below 10, module with complexity between 20 and 50 is considered very risky, anything with complexity higher than 50 is unmaintainable and untestable.Take for example simple case, which almost everybody has done at least once: quadrature encoder. 2 input lines, what gives 4 input values, at least 4 states. Cyclomatic complexity of such code, with full implementation of all transitions in the graph, would be around 16 (or more). Very close to the high risk zone. Now, take unoptimized implementation (trivial implementation as Mealy machine, without any minimization of state diagram), which I wrote in 15 minutes after I got tired of looking for a bug in code written by someone who thought that ifs and switches make easily readable code (this is just part of the code I worked on):Code:unsigned char PROGMEM transitionTable[]={ //00 01 10 110, 5, 2, 1, //00, 3, 4, 1, //10, 6, 2, 1, //20, 3, 6, 1, //30, 6, 4, 1, //40, 5, 6, 1, //50, 6, 6, 1 //6};unsigned char PROGMEM outputTable[]={ // 00 01 10 110, 0, 0, QENCODER_DIR_ERROR, //0QENCODER_DIR_ERROR, 0, 0, 0, //1QENCODER_DIR_ERROR, QENCODER_DIR_ERROR, 0, QENCODER_DIR_LEFT, //2QENCODER_DIR_LEFT, 0, QENCODER_DIR_ERROR, QENCODER_DIR_ERROR, //3QENCODER_DIR_RIGHT, QENCODER_DIR_ERROR, 0, QENCODER_DIR_ERROR, //4QENCODER_DIR_ERROR, 0, QENCODER_DIR_ERROR, QENCODER_DIR_RIGHT, //50, QENCODER_DIR_ERROR, QENCODER_DIR_ERROR, 0 //6};unsigned char QEncoderDecode(unsigned char switches,QEncDecoderState *state){switches&=QENCODER_SW_MASK;unsigned char idx=state->currentState*4+switches;state->currentState=pgm_read_byte(&transitionTable);return pgm_read_byte(&outputTable);}Handles properly all possible situations, including contact bounce and "impossible" transitions. Complexity is close to 1. Equivalent implementation as conditional statements would be several pages long and completely unreadable.BTW. I have no idea about what binary files you're talking about.

author: Jeroen3
author_rank: Super Contributor
timestamp: August 07, 2015, 07:36:25 am
content: If you're not very experienced with these more complicated dynamic object oriented programming designs. You can do a practice run in plain C. C++ is easier, since object oriented is more in the purpose of c++.Think of a routine that read a finite amount of inputs, with a finite amount of conditions, and a finite amount of outputs.Now take all the variables used to execute that function, and put them in a 3 typedef structures or classes.Input_t, Expression_t and Output_t.Where Input describes the type of input (current/voltage/boolean/time),Expression contains some operation and a parameter (equals to, is greater than or is within) the Input to use and the Output to perform.And Output defines the hardware to use as output, a GPIO, dac or adc or timer. Generally, Input_t and Output_t are const because they're hardware specific, and are only referenced by pointers. They'll use a generic approach, for example a word defining the status and an enum defining the type. Or with C++, an ADC output derives from an virtual Output interface class.Now you can use 1 piece of code to run on multiple pieces of datasets.Next, you'll need to write smarts that parses a line like:[Signal#1][>][1.34V];set expression to greaterthan.set input to signal1.set expression parameter to 1.34.set output to output1.set output parameter to boolean.Into the structures you're using in your logic table.Basically you run the array of Expression_t's through your routine, and you're done.Not the most beautiful solution, but it works.Aren't there any related topic or excellent examples on this kind of software architecture in C and C++.Because in unitversity, you don't learn to program like this. They only skip over the language features. You'll have to be creative yourself to build fancy code with it.

author: tggzzz
author_rank: Super Contributor
timestamp: August 07, 2015, 08:36:59 am
content: Quote from: piranha32 on August 07, 2015, 02:06:51 amYes indeed; simply use the "quote" link at the top right of the message!QuoteAs a schoolkid writing my first machine code program (on a 39(!) bit machine) to convert one 5 channel paper tape code to another, I triumphally re-invented FSMs without realising it. My ~70 word program worked first time whereas someone else's 3ft long (teleprinter roll of paper) Algol-60 program never worked.QuoteNeither have I.There's an additional twist you can easily add in C, which is highly beneficial if you have to do any processing on entering a state. All you do is have another 2D event/state table which contains pointers to functions that do the processing. I first used that 30 years ago when reading in floating point numbers.I've seen too many "computer science graduates" that vaguely remember FSMs, but only in the context of compilers.

author: tggzzz
author_rank: Super Contributor
timestamp: August 07, 2015, 08:45:29 am
content: Quote from: HackedFridgeMagnet on August 06, 2015, 11:43:43 pmDiagrams don't have to be binary files. It is possible to have a textual definition that is processed into a pretty picture. You could even use C as the textual description if you wanted to be peverse.Seehttp://smc.sourceforge.net/for one example. You could easily add a VHDL/Verilog backend if you are targeting FPGAs. There are many descriptions of suitable Verilog/VHDL design patterns.QuoteWhich of the three concepts don't you understand?Note the word "can", not "will in all cases".

author: Kalvin
author_rank: Super Contributor
timestamp: August 07, 2015, 09:09:04 am
content: A small tokenizer/parser and RPN interpreter is not too difficult to create.Edit: In the spirit of RPN and Forth-like language and just to show the idea what the tokenizer/parser might produce and which is quite simple to be exectued by a small Forth/RPN-like interpreter:[Signal#1] [==]  [Send Off Signal]:PUSH 1SIGNAL_GETPUSH 5PUSH EQCOMPAREIFCALL SEND_OFF_SIGNALENDIF[Signal#1] [>]  [Toggle State]:PUSH 1SIGNAL_GETPUSH 3PUSH GTCOMPAREIFCALL TOGGLE_STATEENDIF[Signal#1] [==] [Signal#2][==] [Send ON Signal]:PUSH 1SIGNAL_GETPUSH 5PUSH EQCOMPAREPUSH 2SIGNAL_GETPUSH 0PUSH EQCOMPAREANDIFCALL SEND_ON_SIGNALENDIF

author: Chris C
author_rank: Frequent Contributor
timestamp: August 07, 2015, 11:48:09 pm
content: There was some mention of processing this over a serial stream, which might preclude the possibility of preparsing and arranging operators according to correct precedence. This can be solved by using two stacks, one for operands and one for operators. Instead of immediately executing operators, push them onto their stack; but before doing so, check for and execute any pre-existing operators of equal or higher precedence.[Signal#1] [==] [Signal#2][==] THENSIGNAL_GET (pushed to operator stack with priority 3)1 (pushed to operand stack)== (priority 2, 'SIGNAL_GET' on stack has higher priority so is evaluated before pushing '==')5 (pushed to operand stack)AND (priority 1, '==' on stack has higher priority so is evaluated before pushing 'AND')SIGNAL_GET (pushed to operator stack with priority 3)2 (pushed to operand stack)== (priority 2, 'SIGNAL_GET' on stack has higher priority so is evaluated before pushing '==')0 (pushed to operand stack)THEN (priority 0, '==' then 'AND' is evaluated)

author: HackedFridgeMagnet
author_rank: Super Contributor
timestamp: August 08, 2015, 01:13:30 am
content: Quote from: piranha32 on August 07, 2015, 02:06:51 amIf that's what you want.

author: HackedFridgeMagnet
author_rank: Super Contributor
timestamp: August 08, 2015, 01:15:24 am
content: Quote from: tggzzz on August 07, 2015, 08:45:29 amYou answered yourself about the binary files.

author: HackedFridgeMagnet
author_rank: Super Contributor
timestamp: August 08, 2015, 01:27:14 am
content: Quote from: tggzzz on August 07, 2015, 08:45:29 amNote the word "either". I was meaning either in all three cases. As I am sure you are right in what you say, but for now I don't see it. Anyway don't want to look into it further now as it is not my thread.Thanks for posting the code Piranha, that does help clarify what you guys are talking about.

author: piranha32
author_rank: Unknown
timestamp: August 08, 2015, 01:38:36 am
content: Quote from: HackedFridgeMagnet on August 08, 2015, 01:27:14 amI'm glad you found it helpful. Repeating what tggzzz already said, the machine description does not to be in binary format. However it should not be parsed during runtime. Especially in RT system, where it is important to have bounded execution time. For code riddled with branches, it may be impossible to determine.

author: tggzzz
author_rank: Super Contributor
timestamp: August 08, 2015, 06:15:09 am
content: Quote from: piranha32 on August 08, 2015, 01:38:36 amThere's another consideration. Be very wary about creating your own "domain specificlanguage" (DSL). Almost without exception these start out well and with good intentions, butover time they expand like cancer until nobodyincluding the originatorunderstands themthere are no tools for them, e.g. a decent editor or debuggertrying to get anyone else to work on/with/in them is difficult, since nobody in their right mind wants to limit their career by specialising in poor non-transferrable skillsin the worst case, I've seen a DSL significantly contribute to a company closing a division.I once made the mistake of creating my own DSL, but it was for a throwaway job with a lifetime of 3 weeks!OTOH, domain specificlibrariesare essential and invaluable, since they have none of the above disadvantages

author: jnz
author_rank: Frequent Contributor
timestamp: August 10, 2015, 04:47:31 pm
content: I'm concerned you guys are going slightly over my head on some of this - as well as outside the scope of what I wantThe key issues are:I'll have a text file with the config, it'll be in a format that's entirely up to me. I'll need to bring it in over network or USB, store it somewhere and access during the time I'll make output decisions. Storage format will entirely rely on how I code the output section. Could be anything from XML like to encrypted/signed ARM instructions, although of those two I'm more inclined to the formerNo, I definitely don't want a specific language I create. Or a complex method of my own design to compile the options. Seems like a horribly difficult thing to ever be certain of. As Simple As Possible is desiredI'm interested in the array based options to store the config, but I'm still not entirely clear as to this example how the works, you guys keep talking about drawing out the states but as to this example I'm not seeing that.I can severely cut complexity at the cost of user-options, but in the long run, this is probably fine. The user doesn't need infinite options and I could make my life easier by giving them a push on certain things, but that doesn't really solve anything. I'll still need a system that works for 5 options as it does 50 I suppose.Taking a step back, what are some variations if I wanted to keep it slightly more simple than writing my own interpreter - using arrays and points to the functions?

author: Chris C
author_rank: Frequent Contributor
timestamp: August 12, 2015, 07:06:43 pm
content: There are endless variations. I have implemented a domain-specific language as I hinted at above. Some of the other approaches shown here I haven't used, so I can't elaborate on them.There's one method I used on a home automation project with user-selectable logic via combo boxes, which might fall within your needs. Using your prior example:Quote from: jnz on August 05, 2015, 06:21:01 pmConverted to my method, the rules would be:a = InSignal1 == 5Vb = InSignal2 == 0Vc = a And bOutSignal = cWhere a, b, and c are variables. Variables are boolean only, and the user can pick any descriptive name they want; they are created simply by referencing a new name. Notice the very simple format of each rule. Easy to create and evaluate.I did add one other odd feature, intended to make it more friendly to non-coders, and to allow rules to be added/removed/changed *while* the system is operating. The rules don't have to be in any particular order. This works exactly the same:OutSignal = ca = InSignal1 == 5Vc = a And bb = InSignal2 == 0VThis is possible because the variables persist between executions, and execution works like this:1) Set a counter to zero.2) Store a copy of all variable values.3) Execute all rules in the system that DO perform variable assignment, in ANY order.4) Compare all variables to their previous value, and record the names of any that have changed.5) If any variable has changed:5a) And counter <10, increment counter and goto #2.5b) And counter =10, report error, along with names of changed variables in last three iterations, so that user knows where to look for the circular logic.6) Execute all rules in the system that DON'T perform variable assignment.7) Wait a bit, then goto #1.

author: Bruce Abbott
author_rank: Frequent Contributor
timestamp: August 13, 2015, 12:41:29 am
content: Quote from: jnz on August 10, 2015, 04:47:31 pmThe confusion may be because you know what youwant, but you don't understand what thatmeans.You want to send 'options' to your device in text format, but your device is hard coded in compiled c. In order to carry out the user's wishes, your program must examine the text file and modify its behavior accordingly. You want to do this efficiently without the hassle of writing an interpreter, but the very act of translating text into machine operationsisinterpreting it.I put the word 'options' in quotes because it could also be called 'programming'. When the user selects from a number of options which can be combined in complex ways, he is actuallyprogrammingyour device to do whathewants.The text which the user downloads into your device is a program, which must then be interpreted in order to produce the desired output. This would be true even if the language only consisted of the two words 'ON' and 'OFF' - your code would still have to parse the text to determine which command the user sent, then call the appropriate function to execute it.QuoteThere is a name for code which makes decisions based on text that it reads in real time - it's called aninterpreter.QuoteWhatever format you choose for the storage formatisa programming language. If you don't want to create your own then you must choose an existing language (perhaps only a subset of it and/or modified to suit your application, but still...). If you don't need the complexity of a standard programming language then it may be easier to create your own.QuoteYou already discovered that hard coding all possible 'options' into your program is impractical due to its complexity. An interpreter (or compiler) makes the jobeasier, because it follows simple well defined rules no matter how complex the result.QuoteStoring options in an array could work well if each output function just needs to look at its own options to see what it should do. Whether the user can choose from 5 or 50 options doesn't matter, but how those options can interact with each other might.The examples you have given are a form of combinatorial logic, ie. inputs -> logic function - > output. This could be stored in an array of outputs with their associated logic functions and inputs, like so:-Code:output function input #1 input #2 (input #3...)=============== ======== ========== ========== ==========Send_Off_Signal == Signal#1 5VResult#1 > Signal#1 3.3VResult#2 == Signal#2 0VSend_ON_Signal & Result#1 Result#2 Signal#5The text above could be stored directly into the array as strings, or you could develop a more compact (but still readable) format like this...O1 = S1 V5.0R1 > S1 V3.3R2 = S2 V0.0O2 & R1 R2 L5...or you could go further and translate it into byte codes where eg. byte 0 is the output function#, byte 1 the logic function#, and bytes 2~x the signal/voltage/result inputs (0~63 = signal#, 64~127 = result#, 128~255 = index to an array of voltages).But whatever format you store the information in, it must still beinterpretedin order to produce the desired output.QuoteNo matter how you do it, if the output decisions are made in real time according to stored text then the resultisan interpreter, even if you don't recognize it as such. Once you realize that you should see that writing an interpreter doesn't have to be hard.

author: jnz
author_rank: Frequent Contributor
timestamp: August 14, 2015, 06:09:20 pm
content: Using something between Chris C's and piranha32's suggestions. Eventually, I'll combine this into callbacks, but for right now most my outputs are just ON/OFF so I can simplify a lot of what I need.fwiw, you write RULE enough in struct names and comments and it starts to not look like a real word.

author: jnz
author_rank: Frequent Contributor
timestamp: August 14, 2015, 06:15:43 pm
content: Quote from: Bruce Abbott on August 13, 2015, 12:41:29 amYea... No one likes that. And your tone needs work. Like so much that I'd straight fire you for that post if you were working for me.I'm not writing an interpreter in C if I can help it and will do a lot of heavy lifting in Python or JS on my config tool end. Maybe if you took the whole thread into context and not just a single post, you'd see there could be a big difference in "i don't want to write an interpreter" not to mean "derp I don no what programming iz¡" and maybe something closer to "Yea, I'm not sure I'd want to write an user-to-ASM run-in-ram basic compiler or write a custom push/pop routine where expressions are entirely dynamically evaluated in favor of something I'll be able to debug easier despite at the cost of being larger and less efficient".

author: tggzzz
author_rank: Super Contributor
timestamp: August 14, 2015, 08:28:16 pm
content: Quote from: jnz on August 14, 2015, 06:15:43 pmI don't see where Abbot wrote that.QuoteHis tone is terse, but his content is just fine. If you would fire him for that, then you run the risk of being surrounded by obsequious employees that might not tell you hard truths.

author: jnz
author_rank: Frequent Contributor
timestamp: August 14, 2015, 09:29:35 pm
content: Thanks for your input everyone.

author: Kalvin
author_rank: Super Contributor
timestamp: August 15, 2015, 06:58:10 pm
content: I wanted to play a little with Python and the expression parsing problem. Here's a simple LR(1) expression parser written in Python which will generate code for a simple RPN stack machine. The expression parser supports functions, constants, boolean expressions and comparison operators.It is very easy to convert this code to C and write an interpreter for the RPN. The code is not optimized in any way, and it doesn't take advantage of Python's built-in features in order to make it portable to C.Please find the Python source code as the attachment.Edit: Fixed boolean operator evaluation precedence and attached the updated Python source code.Here are two test vectors:Parsing: 'input(1) > 2 * ((((3) + 4) * 5) - 6) + 7'.: PUSH 1: CALL INPUT: PUSH 2: PUSH 3: PUSH 4: ADD: PUSH 5: MUL: PUSH 6: SUB: MUL: PUSH 7: ADD: GTSuccess.Parsing: 'not(input(1) > 2) and input(3) > 4 * 5 or input(6) <= 7 and input( 8 ) <> PI * E'.: PUSH 1: CALL INPUT: PUSH 2: GT: NOT: PUSH 3: CALL INPUT: PUSH 4: PUSH 5: MUL: GT: AND: PUSH 6: CALL INPUT: PUSH 7: LE: PUSH 8: CALL INPUT: PUSH PI: PUSH E: MUL: NE: AND: ORSuccess.