4

Suppose I have a large, multi-line case statement, which checks several variables before returning a final value.

case
  when (foo='bar' OR foo='foo') AND (bar='foo') then 200
  when (foo='jar') AND (bar='glue' OR bar='true') then 212
  else 0
end

I want to generate a table like the following for it:

foo  |bar    |value
'bar'|'foo'  |200
'foo'|'foo'  |200
'jar'|'glue' |212
'jar'|'true' |212
 x   |x      |0

To me, this reminds me of a boolean logic truth table, and it would be useful for documenting code. Is it possible to generate this sort of "truth table" through a program or automated tool, rather than by hand, and how would I go about doing this?

Decimak
  • 69
  • 7
  • 3
    Yes, it is possible. – Tulains Córdova Oct 11 '16 at 18:26
  • I'm unclear how you would generate it programmatically, rather than by hand. Do you mean generate the table, rather than generate the logic you show in your `case` programmatically? – David Arno Oct 11 '16 at 19:30
  • @DavidArno He means he already has the code with the long case structure and he wants to generate a truth table from it (but not type it) – Tulains Córdova Oct 11 '16 at 19:34
  • @DavidArno the explanation that Tualins commented is accurate, I updated my question's wording accordingly – Decimak Oct 11 '16 at 19:57
  • I believe it is not possible in the general case due to the halting problem, but for a constrained sub-language it should be possible through static analysis. How general do your want the solution? – JacquesB Oct 11 '16 at 20:16

4 Answers4

2

It is absolutely possible to generate such a table from a boolean expression, provided that expression doesn't call into functions. In particular, I'll assume we have operators for equality =, negation NOT, conjunction AND, and disjunction OR. Given such an expression, we can transform it into a disjunctive normal form either by hand, or more tediously by implementing a suitable algorithm. In DNF, the expression consists of AND-groups that are OR-ed together. This is interesting, because each row in your table implies an AND for all input values.

Example:

(foo='bar' OR foo='foo') AND (bar='foo')
<=> distributive law
(foo='bar' AND bar='foo') OR (foo='foo' AND bar='foo')

Produces the table:

foo bar
--- ---
bar foo
foo foo

Things become more difficult once we introduce negations:

(foo='bar' OR foo='foo') AND NOT (bar='foo') AND NOT (bar='bar')
<=> distributive law
(foo='bar' AND NOT bar='foo' AND NOT bar='bar') OR (foo='foo' AND NOT bar='foo' AND NOT bar='bar')

This cannot be represented directly in your table, since we are now excluding certain values. Each table cell would need the ability to EXCLUDE a set of values:

foo | bar
----+------------------
bar | EXCLUDE(foo, bar)
foo | EXCLUDE(foo, bar)

With these ideas in mind, it should be possible to generate the tables by hand with a low error rate. If an automated solution is required, the problem is reduced to automated transformation of a boolean expression to DNF, for which approaches exist.

amon
  • 132,749
  • 27
  • 279
  • 375
1

I'll use a quick and dirty solution using Lunix bash commands

It's not fully programatic but at least you will not have to type it by hand (with the ensuing risk of human errors), and it works no matter how long the case statement is. Sample outpus are just samples based in your snippet.

  • Save the multiline case statement (however long) to a file. Let's call it input_data.txt
  • Let's filter out all assignment of foo and bar to a second file. Let's call it all_assignments.txt

$ egrep "\b[[:alpha:]]+\b='[[:alpha:]]+'" input_data.txt -o | sort -u > all_assignments.txt

bar='foo'
bar='glue'
bar='true'
foo='bar'
foo='foo'
foo='jar'
  • Now let's filter foo assignments and bar assignments to separate files (foo.txt and bar.txt)

    $ egrep "^bar" all_assignments.txt > bar.txt

    $ egrep "^foo" all_assignments.txt > foo.txt

  • Now let's create a cartessian product of all possible assignments of bar and foo:

    $ while read a; do while read b; do echo "$a; $b"; done < bar.txt; done < foo.txt

that would yield this:

foo='bar'; bar='foo'
foo='bar'; bar='glue'
foo='bar'; bar='true'
foo='foo'; bar='foo'
foo='foo'; bar='glue'
foo='foo'; bar='true'
foo='jar'; bar='foo'
foo='jar'; bar='glue'
foo='jar'; bar='true'
  • Extract the case structure to a function in your language
  • User a regexp search and replace in an editor to add a formated print statement with the value of foo, bar and the result.

Generated pseudocode:

foo='bar'; bar='foo';print foo bar myFunction(foo,bar);
foo='bar'; bar='glue';print foo bar myFunction(foo,bar);
foo='bar'; bar='true';print foo bar myFunction(foo,bar);
foo='foo'; bar='foo';print foo bar myFunction(foo,bar);
foo='foo'; bar='glue';print foo bar myFunction(foo,bar);
foo='foo'; bar='true';print foo bar myFunction(foo,bar);
foo='jar'; bar='foo';print foo bar myFunction(foo,bar);
foo='jar'; bar='glue';print foo bar myFunction(foo,bar);
foo='jar'; bar='true';print foo bar myFunction(foo,bar);

When you run that, you will get your truth table ready to be imported into a spreadsheet

NOTE:

Techincally what you depict (and my solution provides) is not a truth table. A truth table would be something alone the lines of this:

enter image description here

Tulains Córdova
  • 39,201
  • 12
  • 97
  • 154
1

Is it possible to generate this sort of "truth table" through a program or automated tool, rather than by hand, and how would I go about doing this?

As already shown it is possible, but I'd like to give another view on your issue:

That "truth table" seems to be more readable to you than the original code, and since readability is king, I thus suggest to create the code from the table.

Basically keep your table in a suitable format (CSV as a simple example, XML if you want it more complex) and from that generate both code (e.g. a function implementing above case) and documentation (HTML, markdown, whatever).

One benefit is that you are in charge of the syntax of your table, thus you decide both the complexity of your parser and when (if ever) to change it. Code and documentation can be generated using some (own, open source, ...) templating engine.

Daniel Jour
  • 683
  • 3
  • 14
  • This is a good idea, and I'd tend to agree with it for new development - but in this case I am responsible for documenting older code without refactoring it – Decimak Oct 12 '16 at 15:54
-1

If the values in the case statements are the only legal values for foo and bar, you could do the following sort of construct:

['foo', 'bar', 'jar', 'x'].each do |foo|
    ['foo', 'glue', 'true', 'x'].each do |bar|
        case
            when (foo='bar' OR foo='foo') AND (bar='foo') then result = 200
            when (foo='jar') AND (bar='glue' OR bar='true') then result = 212
            else result = 0
        end
        puts "#{foo},#{bar},#{result}"
    end
end

Replace the puts line with something with appropriate formatting if you want more than CSV output. The basic idea's to iterate through all values of each input, evaluating your expression and outputting the results. It's fairly easy to generate this code by hand for almost any condition. It's a bit harder to construct one that iterates through huge numbers of possible inputs and combines rows that have the same result into single rows for each contiguous range of input values (think a condition where the legal values are the entire set of integers and the condition's complex enough that you don't want to work out the groupings by hand), but it's doable.

Todd Knarr
  • 1,019
  • 1
  • 9
  • 11
  • 1
    This isn't exactly general-purpose, is it? You're asking him to rewrite his case statement in new code to generate the truth table; you might as well write out the truth table by hand. – Robert Harvey Oct 11 '16 at 20:31
  • 1
    this would require OP to type all values, and I guess the snippet in the question is just an example of something much more complex – Tulains Córdova Oct 11 '16 at 20:47
  • You're right, it's problem-specific as written. It does suggest a pattern to the code that could be turned into a method to generate the method required from information about the legal values of the variables in the `case` statement (a more complex form of `#attr_accessor`). I'd make the condition a block passed to that meta-method, that's easier than trying to make a hash/list/class syntax to describe arbitrary conditions. I can visualize how to construct the meta-method, I just don't have the few hours this afternoon it'd take to code it. – Todd Knarr Oct 11 '16 at 20:53
  • That's also how a lot of meta-programming tools get created: write the code to solve a problem by hand a few times, notice a pattern that can be expressed as an algorithm, write a utility using that algorithm to generate code according to the pattern as needed so you don't have to do it by hand again. – Todd Knarr Oct 11 '16 at 20:55
  • The input he has is the existing source code of the multi case statement. – Tulains Córdova Oct 11 '16 at 20:56
  • To quote Tim Knarr: "On my crew I don't want the industrious guy who'll keep doing the job over and over and over. I want the lazy guy who'll figure out how to not *have* to keep re-doing the job." – Todd Knarr Oct 11 '16 at 20:57