Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ by adding `expression` to your list of dependencies in `mix.exs`:
```elixir
def deps do
[
{:expression, "~> 3.0.0-rc.1"}
{:expression, "~> 3.0.0-rc.2"}
]
end
```
Expand Down
21 changes: 14 additions & 7 deletions lib/literal_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,31 @@ defmodule Expression.LiteralHelpers do

def int do
optional(string("-"))
|> times(
integer(min: 1)
|> concat(optional(ignore(utf8_char([?_])))),
min: 1
)
|> concat(digits_with_underscores())
|> reduce({Enum, :join, [""]})
|> map({String, :to_integer, []})
end

def float do
optional(string("-"))
|> concat(utf8_string([?0..?9], min: 1))
|> concat(digits_with_underscores())
|> concat(string("."))
|> concat(utf8_string([?0..?9], min: 1))
|> concat(digits_with_underscores())
|> reduce({Enum, :join, [""]})
|> map({String, :to_float, []})
end

# Digit groups must be captured as strings, not with `integer/1`: parsing
# "000" in "180_000" as an integer collapses it to 0, so the joined number
# became 1800 instead of 180000.
defp digits_with_underscores do
times(
ascii_string([?0..?9], min: 1)
|> concat(optional(ignore(utf8_char([?_])))),
min: 1
)
end

def numeric do
choice([int(), float()])
end
Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
defmodule Expression.MixProject do
use Mix.Project

@version "3.0.0-rc.1"
@version "3.0.0-rc.2"

def project do
[
Expand Down
6 changes: 6 additions & 0 deletions test/expression/parser_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,12 @@ defmodule Expression.ParserTest do
assert_ast([expression: [literal: 11]], "@(1_1)")
assert_ast([expression: [[]], text: "(1__1)"], "@(1__1)")
assert_ast([expression: [literal: 111]], "@(1_1_1)")
# digit groups with leading zeros must keep them: 180_000 used to parse as 1800
assert_ast([expression: [literal: 180_000]], "@(180_000)")
assert_ast([expression: [literal: 1_000_000]], "@(1_000_000)")
assert_ast([expression: [literal: -1_000_000]], "@(-1_000_000)")
assert_ast([expression: [literal: 1_234_567.89]], "@(1_234_567.89)")
assert_ast([expression: [literal: 0.000_5]], "@(0.000_5)")

assert_ast(
[expression: [literal: ~U[2022-05-24 00:00:00.0Z]]],
Expand Down
11 changes: 11 additions & 0 deletions test/expression_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,17 @@ defmodule ExpressionTest do
"message" => "Invalid enumerable"
}
end

test "underscored number literals keep their value in comparisons and arithmetic" do
# 180_000 used to parse as 1800 (each digit group was parsed as an
# integer before joining, dropping leading zeros), which made
# `5000 > 180_000` evaluate to true
assert Expression.evaluate_block!("5000 > 180_000") == false
assert Expression.evaluate_block!("180_000 < 5000") == false
assert Expression.evaluate_block!("200_000 + 1") == 200_001
assert Expression.evaluate_block!("1_000_000") == 1_000_000
assert Expression.evaluate_block!("1_234_567.89") == 1_234_567.89
end
end

describe "evaluate_as_string!" do
Expand Down