Comments (11)
I started to formalize the case ... of
proposal, but I ended up realizing it doesn't work unless you can declare variables inside of the grammar, and I'd like to avoid that. Instead, I'm going to start with a more traditional proposal...
If/Then/Else
Conditional: Expression BooleanOp Expression [...BooleanOp]
if Conditional then Expression else Expression
Example:
if 3d6 + 1 < 3 then 2d6 + 1 else 3d8 + 12
A Conditional
, for this discussion, always evaluates to a Boolean
, and is the result of a BooleanOp
(ex: and
, or
, <
, >=
, !=
, etc.) A BooleanOp
, on the other hand, is an operator that always returns a Boolean
. They are not allowed outside of the conditional part of an if ... then ... else
statement.
Some considerations: without making the else
required, we would have to decide how the entire if ... then
evaluates in the case the condition isn't met. Additionally, where in the grammar would we allow conditionals? Only top level? Anywhere? Things could get very complicated depending on what we decide.
Given these, I propose the following rules:
- The
else
block must always be present. - The
Conditional
must always be a Boolean. (Casts are handled by theBooleanOp
) - The
Expression
portion of thethen
andelse
blocks must always evaluate to aNumber
. - Any
BooleanOp
s must always have well defined casting:0 -> false
andtrue = !false
. (Any number other than0
is true.)
if ... then ... else
must always be allowed where ever anExpression
is allowed by the Grammar.if ... then ... else
must not be evaluated byVariable
.
Examples
if attackBonus > 5 then
2 * (3d8 + [Weapons.Longsword.Damage])
else
3d8 + [Weapons.Longsword.Damage]
if if if if 1d6 > 1 then 2d6 else 3 then 1d6 +1 else -7 then 3(3(3d12 + 7) + 9) else 1d2 + 1 then 2 else 3
3d10 + [Attrs.StrMod] + if [Attrs.DexMod] > 2 then [Attrs.DexMod] else 0
Additional Thoughts
Seeing this on paper, I don't hate it nearly as much. I think, if we implement this, I would like to also support the ternary Condition ? Expression : Expression
syntax, since it's more succinct, and should follow exactly the same rules as if ... then ... else
.
from rpgdice.
Here's another proposal that, I think we could support without changes to the grammar.
Conditional Functions
Conditional: Expression BooleanOp Expression [...BooleanOp]
ThenExpr: Expression
ElseExpr: Expression
if(Condition, ThenExpr, ElseExpr)
Example:
if(3d6 + 1 < 3, 2d6 + 1, 3d8 + 12)
The Conditional
portion would be cast to a Boolean
, no matter what is passed in, meanwhile ThenExpr
and ElseExpr
would throw a TypeError
if it doesn't return a Number
.
A basic implementation of this could be:
function if(cond, thenExpr, elseExpr)
{
cond = cond.eval();
if(cond.value === 0)
{
return thenExpr.eval();
} // end if
return elseExpr.eval();
} // end if
I think that would work the way we want, and should be pretty easy to test with. While I'm not sold on how this feels as a part of the syntax, this could be tenable. I would still want to implement the different BooleanOp
s from above.
Examples
if(
attackBonus > 5,
2 * (3d8 + [Weapons.Longsword.Damage]),
3d8 + [Weapons.Longsword.Damage]
)
if( if( if( if( 1d6 > 1, 2d6, 3), 1d6 +1, -7) 3(3(3d12 + 7) + 9), 1d2 + 1), 2, 3)
3d10 + [Attrs.StrMod] + if([Attrs.DexMod] > 2, [Attrs.DexMod], 0)
from rpgdice.
I've almost convinced myself of my first proposal, to be honest. The function version is ugly.
from rpgdice.
I'm in favor of your If/Then/Else proposal and am willing to help implement it as is, I'm just going to have to put some thought into how to implement the peg parse tree. I think it's going to require two different major branches, one simpler branch for handling only math and one expanded branch for handling both BooleanOp
s and math. Also, just to float the idea, should we only use ... ? ... : ...
and not if ... then ... else
as they are functionally the same, and the readme seems to indicate this module is going for a javascript-like syntax rather than an, I don't know..., python-like one? And we wouldn't have to worry about any confusion between the conditional and variables/functions. Just a thought/personal preference, feel free to ignore.
Alternatively... preemptive type coercion
There is also an alternative to implementing If/Then/Else as is that, while a bit unorthodox, would allow us to keep a unified parse tree. I'm not sure I am going to be able to explain this one well, so I apologize in advance. We could have every op that would return a boolean preemptively use type coercion to return a number instead, because it's the only value type we want to be dealing with anyway. Then every op that needs to test for a boolean would check if(value !== 0)
for true or if(value === 0)
for false. This means these ops won't break any math if they are used unexpectedly outside of a conditional.
Examples
Given that >=
would be:
{
greaterThanOrEqual:
{
symbol: '>=',
evalValue: (left, right) =>
{
return left.value >= right.value ? 1 : 0;
}
}
}
And that ... ? ... : ...
would be something along the lines of:
eval(scope, depth = 1)
{
scope = defaultScope.buildDefaultScope(scope);
this.condition = this.condition.eval(scope, depth + 1);
this.then = this.then.eval(scope, depth + 1);
this.else = this.else.eval(scope, depth + 1);
if(this.condition.value !== 0)
{
this.value = this.then.value;
}
else
{
this.value = this.else.value;
}
}
We can handle the expected use:
(1d20 + 5) >= enemyArmor ? 2d8 + 3 : 0
As well as:
max(1d100 - chancePenalty, 0) ? 2d8 + 3 : 0
And then even be able to handle someone doing something unexpected:
((1d20 + 5) >= enemyArmor) * (2d8 + 3)
I admit that last one is not very intuitive, but I'd argue the input for that last one isn't intuitive either, so it's not our fault. At least we're still able to handle it. And this should still feel very similar to javascript as you can do some weird things like true * 5 = 5
, false * 5 = 0
, and true + true + false = 2
already anyway.
I do feel this idea could still be pretty out there, so I understand if it's too far of a stretch.
from rpgdice.
Actually now that I've slept on it I realize it should be possible to leave the BooleanOp
s in the normal peg tree and to just let javascript handle type coercion natively. This would mean that a user could get a root expression with a boolean value if they did rpgdice.eval('1d20 + 5 >= enemyArmor', {...})
, but that's what they're asking for.
Rereading your proposal I guess I just don't see the advantage of these two points;
- The
Conditional
must always be a Boolean. (Casts are handled by theBooleanOp
)- The
Expression
portion of thethen
andelse
blocks must always evaluate to aNumber
.
Ultimately I'm arguing in favour of the flexibility javascript type coercion allows.
from rpgdice.
Responding in order:
Ternary vs IfThenElse
I kinda want both, if ... then ... else
and ... ? ... : ...
, but for what might be a silly reason; I hate the look of multiline expressions with ternary:
attackBonus > 5
? 2 * (3d8 + [Weapons.Longsword.Damage])
: 3d8 + [Weapons.Longsword.Damage]
But, this is both not javscript, and involves more typing:
if attackBonus > 5 then 2 * (3d8 + [Weapons.Longsword.Damage]) else 3d8 + [Weapons.Longsword.Damage]
(In fact, I've lifted if ... then ... else ...
from Elm; it's syntax is basically exactly what we wanted, it felt like.)
At the end of the day, ?
might be more consistent; as long as you think it'll work for your use case, I do prefer limiting the nesting, or some of the crazier use cases like that.
Preemptive Type Coercion
This isn't as weird as you might have thought; one of the gotchas about everything here is that right now we only have one output type: Numbers. Booleans will need to have some sort of casting to them otherwise people can accidentally do illegal things that will (should) blow up with a TypeError. As an approach, this isn't terrible.
Javascript Coercion
Regarding my points:
- The
Conditional
must always be a Boolean. (Casts are handled by theBooleanOp
)- The
Expression
portion of thethen
andelse
blocks must always evaluate to aNumber
.
My reasoning goes like this: We only need Booleans
to exist for conditionals. I suppose this should be relaxed to "The Conditional
may be a Boolean
." I'm totally find with javascript coercion here.
The part that I'm not cool with is rpgdice.eval('1d20 + 5 >= enemyArmor', {...})
. That should, IMHO throw a TypeError
with a helpful message, like, "Expression does not evaluate to a numeric." What I want is to prevent mixing Boolean
and Numeric
operations in awkward ways.
That being said, Javascript does seem to have some decent rules for this:
> true + 1
2
> false + 1
1
> 50 / true
50
> 50 / false
Infinity
> parseFloat(true)
NaN
> parseInt(true)
NaN
> true + 0
1
>
I just don't want the end result of the evaluation to be true
or false
. It should be a number, always.
from rpgdice.
Ternary vs IfThenElse
It seems we both just have personal preferences on this one, both of which I think are valid, and neither of which should really get in the way of the other. So I say let's just implement both and let the user decide which they find most appealing.
Could you clarify what you mean by limiting the nesting? I believe they should also be right-to-left associative as per Operator Precedence.
No top level BooleanOp
s
Thinking on it not allowing top level booleans makes complete sense, but in order to accomplish this and truly protect against odd behavior I think we might be forced into going with preemptive type coercion. Otherwise what's to stop someone from trying to do this;
let expr = rpgdice.parse('1d20 + 5 >= enemyArmor ? 0 : 0');
expr = expr.condition;
expr.eval({...});
The condition
has no way to know if it's been pulled out of its conditional
. Or should we consider this to be beyond our control and let the user do what they want?
And with preemptive type coercion a user could still do rpgdice.eval('1d20 + 5 >= enemyArmor', {...})
if they really want, and still have the output be usable for their (probable) intended purpose and be a valid number.
from rpgdice.
Limited Nesting
All I meant was, it gets really ugly trying to do:
5 >= armorBonus ? 3 < armorBonus ? 9 > dexBonus ? 3d6 : 2d6 : 9 : 0
Which means the user won't try to do it too much. No actual enforcement requires, and it should definitely follow Operator Precedence.
For now, let's just go with ternary. We can always add if ... then ... else
support later, if people start complaining about formatting nested ternaries.
No top level BooleanOp
s
Yeah, great point. I'm kinda sold on the preemptive type coercion. I want to think about it a little more before moving forward, but I think that's the direction I'd rather go. We then aren't introducing Booleans
, rather, we're just saying that 5 < 3 === 0
. I'm ok with that,I think.
from rpgdice.
Shall I go ahead and mark #7 ready for review, or should I wait for this to be finalized so it can be added in? I'm fine with either.
from rpgdice.
Let's go ahead and mark #7 for review, and put this in another merge.
from rpgdice.
This was closed by #10.
from rpgdice.
Related Issues (6)
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from rpgdice.