Beeminder Forum

Leading Zeros


#1

Did you know you can use arbitrary arithmetic for datapoint values? (Not in advanced entry though, ironically – that’s a can of worms. Same with the email/sms bot.). Oh, and also for setting the rate in the road dial. Relevant UVIs:

http://beeminder.com/changelog#2435
http://beeminder.com/changelog#2441

And did you know that in many programming languages, a number with a leading 0 is parsed as base 8 (aka octal)? So if you have the number 0123 in your code, that’s actually 83, not 123.

Anyway, we implemented the arithmetic feature by stripping out illegal characters and then letting Javascript just evaluate it as if it were code. Some of you are waggling your fingers at us so hard right now, but it’s fine, I’m pretty sure! [1]

Ok, but the part that wasn’t fine was that no one typing “0123” ever means the number 83. That’s ridiculous. So last night we finally fixed that, so we thought, with an elaborate regular expression. But you know how that goes. It goes like this:

Our regular expression accidentally was turning things like “700” into “7”. Forehead smack.

Bee and I spent an hour or so throwing duct tape on our regex (swinging back and forth on the regex vine – https://xkcd.com/208/ ) and it kept falling over, ie, breaking in new ways. Then I had this mini flash of genius:

And now everything is good again the end.

[1] STRAW POLL: If you know Javascript, can you answer this question I asked on Twitter a year and a half ago: https://twitter.com/dreev/status/920169566533140480


#2

I am not sure what is the use case for this (?)


#3

So what does the regex look like?


#4

Oh @dreev. This is a perfect opportunity to add this German marvel to your vocabulary :wink:

I am judging you

Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now they have two problems.

Jamie Zawinski, 1997

Now why did you DIY this and not just use a library such as:

OK that’s GPL, so how about:
https://mathjs.org

Or one of the many other math libraries for JS such as:

I mean clearly the problem has been solved already. So why make the mistakes yourself again? Code you don’t write is code that can’t have bugs.
Besides the other already asked question: Why have this in the first place?


#5

I am going to have to agree with @phi here. Doing it via eval() pretty much 100% guarantees that you still have lots of other weird bugs you don’t even know about yet.


#6

Ha! Thanks everyone! In retrospect we clearly should’ve used a math expression evaluator. But I will accept a wager at @byorgey’s odds that there aren’t further bugs in this thing. I mean, how many more ridiculous things like “leading zero = octal” can there be, right? :slight_smile:

Btw, this is client-side in the browser, which makes it less scary (wholly non-scary?) to eval strings from a user. Though I’d actually be fine trusting the sanitizing we’re doing (only digits, dots, the four arithmetic operators, and parens allowed) and be fine eval’ing them server-side too, hypothetically.

Anyway, as for the regex to strip the leading zeros, @insti, see my StackOverflow answer I linked to.

Oh yeah, and for the use cases, @apolyton, see the changelog (UVI) links above.

I shall, as @mary says, go sit in the box and feel shame now for using eval() in production code even though I still secretly kinda love it.


#7

you want to do 7/5+1/14 for “7 per 5 day week & build a day of safety buffer every 14 days”

Will this somehow work to create a weekend-only goal? E.g 0 for 5 days week and negative 2 buffer day every 7 days :smiley:


#8

Also we can not talk about eval or this…

… without mentioning this old gem :wink:


#9

Another potential argument against doing it like we’re doing it today is Android and iOS support.

Folks get irritated when things don’t work the same between Android and the website (rightfully so!) for things like five minutes being able to be entered like 0:5.

Since we’re doing validation and computation of the datapoints on the client in Javascript, making sure we support every single eval()able edge case that users will find and fall in love–on our mobile native apps–may be tricky. It may be simpler to support it systemwide by doing the computation on the backend–not with eval()–and doing validation in JS (either through one of your fancy regexes or through a different API call.)


#10

I may or may not have put a pomodoro or two into making a PEG parser for parts of your grammar in Scala (which compiles to js). But since @dreev really likes his evil eval hack and there is already libraries for evaluating math expressions I stopped working on it. Anyways here is what the prototype can do:

IN:  1
OUT: Constant(1)
===========================
IN:  2.3
OUT: Constant(2.3)
===========================
IN:  -0.2
OUT: Constant(-0.2)
===========================
IN:  3+2
OUT: Addition(Constant(3),Constant(2))
===========================
IN:  1 - 4
OUT: Subtraction(Constant(1),Constant(4))
===========================
IN:  1 + 2 + 3
OUT: Addition(Addition(Constant(1),Constant(2)),Constant(3))
===========================
IN:  (2)
OUT: Constant(2)
===========================
IN:  2 - (1+1)
OUT: Subtraction(Constant(2),Addition(Constant(1),Constant(1)))
===========================
IN:  -42
OUT: Constant(-42)
===========================
IN:  -4.5
OUT: Constant(-4.5)
===========================
class BeeParser(val input: ParserInput) extends Parser with WhitespaceRules {
  import Exp._

  def InputLine = rule { Expression ~ EOI }

  def Expression: Rule1[Exp] = rule {
    MultiplicationOrDivision ~ zeroOrMore {
      '+' ~ WS ~ MultiplicationOrDivision ~> Addition |
      '-' ~ WS ~ MultiplicationOrDivision ~> Subtraction
    }
  }

  def MultiplicationOrDivision = rule {
    NumberOrParentheses ~ zeroOrMore {
      '*' ~ WS ~ NumberOrParentheses ~> Multiplication |
      '/' ~ WS ~ NumberOrParentheses ~> Division
    }
  }

  def NumberOrParentheses = rule { Number | Parentheses }

  def Parentheses = rule { '(' ~ Expression ~ ')' }

  def Number = rule {
    capture(Signum ~ Digits ~ optional(DecimalSeperator ~ Digits)) ~ WS ~> (createValue _)
  }

  def Signum = rule { optional("-" | "+") }
  def Digits = rule { oneOrMore(CharPredicate.Digit) }
  def DecimalSeperator = rule { "." | "," }

  def createValue(s: String) = Constant(BigDecimal(s))

}

#11

(And no I was not THAT upset by eval that I immediately jumped up and started working on a math parser out of the blue. I was in fact already working on a different one to parse IMAP envelopes so this was more of a little exercise than anything. Turns out it is even simpler as expected to compile a Scala Parser to JS. And I think the code is rather pretty. Also statically type checked of course.)