nydp 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/lisp/core-010-precompile.nydp +5 -6
  3. data/lib/lisp/core-012-utils.nydp +2 -1
  4. data/lib/lisp/core-015-documentation.nydp +17 -11
  5. data/lib/lisp/core-020-utils.nydp +5 -5
  6. data/lib/lisp/core-030-syntax.nydp +29 -9
  7. data/lib/lisp/core-035-flow-control.nydp +15 -6
  8. data/lib/lisp/core-037-list-utils.nydp +22 -0
  9. data/lib/lisp/core-039-module.nydp +24 -0
  10. data/lib/lisp/core-040-utils.nydp +11 -12
  11. data/lib/lisp/core-041-string-utils.nydp +24 -0
  12. data/lib/lisp/core-042-date-utils.nydp +16 -0
  13. data/lib/lisp/core-043-list-utils.nydp +72 -50
  14. data/lib/lisp/core-080-pretty-print.nydp +50 -17
  15. data/lib/lisp/core-090-hook.nydp +13 -1
  16. data/lib/lisp/core-100-utils.nydp +82 -2
  17. data/lib/lisp/core-110-hash-utils.nydp +38 -0
  18. data/lib/lisp/core-120-settings.nydp +11 -2
  19. data/lib/lisp/core-900-benchmarking.nydp +17 -17
  20. data/lib/lisp/tests/accum-examples.nydp +28 -1
  21. data/lib/lisp/tests/at-syntax-examples.nydp +17 -0
  22. data/lib/lisp/tests/best-examples.nydp +9 -0
  23. data/lib/lisp/tests/builtin-tests.nydp +10 -0
  24. data/lib/lisp/tests/case-examples.nydp +14 -0
  25. data/lib/lisp/tests/date-examples.nydp +54 -1
  26. data/lib/lisp/tests/detect-examples.nydp +12 -0
  27. data/lib/lisp/tests/dp-examples.nydp +24 -0
  28. data/lib/lisp/tests/empty-examples.nydp +1 -1
  29. data/lib/lisp/tests/error-tests.nydp +4 -4
  30. data/lib/lisp/tests/hash-examples.nydp +17 -0
  31. data/lib/lisp/tests/list-grep-examples.nydp +40 -0
  32. data/lib/lisp/tests/list-tests.nydp +39 -0
  33. data/lib/lisp/tests/module-examples.nydp +10 -0
  34. data/lib/lisp/tests/parser-tests.nydp +16 -0
  35. data/lib/lisp/tests/pretty-print-tests.nydp +8 -2
  36. data/lib/lisp/tests/settings-examples.nydp +1 -1
  37. data/lib/lisp/tests/string-tests.nydp +48 -0
  38. data/lib/lisp/tests/syntax-tests.nydp +5 -1
  39. data/lib/nydp.rb +6 -3
  40. data/lib/nydp/assignment.rb +10 -3
  41. data/lib/nydp/builtin.rb +1 -1
  42. data/lib/nydp/builtin/abs.rb +8 -0
  43. data/lib/nydp/builtin/date.rb +9 -0
  44. data/lib/nydp/builtin/error.rb +1 -1
  45. data/lib/nydp/builtin/hash.rb +11 -1
  46. data/lib/nydp/builtin/ruby_wrap.rb +69 -0
  47. data/lib/nydp/builtin/string_pad_left.rb +7 -0
  48. data/lib/nydp/builtin/string_pad_right.rb +7 -0
  49. data/lib/nydp/builtin/type_of.rb +9 -6
  50. data/lib/nydp/closure.rb +0 -3
  51. data/lib/nydp/cond.rb +23 -1
  52. data/lib/nydp/context_symbol.rb +14 -6
  53. data/lib/nydp/core.rb +33 -29
  54. data/lib/nydp/core_ext.rb +5 -4
  55. data/lib/nydp/date.rb +17 -17
  56. data/lib/nydp/function_invocation.rb +33 -25
  57. data/lib/nydp/helper.rb +12 -2
  58. data/lib/nydp/interpreted_function.rb +68 -40
  59. data/lib/nydp/literal.rb +1 -1
  60. data/lib/nydp/pair.rb +13 -2
  61. data/lib/nydp/parser.rb +3 -0
  62. data/lib/nydp/symbol_lookup.rb +7 -7
  63. data/lib/nydp/version.rb +1 -1
  64. data/nydp.gemspec +2 -4
  65. data/spec/date_spec.rb +79 -0
  66. data/spec/parser_spec.rb +11 -0
  67. metadata +15 -36
  68. data/lib/nydp/builtin/car.rb +0 -7
  69. data/lib/nydp/builtin/cdr.rb +0 -7
  70. data/lib/nydp/builtin/cons.rb +0 -9
@@ -13,9 +13,8 @@ module Nydp
13
13
 
14
14
  class Base
15
15
  include Helper
16
- def initialize source_expression, sig=nil
17
- @source_expression = source_expression
18
- @sig = sig
16
+ def initialize expr, source, sig=nil
17
+ @expr, @source, @sig = expr, source, sig
19
18
  end
20
19
 
21
20
  def handle e, f, invoker, *args
@@ -35,9 +34,14 @@ module Nydp
35
34
  end
36
35
  end
37
36
 
38
- def inspect ; source.inspect ; end
39
- def source ; @source_expression ; end
40
- def to_s ; source.to_s ; end
37
+ # TODO: speed up compilation by writing custom #lexical_reach for sig-based subclasses (when you know which elements of #expr are lexical symbols)
38
+ def lexical_reach n
39
+ @expr.map { |x| x.lexical_reach n}.max
40
+ end
41
+
42
+ def inspect ; @expr.map { |x| x.inspect }.join ' ' ; end
43
+ def source ; @source ; end
44
+ def to_s ; source.to_s ; end
41
45
  end
42
46
 
43
47
  class Invocation_1 < Invocation::Base
@@ -87,8 +91,8 @@ module Nydp
87
91
  end
88
92
 
89
93
  class Invocation_N < Invocation::Base
90
- def initialize arg_count, source_expression
91
- super source_expression
94
+ def initialize arg_count, expr, source
95
+ super expr, source
92
96
  @arg_count = arg_count
93
97
  end
94
98
 
@@ -107,7 +111,7 @@ module Nydp
107
111
  class Invocation_LEX < Invocation::Base
108
112
  SIGS << self.name
109
113
  def initialize expr, src
110
- super src
114
+ super expr, src
111
115
  @sym = expr.car
112
116
  end
113
117
 
@@ -122,7 +126,7 @@ module Nydp
122
126
  class Invocation_SYM < Invocation::Base
123
127
  SIGS << self.name
124
128
  def initialize expr, src
125
- super src
129
+ super expr, src
126
130
  @sym = expr.car
127
131
  end
128
132
 
@@ -137,7 +141,7 @@ module Nydp
137
141
  class Invocation_LEX_LEX < Invocation::Base
138
142
  SIGS << self.name
139
143
  def initialize expr, src
140
- super src
144
+ super expr, src
141
145
  @lex0 = expr.car
142
146
  @lex1 = expr.cdr.car
143
147
  end
@@ -155,7 +159,7 @@ module Nydp
155
159
  class Invocation_SYM_LEX < Invocation::Base
156
160
  SIGS << self.name
157
161
  def initialize expr, src
158
- super src
162
+ super expr, src
159
163
  @sym = expr.car
160
164
  @lex = expr.cdr.car
161
165
  end
@@ -172,7 +176,7 @@ module Nydp
172
176
  class Invocation_SYM_LIT < Invocation::Base
173
177
  SIGS << self.name
174
178
  def initialize expr, src
175
- super src
179
+ super expr, src
176
180
  @sym = expr.car
177
181
  @lit = expr.cdr.car.expression
178
182
  end
@@ -188,7 +192,7 @@ module Nydp
188
192
  class Invocation_LEX_LEX_LEX < Invocation::Base
189
193
  SIGS << self.name
190
194
  def initialize expr, src
191
- super src
195
+ super expr, src
192
196
  @lex_0 = expr.car
193
197
  @lex_1 = expr.cdr.car
194
198
  @lex_2 = expr.cdr.cdr.car
@@ -208,7 +212,7 @@ module Nydp
208
212
  class Invocation_SYM_LEX_LEX < Invocation::Base
209
213
  SIGS << self.name
210
214
  def initialize expr, src
211
- super src
215
+ super expr, src
212
216
  @sym = expr.car
213
217
  @lex_0 = expr.cdr.car
214
218
  @lex_1 = expr.cdr.cdr.car
@@ -227,7 +231,7 @@ module Nydp
227
231
  class Invocation_SYM_LEX_LEX_LEX < Invocation::Base
228
232
  SIGS << self.name
229
233
  def initialize expr, src
230
- super src
234
+ super expr, src
231
235
  @sym = expr.car
232
236
  @lex_0 = expr.cdr.car
233
237
  @lex_1 = expr.cdr.cdr.car
@@ -248,7 +252,7 @@ module Nydp
248
252
  class Invocation_SYM_LEX_LIT_LEX < Invocation::Base
249
253
  SIGS << self.name
250
254
  def initialize expr, src
251
- super src
255
+ super expr, src
252
256
  @sym = expr.car
253
257
  @lex_0 = expr.cdr.car
254
258
  @lit_1 = expr.cdr.cdr.car.expression
@@ -268,7 +272,7 @@ module Nydp
268
272
  class Invocation_SYM_LIT_LEX < Invocation::Base
269
273
  SIGS << self.name
270
274
  def initialize expr, src
271
- super src
275
+ super expr, src
272
276
  @sym = expr.car
273
277
  @lit_0 = expr.cdr.car.expression
274
278
  @lex_1 = expr.cdr.cdr.car
@@ -288,6 +292,10 @@ module Nydp
288
292
  extend Helper
289
293
  attr_accessor :function_instruction, :argument_instructions
290
294
 
295
+ def lexical_reach n
296
+ function_instruction.car.lexical_reach(n)
297
+ end
298
+
291
299
  def self.build expression, bindings
292
300
  compiled = Compiler.compile_each(expression, bindings)
293
301
  invocation_sig = compiled.map { |x| sig x }.join("_")
@@ -303,15 +311,15 @@ module Nydp
303
311
 
304
312
  invocation = cons case expression.size
305
313
  when 1
306
- Invocation::Invocation_1.new(expression)
314
+ Invocation::Invocation_1.new(compiled, expression)
307
315
  when 2
308
- Invocation::Invocation_2.new(expression)
316
+ Invocation::Invocation_2.new(compiled, expression)
309
317
  when 3
310
- Invocation::Invocation_3.new(expression)
318
+ Invocation::Invocation_3.new(compiled, expression)
311
319
  when 4
312
- Invocation::Invocation_4.new(expression)
320
+ Invocation::Invocation_4.new(compiled, expression)
313
321
  else
314
- Invocation::Invocation_N.new(expression.size, expression)
322
+ Invocation::Invocation_N.new(expression.size, compiled, expression)
315
323
  end
316
324
  new invocation, compiled, expression, cname
317
325
  end
@@ -327,7 +335,7 @@ module Nydp
327
335
  vm.push_ctx_instructions argument_instructions
328
336
  end
329
337
 
330
- def inspect ; @source.inspect ; end
331
- def to_s ; @source.to_s ; end
338
+ def inspect ; @function_instruction.inspect ; end
339
+ def to_s ; @source.to_s ; end
332
340
  end
333
341
  end
@@ -1,7 +1,17 @@
1
1
  module Nydp
2
2
  module AutoWrap
3
- def _nydp_wrapper ; self ; end
4
- def to_ruby ; self ; end
3
+ # include this and be sure to either override #_nydp_ok? or #_nydp_whitelist
4
+ # #_nydp_whitelist should return a list of methods which are safe for nydp to invoke
5
+ def _nydp_wrapper ; self ; end
6
+ def _nydp_ok? method ; _nydp_whitelist.include? method ; end
7
+ def _nydp_safe_send method, *args ; send method, *args if _nydp_ok? method ; end
8
+ def _nydp_get key ; _nydp_safe_send(key.to_s.as_method_name) ; end
9
+ def to_ruby ; self ; end
10
+ end
11
+
12
+ class Struct < ::Struct
13
+ include AutoWrap
14
+ def _nydp_whitelist ; members ; end
5
15
  end
6
16
 
7
17
  module Converter
@@ -5,8 +5,8 @@ require 'nydp/closure'
5
5
  module Nydp
6
6
  class PopArg
7
7
  def self.execute vm ; vm.args.pop ; end
8
- def self.to_s ; "" ; end
9
- def self.inspect ; "#pop_arg" ; end
8
+ def self.to_s ; "" ; end
9
+ def self.inspect ; "#pop_arg" ; end
10
10
  end
11
11
 
12
12
  class InterpretedFunction
@@ -16,42 +16,22 @@ module Nydp
16
16
 
17
17
  attr_accessor :arg_names, :body, :context_builder
18
18
 
19
- def invoke_1 vm, parent_context
20
- vm.push_instructions self.body, set_args_0(parent_context)
21
- end
22
-
23
- def invoke_2 vm, parent_context, arg
24
- vm.push_instructions self.body, set_args_1(parent_context, arg)
25
- end
26
-
27
- def invoke_3 vm, parent_context, arg_0, arg_1
28
- vm.push_instructions self.body, set_args_2(parent_context, arg_0, arg_1)
29
- end
30
-
31
- def invoke_4 vm, parent_context, arg_0, arg_1, arg_2
32
- vm.push_instructions self.body, set_args_3(parent_context, arg_0, arg_1, arg_2)
33
- end
34
-
35
- def invoke vm, parent_context, arg_values
36
- vm.push_instructions self.body, set_args(parent_context, arg_values)
37
- end
38
-
39
- def setup_context context, names, values
40
- if pair? names
41
- context.set names.car, values.car
42
- setup_context context, names.cdr, values.cdr
43
- elsif NIL != names
44
- context.set names, values
45
- end
19
+ def lexical_reach n
20
+ body.map { |b| b.lexical_reach(n - 1) }.max
46
21
  end
47
22
 
48
23
  def self.build arg_list, body, bindings
49
24
  my_params = { }
50
25
  index_parameters arg_list, my_params
51
- ifn = Nydp::InterpretedFunction.new
26
+ body = compile_body body, cons(my_params, bindings), []
27
+ reach = body.map { |b| b.lexical_reach(-1) }.max
28
+
29
+ ifn_klass = reach >= 0 ? InterpretedFunctionWithClosure : InterpretedFunctionWithoutClosure
30
+ ifn = ifn_klass.new
52
31
  ifn.arg_names = arg_list
32
+ ifn.body = body
33
+
53
34
  ifn.extend Nydp::LexicalContextBuilder.select arg_list
54
- ifn.body = compile_body body, cons(my_params, bindings), []
55
35
  ifn
56
36
  end
57
37
 
@@ -62,6 +42,11 @@ module Nydp
62
42
  if Nydp::NIL.is? rest
63
43
  return Pair.from_list(instructions)
64
44
  else
45
+ # PopArg is necessary because each expression pushes an arg onto the arg stack.
46
+ # we only need to keep the arg pushed by the last expression in a function
47
+ # so we need the following line in order to remove unwanted args from the stack.
48
+ # Each expression at some executes vm.push_arg(thing)
49
+ # TODO find a more intelligent way to do this, eg change the meaning of vm or of push_arg in the expression vm.push_arg(thing)
65
50
  instructions << PopArg
66
51
  compile_body rest, bindings, instructions
67
52
  end
@@ -76,19 +61,62 @@ module Nydp
76
61
  end
77
62
  end
78
63
 
79
- def execute vm
80
- # TODO do not create a new Closure if this function does not refer to it
81
- # - including top-level function definitions
82
- # - and any other function that does not refer to any lexically-bound outer variable
83
- # - for example (fn (x) (* x x)) in (map λx(* x x) things) does not need a closure, because
84
- # the function does not refer to any lexical variable outside itself.
85
- vm.push_arg Closure.new(self, vm.current_context)
86
- end
87
-
88
64
  def nydp_type ; "fn" ; end
89
65
  def inspect ; to_s ; end
90
66
  def to_s
91
67
  "(fn #{arg_names.inspect} #{body.map { |b| b.inspect}.join(' ')})"
92
68
  end
93
69
  end
70
+
71
+ class InterpretedFunctionWithClosure < InterpretedFunction
72
+ def invoke_1 vm, ctx
73
+ vm.push_instructions self.body, set_args_0(ctx)
74
+ end
75
+
76
+ def invoke_2 vm, ctx, arg
77
+ vm.push_instructions self.body, set_args_1(ctx, arg)
78
+ end
79
+
80
+ def invoke_3 vm, ctx, arg_0, arg_1
81
+ vm.push_instructions self.body, set_args_2(ctx, arg_0, arg_1)
82
+ end
83
+
84
+ def invoke_4 vm, ctx, arg_0, arg_1, arg_2
85
+ vm.push_instructions self.body, set_args_3(ctx, arg_0, arg_1, arg_2)
86
+ end
87
+
88
+ def invoke vm, ctx, arg_values
89
+ vm.push_instructions self.body, set_args(ctx, arg_values)
90
+ end
91
+
92
+ def execute vm
93
+ vm.push_arg Closure.new(self, vm.current_context)
94
+ end
95
+ end
96
+
97
+ class InterpretedFunctionWithoutClosure < InterpretedFunction
98
+ def invoke_1 vm
99
+ vm.push_instructions self.body, set_args_0(vm.current_context)
100
+ end
101
+
102
+ def invoke_2 vm, arg
103
+ vm.push_instructions self.body, set_args_1(vm.current_context, arg)
104
+ end
105
+
106
+ def invoke_3 vm, arg_0, arg_1
107
+ vm.push_instructions self.body, set_args_2(vm.current_context, arg_0, arg_1)
108
+ end
109
+
110
+ def invoke_4 vm, arg_0, arg_1, arg_2
111
+ vm.push_instructions self.body, set_args_3(vm.current_context, arg_0, arg_1, arg_2)
112
+ end
113
+
114
+ def invoke vm, arg_values
115
+ vm.push_instructions self.body, set_args(vm.current_context, arg_values)
116
+ end
117
+
118
+ def execute vm
119
+ vm.push_arg self
120
+ end
121
+ end
94
122
  end
@@ -18,7 +18,7 @@ module Nydp
18
18
  def nydp_type ; :literal ; end
19
19
  def inspect ; @expression.inspect ; end
20
20
  def to_s ; @expression.to_s ; end
21
- def to_ruby ; n2r @expression ; end
21
+ def to_ruby ; n2r @expression ; end
22
22
 
23
23
  def coerce _
24
24
  [_, expression]
@@ -16,7 +16,8 @@ class Nydp::Pair
16
16
  def cddr ; cdr.cdr ; end
17
17
  def car= thing ; @car = thing ; @_hash = nil ; end
18
18
  def cdr= thing ; @cdr = thing ; @_hash = nil ; end
19
- def hash ; @_hash ||= (car.hash + cdr.hash) ; end
19
+ # def hash ; @_hash ||= (car.hash + cdr.hash) ; end
20
+ def hash ; (car.hash + cdr.hash) ; end # can't cache hash of symbol, breaks when unmarshalling
20
21
  def eql? other ; self == other ; end
21
22
  def copy ; cons(car, cdr.copy) ; end
22
23
  def + other ; copy.append other ; end
@@ -27,6 +28,16 @@ class Nydp::Pair
27
28
  def - other ; self.class.from_list((Set.new(self) - other).to_a) ; end
28
29
  def proper? ; Nydp::NIL.is?(cdr) || (cdr.is_a?(Nydp::Pair) && cdr.proper?) ; end
29
30
 
31
+ def index_of x
32
+ if x == car
33
+ 0
34
+ elsif pair?(cdr)
35
+ 1 + cdr.index_of(x)
36
+ else
37
+ nil
38
+ end
39
+ end
40
+
30
41
  # returns Array of elements after calling #n2r on each element
31
42
  def to_ruby list=[]
32
43
  list << n2r(car)
@@ -51,7 +62,7 @@ class Nydp::Pair
51
62
  if n >= list.size
52
63
  last
53
64
  else
54
- new list[n], from_list(list, last, n+1)
65
+ Nydp::Pair.new list[n], from_list(list, last, n+1)
55
66
  end
56
67
  end
57
68
 
@@ -54,6 +54,7 @@ module Nydp
54
54
  [ /\$/, "dollar-syntax" ],
55
55
  [ /->/, "arrow-syntax" ],
56
56
  [ /[=][>]/, "rocket-syntax" ],
57
+ [ /@/, "at-syntax" ],
57
58
  ]
58
59
 
59
60
  def parse_symbol txt
@@ -73,6 +74,8 @@ module Nydp
73
74
  Pair.from_list [sym(:unquote), parse_symbol($1)]
74
75
  when /^\.$/
75
76
  sym txt
77
+ when /^@$/
78
+ sym txt
76
79
  else
77
80
  SYMBOL_OPERATORS.each do |rgx, name|
78
81
  syms = txt.split(rgx, -1)
@@ -11,17 +11,17 @@ module Nydp
11
11
  bindings
12
12
  end
13
13
 
14
- def self.build name, bindings
15
- bindings = skip_empty bindings
16
- depth = 0
17
- while NIL != bindings
18
- here = bindings.car
14
+ def self.build name, original_bindings
15
+ effective_bindings = skip_empty original_bindings
16
+ depth = 0
17
+ while NIL != effective_bindings
18
+ here = effective_bindings.car
19
19
  if here.key? name
20
20
  binding_index = here[name]
21
- return ContextSymbol.build(depth, name, binding_index)
21
+ return ContextSymbol.build(depth, name, binding_index, original_bindings.index_of(here))
22
22
  else
23
23
  depth += 1
24
- bindings = skip_empty bindings.cdr
24
+ effective_bindings = skip_empty effective_bindings.cdr
25
25
  end
26
26
  end
27
27
  name
@@ -1,3 +1,3 @@
1
1
  module Nydp
2
- VERSION = "0.4.3"
2
+ VERSION = "0.4.5"
3
3
  end
@@ -18,8 +18,6 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_development_dependency "bundler", "~> 1.3"
22
- spec.add_development_dependency "rake", "~> 10.0"
23
- spec.add_development_dependency 'rspec', '~> 3.1'
24
- spec.add_development_dependency 'rspec_numbering_formatter'
21
+ spec.add_development_dependency "rake", "~> 12"
22
+ spec.add_development_dependency 'rspec' #, '~> 3.1'
25
23
  end
@@ -138,6 +138,40 @@ describe Nydp::Date do
138
138
  expect(nd._nydp_get(:"end-of-month").to_s). to eq "2015-06-30"
139
139
  end
140
140
 
141
+ it "calculates end-of-month all year around" do
142
+ expect(Nydp.r2n(Date.parse("2015-01-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-01-31"
143
+ expect(Nydp.r2n(Date.parse("2015-02-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-02-28"
144
+ expect(Nydp.r2n(Date.parse("2016-02-08"))._nydp_get(:"end-of-month").to_s).to eq "2016-02-29"
145
+ expect(Nydp.r2n(Date.parse("2015-03-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-03-31"
146
+ expect(Nydp.r2n(Date.parse("2015-04-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-04-30"
147
+ expect(Nydp.r2n(Date.parse("2015-05-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-05-31"
148
+ expect(Nydp.r2n(Date.parse("2015-06-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-06-30"
149
+ expect(Nydp.r2n(Date.parse("2015-07-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-07-31"
150
+ expect(Nydp.r2n(Date.parse("2015-08-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-08-31"
151
+ expect(Nydp.r2n(Date.parse("2015-09-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-09-30"
152
+ expect(Nydp.r2n(Date.parse("2015-10-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-10-31"
153
+ expect(Nydp.r2n(Date.parse("2015-11-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-11-30"
154
+ expect(Nydp.r2n(Date.parse("2015-12-08"))._nydp_get(:"end-of-month").to_s).to eq "2015-12-31"
155
+ end
156
+
157
+ it "manages leap years" do
158
+ rd = Date.parse "2020-02-08"
159
+ nd = Nydp.r2n rd
160
+
161
+ expect(nd._nydp_get(:"last-month").to_s). to eq "2020-01-08"
162
+ expect(nd._nydp_get(:"next-month").to_s). to eq "2020-03-08"
163
+ expect(nd._nydp_get(:"beginning-of-month").to_s). to eq "2020-02-01"
164
+ expect(nd._nydp_get(:"end-of-month").to_s). to eq "2020-02-29"
165
+
166
+ rd = Date.parse "2020-02-28"
167
+ nd = Nydp.r2n rd
168
+ expect(nd._nydp_get(:"tomorrow").to_s). to eq "2020-02-29"
169
+
170
+ rd = Date.parse "2020-03-01"
171
+ nd = Nydp.r2n rd
172
+ expect(nd._nydp_get(:"yesterday").to_s). to eq "2020-02-29"
173
+ end
174
+
141
175
  it "returns relative dates by week" do
142
176
  rd = Date.parse "2015-03-12"
143
177
  nd = Nydp.r2n rd
@@ -161,4 +195,49 @@ describe Nydp::Date do
161
195
  allow(::Date).to receive_messages(today: Date.parse("2016-06-21"))
162
196
  expect(nd._nydp_get(:age)).to eq({ years: -78, months: -5 })
163
197
  end
198
+
199
+ describe "#change" do
200
+ let(:nd) { Nydp::Date.new(Date.parse("1965-06-08")) }
201
+
202
+ it "advances by weeks" do
203
+ expect((nd.change 1, :week).to_s).to eq "1965-06-15"
204
+ expect((nd.change -1, :week).to_s).to eq "1965-06-01"
205
+ expect((nd.change -2, :week).to_s).to eq "1965-05-25"
206
+ expect((nd.change 2, :week).to_s).to eq "1965-06-22"
207
+ end
208
+
209
+ it "advances by days" do
210
+ expect((nd.change 1, :day).to_s).to eq "1965-06-09"
211
+ expect((nd.change -1, :day).to_s).to eq "1965-06-07"
212
+ expect((nd.change -2, :day).to_s).to eq "1965-06-06"
213
+ expect((nd.change 2, :day).to_s).to eq "1965-06-10"
214
+ end
215
+
216
+ it "advances by months" do
217
+ expect((nd.change 1, :month).to_s).to eq "1965-07-08"
218
+ expect((nd.change -1, :month).to_s).to eq "1965-05-08"
219
+ expect((nd.change -2, :month).to_s).to eq "1965-04-08"
220
+ expect((nd.change 2, :month).to_s).to eq "1965-08-08"
221
+ end
222
+
223
+ it "advances by years" do
224
+ expect((nd.change 1, :year).to_s).to eq "1966-06-08"
225
+ expect((nd.change -1, :year).to_s).to eq "1964-06-08"
226
+ expect((nd.change -2, :year).to_s).to eq "1963-06-08"
227
+ expect((nd.change 2, :year).to_s).to eq "1967-06-08"
228
+ end
229
+
230
+ it "handles leap years and small months" do
231
+ d = Nydp::Date.new(Date.parse("2019-12-31"))
232
+
233
+ expect(d.change( 2, :month).to_s).to eq "2020-02-29"
234
+ expect(d.change(12, :month).to_s).to eq "2020-12-31"
235
+ expect(d.change(14, :month).to_s).to eq "2021-02-28"
236
+ expect(d.change( 6, :month).to_s).to eq "2020-06-30"
237
+ expect(d.change( 7, :month).to_s).to eq "2020-07-31"
238
+
239
+ d = Nydp::Date.new(Date.parse("2020-02-29"))
240
+ expect(d.change(12, :month).to_s).to eq "2021-02-28"
241
+ end
242
+ end
164
243
  end