From 3169f1fae72ffbbeb8dea86bf509caa2afd03876 Mon Sep 17 00:00:00 2001 From: Jon Date: Sat, 31 Mar 2018 17:31:24 -0400 Subject: [PATCH] [nativelib] (#174) Implement various type conversions for primitive values. This includes `Integer#to_f`, `Float#to_i`, `Float#round`, `String#to_i`, and `String#to_f`. These are essentially passthroughs to the native Crystal methods. The String methods, which take arguments in the Crystal version, are implemented with all of the flags enabled for simplicity. This will likely change when named arguments are allowed. --- spec/myst/float_spec.mt | 52 ++++++++++++++ spec/myst/integer_spec.mt | 8 +++ spec/myst/string_spec.mt | 84 ++++++++++++++++++++++ src/myst/interpreter/native_lib/float.cr | 36 ++++++---- src/myst/interpreter/native_lib/integer.cr | 5 ++ src/myst/interpreter/native_lib/string.cr | 19 +++++ 6 files changed, 191 insertions(+), 13 deletions(-) diff --git a/spec/myst/float_spec.mt b/spec/myst/float_spec.mt index c4f6f35..333c72d 100644 --- a/spec/myst/float_spec.mt +++ b/spec/myst/float_spec.mt @@ -317,6 +317,58 @@ describe("Float#%") do end +describe("Float#to_i") do + it("returns a new Integer representing the Float") do + assert(1.0.to_i).equals(1) + assert(100.0.to_i).equals(100) + end + + it("truncates decimals to create the Integer") do + assert(1.4.to_i).equals(1) + assert(123.456.to_i).equals(123) + end + + it("does not perform rounding when truncating") do + assert(1.7.to_i).equals(1) + assert(10.5.to_i).equals(10) + end + + it("raises an error if the Float cannot fit into an Integer value") do + # This number is larger than the maximum storable in a 64-bit integer. + assert{ 9_223_372_036_854_775_810.0.to_i }.raises + end +end + + +describe("Float#round") do + it("returns a new Integer representing the Float") do + assert(1.0.round).equals(1) + assert(100.0.round).equals(100) + end + + it("rounds to the nearest Integer") do + assert(1.4.round).equals(1) + assert(123.789.round).equals(124) + end + + it("rounds .5 decimals to the higher nearest Integer") do + assert(1.5.round).equals(2) + assert(214.5.round).equals(215) + end + + it("accurately rounds decimals around *.5") do + assert(100.49999.round).equals(100) + assert(100.50000.round).equals(101) + assert(100.50001.round).equals(101) + end + + it("raises an error if the Float cannot fit into an Integer value") do + # This number is larger than the maximum storable in a 64-bit integer. + assert{ 9_223_372_036_854_775_810.0.round }.raises + end +end + + describe("Float#to_s") do it("returns the string representation of the number") do assert(1.0.to_s).equals("1.0") diff --git a/spec/myst/integer_spec.mt b/spec/myst/integer_spec.mt index fc39b36..c7d2218 100644 --- a/spec/myst/integer_spec.mt +++ b/spec/myst/integer_spec.mt @@ -346,6 +346,14 @@ describe("Integer#times") do end +describe("Integer#to_f") do + it("returns a new Float representing the Integer") do + assert(1.to_f).equals(1.0) + assert(100.to_f).equals(100.0) + end +end + + describe("Integer#to_s") do it("returns the string representation of the integer") do assert(1.to_s).equals("1") diff --git a/spec/myst/string_spec.mt b/spec/myst/string_spec.mt index b468093..8a516c4 100644 --- a/spec/myst/string_spec.mt +++ b/spec/myst/string_spec.mt @@ -137,6 +137,88 @@ describe("String#empty?") do end +describe("String#to_i") do + it("returns the Integer represented in the String") do + assert("100".to_i).equals(100) + end + + it("defaults to parsing in base 10") do + assert("1234567890".to_i).equals(1234567890) + end + + it("allows whitespace on either side of the integer") do + assert(" 100 ".to_i).equals(100) + assert("100 \t ".to_i).equals(100) + assert(" \t 100".to_i).equals(100) + assert("\n\n100\n\n".to_i).equals(100) + end + + it("allows underscores in the integer value") do + assert("1_00_0".to_i).equals(1000) + assert("1_000".to_i).equals(1000) + end + + it("allows prefixes to change the base") do + assert("0b10".to_i).equals(2) + assert("010".to_i).equals(8) + assert("0x10".to_i).equals(16) + end + + it("raises an error if the string does not represent an integer") do + assert{ "hello".to_i }.raises + end + + it("raises an error if the string contains a float value") do + assert{ "1.0".to_i }.raises + end + + it("raises an error if the String contains extraneous characters after the integer") do + assert{ "100abc".to_i }.raises + end + + describe("with a base argument") do + it("interprets the integer in the given base") do + assert("111".to_i(2)).equals(7) + assert("777".to_i(8)).equals(511) + end + + it("expects the argument to be an Integer") do + assert{ "1".to_i(:f) }.raises + end + end +end + + +describe("String#to_f") do + it("returns the Float represented in the String") do + assert("100.123".to_f).equals(100.123) + end + + it("defaults to parsing in base 10") do + assert("123450.6789".to_f).equals(123450.6789) + end + + it("allows whitespace on either side of the Float") do + assert(" 1.2 ".to_f).equals(1.2) + assert("1.2 \t ".to_f).equals(1.2) + assert(" \t 1.2".to_f).equals(1.2) + assert("\n\n1.2\n".to_f).equals(1.2) + end + + it("can parse an integer value into a Float") do + assert("100".to_f).equals(100.0) + end + + it("raises an error if the string does not represent an integer") do + assert{ "hello".to_f }.raises + end + + it("raises an error if the String contains extraneous characters after the integer") do + assert{ "100abc".to_f }.raises + end +end + + describe("String#to_s") do it("returns itself") do assert("".to_s).equals("") @@ -146,6 +228,7 @@ describe("String#to_s") do end end + describe("String#chars") do it("Returns a List with chars in a string") do assert("abc".chars).equals(["a", "b", "c"]) @@ -153,6 +236,7 @@ describe("String#chars") do end end + describe("String#downcase") do it("returns a lowercased version of itself") do assert("HELLO".downcase).equals("hello") diff --git a/src/myst/interpreter/native_lib/float.cr b/src/myst/interpreter/native_lib/float.cr index 8bc3c12..c7cb019 100644 --- a/src/myst/interpreter/native_lib/float.cr +++ b/src/myst/interpreter/native_lib/float.cr @@ -47,6 +47,14 @@ module Myst end end + NativeLib.method :float_to_i, Float64 do + this.to_i64 + end + + NativeLib.method :float_round, Float64 do + this.round + end + NativeLib.method :float_to_s, Float64 do this.to_s end @@ -103,19 +111,21 @@ module Myst float_type = TType.new("Float", kernel.scope) float_type.instance_scope["type"] = float_type - NativeLib.def_instance_method(float_type, :+, :float_add) - NativeLib.def_instance_method(float_type, :-, :float_subtract) - NativeLib.def_instance_method(float_type, :*, :float_multiply) - NativeLib.def_instance_method(float_type, :/, :float_divide) - NativeLib.def_instance_method(float_type, :%, :float_modulo) - NativeLib.def_instance_method(float_type, :==, :float_eq) - NativeLib.def_instance_method(float_type, :!=, :float_not_eq) - NativeLib.def_instance_method(float_type, :to_s, :float_to_s) - NativeLib.def_instance_method(float_type, :negate,:float_negate) - NativeLib.def_instance_method(float_type, :<, :float_lt) - NativeLib.def_instance_method(float_type, :<=, :float_lte) - NativeLib.def_instance_method(float_type, :>, :float_gt) - NativeLib.def_instance_method(float_type, :>=, :float_gte) + NativeLib.def_instance_method(float_type, :+, :float_add) + NativeLib.def_instance_method(float_type, :-, :float_subtract) + NativeLib.def_instance_method(float_type, :*, :float_multiply) + NativeLib.def_instance_method(float_type, :/, :float_divide) + NativeLib.def_instance_method(float_type, :%, :float_modulo) + NativeLib.def_instance_method(float_type, :==, :float_eq) + NativeLib.def_instance_method(float_type, :!=, :float_not_eq) + NativeLib.def_instance_method(float_type, :to_i, :float_to_i) + NativeLib.def_instance_method(float_type, :round, :float_round) + NativeLib.def_instance_method(float_type, :to_s, :float_to_s) + NativeLib.def_instance_method(float_type, :negate, :float_negate) + NativeLib.def_instance_method(float_type, :<, :float_lt) + NativeLib.def_instance_method(float_type, :<=, :float_lte) + NativeLib.def_instance_method(float_type, :>, :float_gt) + NativeLib.def_instance_method(float_type, :>=, :float_gte) float_type end diff --git a/src/myst/interpreter/native_lib/integer.cr b/src/myst/interpreter/native_lib/integer.cr index 9a31d43..d8d6dde 100644 --- a/src/myst/interpreter/native_lib/integer.cr +++ b/src/myst/interpreter/native_lib/integer.cr @@ -50,6 +50,10 @@ module Myst end end + NativeLib.method :int_to_f, Int64 do + this.to_f64 + end + NativeLib.method :int_to_s, Int64 do this.to_s end @@ -113,6 +117,7 @@ module Myst NativeLib.def_instance_method(integer_type, :%, :int_modulo) NativeLib.def_instance_method(integer_type, :==, :int_eq) NativeLib.def_instance_method(integer_type, :!=, :int_not_eq) + NativeLib.def_instance_method(integer_type, :to_f, :int_to_f) NativeLib.def_instance_method(integer_type, :to_s, :int_to_s) NativeLib.def_instance_method(integer_type, :negate,:int_negate) NativeLib.def_instance_method(integer_type, :<, :int_lt) diff --git a/src/myst/interpreter/native_lib/string.cr b/src/myst/interpreter/native_lib/string.cr index 7611658..d671374 100644 --- a/src/myst/interpreter/native_lib/string.cr +++ b/src/myst/interpreter/native_lib/string.cr @@ -19,6 +19,23 @@ module Myst end end + NativeLib.method :string_to_i, String, base : Int64? do + begin + base = 10 if base.nil? + this.to_i64(base: base, whitespace: true, underscore: true, prefix: true, strict: true) + rescue ex : ArgumentError + __raise_runtime_error(ex.message.not_nil!) + end + end + + NativeLib.method :string_to_f, String, base : Int64? do + begin + this.to_f64(whitespace: true, strict: true) + rescue ex : ArgumentError + __raise_runtime_error(ex.message.not_nil!) + end + end + NativeLib.method :string_to_s, String do this end @@ -117,6 +134,8 @@ module Myst NativeLib.def_instance_method(string_type, :==, :string_eq) NativeLib.def_instance_method(string_type, :!=, :string_not_eq) NativeLib.def_instance_method(string_type, :[], :string_at) + NativeLib.def_instance_method(string_type, :to_i, :string_to_i) + NativeLib.def_instance_method(string_type, :to_f, :string_to_f) NativeLib.def_instance_method(string_type, :to_s, :string_to_s) NativeLib.def_instance_method(string_type, :size, :string_size) NativeLib.def_instance_method(string_type, :split, :string_split)