Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Range type for representing intervals of values. #190

Merged
merged 1 commit into from
Apr 29, 2018
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
90 changes: 90 additions & 0 deletions spec/myst/range_spec.mt
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
require "stdlib/spec.mt"


describe("Range") do
range = %Range{0, 10}

describe("#first") do
it("returns the lower bound of the Range") do
assert(range.first).equals(0)
end
end

describe("#last") do
it("returns the upper bound of the Range") do
assert(range.last).equals(10)
end
end


describe("#each") do
it("calls the block for every element in the Range's interval") do
range = %Range{5, 10}
counter = 0
range.each{ |_| counter += 1 }
# This is 6 instead of 5 because the last element is included as part of
# the range. 5, 6, 7, 8, 9, 10.
assert(counter).equals(6)
end

it("iterates forward through the interval, in order") do
range = %Range{5, 10}
list = []
range.each{ |e| list.push(e) }

assert(list).equals([5, 6, 7, 8, 9, 10])
end
end

describe("#reverse_each") do
it("calls the block for every element in the Range's interval") do
range = %Range{5, 10}
counter = 0
range.reverse_each{ |_| counter += 1 }
# This is 6 instead of 5 because the last element is included as part of
# the range. 5, 6, 7, 8, 9, 10.
assert(counter).equals(6)
end

it("iterates backward through the interval, in order") do
range = %Range{5, 10}
list = []
range.reverse_each{ |e| list.push(e) }

assert(list).equals([10, 9, 8, 7, 6, 5])
end
end


describe("#includes?") do
range = %Range{0, 10}

it("returns true when the value is within the Range's interval") do
assert(range.includes?(5)).is_true
end

it("returns false when the value is above the Range's interval") do
assert(range.includes?(11)).is_false
end

it("returns false when the value is below the Range's interval") do
assert(range.includes?(-1)).is_false
end

it("returns true when the value is the Range's lower bound") do
assert(range.includes?(0)).is_true
end

it("returns true when the value is the Range's upper bound") do
assert(range.includes?(10)).is_true
end
end


describe("#to_s") do
it("returns a string representing the bounds of the interval") do
range = %Range{0, 10}
assert(range.to_s).equals("(0..10)")
end
end
end
1 change: 1 addition & 0 deletions spec/myst/spec.mt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require "./map_spec.mt"
require "./nil_spec.mt"
require "./not_spec.mt"
require "./random_spec.mt"
require "./range_spec.mt"
require "./string_spec.mt"
require "./symbol_spec.mt"
require "./type_spec.mt"
Expand Down
14 changes: 14 additions & 0 deletions stdlib/integer.mt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,18 @@ deftype Integer

self
end

#doc successor -> integer
#| Returns the next smallest integer that is greater than this integer. This
#| method is primarily intended for use with the `Range` type.
def successor
self + 1
end

#doc predeccesor -> integer
#| Returns the next largest integer that is smaller than this integer. This
#| method is primarily intended for use with the `Range` type.
def predecessor
self - 1
end
end
1 change: 1 addition & 0 deletions stdlib/prelude.mt
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ require "./integer.mt"
require "./io.mt"
require "./list.mt"
require "./map.mt"
require "./range.mt"
require "./string.mt"
require "./time.mt"
89 changes: 89 additions & 0 deletions stdlib/range.mt
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#doc Range
#| The `Range` type represents the interval between two values. By default,
#| Ranges are _inclusive_, meaning both the first and last values are included
#| as part of the Range.
#|
#| The only values stored by a Range are the lower and upper bounds. The values
#| within the interval are calculated lazily, and only when necessary. With the
#| exceptions of `#each` and `#reverse_each`, most methods are implemented using
#| only comparisons between these bounds.
#|
#| As Myst does not currently provide a literal syntax for Ranges, creating a
#| new Range is done with normal type instantiation, providing the first and
#| last values of the interval as arguments: `%Range{10, 20}`.
#|
#| Any value type can be used in a Range so long as it implements the `<` and
#| `<=` comparison operators. However, to enable iterating through the Range,
#| value types must also implement a `#successor` that returns the next element
#| of the interval.
#|
#| Ranges can also be used in reverse (e.g., with `#reverse_each`) if the
#| value type defines a `#predecessor` method returning the previous element
#| of the interval.
#|
#| `Range` includes `Enumerable`, so all of Enumerable's methods can be used
#| directly on Ranges. Where possible, Range provides optimized implementations
#| of Enumerable methods to avoid having to iterate all values in the interval
#| (e.g., `#includes?`).
deftype Range
# Range defines `#each`, so any type that satisfies the conditions of Range
# can also be used as an Enumerable.
include Enumerable

#doc initialize
#| Creates a new range for the interval `[first, last]`.
def initialize(@first, @last); end

#doc first -> element
#| Returns the first element of this Range; the lower bound.
def first; @first; end
#doc last -> element
#| Returns the last element of this Range; the upper bound.
def last; @last; end


#doc each(&block) -> self
#| Iterates forward through the Range (starting at `first` and incrementing
#| to `last`), calling the block for every element in the interval.
def each(&block)
current = @first
while current <= @last
block(current)
current = current.successor
end

self
end

#doc reverse_each(&block) -> self
#| Iterates backward through the Range (starting at `last` and decrementing
#| to `first`), calling the block for every element in the interval.
def reverse_each(&block)
current = @last
while @first <= current
block(current)
current = current.predecessor
end

self
end


#doc includes?(value) -> boolean
#| Returns true if `value` exists within the interval of this Range.
#|
#| This method has an `O(1)` implementation using only comparisons with the
#| bounds of the interval.
def includes?(value)
!!(@first <= value && value <= @last)
end


#doc to_s -> string
#| Returns an abstract string representation of the interval covered by this
#| Range. Note that this does _not_ return a string of all the values in the
#| interval.
def to_s
"(<(@first)>..<(@last)>)"
end
end
4 changes: 2 additions & 2 deletions stdlib/spec/describe_container.mt
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
defmodule Spec
deftype DescribeContainer
def initialize(name : String)
@name = name
@name = name
end

def name; @name; end

def get_path(current : String, stack_index)
when !describe_stack.empty? && next_describe = describe_stack[stack_index]
return describe_stack[stack_index].get_path("<(@name)> <(current)>", stack_index - 1)
return describe_stack[stack_index].get_path("<(current)>", stack_index - 1)
else
"<(@name)> <(current)>"
end
Expand Down