diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..4ae4509 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: vporton # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] \ No newline at end of file diff --git a/Makefile b/Makefile index a43ee64..65158b3 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ .PHONY: check test check: - find src -type f -name '*.mo' -print0 | xargs -0 $(shell vessel bin)/moc $(shell vessel sources 2>/dev/null) --check + find src -type f -name '*.mo' -print0 | xargs -0 moc $(shell vessel sources 2>/dev/null) --check test: - find test -type f -name '*.mo' -print0 | xargs -0 $(shell vessel bin)/moc $(shell vessel sources 2>/dev/null) -r + find test -type f -name '*.mo' -print0 | xargs -0 moc $(shell vessel sources 2>/dev/null) -r diff --git a/src/Combinators.mo b/src/Combinators.mo index 68ca499..8233805 100644 --- a/src/Combinators.mo +++ b/src/Combinators.mo @@ -3,6 +3,8 @@ import Iter "mo:base-0.7.3/Iter"; import List "mo:base-0.7.3/List"; import Nat32 "mo:base-0.7.3/Nat32"; import Text "mo:base-0.7.3/Text"; +import F "mo:base-0.7.3/Float"; +import Debug "mo:base-0.7.3/Debug"; import P "Parser"; import L "List"; @@ -345,4 +347,84 @@ module { } }; }; + + public module Float { + private func parseNat(t: Text): Nat { + var result = 0; + for (d in Text.toIter(t)) { + result += Nat32.toNat(Char.toNat32(d) - Char.toNat32('0')); + result *= 10; + }; + result; + }; + + public func nonNegativeFraction() : Parser { + func (input: List): ?(Float, List) { + let wf = seq( + Nat.nat(), + choose( + bind( + seq( + Character.char('.'), + many1(Character.digit()), + ), + func (p: (Char, List)): Parser { + P.result(Text.fromIter(List.toIter(p.1))) + } + ), + P.result("0"), + ) + )(input); + let ?((w: Nat, f: Text), rest) = wf else { + return null; + }; + let f2 = parseNat(f) else { + Debug.trap("parser-combinators: programming error"); + }; + // Debug.print(debug_show(w) # "~" # debug_show(f2) # "~" # debug_show(10**(Text.size(f)+1))); + ?(F.fromInt(w) + F.fromInt(f2) / F.fromInt(10**(Text.size(f)+1)), rest); + }; + }; + + public func fraction() : Parser { + func (xs : List) { + let (op, ys) = switch(Character.char('-')(xs)) { + case (null) { (func (n : Float) : Float { n; }, xs); }; + case (? (_, xs)) { (func (n : Float) : Float { -n; }, xs); }; + }; + map( + Float.nonNegativeFraction(), + op, + )(ys); + } + }; + + public func float(): Parser { + func (xs : List): ?(Float, List) { + let r0 = seq( + Int.int(), + sat(func (x : Char) : Bool { + x == 'e' or x == 'E' or x == '.'; + }), + )(xs); + if (r0 == null) { + return null; // It's Int + }; + + let r = seq( + Float.fraction(), + seq( + sat(func (x : Char) : Bool { + x == 'e' or x == 'E'; + }), + Int.int(), + ), + )(xs); + let ?((n, (_, e)), rest) = r else { + return Float.fraction()(xs); + }; + ?(n * 10**F.fromInt(e), rest); + }; + }; + }; }; diff --git a/test/Float.mo b/test/Float.mo new file mode 100644 index 0000000..f209d78 --- /dev/null +++ b/test/Float.mo @@ -0,0 +1,43 @@ +import C "../src/Combinators"; +import L "../src/List"; +import F "mo:base-0.7.3/Float"; +import Debug "mo:base-0.7.3/Debug"; + +let fraction = C.Float.fraction(); +switch (fraction(L.fromText("10.1"))) { + case (null) { assert(false); }; + case (? (x, xs)) { + assert(x == 10.1); + assert(xs == null); + }; +}; +switch (fraction(L.fromText("10"))) { + case (null) { assert(false); }; + case (? (x, xs)) { + assert(x == 10); + assert(xs == null); + }; +}; +switch (fraction(L.fromText("0.13"))) { + case (null) { assert(false); }; + case (? (x, xs)) { + assert(x == 0.13); + assert(xs == null); + }; +}; +switch (fraction(L.fromText("-0.13"))) { + case (null) { assert(false); }; + case (? (x, xs)) { + assert(x == -0.13); + assert(xs == null); + }; +}; + +let float = C.Float.float(); +switch (float(L.fromText("-0.13e-1"))) { + case (null) { assert(false); }; + case (? (x, xs)) { + assert(F.abs(x - -0.013) < 10e-8); + assert(xs == null); + }; +};