nydp 0.4.3 → 0.4.5

Sign up to get free protection for your applications and to get access to all the features.
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