fancy 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/LICENSE +1 -1
  2. data/README.md +4 -1
  3. data/Rakefile +0 -52
  4. data/bin/fspec +22 -12
  5. data/bin/ifancy +1 -1
  6. data/boot/fancy_ext/class.rb +1 -0
  7. data/boot/fancy_ext/object.rb +8 -6
  8. data/boot/rbx-compiler/compiler/ast/method_def.rb +2 -0
  9. data/boot/rbx-compiler/parser/fancy_parser.bundle +0 -0
  10. data/boot/rbx-compiler/parser/parser.y +9 -0
  11. data/doc/api/fancy.jsonp +1 -1
  12. data/examples/stupid_quicksort.fy +11 -9
  13. data/lib/array.fy +26 -58
  14. data/lib/block.fy +0 -1
  15. data/lib/boot.fy +2 -2
  16. data/lib/class.fy +85 -0
  17. data/lib/compiler/ast/class_def.fy +1 -1
  18. data/lib/compiler/ast/expression_list.fy +4 -12
  19. data/lib/compiler/ast/identifier.fy +3 -3
  20. data/lib/compiler/ast/method_def.fy +3 -1
  21. data/lib/compiler/ast/singleton_method_def.fy +4 -1
  22. data/lib/contracts.fy +53 -56
  23. data/lib/dynamic_slot_object.fy +39 -3
  24. data/lib/enumerable.fy +144 -47
  25. data/lib/fancy_spec.fy +2 -6
  26. data/lib/file.fy +67 -0
  27. data/lib/future.fy +42 -3
  28. data/lib/hash.fy +35 -29
  29. data/lib/html.fy +1 -1
  30. data/lib/integer.fy +34 -0
  31. data/lib/main.fy +13 -7
  32. data/lib/message_sink.fy +1 -1
  33. data/lib/number.fy +10 -0
  34. data/lib/object.fy +27 -1
  35. data/lib/package.fy +2 -0
  36. data/lib/package/handler.fy +56 -0
  37. data/lib/package/installer.fy +21 -51
  38. data/lib/package/specification.fy +12 -5
  39. data/lib/package/uninstaller.fy +22 -3
  40. data/lib/parser/ext/parser.y +9 -0
  41. data/lib/proxy.fy +25 -2
  42. data/lib/rbx.fy +2 -1
  43. data/lib/rbx/array.fy +16 -1
  44. data/lib/rbx/class.fy +34 -9
  45. data/lib/rbx/file.fy +2 -2
  46. data/lib/rbx/fixnum.fy +1 -11
  47. data/lib/rbx/io.fy +4 -0
  48. data/lib/rbx/module.fy +11 -0
  49. data/lib/rbx/object.fy +12 -12
  50. data/lib/rbx/proc.fy +7 -0
  51. data/lib/rbx/string.fy +5 -1
  52. data/lib/rbx/symbol.fy +9 -0
  53. data/lib/string.fy +1 -1
  54. data/lib/tuple.fy +37 -35
  55. data/lib/version.fy +6 -5
  56. data/tests/array.fy +14 -2
  57. data/tests/class.fy +79 -0
  58. data/tests/dynamic_key_hash.fy +16 -0
  59. data/tests/dynamic_slot_object.fy +28 -0
  60. data/tests/dynamic_value_array.fy +12 -0
  61. data/tests/enumerable.fy +46 -0
  62. data/tests/file.fy +38 -0
  63. data/tests/fixnum.fy +22 -0
  64. data/tests/future.fy +40 -0
  65. data/tests/hash.fy +8 -7
  66. data/tests/object.fy +31 -5
  67. data/tests/set.fy +1 -1
  68. data/tests/string.fy +18 -2
  69. data/tests/tuple.fy +7 -0
  70. data/tools/fancy-mode.el +10 -0
  71. metadata +9 -12
  72. data/examples/99bottles.fy +0 -5
  73. data/examples/conditions_exceptions.fy +0 -9
  74. data/examples/conditions_parsing.fy +0 -68
  75. data/examples/dynamic.fy +0 -8
  76. data/examples/greeter.fy +0 -9
  77. data/examples/parsing.fy +0 -1
  78. data/lib/rbx/process.fy +0 -13
  79. data/lib/remote_object.fy +0 -59
@@ -175,7 +175,7 @@ class FancySpec {
175
175
  @@current
176
176
  }
177
177
 
178
- def SpecTest print_failures: start_time {
178
+ def SpecTest print_failures: start_time no_failures: ok_block else: error_block {
179
179
  @@failed_positive each: |test_obj failed_tests| {
180
180
  failed_tests each: |t| {
181
181
  Console newline
@@ -194,11 +194,7 @@ class FancySpec {
194
194
 
195
195
  Console newline
196
196
  "Ran #{@@total_tests} tests (#{@@total_expectations} expectations) with #{@@failed_count} failures in #{Time now - start_time} seconds." println
197
- if: (@@failed_count > 0) then: {
198
- System exit: 1
199
- } else: {
200
- System exit: 0
201
- }
197
+ if: (@@failed_count == 0) then: ok_block else: error_block
202
198
  }
203
199
 
204
200
  def initialize: @info_str block: @block {
@@ -26,6 +26,25 @@ class File {
26
26
  File open: filename modes: ['read] with: block
27
27
  }
28
28
 
29
+ def File read_binary: filename with: block {
30
+ """
31
+ @filename Filename of @File@ to read from.
32
+ @block @Block@ called with a @File@ object to read from.
33
+
34
+ Opens a @File@ for reading and calls @block with it.
35
+ """
36
+
37
+ File open: filename modes: ['read, 'binary] with: block
38
+ }
39
+
40
+ def File read_binary: filename {
41
+ content = nil
42
+ File read_binary: filename with: |f| {
43
+ content = f read
44
+ }
45
+ content
46
+ }
47
+
29
48
  def File touch: filename {
30
49
  """
31
50
  @filename Name of @File@ to be created, if not already existant.
@@ -39,6 +58,54 @@ class File {
39
58
  }
40
59
  }
41
60
 
61
+ def File eval: filename {
62
+ """
63
+ @filename Name of Fancy source file (*.fy) to be read and evaluated.
64
+ @return Value of evaluating code in @filename.
65
+ """
66
+
67
+ File read: filename . eval
68
+ }
69
+
70
+ def File read_config: filename {
71
+ """
72
+ @filename @String@ that is the name of file to be read as a config file.
73
+ @return @Hash@ of key-value pairs of the config file.
74
+
75
+ Reads a .fy source file as a config file.
76
+
77
+ Example:
78
+ # Given a file config.fy with these contents:
79
+ {
80
+ host: \"127.0.0.1\"
81
+ port: 1234
82
+ names: [
83
+ 'foo,
84
+ 'bar,
85
+ 'baz
86
+ ]
87
+ something_else: {
88
+ another_value: 'foo
89
+ }
90
+ }
91
+
92
+ # It can be read like so:
93
+ config = File read_config: \"config.fy\"
94
+
95
+ # config is now:
96
+ <[
97
+ 'host => \"127.0.0.1\",
98
+ 'port => 1234,
99
+ 'names => ['foo, 'bar, 'baz],
100
+ 'something_else: <[
101
+ 'another_value => 'foo
102
+ ]>
103
+ ]>
104
+ """
105
+
106
+ File read: filename . eval to_hash_deep
107
+ }
108
+
42
109
  def writeln: x {
43
110
  """
44
111
  Writes a given argument as a String followed by a newline into the
@@ -1,11 +1,22 @@
1
1
  class FutureSend {
2
- read_slots: [ 'receiver, 'message, 'params ]
2
+ """
3
+ A @FutureSend@ gets created whenever an asynchronous message via the @@ operator gets sent, yielding a @FutureSend@.
4
+ They represent Futures/Promises in Fancy.
5
+
6
+ Example:
7
+ f = some_object @ some_method: some_argument
8
+ f class # => FutureSend
9
+ f value # => Value returned by some_method, but may block the current Thread if f hasn't completed yet.
10
+ """
11
+
12
+ read_slots: ('receiver, 'message, 'params)
3
13
  def initialize: @actor receiver: @receiver message: @message with_params: @params ([]) {
4
14
  @completed_mutex = Mutex new
5
15
  @condvar = ConditionVariable new
6
16
  @completed = false
7
17
  @failed = false
8
18
  @continuations = []
19
+ @fail_continuations = []
9
20
  @actor ! ('future, (@message, @params), self)
10
21
  }
11
22
 
@@ -27,9 +38,20 @@ class FutureSend {
27
38
 
28
39
  def completed! {
29
40
  @condvar broadcast
30
- unless: @failed do: {
31
- @continuations each: @{ call: [@value] }
41
+ if: @failed then: {
42
+ try {
43
+ @fail_continuations each: @{ call: [@fail_reason] }
44
+ } catch Exception => ex {
45
+ *stderr* println: "Error in FutureSend#completed! while calling fail continuations: #{ex}"
46
+ }
47
+ } else: {
48
+ try {
49
+ @continuations each: @{ call: [@value] }
50
+ } catch Exception => ex {
51
+ *stderr* println: "Error in FutureSend#completed! while calling success continuations: #{ex}"
52
+ }
32
53
  }
54
+ @fail_continuations = []
33
55
  @continuations = []
34
56
  }
35
57
 
@@ -113,6 +135,23 @@ class FutureSend {
113
135
  value send_future: message with_params: params
114
136
  }
115
137
 
138
+ def when_failed: block {
139
+ """
140
+ @block @Block@ to be registered as a continuation when @self fails.
141
+
142
+ Registers @block as a continuation to be called with @self's fail reason in case of failure.
143
+ """
144
+
145
+ { return nil } if: succeeded?
146
+ @completed_mutex synchronize: {
147
+ if: @failed then: {
148
+ block call: [@fail_reason]
149
+ } else: {
150
+ @fail_continuations << block
151
+ }
152
+ }
153
+ }
154
+
116
155
  def when_done: block {
117
156
  """
118
157
  @block @Block@ to be registered as a continuation when @self succeeds.
@@ -6,6 +6,9 @@ class Hash {
6
6
 
7
7
  include: Fancy Enumerable
8
8
 
9
+ alias_method: 'get_slot: for: 'at:
10
+ alias_method: 'set_slot:value: for: 'at:put:
11
+
9
12
  def [key] {
10
13
  """
11
14
  @key Key for value to get.
@@ -25,13 +28,44 @@ class Hash {
25
28
 
26
29
  Returns the value for a given key.
27
30
  If the key is not found, calls @else_block and returns the value it yields.
31
+
32
+ Example:
33
+ <['foo => 'bar]> at: 'foo else: { 42 } # => 'bar
34
+ <['foo => 'bar]> at: 'unknown else: { 42 } # => 42
35
+ <['nil => nil]> at: 'nil else: { 'not_found } # => nil
36
+ """
37
+
38
+ if: (includes?: key) then: {
39
+ at: key
40
+ } else: {
41
+ else_block call: [key]
42
+ }
43
+ }
44
+
45
+ alias_method: 'fetch:else: for: 'at:else:
46
+
47
+ def at: key else_put: else_block {
48
+ """
49
+ @key Key of value to get.
50
+ @else_block @Block@ that gets called and its value inserted into @self if @key not in @self.
51
+
52
+ Example:
53
+ h = <['foo => 'bar]>
54
+ h at: 'foo else_put: { 42 } # => 'bar
55
+ h['foo] # => 'bar
56
+ h at: 'undefined else_put: { 42 } # => 42
57
+ h['undefined] # => 42
28
58
  """
29
59
 
30
60
  if: (includes?: key) then: {
31
61
  at: key
32
- } else: else_block
62
+ } else: {
63
+ at: key put: $ else_block call: [key]
64
+ }
33
65
  }
34
66
 
67
+ alias_method: 'fetch:else_put: for: 'at:else_put:
68
+
35
69
  def each: block {
36
70
  """
37
71
  @block @Block@ to be called with each key and value in @self.
@@ -85,16 +119,6 @@ class Hash {
85
119
  map: |pair| { pair }
86
120
  }
87
121
 
88
- def to_s {
89
- """
90
- @return @String@ representation of @self.
91
-
92
- Returns a @String@ representation of a @Hash@.
93
- """
94
-
95
- to_a to_s
96
- }
97
-
98
122
  def to_object {
99
123
  """
100
124
  @return New @Object@ with slots defined by keys and values in @self.
@@ -139,22 +163,4 @@ class Hash {
139
163
 
140
164
  keys map: |k| { at: k }
141
165
  }
142
-
143
- def fetch: key else: else_block {
144
- """
145
- @key Key of value to get.
146
- @else_block @Block@ that gets called if @key not in @self.
147
-
148
- Example:
149
- <['foo => 'bar]> fetch: 'foo else: { 42 } # => 'bar
150
- <['foo => 'bar]> fetch: 'unknown else: { 42 } # => 42
151
- <['nil => nil]> fetch: 'nil else: { 'not_found } # => nil
152
- """
153
-
154
- if: (includes?: key) then: {
155
- at: key
156
- } else: {
157
- else_block call: [key]
158
- }
159
- }
160
166
  }
@@ -8,7 +8,7 @@ class HTML {
8
8
  html: @{
9
9
  head: @{ title: \"My Fancy Website\" }
10
10
  body: @{
11
- div: { id: \”main\” } with: \"Hello, Fancy World!\"
11
+ div: { id: \"main\" } with: \"Hello, Fancy World!\"
12
12
  }
13
13
  }
14
14
  } . to_s
@@ -16,4 +16,38 @@ class Integer {
16
16
  block call: [i + offset]
17
17
  }
18
18
  }
19
+
20
+ def times_try: block retry_with: retry_block ({}) {
21
+ """
22
+ @block @Block@ to be called and retries @self amount of times if exceptions get thrown.
23
+ @retry_block @Block@ to be called before retrying execution of @block. Defaults to an empty @Block@.
24
+ @return Return value of calling @block or raises an exception after @self tries.
25
+ Returns @nil if @self <= 0.
26
+
27
+ Tries to call a @Block@ @self amount of times, returning its return
28
+ value or raising the last exception raised frin @block after @self tries.
29
+
30
+ Example:
31
+ 2 times_try: {
32
+ # this code will be tried 2 times.
33
+ # if it succeeds the first time, simply return its value, otherwise try once more.
34
+ # if it still fails after 2 attempts, raises the exception thrown (in this case probably an IOError).
35
+ @connection readln
36
+ } retry_with: { @connection reconnect }
37
+ """
38
+
39
+ max_retries = self
40
+ { return nil } if: $ max_retries <= 0
41
+ value = nil
42
+ try {
43
+ value = block call: [max_retries]
44
+ } catch Exception => e {
45
+ max_retries = max_retries - 1
46
+ { e raise! } unless: $ max_retries > 0
47
+ retry_block call
48
+ retry
49
+ } finally {
50
+ return value
51
+ }
52
+ }
19
53
  }
@@ -6,7 +6,7 @@
6
6
 
7
7
  if: (ARGV size == 1) then: {
8
8
  ARGV for_options: ["-v", "--version"] do: {
9
- "Fancy " ++ FANCY_VERSION println
9
+ "Fancy #{Fancy VERSION}" println
10
10
  "(C) 2010, 2011, 2012 Christopher Bertels <chris@fancy-lang.org>" println
11
11
  System exit
12
12
  }
@@ -20,12 +20,13 @@ if: (ARGV size == 1) then: {
20
20
  " -I directory Add directory to Fancy's LOAD_PATH",
21
21
  " -e 'command' One line of Fancy code that gets evaluated immediately",
22
22
  " -c [filenames] Compile given files to Rubinius bytecode",
23
- " -cv [filenames] Compile given files to Rubinius bytecode verbosely (outputting the generated bytecode).",
23
+ " -cv [filenames] Compile given files to Rubinius bytecode verbosely (outputting the generated bytecode)",
24
24
  "",
25
25
  "Fancy package management:",
26
26
  " install [packagename] Install a Fancy package with a given name to $FANCYPACK_DIR",
27
- " install --deps Install dependencies specified in .fancypack file (expected in current directory).",
28
- " uninstall [packagename] Uninstall a Fancy package with a given name from $FANCYPACK_DIR"] println
27
+ " install --deps Install dependencies specified in .fancypack file (expected in current directory)",
28
+ " uninstall [packagename] Uninstall a Fancy package with a given name from $FANCYPACK_DIR",
29
+ " list-packages List all installed packages"] println
29
30
  System exit # quit when running --help
30
31
  }
31
32
  }
@@ -41,9 +42,14 @@ ARGV for_option: "-e" do: |eval_string| {
41
42
 
42
43
  ARGV for_option: "-c" do: {
43
44
  ARGV index: "-c" . if_true: |idx| {
44
- ARGV[[idx + 1, -1]] each: |filename| {
45
- "Compiling " ++ filename println
46
- Fancy Compiler compile_file: filename to: nil line: 1 print: false
45
+ ARGV[[idx + 1, -1]] . tap: |filenames| {
46
+ filenames each: |filename| {
47
+ "Compiling " ++ filename println
48
+ Fancy Compiler compile_file: filename to: nil line: 1 print: false
49
+ }
50
+ files = "file"
51
+ { files = "files" } if: $ filenames size != 1
52
+ "Compiled #{filenames size} #{files}." println
47
53
  }
48
54
  }
49
55
  System exit
@@ -1,5 +1,5 @@
1
1
  class Fancy {
2
- class MessageSink : BasicObject {
2
+ class MessageSink : Fancy BasicObject {
3
3
  """
4
4
  A MessageSink just swallows all messages that are sent to it.
5
5
  """
@@ -210,4 +210,14 @@ class Number {
210
210
  other
211
211
  }
212
212
  }
213
+
214
+ def random {
215
+ """
216
+ @return Random number between 0 and @self.
217
+
218
+ Returns a random number between 0 and @self.
219
+ """
220
+
221
+ rand(self)
222
+ }
213
223
  }
@@ -290,13 +290,16 @@ class Object {
290
290
  cond if_true: else_block else: block
291
291
  }
292
292
 
293
+ alias_method: 'unless:then: for: 'unless:do:
294
+ alias_method: 'unless:then:else: for: 'unless:do:else:
295
+
293
296
  def method: method_name {
294
297
  """
295
298
  @return @Method@ with @method_name defined for @self, or @nil.
296
299
  Returns the method with a given name for self, if defined.
297
300
  """
298
301
 
299
- method(message_name: method_name)
302
+ method(method_name message_name)
300
303
  }
301
304
 
302
305
  def documentation {
@@ -572,6 +575,29 @@ class Object {
572
575
  }
573
576
  }
574
577
 
578
+ def with_mutable_slots: slotnames do: block {
579
+ """
580
+ @slotnames @Fancy Enumerable@ of slotnames to be mutable within @block.
581
+ @block @Block@ to be called with @self.
582
+
583
+ Calls @block with @self while having slots defined in @slotnames
584
+ be mutable during execution of @block.
585
+ """
586
+
587
+ metaclass read_write_slots: slotnames
588
+ val = nil
589
+ try {
590
+ val = block call: [self]
591
+ } finally {
592
+ slotnames each: |s| {
593
+ metaclass undefine_method: s
594
+ metaclass undefine_method: "#{s}:"
595
+ }
596
+ return val
597
+ }
598
+ }
599
+ private: 'with_mutable_slots:do:
600
+
575
601
  def <=> other {
576
602
  """
577
603
  @other Other object to compare against.
@@ -1,3 +1,4 @@
1
+ require: "package/handler"
1
2
  require: "package/installer"
2
3
  require: "package/dependency_installer"
3
4
  require: "package/uninstaller"
@@ -29,6 +30,7 @@ class Fancy Package {
29
30
  DEFAULT_FANCY_ROOT = ENV["HOME"] ++ "/.fancy"
30
31
  DEFAULT_PACKAGES_PATH = DEFAULT_FANCY_ROOT ++ "/packages"
31
32
  DEFAULT_PACKAGES_LIB_PATH = DEFAULT_PACKAGES_PATH ++ "/lib"
33
+ DEFAULT_PACKAGES_BIN_PATH = DEFAULT_PACKAGES_PATH ++ "/bin"
32
34
 
33
35
  def self install: package_name version: version ('latest) {
34
36
  """
@@ -0,0 +1,56 @@
1
+ class Fancy Package {
2
+ class Handler {
3
+ read_write_slots: ('user, 'repository, 'version)
4
+
5
+ def initialize: @package_name install_path: @install_path (ENV["FANCY_PACKAGE_DIR"]) {
6
+ splitted = @package_name split: "/"
7
+ @user, @repository = splitted
8
+
9
+ # check for version, e.g. when passing in:
10
+ # $ fancy install bakkdoor/fyzmq=1.0.1
11
+ splitted = @repository split: "="
12
+ if: (splitted size > 1) then: {
13
+ @repository, @version = splitted
14
+ @package_name = @user + "/" + @repository
15
+ } else: {
16
+ @version = 'latest
17
+ }
18
+
19
+ @install_path if_nil: {
20
+ @install_path = Fancy Package DEFAULT_PACKAGES_PATH
21
+ Directory create!: $ Fancy Package DEFAULT_FANCY_ROOT
22
+ Directory create!: $ Fancy Package DEFAULT_PACKAGES_PATH
23
+ Directory create!: $ Fancy Package DEFAULT_PACKAGES_LIB_PATH
24
+ Directory create!: $ Fancy Package DEFAULT_PACKAGES_BIN_PATH
25
+ Directory create!: $ Fancy Package DEFAULT_PACKAGES_PATH ++ "/downloads"
26
+ }
27
+
28
+ @download_path = @install_path ++ "/downloads"
29
+ }
30
+
31
+ def load_fancypack: success_block else: else_block ({}) {
32
+ """
33
+ Loads the @.fancypack file within the downloaded package directory.
34
+ If no @.fancypack file is found, raise an error.
35
+ """
36
+
37
+ Dir glob(installed_path ++ "/*.fancypack") first if_true: |fpackfile| {
38
+ require: fpackfile
39
+ }
40
+
41
+ if: (Specification[@repository]) then: success_block else: else_block
42
+ }
43
+
44
+ def installed_path {
45
+ "#{@install_path}/#{@user}_#{@repository}-#{@version}"
46
+ }
47
+
48
+ def lib_path {
49
+ @install_path + "/lib"
50
+ }
51
+
52
+ def bin_path {
53
+ @install_path + "/bin"
54
+ }
55
+ }
56
+ }