FGI - Re: UDR: Authoritative Feature List / Grammar

  • From: James Gill <james@xxxxxxxxxxxxxxxxxxx>
  • To: fgi@xxxxxxxxxxxxx
  • Date: Fri, 4 Sep 2020 09:26:38 +1000

Can i interject with why (if were going to continue with the UDR) we dont
just enforce a more opinionated/strict format to rolling and then only
parse to that format? Anything outside that just fails? (worst case, if we
form a standard, we can enforce it, and gives us a much more focused
scope/brief)
Is this a backwards compat thing with rulesets? If the core motivation of
getting people other than Matt involved is to make our games better, I
would strongly advise we use our limited resources to that end, rather than
spin tyres on the minutia of a one-size-fits-all dice roller. Not to
invalidate the work you've all done; more to be the voice of sanity and
just ask "yeah but why!?"



On Fri, 4 Sep 2020 at 07:16, Ben Kolera <ben.kolera@xxxxxxxxx> wrote:

Doing a LL(2) parser? I know its not as "clean" as an LL(1), but if
necessary, are they that much harder - or have I missed the point entirly?



Yes you have missed the point. I gave you counterexamples of how this

look ahead hurts modularity of the parsers and even very real bugs

that happen from extending such a language. None of it is impossible

to get working, of course, but it's unnecessarily harder than it needs

to be. Especially not where there are easy syntactic alternatives that

remove the ambiguities that need further context. Doing further

context is something that you want to do only when you really really

need it, not as the default choice. I really don't think that it's

worth it here.





But it's okay. Go nuts. I'm really trying to help make things simpler

and easier to manage. But if that's not what we're actually after,

that's fine. I'm out of energy on this.



On Thu, 3 Sep 2020 at 23:36, duluxoz <duluxoz@xxxxxxxxx> wrote:



I do not think there is a flaw (critical or otherwise) in my mental

model (but will be happy to be correct if there is). EBNF is a language

used to describe the syntax of a context-free grammar of a language. A

LL(1) parser is a Left-to-Right Read, Left-to-Right Derivation,

1-Character-Look-Ahead parser. A LL(1) parser should be able to take an

EBNF grammar and, well, parse it.

There's nothing to say that a multi-layer grammar cannot be specified in

EBNF form, and thus LL(1) parsers should (when written correctly) be

able to parse a multi-layer grammar when they written in EBNF form.



Aside from that (because its not really relevant to the discussion, I

believe), our "dice-roll language" has either number-expressions, the

Die-Code-D code, or flags (die-code-modifers), separated by operands -

where there is no operand then we assume '+' (or '*' for 'NdX' ie

'N*dX'), and where there is no N we assume '+1' (or '*1'). With addition

of parenthesis and die-groups to help with precedence, there should be

any ambiguity - remembering that the Grammar.md document is not
up-to-date.



OK so, with the first issue, what's wrong with:



Doing a LL(2) parser? I know its not as "clean" as an LL(1), but if

necessary, are they that much harder - or have I missed the point
entirly?



I'm pretty sure that I can put things in EBNF form (ie update the

Grammar.md document) to allow for an LL(1) parser anyway. I'll put in an

element for the Die-Code-D 'd' that takes precedence over the

Exponentials ('^','**') but after the Parentheses ('()'). See also the

rest of this post.



Issue 2:



Then let's use 'b' for bust, as I suggested as a possible solution

earlier, if we were going down this route. :-)



Moving on to a different (sub-)topic.



Let's see if I can articulate my (initial and on-going) thought patterns:



A sub-die-roll is a die-roll (ie NdX+MdY+Z, etc) and a set of

flags/options forming a tuple (possible the wrong term) ie [D,F], where

D=NdX+MdY+Z, etc, and F is the set of flags/options (ie

'!','k','dl','s',etc). It is possible for F={}.



(I'm using "[","]" instead of the traditional "{","}" for the tuple(s)

to avoid confusion with the die-group operators "{","}".)



A full-die-roll would then consist of the global-tuple (guple) [ { [D,F]

, [D,F]* },G] where G is the set of group-flags/options (ie

,'k','<N','s',etc) and '*' is as in regex ie '*' =='zero or more'.



In writing this I now realise that G is *not* a subset of F (as is

currently written in the Die-Codes.md document), but instead:



  * F={'','!','a','dh','dl','h','k','kh','kl','m','mt','o','p','rr','s'}

  *
G={'','a','b','c','dh','dl','f','k','kh','kl','m','mt','o','p','r',rr'

    ,'s','<','>','='}



Note: The tokens ('<N','>N','=N') are allowed as part of F when they are

paired with (proceeded by) ('!','h','o','rr'), in which case '=N' and be

shortened to 'N'.



Note that all of the Target Number, Crit, Crit-Fail, Bust, and Raise

Codes (the "Target Codes"=={'b','c','f','r','<','>','='}) can only

appear in G; the "Explode Codes"== {'!','h'} can only appear in F, and

the "Common Codes" (the "Keep Codes"=={'dh','dl','k','kh','kl'}, the

"Sort Codes"=={'a','s'}), the "Match Codes"=={'m','mt'}, and the "Reroll

Codes"== {'o','rr'}) can appear in both F and G.



It should be obvious what to do when encountering a code depending upon

context ie when a Common Code appears in F it is applied to the

corresponding D; when it appears in G it applies to all of the dice in

all of the encapsulated tuples.



Also, the following codes are mutually exclusive within a tuple (ie only

one instance of each code can appear in each F) and within the guple

(within G):



  * The Explode Codes == {'!','h'}

  * The Keep Codes == {'dh','dl','k','kh','kl'}

  * The Sort Codes == {'a','s'}

  * The Match Codes == {'m','mt'}

  * The Reroll Codes == {'o','rr'}

  * The Target Number Codes == {'<','>','='} - as a Target Number, not

    as the compare point.

  * The Fail Codes == {'b','f}'



For completeness, we also have:



  * The Pool Code == {'p'}.

  * The Crit Code =={'c'}.

  * The Raise Code=={'r'}.

  * The Target Codes =={'Crit Code','Fail Codes','Raise Code','Target

    Number Codes'}

  * The Common Codes=={'Keep Codes','Sort Codes','Match Codes','Reroll

    Codes'}



QUESTION: How should we treat the Pool Code:



  * Appearing only in F (ie as an element of the Common Codes)?

  * Appearing only in G?

  * Appearing in both?

  * What does each of these mean in terms of a multi-tuple guple?



ACTION: We obviously need to change the Possible_Die-Code.md and

Die-Code_EBNF_Grammar_Definition.md documents. I'll do so once we agree

on what we're discussing.



Once we've settled upon the above (or whatever):



The UDR, or at least the back-end to the UDR (I think that's what you're

referring to as the API), is/should:



 1. Take a guple, and

     1. Manipulate the D-compart of the encapsulated tuple(s) according

        to the relevant Flags in the F-part of the tuple(s).

     2. Feed the resulting "raw" die-rolls into the FG-(die)engine.

     3. Explode where applicable, which means re-feeding into the
FG-engine.

     4. Manipulate those die-roll-results according the relevant Flags.

     5. Manipulate the group-results according to the Flags in the

        G-part of the guple.

     6. Present the final manipulations in:

         1. The Chat Box.

         2. Any OOB constructed required (most likely as a simple

            integer or set of integers, as required by the Ruleset).

         3. Any further functions as required by individual Rulesets

            (most likely as a simple integer or set of integers, as

            required by the Ruleset).



The front-ends of the UDR, which obviously feed into the back-end,

should (each):



 1. Take a die code from the ChatBox.

 2. This obviously is the Parser/Lexer, either as a single

    fpParserLexer() or as a fpParser()-fpLexer() pair (or whatever).

 3. Take a Drag-&-Drop from the V-Dice to the ChatBox.

 4. Take a Drag-&-Drop from the V-Dice to the Tower.

 5. Take a Button/Diefield-Click from somewhere (eg Character Sheet).

 6. Take a Drag-&-Drop from elsewhere (eg Character Sheet) to the
Chatbox.

 7. Take a Drag-&-Drop from elsewhere (eg Character Sheet) to the Tower.



All front-ends need to modify the guple by including any Modifier (the

value of the first Die-Mod Box) and including any Flags set by the

Die-Mod Boxes. If there is a conflict (ie the Flag is already set) then

the Flags from Die-Mod Boxes should be ignored (not the case with the

Die-Modifer ie the value from the first Die-Mod Box).



That's what I think the UDR should do. I also think the various parts of

the UDR should be created in the following order:



 1. Back-end

     1. Arithmetic operands ie Number-Expressions

     2. The Die-Code_D ie get 'NdX+MdY+Z' working/passed through to the

        FG-Engine, etc

     3. Explode Codes

     4. Sort Codes

     5. Keep Codes

     6. Reroll Codes

     7. Target Codes: Target Number Codes, Raise Code, Crit Code,Fail
Codes

     8. Match Codes

 2. Die-Code Front-End

 3. V-Dice To Chatbox Front-End

 4. V-Dice To Tower Front-End

 5. Click Button/Diefield Front-End

 6. Sheet-Drag To Chatbox Front-End

 7. Sheet-Drag To Tower Front-End



Thoughts?



I think this will allow up to achieve our goals of building a really

solid UDR, and also allow us to do the UDRF easier as well, plus make

the it relative easy to hard-code character sheets, etc.



If we get an agreement then I'll write up new versions of the Grammar

and Die-Code documents, which together will form our "spec?" - and if

there is any further discrepancy then we can discus it and resolve it.



Again, thoughts, yay, nay?



(I'd like everyone to give us their input on this, & not just leave it

to Ben and I - even if its just a "Looks good/yay".)



On 03/09/2020 19:28, Ben Kolera wrote:

Good that we agree on the targets and successes being a top level

construct. It's really the only thing that makes sense. Awesome.



1st Issue

There is a critical flaw in your mental model of ebnf and LL(1)

parsers. I'll have a think about how I can reword things and make the

examples more concrete than my last email. You're not properly taking

into account the ambiguities that arise in such grammars around

optional elements and you're thinking like a human rather than as a

computer. :) We'll have to shelve the discussion about modifiers till

we can reconcile this or till we ditch the idea of doing arithmetic

(at least in the DieSides position).



2nd Issue

Wouldn't we be better off simple coding that into the Ruleset?

Yes, probably, but we'd also be better off not implementing arithmetic

in dice codes if we're happy doing stuff in the ruleset too. We don't

need to implement crits (they are pretty meaningless unless

interpreted by the ruleset anyway so the ruleset may as well do all of

that interpretation) or any arithmetic because that's all trivially

done by the ruleset too. The only time arithmetic would be actually

necessary to implement in the UDR is if we had some crazy system where

numbers of dice/sides/keep/targets/successes were random and dependent

on rolls of dice (roll20 doesn't do this, in fact, it's pretty garbage

and easy to confuse). What's the thought process for some things being

in and some things being out (other than roll20 does the ones

currently in our docs)?



To me, it feels like we're chasing features in the UDR before thinking

properly about the api between the UDR and the rulesets. Instead of

chasing maximal roller functionality, I instead propose that we get it

at feature parity to what we use in our games and start playing around

with how the ruleset on top will interact with the UDR as a proper

API. I think that if we start making that API more concrete we'll have

a better idea of where to draw the line of what the UDR needs to do

and what can be the ruleset's responsibility. The die code rolls are

only a part of the API and we should really consider all of it when

defining UDR scope.



----



Okay, lets have a try at this EBNF / LL(1) example. I'm going to

simplify the grammar so that we can walk through a parser:



DieCode = NumberExpr, "d", "NumberExpr", ["!"], [GlobalModifier]

GlobalModifier = ("+"|"-"), Number

NumberExpr = Number, ["+", NumberExpr]

Number = { Digit }

Digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ;



So with an EBNF written grammar (otherwise we should not use ebnf!)

we're explicitly talking about LL(1) grammars/parsers [1] where all

the context that the parser has in any given rule is the leftmost

character. A parser at any given point has one or more rules to try

and match that character to. All it can do is give a thumbs up to that

character and move onto the next step (or the end of the rule and

returning the result) or error.



You're right in thinking that with 5d12!+5kt7s5, the global modifier

gets into the right place because the exploding code disambiguates

things. But, that code is optional!



Lets walk through what happens with 5d5+5.



1. We start at DieCode. Our only rule that we can match is NumberExpr.

Recurse into NumberExpr

2. At number expression, our first rule is Number, so let's go there.

3. This has to match 1 or more digits, so lets recurse

4. We have a set of possible rules, so let's go through in order, we

try 0,1,2,3,4 and eventually match 5. This is a primitive, awesome!

Return "5".

5. We're back in Number, but when we try getting another digit that

parser fails so we're done with the number. Return 5.

6. We're back in NumberExpr, and the next character is "d" not "+" but

that's okay because that rule was optional. We return 5.

7. We're back in DieCode. Our current char is "d" and our only rule to

match is "d". Phew!, Move onto another number expression.

8. Now we have another number to match, recurse, then recurse down to
match 5.

9. We now have a plus to match and we're in Number expression with an

optional "+" token, okay, lets go!

10. We then match another 5 and return to die code.

11. We don't have any characters left, so we ignore the optional "!"

and Global modifier tokens.

12. Return (5)d(5+5)



There's nothing in ebnf that allows for negative look aheads to say an

ebnf rule like: Give me an optional "+",NumberExpr so long as it isn't

followed by any of the tokens following our number expression



Because this is a losing game. Even if we can invent a negative

lookahead type thing and not actually write ebnf anymore, we'd have to

make (even in this simple grammar) a rule like:

NumberExpr = Number, ["+",NumberExpr,~("!"|EOF)]



But then we add in keep to our grammar, and we have to remember to add

"k" to that set. Then we add crits, rerolls, etc and have to do it all

over again. It also means that our NumberExpression is not modular

anymore. Let's go through the thought process of adding a negative

lookahead in with keep and target and successes:



DieCode = NumberExpr, "d", NumberExpr, ["!"],[Keep][Target][Success]

Keep = "k", NumberExpr

Target = "t", NumberExpr

Success = "s", NumberExpr

NumberExpr = Number, ["+",NumberExpr,~("!"|"k"|"t"|"s"|EOF)]



Then we yell and scream at wtf "5d12!k1+2" "5d12!k1+2t" etc are all

failing to parse. Then we have to make a special NumberExpression

specially for DieSides and we die on the inside a little and wonder

why we didn't just write the global modifier as a lexically distinct

token from a continuing number expression so we don't have to hack

these lookaheads in (like I suggested).



This is why the existing lexer has so many super gross and confusing

look aheads in it (like this:


https://github.com/Dulux-Oz/FGI/blob/master/DORCoreMin/UDR/Scripts/lsUDRDieManager.lua#L778
).

It took me so long to properly understand the existing parser because

we just played the negative lookahead whack-a-mole game instead of

refining the grammar. And it does lots of silly and unexpected things

even though it tries to do parens and addition.



/die (5)d(10)

<parser fails>

/die 5d(5+5)

Rolls no /rdice and returns a flat 10

/die 5d6+5

Parses that as a global modifier (so it's negative lookahead for the

modifier works)

/die 5d6!kt7+5s5

Parses this as (5d6!kt7s5)+5



These lookaheads and the behaviour of the last result is what I mean

when I say that it is *way* too much work to get the current thing to

the full grammar as wanted. Teasing apart the ball of mud of these

very weird anti-modular lookaheads but also having some other parsing

rules be completely context-less to what has been prior is just not a

good thing.



Hopefully this clears things up!



[1] https://en.wikipedia.org/wiki/LL_grammar





On Thu, 3 Sep 2020 at 14:25, duluxoz <duluxoz@xxxxxxxxx> wrote:

Authoritative Source:

It's up to us, really, as the Project Doco is subject to

improvement/updates just like everything else. The UDR Grammar
document

was written for the first draft of the UDR, and I don't think I
updated

it when I updated the  Possible Die Codes document and set up the

Project - in fact, I'm sure I didn't.



So, of the two, the Possible Die Codes document is more up-to-date, so

that's the one *I'd* be using as Authoritative - and so the Grammar

document, which was used as a guide for the first Parser/Lexer, should

be updated so that it matches what's being done in the new
Parser/Lexer.



So follow the Possible Die Codes and we'll update the Grammar - this

should resolve a lot of the issues @Ben raised - plus there's some

notes/thoughts below.



5d12!kt7s5+2d4!kt3s5:



I don't thinks this is legal. Target numbers (& therefore successes)
should only apply to final rolls, not sub rolls, and thus should be the
last set of die-code-elements, and so we should return this as an error (as
we should do with all "illegal rolls"). It's a bit of a contrived example
(although a good one, because it highlights the issue) because I can't
think of *any* game system where you'd want a roll like that - but please
chip in people and tell me I'm wrong.



A similar (legal) roll, without any ambiguity, would be:



({5d12!k}+{2d4!k})t3s5 = 5d12!k+2d4!kt3s5



Anything in the Die Boxes would/should apply to the entire roll -
there's no other way to treat it without going insane - and yes, that means
that at the moment we can't set up a Deadlands Classic Weapon Melee Roll
(DLCWMR: eg 3d10!k+2d6!t5s5) because there's no way to specify that a Keep
Flag should only apply to one of the die-sets. - at the moment if you set
up the Die Boxes to Explode, Keep=5, Success=5, and then dropped 3d10 & 2d6
(or put in /roll 3d10+2d6) what you'd get would be:



(3d10!+2d6!)kt5s5



How do we fix this? I'm not sure, but a DLCWMR is the most complex
roll I've ever come across, so maybe we don't need to, as DLCMWRs should be
coded into the Character Sheet.



I think the best way to handle things (via dice code) is to "error"
anything that is ambiguous.



Happy to have a more detailed (group) discussion around this and any
other complex rolls people know about.



1st Issue:



Dice should have precedence over addition, multiplication, etc, so
d5+5 = (d5)+5, and if we want d(5+5) then that's what we need to write. I
thought I'd implied that in the Precedence section of the one of the docos,
but I mustn't have had. a "d" should go right after "{}" and "()" in
precedence order.



We don't need and (extra) syntax because we already have it ie "()".



The second example also sort of "goes away" because of the statement
above: "Target numbers (& therefore successes) should only apply to final
rolls... and thus should be the last set of die-code-elements".
5d12!kt7s5+2 (without the "|") should be interpreted as 5d12!kt7s(5+2) - if
we want to add a modifier to the roll it need to go *before* the "k", etc
(or use "()"):



5d12+2!kt7s5 or 5d12!+2kt7s5



I think a good "rule" is to enforce all die rolls and modifiers come
before all keeps, crits, target numbers, etc - thoughts everyone?



2nd Issue:



If you bust the strength part of a DLCWMR then the whole roll is a
bust (you're not strong enough to do any damage - you've got to do some
damage with the strength part to get any damage from the weapon part - &
you can't go bust on a non-strength-part damage roll).



Do we need an extra die code for this? All rolls is Deadlands which
we "k1" also (have the potential to) bust - at least as I can recall (jump
in guys and let me know if I'm wrong). Wouldn't we be better off simple
coding that into the Ruleset?



However, if we do need a new code then let's use "b" by itself; we
don't need "bN" because N is a quick calculation based off of MdX ie
NÎil(M/2+0.5).



Open Qs:



As I said, the Grammar was to help me to the initial Parser/Lexer.
I'm OK if we don't update it, but I like we should (as it should form part
of the final doco). And I have no problem with it being a hybrid- or
two-stage grammar (if that's what's required).





On 02/09/2020 09:26, Ben Kolera wrote:



Heya,



What's the authoritative source of what we want to do with the UDR? I

ask because the Grammar [1] and the Possible die codes [2] disagree.

Are we intending to keep the grammar up to date (the Grammar already

doesn't line up to develop) and correct as we go (like actually make

it proper ebnf, lol) or will the code and tests become the doco and

examples of the die codes that we support?



Possible_Die-Codes.md suggests this an example:



{4d8+3d12,5d20+3,5d10+1}>20



But if you look at the grammar the only things that can be part of

expressions are number literals, so the above case is not something

that is allowed by the grammar.



Taking a step back from all the documentation though, as users of our

two current games we want to be able to do:



3d6!+5d10!k  (This lines up to a sabre damage roll with the strength

and weapon damage smooshed into one).



The current rewrite went down the route of treating DicePools as the

whole set of {NumDice,Sides,Exploding,Keep,Target,Raises} (it doesn't

do rerolls or crits yet) and it works out poorly when trying to add

these things together. I mean, what should the result of



5d12!kt7s5+2d4!kt3s5



be? Is it just adding the successes of both groups together? Is the

entire roll a success or a failure if one reaches its target and the

other fails? If you have a modifier in the modifier box in the UI,

does it apply to both groups or does it add extra successes to the

final result? It is really messy and suggests that this needs more

thought and precision.



I don't think that these have any clear answers in the context of our

game (and maybe not clear answers in general), so I'd prefer it if we

disallowed adding anything that has a target/raises from the grammar

but while still allowing the sabre roll that would be handy for

deadlands.



What I suggest is that the Target and Raises syntax can only apply at

the top level something like this (I've ignored functions and

Multiplication and Exponents here because I don't want to write out

the nested precedence, lol):



DieCode = DiceGroupExpr,[GlobalModifier],[Target],[Crit],[SortCode]

GlobalModifier=("+"|"-"), NumberLit

Target=("t"|"<"|">"),NumberExpr,[("s"|"r"),NumberExpr]

SortCode= "s", ("a" | "d")

DiceGroupExpr=DiceGroup,["+",DiceGroupExpr]

DiceGroup= DicePool | DicePools

DicePools = "{", { DicePool }, "}"

DicePool = [NumberExpr], "d", DieSides, [Exploding], [Keep], [Reroll]

<snip>



This seem to work in my head except for two issues:



1st Issue:

The global modifier is ambiguous! In spirit, it's supposed to handle

cases like these: "5d12!k+2t7s5" and "d100-60" but because we allow

arbitrary numeric expression in the diesides there's nothing in the

grammar that distinguishes "5d(5+5)" from "(5d5)+5". We need to

disambiguate this somehow. Some ideas:

    - Always forcing dice pools into a grouping even if single pooled

(kinda ugly) "{5d5}+5"

    - Putting some syntactic distinguisher before the global modifier

like "5d5|+5". Kinda looks weird with keep dice though
"5d12!kt7s5|+2"

(is that clear that it'll add the +2 before doing the target number

calc? "5d12!k|+5t7s5" just looks weird so if we thought the ordering

is important we may need a better distinguisher than "|")



2nd Issue:

I want to add some syntax to the grammar that marks a pool with the

fact that it will bust if there are a majority of 1s in the pool.

However, this would have to go on the dicepools and with the proposed

grammar above that's still part of arithmetic so we need to figure
out

what it means to do arithmetic on potentially busted pools is (5 +

bust = 5, or 5 + bust = 5?). I think that it's good to ground this in

deadlands. What happens if you bust a strength check on a weapon

damage roll? Do you still get the weapon damage or do you do a big
fat

0 damage? Or do strength checks for hand to hand damage not bust? My

intuition is that a bust is an annihilator[3] (i.e X + bust = bust
and

bust + X = bust, func(bust) = bust) that's akin to the expression

throwing an exception.



--- end issues ----



This is the problem with striving towards the kitchen sink! Trying to

figure out ways to make sure all of the features work precisely when

combined becomes harder. Being perfectionistic and wanting a kitchen

sink but being imprecise and doing funky things is a very sad state
to

be in and I absolutely don't want the UDR ending up that way on my

watch. Precision is key with such a core piece of things, so we
either

have something simple that covers our usecases in our games precisely

or we have to go through these thought exercises trying to hammer out

precisely what we mean and want in all these cases that we don't have

a concrete use case for.



---



Summarising, the proposed action points for this email are:

- Targets & Raises at top level only

- Need to distinguish the global modifier syntactically. Just going
to

roll with a code terminating "|",("+","-"),NumberLit for now but we

can change that later if we want

- Crits go on the top level (they are basically a second target
number)

- Busts go on the pool and (X <opr> bust = bust, bust <opr> X = bust

and func(bust) = bust).



Open Questions are:

- Are we planning to output a proper EBNF at the end of the UDR work

or is a test suite and the way that the parser is defined enough (the

nice thing about LPeg style parsers is that they read a lot like ebnf

anyway)? My vote would be that the parser & tests will be enough and

are better because they are machine checked. If we were generating a

parser from the ebnf this would be different, but we aren't so the

docs have the great risk of getting stale (the current ones are stale

and also contain a handful of errors).



I won't be doing any work on UDR today but when I get back to this

probably tomorrow or Friday I won't be blocked if folk haven't had
the

time for a response yet so dw. We need to move in the direction of
the

proposals anyway (unless we're cutting features, ofc!) so it feels

like the main things we'll need to discuss here are finer
bikeshedding

type stuff anyway. I can just move as though those action points are

decided and I can adjust as we discuss the colour of the bikeshed. :)



Cheers,

Ben



[1]
https://github.com/Dulux-Oz/FGI/blob/master/Support_Files/Die-Code_EBNF_Grammar_Definition.md

[2]
https://github.com/Dulux-Oz/FGI/blob/master/Support_Files/Possible_Die-Codes.md

[3] https://en.wikipedia.org/wiki/Annihilator_(ring_theory)



--

Peregrine IT Signature



*Matthew J BLACK*

    M.Inf.Tech.(Data Comms)

    MBA

    B.Sc.

    MACS (Snr), CP, IP3P



When you want it done /right/ â€’ the first time!



Phone:  +61 4 0411 0089

Email:  matthew@xxxxxxxxxxxxxxx <mailto:matthew@xxxxxxxxxxxxxxx>

Web:    www.peregrineit.net <http://www.peregrineit.net>



View Matthew J BLACK's profile on LinkedIn

<http://au.linkedin.com/in/mjblack>



This Email is intended only for the addressee.  Its use is limited to

that intended by the author at the time and it is not to be
distributed

without the author’s consent.  You must not use or disclose the
contents

of this Email, or add the sender’s Email address to any database, list

or mailing list unless you are expressly authorised to do so.  Unless

otherwise stated, Peregrine I.T. Pty Ltd accepts no liability for the

contents of this Email except where subsequently confirmed in

writing.  The opinions expressed in this Email are those of the author

and do not necessarily represent the views of Peregrine I.T. Pty

Ltd.  This Email is confidential and may be subject to a claim of
legal

privilege.



If you have received this Email in error, please notify the author and

delete this message immediately.









--

Peregrine IT Signature



*Matthew J BLACK*

   M.Inf.Tech.(Data Comms)

   MBA

   B.Sc.

   MACS (Snr), CP, IP3P



When you want it done /right/ â€’ the first time!



Phone:  +61 4 0411 0089

Email:  matthew@xxxxxxxxxxxxxxx <mailto:matthew@xxxxxxxxxxxxxxx>

Web:    www.peregrineit.net <http://www.peregrineit.net>



View Matthew J BLACK's profile on LinkedIn

<http://au.linkedin.com/in/mjblack>



This Email is intended only for the addressee.  Its use is limited to

that intended by the author at the time and it is not to be distributed

without the author’s consent.  You must not use or disclose the contents

of this Email, or add the sender’s Email address to any database, list

or mailing list unless you are expressly authorised to do so.  Unless

otherwise stated, Peregrine I.T. Pty Ltd accepts no liability for the

contents of this Email except where subsequently confirmed in

writing.  The opinions expressed in this Email are those of the author

and do not necessarily represent the views of Peregrine I.T. Pty

Ltd.  This Email is confidential and may be subject to a claim of legal

privilege.



If you have received this Email in error, please notify the author and

delete this message immediately.














Other related posts: