From 6a7055424fb6789fbf11fb3af36702c7f5470e4c Mon Sep 17 00:00:00 2001 From: Adam Beynon Date: Mon, 2 May 2011 21:10:35 +0100 Subject: [PATCH] Initial cleanup commit --- LICENSE | 75 + README.md | 197 +++ examples/browser/README.md | 21 + examples/browser/Rakefile | 10 + examples/browser/browser.js | 46 + examples/browser/browser.rb | 46 + examples/browser/index.html | 11 + examples/dev_tools/index.html | 21 + examples/rquery/Rakefile | 10 + examples/rquery/index.html | 30 + examples/rquery/rquery_demo.js | 37 + examples/rquery/rquery_demo.rb | 37 + lib/core.rb | 109 ++ lib/core/array.rb | 1440 +++++++++++++++++ lib/core/basic_object.rb | 43 + lib/core/class.rb | 49 + lib/core/dir.rb | 26 + lib/core/enumerable.rb | 33 + lib/core/error.rb | 78 + lib/core/false_class.rb | 81 + lib/core/file.rb | 62 + lib/core/hash.rb | 765 +++++++++ lib/core/kernel.rb | 248 +++ lib/core/match_data.rb | 33 + lib/core/module.rb | 86 + lib/core/nil_class.rb | 54 + lib/core/numeric.rb | 411 +++++ lib/core/object.rb | 6 + lib/core/proc.rb | 56 + lib/core/range.rb | 17 + lib/core/regexp.rb | 61 + lib/core/string.rb | 375 +++++ lib/core/symbol.rb | 38 + lib/core/top_self.rb | 8 + lib/core/true_class.rb | 41 + spec/core/array/append_spec.rb | 31 + spec/core/array/assoc_spec.rb | 29 + spec/core/array/at_spec.rb | 37 + spec/core/array/clear_spec.rb | 22 + spec/core/array/collect_bang_spec.rb | 27 + spec/core/array/collect_spec.rb | 27 + spec/core/array/compact_spec.rb | 41 + spec/core/array/concat_spec.rb | 15 + spec/core/array/constructor_spec.rb | 14 + spec/core/array/each_spec.rb | 9 + spec/core/array/element_reference_spec.rb | 4 + spec/core/array/first_spec.rb | 35 + spec/core/array/include_spec.rb | 9 + spec/core/array/join_spec.rb | 6 + spec/core/array/last_spec.rb | 51 + spec/core/array/length_spec.rb | 6 + spec/core/array/map_spec.rb | 33 + spec/core/array/reverse_spec.rb | 6 + spec/core/array/select_spec.rb | 7 + .../builtin_constants_spec.rb | 7 + spec/core/false/and_spec.rb | 10 + spec/core/false/inspect_spec.rb | 6 + spec/core/false/or_spec.rb | 10 + spec/core/false/to_s_spec.rb | 6 + spec/core/false/xor_spec.rb | 10 + spec/core/file/join_spec.rb | 19 + spec/core/hash/assoc_spec.rb | 32 + spec/core/kernel/instance_eval_spec.rb | 0 spec/core/kernel/loop_spec.rb | 24 + spec/core/kernel/raise_spec.rb | 0 spec/core/module/attr_accessor_spec.rb | 28 + spec/core/number/lt_spec.rb | 12 + spec/core/string/sub_spec.rb | 24 + spec/core/true/and_spec.rb | 10 + spec/core/true/inspect_spec.rb | 6 + spec/core/true/or_spec.rb | 10 + spec/core/true/to_s_spec.rb | 6 + spec/core/true/xor_spec.rb | 10 + spec/language/and_spec.rb | 64 + spec/language/array_spec.rb | 70 + spec/language/block_spec.rb | 63 + spec/language/break_spec.rb | 26 + spec/language/case_spec.rb | 103 ++ spec/language/def_spec.rb | 21 + spec/language/eigenclass_spec.rb | 69 + spec/language/file_spec.rb | 13 + spec/language/fixtures/array.rb | 0 spec/language/fixtures/block.rb | 21 + spec/language/fixtures/break.rb | 41 + spec/language/fixtures/private.rb | 44 + spec/language/fixtures/super.rb | 293 ++++ spec/language/hash_spec.rb | 29 + spec/language/if_spec.rb | 54 + spec/language/loop_spec.rb | 11 + spec/language/metaclass_spec.rb | 21 + spec/language/method_spec.rb | 124 ++ spec/language/next_spec.rb | 25 + spec/language/or_spec.rb | 34 + spec/language/private_spec.rb | 38 + spec/language/redo_spec.rb | 24 + spec/language/regexp_spec.rb | 26 + spec/language/rescue_spec.rb | 20 + spec/language/return_spec.rb | 47 + spec/language/string_spec.rb | 25 + spec/language/super_spec.rb | 33 + spec/language/until_spec.rb | 157 ++ spec/language/variables_spec.rb | 155 ++ spec/language/while_spec.rb | 164 ++ spec/spec_helper.rb | 5 + 104 files changed, 7120 insertions(+) create mode 100644 LICENSE create mode 100755 README.md create mode 100644 examples/browser/README.md create mode 100644 examples/browser/Rakefile create mode 100644 examples/browser/browser.js create mode 100644 examples/browser/browser.rb create mode 100644 examples/browser/index.html create mode 100644 examples/dev_tools/index.html create mode 100644 examples/rquery/Rakefile create mode 100644 examples/rquery/index.html create mode 100644 examples/rquery/rquery_demo.js create mode 100644 examples/rquery/rquery_demo.rb create mode 100644 lib/core.rb create mode 100644 lib/core/array.rb create mode 100644 lib/core/basic_object.rb create mode 100644 lib/core/class.rb create mode 100644 lib/core/dir.rb create mode 100644 lib/core/enumerable.rb create mode 100644 lib/core/error.rb create mode 100644 lib/core/false_class.rb create mode 100644 lib/core/file.rb create mode 100644 lib/core/hash.rb create mode 100644 lib/core/kernel.rb create mode 100644 lib/core/match_data.rb create mode 100644 lib/core/module.rb create mode 100644 lib/core/nil_class.rb create mode 100644 lib/core/numeric.rb create mode 100644 lib/core/object.rb create mode 100644 lib/core/proc.rb create mode 100644 lib/core/range.rb create mode 100644 lib/core/regexp.rb create mode 100644 lib/core/string.rb create mode 100644 lib/core/symbol.rb create mode 100644 lib/core/top_self.rb create mode 100644 lib/core/true_class.rb create mode 100644 spec/core/array/append_spec.rb create mode 100644 spec/core/array/assoc_spec.rb create mode 100644 spec/core/array/at_spec.rb create mode 100644 spec/core/array/clear_spec.rb create mode 100644 spec/core/array/collect_bang_spec.rb create mode 100644 spec/core/array/collect_spec.rb create mode 100644 spec/core/array/compact_spec.rb create mode 100644 spec/core/array/concat_spec.rb create mode 100644 spec/core/array/constructor_spec.rb create mode 100644 spec/core/array/each_spec.rb create mode 100644 spec/core/array/element_reference_spec.rb create mode 100644 spec/core/array/first_spec.rb create mode 100644 spec/core/array/include_spec.rb create mode 100644 spec/core/array/join_spec.rb create mode 100644 spec/core/array/last_spec.rb create mode 100644 spec/core/array/length_spec.rb create mode 100644 spec/core/array/map_spec.rb create mode 100644 spec/core/array/reverse_spec.rb create mode 100644 spec/core/array/select_spec.rb create mode 100644 spec/core/builtin_constants/builtin_constants_spec.rb create mode 100644 spec/core/false/and_spec.rb create mode 100644 spec/core/false/inspect_spec.rb create mode 100644 spec/core/false/or_spec.rb create mode 100644 spec/core/false/to_s_spec.rb create mode 100644 spec/core/false/xor_spec.rb create mode 100644 spec/core/file/join_spec.rb create mode 100644 spec/core/hash/assoc_spec.rb create mode 100644 spec/core/kernel/instance_eval_spec.rb create mode 100644 spec/core/kernel/loop_spec.rb create mode 100644 spec/core/kernel/raise_spec.rb create mode 100644 spec/core/module/attr_accessor_spec.rb create mode 100644 spec/core/number/lt_spec.rb create mode 100644 spec/core/string/sub_spec.rb create mode 100644 spec/core/true/and_spec.rb create mode 100644 spec/core/true/inspect_spec.rb create mode 100644 spec/core/true/or_spec.rb create mode 100644 spec/core/true/to_s_spec.rb create mode 100644 spec/core/true/xor_spec.rb create mode 100644 spec/language/and_spec.rb create mode 100644 spec/language/array_spec.rb create mode 100644 spec/language/block_spec.rb create mode 100644 spec/language/break_spec.rb create mode 100644 spec/language/case_spec.rb create mode 100644 spec/language/def_spec.rb create mode 100644 spec/language/eigenclass_spec.rb create mode 100644 spec/language/file_spec.rb create mode 100644 spec/language/fixtures/array.rb create mode 100644 spec/language/fixtures/block.rb create mode 100644 spec/language/fixtures/break.rb create mode 100644 spec/language/fixtures/private.rb create mode 100644 spec/language/fixtures/super.rb create mode 100644 spec/language/hash_spec.rb create mode 100644 spec/language/if_spec.rb create mode 100644 spec/language/loop_spec.rb create mode 100644 spec/language/metaclass_spec.rb create mode 100644 spec/language/method_spec.rb create mode 100644 spec/language/next_spec.rb create mode 100644 spec/language/or_spec.rb create mode 100644 spec/language/private_spec.rb create mode 100644 spec/language/redo_spec.rb create mode 100644 spec/language/regexp_spec.rb create mode 100644 spec/language/rescue_spec.rb create mode 100644 spec/language/return_spec.rb create mode 100644 spec/language/string_spec.rb create mode 100644 spec/language/super_spec.rb create mode 100644 spec/language/until_spec.rb create mode 100644 spec/language/variables_spec.rb create mode 100644 spec/language/while_spec.rb create mode 100644 spec/spec_helper.rb diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000..7728bdfab8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,75 @@ +Copyright (C) 2011 by Adam Beynon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + + +Opalspec based on RSPEC, original license: +========================================== + +Copyright (c) 2005-2010 The RSpec Development Team + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Opalite contains Sizzle, original license: +========================================== + +MIT License +---- + +Copyright (c) 2009 John Resig + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + diff --git a/README.md b/README.md new file mode 100755 index 0000000000..0a10feffd6 --- /dev/null +++ b/README.md @@ -0,0 +1,197 @@ +Opal: Ruby runtime for javascript +================================= + +**Homepage**: [http://opalscript.org](http://opalscript.org) +**Github**: [http://github.com/adambeynon/opal](http://github.com/adambeynon/opal) +**Documentation**: [http://adambeynon.github.com/opal/index.html](http://adambeynon.github.com/opal/index.html) + +Description +----------- + +Opal is a ruby runtime and set of core libraries designed to run +directly on top of javascript. It supports the gem packaging system for +building ruby ready for the browser. It also uses therubyracer to +provide a command line REPL and environment for running ruby server-side +inside a javascript context. + +Installation +------------ + +Opal is distributed as a gem, so install with: + + $ gem install opal + +Alternativley, you may just clone this git repo. No building is +neccessary. + +Usage +----- + +The opal build tools can be used in three ways. As a simple repl, the +bundler tasks and the simple build tasks. + +**1. opal Command-line tool** + +In its current form, the `opal` executable is very simple - it provides +a simple ruby repl that uses therubyracer to provide an embedable +javascript context. If you have installed the gem, to run the REPL, +simply type: + + $ opal + +or if you have cloned this repository, type: + + $ bin/opal + +The REPL can be used like any other repl, and internally each command is +being compiled into javascript, and run in the context which opal is +already loaded into. + +**2. Builder Rake Task** + +To build simple ruby files (without a .gemspec) is to use the +BuilderTask class in a rake file. This can be done by adding the +following to a `Rakefile`: + + require 'opal' + + Opal::Rake::BuilderTask.new do |t| + t.files = 'ruby/**/*.rb' + t.out = 'js/ruby_code.js' + end + +When using the rake task, the `files` and `out` optional are usually +best required. The `files` will take an array, or a single string, and +can be globs. It is important to use relative paths here not absolute +paths. This will be default create a rake task called `opal` in your +rakefile, but you can rename it by passing another name into `new`. To +compile the javascript run: + + $ rake opal + +The out file will now contain the compiled ruby code. Run this in a +browser by including it into a html document, but make sure to include +the latest `opal.js` file first. + +**Main file** + +By default the builder task will automatically load the first listed +file in the `files` array when the file is loaded in the browser. The +`main` option allows you to specify another file: + + Opal::Rake::BuilderTask.new do |t| + t.files = ['ruby/file_1.rb', 'ruby/file_2.rb', 'ruby/file_3.rb'] + t.out = 'out.js' + t.main = 'ruby/file_2.rb' + end + +**File watching** + +To save manually compiling each time you change a file, the `watch` +option can be set to automatically recompile everytime a listed file is +modified. Observe the command line after using this task: + + Opal::Rake::BuilderTask.new do |t| + t.files = 'ruby/*.rb' + t.out = 'my_code.js' + t.watch = true + end + +**Examples** + +See the `examples/` directory for runnable demos. + +Built-in gems +------------ + +Opal uses gems as the main means of distributing and building code ready +for the browser. Opal uses its own gem system, and cannot access gems +included in the standard ruby installation. To aid this, the actual opal +gem includes several gems that offer the basic features. These gems are: + +**core** + +The core gem offers the ruby core library. It is mostly written in ruby, +but has a large proportion of inline javascript to make it as performant +as possible. Many of the classes are toll-free bridged to native +javascript objects, including Array, String, Numeric and Proc. Read the +[core library documentation](http://adambeynon.github.com/opal/gems/core/index.html) +for more information. + +**dev** + +The dev gem contains a ruby parser and compiler written in javascript. +It is a clone of the default opal parser written in ruby. This gem is in +the process of being replaced with the pure ruby version, which will be +compiled into javascript for this gem. This gem can be loaded into +javascript to allow in browser compilation of ruby sources from external +files or through html `script` tags. + +**json** + +The json gem included methods for parsing json strings and then +generating json objects from a string of json code. This gem aims to be +API compatible with the standard json library. Similarly to that +library, json objects are mapped to Hash, Array, true, false, nil, +String and Numeric as appropriate. See the +[JSON documentation](http://adambeynon.github.com/opal/gems/json/index.html) +for the full api. + +**rquery** + +RQuery is a port/abstraction of the jQuery javascript library for DOM +manipulation. jQuery is actually included as part of the gem, and top +level classes like Element, Document and Request are used to interact +with the document and elements. The api tries to stay as close to the +jquery interface as possible, but adds appropriate ruby syntax features +on top. See the [rquery documentation](http://adambeynon.github.com/opal/gems/rquery/index.html) +for the full api. + +**ospec** + +OSpec is a minimal clone of the ruby rspec library. It implements the +core features available in rspec, and works both from the command line +and in browser. The core, json and rquery gems all have tests/specs +written ready for ospec. See the [ospec guide](http://adambeynon.github.com/opal/gems/ospec/index.html) +to get started. + +Browser version +--------------- + +**Latest version**: 0.3.2 +**Minified and gzipped**: 42kb +**Download**: +[http://adambeynon.github.com/opal/opal.js](http://adambeynon.github.com/opal/opal.js) + +Opal is distributed ready for the browser in a single file `opal.js`. +This file contains the opal bootloader, the core library gem, the json +gem and the rquery gem. In addition to these, jquery is also included as +part of rquery. This self contained file is all that is needed to get +started with opal. + +Using the `BuilderTask` as described above, running that file is as +simple as using the given html file: + + + + + + + + + +Changelog +--------- + +- **31 March 2011**: 0.3.2 Release + - Added BuilderTask for easy building for simple projects. + - Amended build options in Builder to support new rake task. + +- **30 March 2011**: 0.3.1 Release + - Fix to make `opal` an executable + +- **30 March 2011**: 0.3.0 Release + - Major redesign of build tools to use v8 for server side opal + - Split all opal packages into actual gems + - File and Dir classes for both browser and v8 gem runtimes + diff --git a/examples/browser/README.md b/examples/browser/README.md new file mode 100644 index 0000000000..a474d66bfa --- /dev/null +++ b/examples/browser/README.md @@ -0,0 +1,21 @@ +Browser demo +============ + +This directory contains a very simple browser demo. It is meant to be +run from within this repo, as it relies on the opal.js build being +located in its doc/opal.js location. To run this example manually, alter +the html file to include your local copy of `opal.js`. + +Running the demo +---------------- + +There is a rake task `opal` in the bundled file, which will build the +`browser.rb` file to the needed `browser.js` file. The `Rakefile` is +setup to use the development version of opal, but to use the gem simply +comment out the first line. So, to build: + + $ rake opal + +From there, open `index.html` in your browser of choice and view the +debug console to view the additional output. + diff --git a/examples/browser/Rakefile b/examples/browser/Rakefile new file mode 100644 index 0000000000..cc9985a0d8 --- /dev/null +++ b/examples/browser/Rakefile @@ -0,0 +1,10 @@ +# Uncomment this line to use the gem version of opal +$:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib') + +require 'opal' + +Opal::Rake::BuilderTask.new do |t| + t.files = 'browser.rb' + t.out = 'browser.js' +end + diff --git a/examples/browser/browser.js b/examples/browser/browser.js new file mode 100644 index 0000000000..2814b4dfd4 --- /dev/null +++ b/examples/browser/browser.js @@ -0,0 +1,46 @@ +opal.register('browser.js', function(VM, self, __FILE__) { var nil = VM.Qnil, $class = VM.dc, $def = VM.dm, $symbol = VM.Y, $hash = VM.H, $block = VM.P, Qtrue = VM.Qtrue, Qfalse = VM.Qfalse;VM.mm(['puts', 'to_s', 'each', 'inspect', 'new', 'try_this', 'do_assign=', '[]=', 'is_this_true?', 'no_its_not!', '+', '[]', '-@', 'raise']);var __a, __b, title, __c, e;if (self['@cls'] == undefined) { self['@cls'] = nil; } +(__a = self).$m.puts(__a, "Hello, world! Running in the browser.."); + + +"All code is generated to keep the same line as the original ruby"; +(__a = self).$m.puts(__a, ("This code is generated from line " + (__b = 6).$m.to_s(__b) + ", in file: " + (__b = __FILE__).$m.to_s(__b))); + + +title = document.title; +(__a = self).$m.puts(__a, ("The document title is '" + (__b = title).$m.to_s(__b) + "'")); + + +(__a = [1, 2, 3, 4, 5], __b = [__a], ($block.p = function(self, a) { var __a; + return (__a = self).$m.puts(__a, a); +}).$self = self, ($block.f = __a.$m.each).apply(__a, __b)); + + +$class(self, nil, 'ClassA', function() { var self = this; + $def(self, 'method_missing', function(self, method_id, args) { var __a, __b, __c;args = [].slice.call(arguments, 2); + return (__a = self).$m.puts(__a, ("Tried to call '" + (__b = method_id).$m.to_s(__b) + "' with: " + (__b = (__c = args).$m.inspect(__c)).$m.to_s(__b))); + }, 0); +}, 0); + +self['@cls'] = (__b = rb_vm_cg(self, 'ClassA')).$m['new'](__b); +(__b = self['@cls']).$m.try_this(__b); +(__b = self['@cls']).$m['do_assign='](__b, "this won't work"); +(__b = self['@cls']).$m['[]='](__b, 'neither', $symbol('will_this')); +(__b = self['@cls']).$m['is_this_true?'](__b); +(__b = self['@cls']).$m['no_its_not!'](__b); +(__b = self['@cls']).$m['+'](__b, 'something to add'); + + +(__b = self).$m.puts(__b, (__a = (1)).$m['+'](__a, 2)); +(__b = self).$m.puts(__b, (__a = [1, 2, 3, 4]).$m['[]'](__a, 0)); +(__b = self).$m.puts(__b, (__a = [1, 2, 3, 4]).$m['[]'](__a, (__c = (2)).$m['-@'](__c))); + + +$class(self, rb_vm_cg(self, 'Exception'), 'CustomBrowserException', function() { var self = this;nil}, 0); + +try { + (__b = self).$m.raise(__b, rb_vm_cg(self, 'CustomBrowserException'), "some error happened");} catch (__err__) { + if (true){e = __err__; + (__b = self).$m.puts(__b, "caught error:"); + (__b = self).$m.puts(__b, (__a = e).$m.inspect(__a));} +}; }); +opal.require('browser'); diff --git a/examples/browser/browser.rb b/examples/browser/browser.rb new file mode 100644 index 0000000000..aae3ced90b --- /dev/null +++ b/examples/browser/browser.rb @@ -0,0 +1,46 @@ +# Browser demo. Check out your browsers' debug console to see these +puts "Hello, world! Running in the browser.." + +# Generated code is debug friendly +"All code is generated to keep the same line as the original ruby" +puts "This code is generated from line #{__LINE__}, in file: #{__FILE__}" + +# Opal supports inline javascript +title = `document.title` +puts "The document title is '#{title}'" + +# Blocks +[1, 2, 3, 4, 5].each do |a| + puts a +end + +# method_missing is fully supported with full ruby naming +class ClassA + def method_missing(method_id, *args) + puts "Tried to call '#{method_id}' with: #{args.inspect}" + end +end + +@cls = ClassA.new +@cls.try_this +@cls.do_assign = "this won't work" +@cls['neither'] = :will_this +@cls.is_this_true? +@cls.no_its_not! +@cls + 'something to add' + +# Full operator overloading +puts(1 + 2) +puts [1, 2, 3, 4][0] +puts [1, 2, 3, 4][-2] + +# Exceptions work on top of native error/try/catch/throw +class CustomBrowserException < Exception; end + +begin + raise CustomBrowserException, "some error happened" +rescue => e + puts "caught error:" + puts e.inspect +end + diff --git a/examples/browser/index.html b/examples/browser/index.html new file mode 100644 index 0000000000..a427ab7d44 --- /dev/null +++ b/examples/browser/index.html @@ -0,0 +1,11 @@ + + + + Browser demo for Opal + + + + + + + diff --git a/examples/dev_tools/index.html b/examples/dev_tools/index.html new file mode 100644 index 0000000000..301e9ad97f --- /dev/null +++ b/examples/dev_tools/index.html @@ -0,0 +1,21 @@ + + + + Dev tools demo + + + + + + + + + + diff --git a/examples/rquery/Rakefile b/examples/rquery/Rakefile new file mode 100644 index 0000000000..47c00d192f --- /dev/null +++ b/examples/rquery/Rakefile @@ -0,0 +1,10 @@ +# Uncomment this line to use the gem version of opal +$:.unshift File.join(File.dirname(__FILE__), '..', '..', 'lib') + +require 'opal' + +Opal::Rake::BuilderTask.new do |t| + t.files = 'rquery_demo.rb' + t.out = 'rquery_demo.js' +end + diff --git a/examples/rquery/index.html b/examples/rquery/index.html new file mode 100644 index 0000000000..7640bf43ef --- /dev/null +++ b/examples/rquery/index.html @@ -0,0 +1,30 @@ + + + + RQuery demo for Opal + + + + + + + + + Some Link + +
    +
  1. Item 1
  2. +
  3. Item 2
  4. +
  5. Item 3
  6. +
+ + + diff --git a/examples/rquery/rquery_demo.js b/examples/rquery/rquery_demo.js new file mode 100644 index 0000000000..83776f90ec --- /dev/null +++ b/examples/rquery/rquery_demo.js @@ -0,0 +1,37 @@ +opal.register('rquery_demo.js', function(VM, self, __FILE__) { var nil = VM.Qnil, $arg = VM.ac, $class = VM.dc, $def = VM.dm, $symbol = VM.Y, $hash = VM.H, $block = VM.P, Qtrue = VM.Qtrue, Qfalse = VM.Qfalse;VM.mm(['require', 'ready?', 'include', '[]', 'click', 'alert', 'add_class', 'find', 'each', 'puts', 'html']);var __a, __b; +(__a = self).$m.require(__a, 'rquery'); + + +(__a = VM.gg('$document'), __b = [__a], ($block.p = function(self) { return nil; + +}).$self = self, ($block.f = __a.$m['ready?']).apply(__a, __b)); + + +(__b = self).$m.include(__b, rb_vm_cg(self, 'RQuery')); + + +(__b = rb_vm_cg(self, 'Document'), __a = [__b], ($block.p = function(self) { return nil; + +}).$self = self, ($block.f = __b.$m['ready?']).apply(__b, __a)); + + +(__a = VM.gg('$document'), __b = [__a], ($block.p = function(self) { var __a, __b; + return (__a = (__b = VM.gg('$document')).$m['[]'](__b, 'a'), __b = [__a], ($block.p = function(self) { var __a; return (__a = self).$m.alert(__a, "Hello world!");}).$self = self, ($block.f = __a.$m.click).apply(__a, __b)); +}).$self = self, ($block.f = __a.$m['ready?']).apply(__a, __b)); + + +(__b = VM.gg('$document'), __a = [__b], ($block.p = function(self) { var __a, __b, __c, __d; + return (__a = (__b = (__c = (__d = VM.gg('$document')).$m['[]'](__d, '#orderedlist')).$m.add_class(__c, 'red')).$m.find(__b, 'li')).$m.add_class(__a, 'blue'); +}).$self = self, ($block.f = __b.$m['ready?']).apply(__b, __a)); + + +(__a = VM.gg('$document'), __b = [__a], ($block.p = function(self) { var __a, __b; + return (__a = (__b = VM.gg('$document')).$m['[]'](__b, 'li'), __b = [__a], ($block.p = function(self, elem) { var __a, __b;if (elem === undefined) { elem = nil; } return (__a = self).$m.puts(__a, (__b = elem).$m.html(__b));}).$self = self, ($block.f = __a.$m.each).apply(__a, __b)); +}).$self = self, ($block.f = __a.$m['ready?']).apply(__a, __b)); + +(__b = VM.gg('$document'), __a = [__b], ($block.p = function(self) { var __a, __b; + return (__a = VM.gg('$document'), __b = [__a], ($block.p = function(self) { var __a; + return (__a = self).$m.puts(__a, "clicked doc!"); + }).$self = self, ($block.f = __a.$m.click).apply(__a, __b)); +}).$self = self, ($block.f = __b.$m['ready?']).apply(__b, __a)); }); +opal.require('rquery_demo'); diff --git a/examples/rquery/rquery_demo.rb b/examples/rquery/rquery_demo.rb new file mode 100644 index 0000000000..cbda9fb22f --- /dev/null +++ b/examples/rquery/rquery_demo.rb @@ -0,0 +1,37 @@ +# We need to require rquery lib +require 'rquery' + +# Run code only when the document becomes ready +$document.ready? do + # $document is ready so do stuff +end + +# Including RQuery brings the core classes/modules top level +include RQuery + +# Document module now an alternative to $document +Document.ready? do + # Document ready +end + +# Show an alert when clicking a link +$document.ready? do + $document['a'].click { alert "Hello world!" } +end + +# Adding some css classes +$document.ready? do + $document['#orderedlist'].add_class('red').find('li').add_class 'blue' +end + +# Loop over each element individually +$document.ready? do + $document['li'].each { |elem| puts elem.html } +end + +$document.ready? do + $document.click do + puts "clicked doc!" + end +end + diff --git a/lib/core.rb b/lib/core.rb new file mode 100644 index 0000000000..a1c786779d --- /dev/null +++ b/lib/core.rb @@ -0,0 +1,109 @@ +class Module + + def private(*args) + `VM.private_methods(self, args);` + + self + end + + def public(*args) + `VM.public_methods(self, args);` + + self + end + + def include(*mods) + `var i = mods.length - 1, mod; + while (i >= 0) { + mod = mods[i]; + #{`mod`.append_features self}; + #{`mod`.included self}; + i--; + } + return self;` + end + + def append_features(mod) + `VM.include_module(mod, self);` + self + end + + def included(mod) + nil + end +end + +module Kernel + private + + # Try to load the library or file named `path`. An error is thrown if the + # path cannot be resolved. + # + # @param [String] path The path to load + # @return [true, false] + def require(path) + `VM.require(path);` + true + end + + # Prints each argument in turn to the browser console. Currently there + # is no use of `$stdout`, so it is hardcoded into this method to write + # to the console directly. + # + # @param [Array] args Objects to print using `to_s` + # @return [nil] + def puts(*args) + `for (var i = 0; i < args.length; i++) { + console.log(#{`args[i]`.to_s}); + }` + nil + end +end + + +class Object + include Kernel +end + +class Symbol + def to_s + `return self.toString();` + end +end + +class String + def to_s + `return self;` + end +end + +require 'core/basic_object' +require 'core/object' +require 'core/module' +require 'core/class' +require 'core/kernel' +require 'core/top_self' +require 'core/nil_class' +require 'core/true_class' +require 'core/false_class' +require 'core/enumerable' +require 'core/array' +require 'core/numeric' +require 'core/hash' +require 'core/error' +require 'core/string' +require 'core/symbol' +require 'core/proc' +require 'core/range' +require 'core/regexp' +require 'core/match_data' +require 'core/file' +require 'core/dir' + +`var platform = opal.platform;` +RUBY_PLATFORM = `platform.platform` +RUBY_ENGINE = `platform.engine` +RUBY_VERSION = `platform.version` + +ARGV = `platform.argv` + diff --git a/lib/core/array.rb b/lib/core/array.rb new file mode 100644 index 0000000000..6dd590ad3a --- /dev/null +++ b/lib/core/array.rb @@ -0,0 +1,1440 @@ +# Arrays are ordered collections, indexed by integers starting at `0`. +# Indexes may be negative, where `-1` represents the last item in the +# array, `-2` the last but one, etc. Arrays may be constructed by using a +# method like {Array.[]}, or by using an array literal: +# +# Array[1, 2, 3, 4, 5] # => [1, 2, 3, 4, 5] +# ['a', 'b', 'c', 'd'] # => ["a", "b", "c", "d"] +# +# Implementation details +# ---------------------- +# +# Ruby arrays are toll-free bridged to native javascript arrays, meaning +# that anywhere that a ruby array is required, a normal javascript array +# may be passed instead. The {Array} class infact makes use of a lot of +# the standard javascript functions on array prototypes to make its +# functionality as fast as possible. +# +# Due to the fact that arrays may be constructed in a javascript +# environment, and then passed through to a ruby method, Opal cannot +# guarantee that an array will not have a bad value. Bad values are +# those which ruby cannot send messages to, and therefore is an object +# that will raise an error when it is accessed by methods in {Array}, or +# any other object accessing an arrays elements. Bad values from +# javascript include the native `true`, `false`, `null` and `undefined` +# values from javascript, as well as any object literal. +# +# Ruby compatibility +# ------------------ +# +# As instances of {Array} are actually javascript arrays, they can +# perform all the same functionality as Rubyspec defines arrays should. +# While not 100% of methods are currently implemented, the missing +# methods are being added quickly. All implemented methods are listed in +# this file. Any method that is only partially implemented also contains +# a list of restrictions in its description. +# +# The main area of partialy implemented methods are the enumerating +# methods like {#each}, {#each\_index} and {#reverse\_each}. Rubyspec +# defines that they should return an Enumerator if no block is passed to +# that method. Currently this does not happen, and `self` is returned +# with no side effects. +# +# Custom subclasses of {Array} may also be defined, and this is +# implemented in {.allocate}, when the array is created using {.new}. +# Internally a native javascript array is still used, but its class and +# method table are swizzled. +# +# Finally the {Array} class does not include the Enumerable module. Its +# methods are mostly implemented directly on the Array class. The +# Enumerable module will be added shortly, and the relevant methods +# moved back into that module. +class Array + include Enumerable + + # Returns a new array populated with the given objects. + # + # @example + # + # Array['a', 'b', 'c'] # => ['a', 'b', 'c'] + # + # **FIXME** should support custom subclasses + # + # @param [Object] objs + # @return [Array] + def self.[](*objs) + objs + end + + # **FIXME** should support custom subclasses + def self.allocate + [] + end + + def initialize(*objs) + `for (var i = 0, length = objs.length; i < length; i++) { + self.push(objs[i]); + } + + return self;` + end + + # Returns a formatted, printable version of the array. {#inspect} is called + # on each of the elements and appended to the string. + # + # @return [String] string representation of the receiver + def inspect + `var description = []; + + for (var i = 0, length = self.length; i < length; i++) { + description.push(#{`self[i]`.inspect}); + } + + return '[' + description.join(', ') + ']';` + end + + # Returns a simple string version of the array. {#to_s} is applied to each + # of the child elements with no seperator. + def to_s + `var description = []; + + for (var i = 0, length = self.length; i < length; i++) { + description.push(#{`self[i]`.to_s}); + } + + return description.join('');` + end + + # Append - pushes the given object onto the end of this array. This + # expression returns the array itself, so several appends may be chained + # together. + # + # @example + # + # [1, 2] << "c" << "d" << [3, 4] + # # => [1, 2, "c", "d", [3, 4]] + # + # @param [Object] obj the object to append + # @return [Array] returns the receiver + def <<(obj) + `self.push(obj);` + self + end + + # Returns the number of elements in `self`. May be zero. + # + # @example + # + # [1, 2, 3, 4, 5].length + # # => 5 + # + # @return [Numeric] length + def length + `return self.length;` + end + + def size + `return self.length;` + end + + # Yields the block once for each element in `self`, passing that element as + # a parameter. + # + # If no block is given, an enumerator is returned instead. + # + # @example + # + # a = ['a', 'b', 'c'] + # a.each { |x| puts x } + # # => 'a' + # # => 'b' + # # => 'c' + # + # **TODO** needs to return enumerator for no block. + # + # @return [Array] returns the receiver + def each + raise "Array#each no block given" unless block_given? + + `for (var i = 0, ii = self.length; i < ii; i++) { + #{ yield `self[i]` }; + }` + self + end + + # Similar to {#each}, but also passes in the current element index to the + # block. + def each_with_index + raise "Array#each_with_index no block given" unless block_given? + + `for (var i = 0, ii = self.length; i < ii; i++) { + #{ yield `self[i]`, `i` }; + }` + self + end + + # Same as {#each}, but passes the index of the element instead of the + # element itself. + # + # If no block given, an enumerator is returned instead. + # + # **TODO** enumerator functionality not yet implemented. + # + # @example + # + # a = [1, 2, 3] + # a.each_index { |x| puts x } + # # => 0 + # # => 1 + # # => 2 + # + # @return [Array] returns receiver + def each_index + raise "Array#each_index no block given" unless block_given? + + `for (var i = 0, ii = self.length; i < ii; i++) {` + yield `i` + `}` + self + end + + # Append - pushes the given object(s) onto the end of this array. This + # expression returns the array itself, so several appends may be chained + # together. + # + # @example + # + # a = ['a', 'b', 'c'] + # a.push 'd', 'e', 'f' + # # => ['a', 'b', 'c', 'd', 'e', 'f' + # + # @param [Object] obj the object(s) to push onto the array + # @return [Array] returns the receiver + def push(*objs) + `for (var i = 0, length = objs.length; i < length; i++) { + self.push(objs[i]); + } + + return self;` + end + + # Returns the index of the first object in `self` such that it is `==` to + # `obj`. If a block is given instead of an argument, returns first object for + # which the block is true. Returns `nil` if no match is found. See also + # {#rindex}. + # + # @example + # + # a = ['a', 'b', 'c'] + # a.index('a') # => 0 + # a.index('z') # => nil + # a.index { |x| x == 'b' } # => 1 + # + # @param [Object] obj the object to look for + # @return [Numeric, nil] result + def index(obj) + `for (var i = 0, length = self.length; i < length; i++) { + if (#{`self[i]` == obj}.$r) { + return i; + } + } + + return nil;` + end + + # Concatenation - returns a new array built by concatenating the two arrays + # together to produce a third array. + # + # @example + # + # [1, 2, 3] + [4, 5] + # # => [1, 2, 3, 4, 5] + # + # @param [Array] other the array to concat with + # @return [Array] returns new concatenated array + def +(other) + `return self.concat(other);` + end + + # Difference. Creates a new array that is a copy of the original array, + # removing any items that also appear in `other`. + # + # @example + # + # [1, 2, 3, 3, 4, 4, 5] - [1, 2, 4] + # # => [3, 3, 5] + # + # @param [Array] other array to use for difference + # @return [Array] new array + def -(other) + raise "Array#- not yet implemented" + end + + # Equality. Two arrays are equal if they contain the same number of elements + # and if each element is equal to (according to {BasicObject#==} the + # corresponding element in the second array. + # + # @example + # + # ['a', 'c'] == ['a', 'c', 7] # => false + # ['a', 'c', '7'] == ['a', 'c', 7] # => true + # ['a', 'c', 7] == ['a', 'd', 'f'] # => false + # + # @param [Array] other array to compare self with + # @return [Boolean] if the arrays are equal + def ==(other) + `if (self.$hash() == other.$hash()) return Qtrue; + if (self.length != other.length) return Qfalse; + + for (var i = 0; i < self.length; i++) { + if (!#{`self[i]` == `other[i]`}.$r) { + return Qfalse; + } + } + + return Qtrue;` + end + + # Searches through an array whose elements are also arrays, comparing `obj` + # with their first element of each contained array using {BasicObject#==}. + # Returns the first contained array that matches (that is, the first + # associated array) or `nil` if no match is found. See also {#rassoc}. + # + # @example + # + # s1 = ['colors', 'red', 'blue', 'green'] + # s2 = ['letters', 'a', 'b', 'c'] + # s3 = 'foo' + # a = [s1, s2, s3] + # + # a.assoc 'letters' # => ['letters', 'a', 'b', 'c'] + # a.assoc 'foo' # => nil + def assoc(obj) + `var arg; + + for (var i = 0; i < self.length; i++) { + arg = self[i]; + + if (arg.length && #{`arg[0]` == obj}.$r) { + return arg; + } + } + + return nil;` + end + + # Returns the element at `index`. A negative index counts from the end of the + # receiver. Returns `nil` if the given index is out of range. See also {#[]}. + # + # @example + # + # a = ['a', 'b', 'c', 'd', 'e'] + # a.at 0 # => 'a' + # a.at -1 # => 'e' + # a.at 324 # => nil + # + # @param [Numeric] index the index to get + # @return [Object, nil] returns nil or the result + def at(idx) + `if (idx < 0) idx += self.length; + + if (idx < 0 || idx >= self.length) return nil; + return self[idx];` + end + + # Removes all elements from the receiver. + # + # @example + # + # a = ['a', 'b', 'c', 'd', 'e'] + # a.clear # => [] + # + # @return [Array] returns the receiver + def clear + `self.splice(0); + return self;` + end + + # Yields the block, passing in successive elements from the receiver, + # returning an array containing those elements for which the block returns a + # true value. + # + # @example + # + # a = [1, 2, 3, 4, 5, 6] + # a.select { |x| x > 4 } + # # => [5, 6] + # + # @return [Array] returns a new array of selected elements + def select + `var result = [], arg; + + for (var i = 0, ii = self.length; i < ii; i++) { + arg = self[i]; + + if (#{yield `arg`}.$r) { + result.push(arg); + } + } + + return result;` + end + + # Yields the block once for each element of the receiver. Creates a new array + # containing the values returned by the block. See also `Enumerable#collect`. + # + # @example + # + # a = ['a', 'b', 'c', 'd'] + # a.collect { |x| x + '!' } # => ['a!', 'b!', 'c!', 'd!'] + # a # => ['a', 'b', 'c', 'd'] + # + # @return [Array] new array + def collect + raise "Array#collect no block given" unless block_given? + + `var result = []; + + for (var i = 0, ii = self.length; i < ii; i++) { + result.push(#{ yield `self[i]` }); + } + + return result;` + end + + # alias_method 'map', 'collect' + + # Yields the block once for each element of `self`, replacing the element with + # the value returned by the block. See also `Enumerable#collect`. + # + # @example + # + # a = ['a', 'b', 'c', 'd'] + # a.collect { |x| x + '!' } + # # => ['a!', 'b!', 'c!', 'd!'] + # a + # # => ['a!', 'b!', 'c!', 'd!'] + # + # @return [Array] returns the receiver + def collect! + `for (var i = 0, ii = self.length; i < ii; i++) { + self[i] = #{yield `self[i]`}; + } + + return self;` + end + + # Duplicate. + def dup + `return self.slice(0);` + end + + # Returns a copy of the receiver with all nil elements removed + # + # @example + # + # ['a', nil, 'b', nil, 'c', nil].compact + # # => ['a', 'b', 'c'] + # + # @return [Array] new Array + def compact + `var result = [], length = self.length; + + for (var i = 0; i < length; i++) { + if (self[i] != nil) { + result.push(self[i]); + } + } + + return result;` + end + + # Removes nil elements from the receiver. Returns nil if no changes were made, + # otherwise returns self. + # + # @example + # + # ['a', nil, 'b', nil, 'c'].compact! + # # => ['a', 'b', 'c'] + # + # ['a', 'b', 'c'].compact! + # # => nil + # + # @return [Array, nil] returns either the receiver or nil + def compact! + `var length = self.length; + + for (var i = 0; i < length; i++) { + if (self[i] == nil) { + self.splice(i, 1); + i--; + } + } + + return length == self.length ? nil : self;` + end + + # Appends the elements of `other` to `self`. + # + # @example + # + # ['a', 'b'].concat ['c', 'd'] + # # => ['a', 'b', 'c', 'd'] + # + # @param [Array] other array to concat + # @return [Array] returns the receiver + def concat(other) + `var length = other.length; + + for (var i = 0; i < length; i++) { + self.push(other[i]); + } + + return self;` + end + + # Returns the number of elements. If an argument is given, counts the number + # of elements which equals to `obj`. If a block is given, counts the number of + # elements yielding a true value. + # + # @example + # + # ary = [1, 2, 4, 2] + # ary.count # => 4 + # ary.count(2) # =>2 + # + # @param [Object] obj object to check + # @return [Numeric] count or count of obj + def count(obj) + `if (obj != undefined) { + var total = 0; + + for (var i = 0; i < self.length; i++) { + if (#{`self[i]` == obj}.$r) { + total++; + } + } + + return total; + } else { + return self.length; + }` + end + + # Deletes items from `self` that are equal to `obj`. If any items are found, + # returns `obj`. If the item is not found, returns `nil`. If the optional code + # block is given, returns the result of block if the item is not found. + # + # @example + # + # a = ['a', 'b', 'b', 'b', 'c'] + # + # a.delete 'b' + # # => 'b' + # a + # # => ['a', 'c'] + # + # a.delete 'z' + # # => nil + # + # @param [Object] obj object to delete + # @return [Object, nil] returns obj or nil + def delete(obj) + `var length = self.length; + + for (var i = 0; i < self.length; i++) { + if (#{`self[i]` == obj}.$r) { + self.splice(i, 1); + i--; + } + } + + return length == self.length ? nil : obj;` + end + + # Deletes the element at the specified index, returning that element, or nil + # if the index is out of range. + # + # @example + # + # a = ['ant', 'bat', 'cat', 'dog'] + # a.delete_at 2 + # # => 'cat' + # a + # # => ['ant', 'bat', 'dog'] + # a.delete_at 99 + # # => nil + # + # @param [Numeric] idx the index to delete + # @return [Object, nil] returns the deleted object or nil + def delete_at(idx) + `if (idx < 0) idx += self.length; + if (idx < 0 || idx >= self.length) return nil; + var res = self[idx]; + self.splice(idx, 1); + return self;` + end + + # Deletes every element of `self` for which `block` evaluates to true. + # + # @example + # + # a = [1, 2, 3] + # a.delete_if { |x| x >= 2 } + # # => [1] + # + # @return [Array] returns amended receiver + def delete_if + `for (var i = 0, ii = self.length; i < ii; i++) { + if (#{yield `self[i]`}.$r) { + self.splice(i, 1); + i--; + ii = self.length; + } + } + return self;` + end + + + # Drop first `n` elements from receiver, and returns remaining elements in + # array. + # + # @example + # + # a = [1, 2, 3, 4, 5, 6] + # a.drop 3 + # # => [4, 5, 6] + # + # @param [Number] n number of elements to drop + # @return [Array] returns new array + def drop(n) + `if (n > self.length) return []; + return self.slice(n);` + end + + # Drop elements up to, but not including, the first element for which the + # block returns nil or false, and returns an array containing the remaining + # elements. + # + # @example + # + # a = [1, 2, 3, 4, 5, 6] + # a.drop_while { |i| i < 3 } + # # => [3, 4, 5, 6] + # + # @return [Array] returns a new array + def drop_while + `for (var i = 0; i < self.length; i++) { + if (!#{yield `self[i]`}.$r) { + return self.slice(i); + } + } + + return [];` + end + + # Returns `true` if the receiver contains no elements, `false` otherwise. + # + # @example + # + # [].empty? + # # => true + # + # @return [false, true] empty or not + def empty? + `return self.length == 0 ? Qtrue : Qfalse;` + end + + # Tries to return the element as position `index`. If the index lies outside + # the array, the first form throws an IndexError exception, the second form + # returns `default`, and the third form returns the value of invoking the + # block, passing in the index. Negative values of `index` count from the end + # of the array. + # + # @example First form + # + # a = [11, 22, 33, 44] + # a.fetch 1 + # # => 22 + # a.fetch -1 + # # => 44 + # + # @example Second form + # + # a.fetch 4, 'cat' + # # => 'cat' + # + # @example Third form + # + # a.fetch 4 { |i| i * i } + # # => 16 + # + # @param [Numeric] idx + # @param [Object] defaults + # @return [Object] returns result + def fetch(idx, defaults) + `var original = idx; + + if (idx < 0) idx += self.length; + if (idx < 0 || idx >= self.length) { + if (defaults == undefined) + return rb_raise("Index Error: Array#fetch"); + else if (__block__) + return #{yield `original`}; + else + return defaults; + } + + return self[idx];` + end + + # Returns the first element, or the first `n` elements, of the array. If the + # array is empty, the first form returns `nil`, and the second form returns an + # empty array. + # + # @example + # + # a = ['q', 'r', 's', 't'] + # a.first + # # => q + # a.first 2 + # # => ['q', 'r'] + # + # @param [Numeric] count number of elements + # @return [Object, Array] object or array of objects + def first(count = nil) + `if (count == nil) { + if (self.length == 0) return nil; + return self[0]; + } + return self.slice(0, count);` + end + + # Returns a new array that is a one-dimensional flattening of this array + # (recursively). That is, for evey element that is an array, extract its + # elements into the new array. If the optional `level` argument determines the + # level of recursion to flatten. + # + # @example + # + # s = [1, 2, 3] + # # => [a, 2, 3] + # t = [4, 5, 6, [7, 8]] + # # => [4, 5, 6, [7, 8]] + # a = [s, t, 9, 10] + # # => [[1, 2, 3], [4, 5, 6, [7, 8]], 9, 10] + # a.flatten + # # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # a = [1, 2, [3, [4, 5]]] + # a.flatten 1 + # # => [1, 2, 3, [4, 5]] + # + # @param [Numeric] level the level to flatten + # @return [Array] returns new array + def flatten(level = nil) + `var result = [], item; + + for (var i = 0; i < self.length; i++) { + item = self[i]; + + if (item.hasOwnProperty('length')) { + if (level == nil) + result = result.concat(#{`item`.flatten}); + else if (level == 0) + result.push(item); + else + result = result.concat(#{`item`.flatten `level - 1`}); + } else { + result.push(item); + } + } + + return result;` + end + + # Flattens the receiver in place. Returns `nil` if no modifications were made. + # If the optional level argument determines the level of recursion to flatten. + # + # @example + # + # a = [1, 2, [3, [4, 5]]] + # a.flatten! + # # => [1, 2, 3, 4, 5] + # a.flatten! + # # => nil + # a + # # => [1, 2, 3, 4, 5] + # + # @param [Number] level to flatten to + # @return [Array] returns the receiver + def flatten!(level = nil) + `var length = self.length; + var result = #{self.flatten level}; + self.splice(0); + + for (var i = 0; i < result.length; i++) { + self.push(result[i]); + } + + if (self.length == length) + return nil; + + return self;` + end + + # Returns true if the given object is present in `self`, false otherwise. + # + # @example + # + # a = ['a', 'b', 'c'] + # a.include? 'b' + # # => true + # a.include? 'z' + # # => false + def include?(member) + `for (var i = 0; i < self.length; i++) { + if (#{`self[i]` == member}.$r) { + return #{true}; + } + } + + return #{false};` + end + + # Replaces the contents of `self` with the contents of `other`, truncating or + # expanding if necessary. + # + # @example + # + # a = ['a', 'b', 'c', 'd', 'e'] + # a.replace ['x', 'y', 'z'] + # # => ['x', 'y', 'z'] + # a + # # => ['x', 'y', 'z'] + # + # @param [Array] other array to replace contents with + # @return [Array] returns the receiver + def replace(other) + `self.splice(0); + + for (var i = 0; i < other.length; i++) { + self.push(other[i]); + } + + return self;` + end + + # Inserts the given values before the element with the given index (which may + # be negative). + # + # @example + # + # a = ['a', 'b', 'c', 'd'] + # a.insert 2, 99 + # # => ['a', 'b', 99, 'c', 'd'] + # a.insert -2, 1, 2, 3 + # # => ['a', 'b', 99, 'c', 1, 2, 3, 'd'] + # + # @param [Numeric] idx the index for insertion + # @param [Object] objs objects to insert + # @return [Array] returns the receiver + def insert(idx, *objs) + `if (idx < 0) idx += self.length; + + if (idx < 0 || idx >= self.length) + rb_raise("IndexError: out of range"); + + self.splice.apply(self, [idx, 0].concat(objs)); + return self;` + end + + # Returns a string created by converting each element of the array to a string + # seperated by `sep`. + # + # @example + # + # ['a', 'b', 'c'].join + # # => 'abc' + # ['a', 'b', 'c'].join '-' + # # => 'a-b-c' + # + # @param [String] sep the separator + # @return [String] joined string + def join(sep = '') + `var result = []; + + for (var i = 0; i < self.length; i++) { + result.push(#{`self[i]`.to_s}); + } + + return result.join(sep);` + end + + # Deletes every element of `self` for which the block evaluates to false. + # + # @example + # + # a = [1, 2, 3, 4, 5, 6] + # a.keep_if { |x| x < 4 } + # # => [1, 2, 3] + # + # @return [Array] returns the receiver + def keep_if + `for (var i = 0; i < self.length; i++) { + if (!#{yield `self[i]`}.$r) { + self.splice(i, 1); + i--; + } + } + + return self;` + end + + # Return the last element(s) of `self`. If the array is empty, the first form + # returns `nil`. + # + # @example + # + # a = ['w', 'x', 'y', 'z'] + # a.last + # # => 'z' + # a.last 2 + # # => ['y', 'z'] + # + # @param [Number] count the number of items to get + # @return [Object, Array] result + def last(count = nil) + `if (count == nil) { + if (self.length == 0) return nil; + return self[self.length - 1]; + } else { + if (count > self.length) count = self.length; + return self.slice(self.length - count, self.length); + }` + end + + # Removes the last element from `self` and returns it, or `nil` if the array + # is empty. If a count is given, returns an array of the last `count` + # elements (or less). + # + # @example + # + # a = ['a', 'b', 'c', 'd'] + # a.pop + # # => 'd' + # a.pop 2 + # # => 'b', 'c' + # a + # # => ['a'] + # + # @param [Numeric] count number to pop + # @return [Array] returns popped items + def pop(count = nil) + `if (count == nil) { + if (self.length) return self.pop(); + return nil; + } else { + return self.splice(self.length - count, self.length); + }` + end + + # Searches through the array whose elements are also arrays. Compares `obj` + # with the second element of each contained array using `==`. Returns the + # first contained array that matches. + # + # @example + # + # a = [[1, 'one'], [2, 'two'], [3, 'three'], ['ii', 'two']] + # a.rassoc 'two' + # # => [2, 'two'] + # a.rassoc 'four' + # # => nil + # + # @param [Object] obj object to search for + # @return [Object, nil] result or nil + def rassoc(obj) + `var test; + + for (var i = 0; i < self.length; i++) { + test = self[i]; + if (test.hasOwnProperty('length') && test[1] != undefined) { + if (#{`test[1]` == obj}.$r) return test; + } + } + + return nil;` + end + + # Returns a new array containing the items in `self` for which the block is + # not true. See also `#delete_if`. + # + # @example + # + # a = [1, 2, 3, 4, 5, 6] + # a.reject { |x| x > 3 } + # # => [1, 2, 3] + # a + # # => [1, 2, 3, 4, 5, 6] + # + # @return [Array] returns the receiver + def reject + `var result = []; + + for (var i = 0; i < self.length; i++) { + if (!#{yield `self[i]`}.$r) { + result.push(self[i]); + } + } + + return result;` + end + + # Equivalent to `#delete_if!`, deleting elements from self for which the block + # evaluates to true, but returns nil if no changes were made. + # + # @example + # + # a = [1, 2, 3, 4, 5, 6] + # a.reject! { |x| x > 3 } + # # => [1, 2, 3] + # a.reject! { |x| x > 3 } + # # => nil + # a + # # => [1, 2, 3] + # + # @return [Array] returns receiver + def reject! + `var length = self.length; + + for (var i = 0; i < self.length; i++) { + if (#{yield `self[i]`}.$r) { + self.splice(i, 1); + i--; + } + } + + return self.length == length ? nil : self;` + end + + # Returns a new array containing the receiver's elements in reverse order. + # + # @example + # + # ['a', 'b', 'c'].reverse + # # => ['c', 'b', 'a'] + # [1].reverse + # # => [1] + # + # @return [Array] return new array + def reverse + `var result = []; + + for (var i = self.length - 1; i >= 0; i--) { + result.push(self[i]); + } + + return result;` + end + + # Reverses the receiver in place. + # + # @example + # + # a = ['a', 'b', 'c'] + # a.reverse! + # # => ['c', 'b', 'a'] + # a + # # => ['c', 'b', 'a'] + # + # @return [Array] returns the receiver + def reverse! + `var length = self.length / 2, tmp; + + for (var i = 0; i < length; i++) { + tmp = self[i]; + self[i] = self[self.length - (i + 1)]; + self[self.length - (i + 1)] = tmp; + } + + return self;` + end + + # Same as {#each}, but traverses the receiver in reverse order + # + # @example + # + # a = ['a', 'b', 'c'] + # a.reverse_each { |x| puts x } + # # => 'c' + # # => 'b' + # # => 'a' + # + # @return [Array] returns the receiver + def reverse_each + `for (var i = self.length - 1; i >= 0; i--) { + #{yield `self[i]`}; + } + + return self;` + end + + # Returns the index of the last object in self that is == to object. If a + # block is given instead of an argument, returns the first object for which + # block is true, starting from the last object. Returns `nil` if no match is + # found. + # + # @example + # + # a = ['a', 'b', 'b', 'b', 'c'] + # a.rindex 'b' + # # => 3 + # a.rindex 'z' + # # => nil + # a.rindex { |x| x == 'b' } + # # => 3 + # + # @return [Object, nil] returns result or nil + def rindex(obj = `undefined`) + `if (obj != undefined) { + for (var i = self.length - 1; i >=0; i--) { + if (#{`self[i]` == obj}.$r) { + return i; + } + } + } else if (true || __block__) { + rb_raise("array#rindex needs to do block action"); + } + + return nil;` + end + + # Invokes the block passing in successive elements from `self`, deleting the + # elements for which the block returns a false value. It returns `self` if + # changes were made, otherwise it returns `nil`. + # + # @example + # + # a = [1, 2, 3, 4, 5, 6] + # a.select! { |x| x > 4 } + # # => [5, 6] + # a.select! { |x| x > 4 } + # # => nil + # a + # # => [5, 6] + # + # @return [Array] returns receiver + def select! + `var length = self.length; + + for (var i = 0; i < self.length; i++) { + if (!#{yield `self[i]`}.$r) { + self.splice(i, 1); + i--; + } + } + + return self.length == length ? nil : self;` + end + + # Returns the first element of `self` and removes it (shifting all other + # elements down by one). Returns `nil` if the array is empty. + # + # If a number `n` is given, returns an array of the first n elements (or + # less), just like `#slice` does. + # + # @example + # + # a = ['a', 'b', 'c'] + # a.shift + # # => 'a' + # a + # # => ['b', 'c'] + # a = ['a', 'b', 'c'] + # a.shift 2 + # # => ['a', 'b'] + # a + # # => ['c'] + # + # @param [Numeric] count elements to shift + # @return [Array] result + def shift(count = nil) + `if (count != nil) + return self.splice(0, count); + + if (self.length) + return self.shift(); + + return nil;` + end + + # Deletes the element(s) given by an `index` (optionally with a length) or + # by a range. Returns the deleted object(s), or `nil` if the index is out of + # range. + # + # @example + # + # a = ['a', 'b', 'c'] + # a.slice! 1 + # # => 'b' + # a + # # => ['a', 'c'] + # a.slice! -1 + # # => 'c' + # a + # # => ['a'] + # a.slice! 100 + # # => nil + # + # **TODO** does not yet work with ranges + # + # @param [Range, Number] index to begin with + # @param [Number] length last index + # @return [Array, nil] result + def slice!(index, length = nil) + `var size = self.length; + + if (index < 0) index += size; + + if (index >= size || index < 0) return nil; + + if (length != nil) { + if (length <= 0 || length > self.length) return nil; + return self.splice(index, index + length); + } else { + return self.splice(index, 1)[0]; + }` + end + + # Returns first `count` elements from ary. + # + # @example + # + # a = [1, 2, 3, 4, 5, 6] + # a.take 3 + # # => [1, 2, 3] + # + # @return [Array] array of elements + def take(count) + `return self.slice(0, count);` + end + + + # Passes elements to the block until the block returns a false value, then + # stops iterating and returns an array of all prior elements. + # + # @example + # + # a = [1, 2, 3, 4, 5, 6] + # a.take_while { |i| i < 3 } + # # => [1, 2] + # + # @return [Array] new array with elements + def take_while + `var result = [], arg; + + for (var i = 0, ii = self.length; i < ii; i++) { + arg = self[i]; + if (#{yield `arg`}.$r) { + result.push(self[i]); + } else { + break; + } + } + + return result;` + end + + # Returns the receiver. + # + # @example + # + # a = [1, 2, 3] + # a.to_a + # # => [1, 2, 3] + # + # @return [Array] returns the receiver + def to_a + self + end + + # Returns a new array by removing duplicate values in `self`. + # + # @example + # + # a = ['a', 'a', 'b', 'b', 'c'] + # a.uniq + # # => ['a', 'b', 'c'] + # a + # # => ['a', 'a', 'b', 'b', 'c'] + # + # @return [Array] + def uniq + `var result = [], seen = []; + + for (var i = 0; i < self.length; i++) { + var test = self[i], hash = test.$hash(); + if (seen.indexOf(hash) == -1) { + seen.push(hash); + result.push(test); + } + } + + return result;` + end + + # Removes duplicate elements from `self`. Returns `nil` if no changes are + # made (that is, no duplicates are found). + # + # @example + # + # a = ['a', 'a', 'b', 'b', 'c'] + # a.uniq! + # # => ['a', 'b', 'c'] + # a.uniq! + # # => nil + # + # @return [Array] returns receiver + def uniq! + `var seen = [], length = self.length; + + for (var i = 0; i < self.length; i++) { + var test = self[i], hash = test.$hash(); + if (seen.indexOf(hash) == -1) { + seen.push(hash); + } else { + self.splice(i, 1); + i--; + } + } + + return self.length == length ? nil : self;` + end + + # Prepends objects to the front of `self`, moving other elements upwards. + # + # @example + # + # a = ['b', 'c', 'd'] + # a.unshift 'a' + # # => ['a', 'b', 'c', 'd'] + # a.unshift 1, 2 + # # => [1, 2, 'a', 'b', 'c', 'd'] + # + # @param [Object] objs objects to add + # @return [Array] returns the receiver + def unshift(*objs) + `for (var i = objs.length - 1; i >= 0; i--) { + self.unshift(objs[i]); + } + + return self;` + end + + # Set intersection - Returns a new array containing elements common to the + # two arrays, with no duplicates. + # + # @example + # + # [1, 1, 3, 5] & [1, 2, 3] + # # => [1, 3] + # + # @param [Array] other second array to intersect + # @return [Array] new intersected array + def &(other) + `var result = [], seen = []; + + for (var i = 0; i < self.length; i++) { + var test = self[i], hash = test.$hash(); + + if (seen.indexOf(hash) == -1) { + for (var j = 0; j < other.length; j++) { + var test_b = other[j], hash_b = test_b.$hash(); + + if ((hash == hash_b) && seen.indexOf(hash) == -1) { + seen.push(hash); + result.push(test); + } + } + } + } + + return result;` + end + + # Repitition - When given a string argument, acts the same as {#join}. + # Otherwise, returns a new array build by concatenating the `num` copies of + # self. + # + # @example With Number + # + # [1, 2, 3] * 3 + # # => [1, 2, 3, 1, 2, 3, 1, 2, 3] + # + # @example With String + # + # [1, 2, 3] * ',' + # # => '1,2,3' + # + # @param [String, Number] num string or number used to join or concat + # @return [String, Array] depending on argument + def *(arg) + `if (typeof arg == 'string') { + return #{self.join `arg`}; + } else { + var result = []; + for (var i = 0; i < parseInt(arg); i++) { + result = result.concat(self); + } + + return result; + }` + end + + # Element Reference - Returns the element at `index`, or returns a subarray + # at index and counting for length elements, or returns a subarray if index + # is a range. Negative indecies count backward from the end of the array (-1 + # is the last element). Returns `nil` if the index (or starting index) are + # out of range. + # + # @example + # + # a = ['a', 'b', 'c', 'd', 'e'] + # a[2] + a[0] + a[1] # => 'cab' + # a[6] # => nil + # a[1, 2] # => ['b', 'c'] + # a[1..3] # => ['b', 'c', 'd'] + # a[4..7] # => ['e'] + # a[6..10] # => nil + # a[-3, 3] # => ['c', 'd', 'e'] + # a[5] # => nil + # a[5, 1] # => [] + # a[5..10] # => [] + # + # **TODO** does not yet work with ranges + # + # @param [Range, Numeric] index to begin + # @param [Numeric] length last index + # @return [Array, Object, nil] result + def [](index, length = `undefined`) + `var size = self.length; + + if (index < 0) index += size; + + if (index >= size || index < 0) return nil; + + if (length != undefined) { + if (length <= 0) return []; + return self.slice(index, index + length); + } else { + return self[index]; + }` + end + + # Element reference setting. + # + # **TODO** need to expand functionlaity. + def []=(index, value) + `return self[index] = value;` + end +end + diff --git a/lib/core/basic_object.rb b/lib/core/basic_object.rb new file mode 100644 index 0000000000..d5c6c39386 --- /dev/null +++ b/lib/core/basic_object.rb @@ -0,0 +1,43 @@ +# BasicObject is the root object in opal. Even {Object} inherits from +# {BasicObject}. Instances of BasicObject (or subclasses of) are useful +# as they give almost a clean interface in which the absolute minimum of +# methods are defined on it. It therefore becomes useful for such +# applications as HashStructs. +class BasicObject + + def initialize(*a) + # ... + end + + def ==(other) + `if (self == other) return Qtrue; + return Qfalse;` + end + + def equal?(other) + self == other + end + + def __send__(method_id, *args, &block) + `args.unshift(self); + var method = self.$m[#{method_id.to_s}]; + + + if ($block.f == arguments.callee) { + $block.f = method; + } + + return method.apply(self, args);` + end + + def instance_eval(&block) + raise ArgumentError, "block not supplied" unless block_given? + `block(self);` + self + end + + def method_missing(sym, *args) + raise NoMethodError, "undefined method `#{sym}` for #{self.inspect}" + end +end + diff --git a/lib/core/class.rb b/lib/core/class.rb new file mode 100644 index 0000000000..07550cf3c4 --- /dev/null +++ b/lib/core/class.rb @@ -0,0 +1,49 @@ +class Class < Module + + def allocate + `return new VM.RObject(self, VM.T_OBJECT);` + end + + # This needs to support forwaring blocks to .initialize + # + # if (VM.P.f == arguments.callee) { + # VM.P.f = obj.$m.initialize + # } + # + def new(*args) + obj = allocate + + `if ($block.f == arguments.callee) { + $block.f = obj.$m.initialize; + }` + + obj.initialize *args + obj + end + + def superclass + `var sup = self.$super; + + if (!sup) { + if (self == VM.BasicObject) return nil; + throw new Error('RuntimeError: uninitialized class'); + } + + return sup;` + end + + # Use the receiver class as a wrapper around the given native + # prototype. This should be the actual prototype itself rather than + # the constructor function. For example, the core Array class may use + # this like so: + # + # class Array + # native_prototype `Array.prototype` + # end + # + # @return [Class] Returns the receiver + def native_prototype(prototype) + `return VM.native_prototype(prototype, self);` + end +end + diff --git a/lib/core/dir.rb b/lib/core/dir.rb new file mode 100644 index 0000000000..0627c37799 --- /dev/null +++ b/lib/core/dir.rb @@ -0,0 +1,26 @@ + +class Dir + # OPAL_FS simply points to the main opal.fs namespace. This might be the + # default fs in the browser, or may be overriden within the opal build tools + # when running on top of the gem runtime. Both are compatible interfaces. + `var OPAL_FS = VM.opal.fs;` + + # Returns a string that is the current working directory for this process. + # + # @return [String] + def self.getwd + `return OPAL_FS.cwd;` + end + + # Returns a string that is the current working directory for this process. + # + # @return [String] + def self.pwd + `return OPAL_FS.cwd;` + end + + def self.[](*a) + `return OPAL_FS.glob.apply(OPAL_FS, a);` + end +end + diff --git a/lib/core/enumerable.rb b/lib/core/enumerable.rb new file mode 100644 index 0000000000..13663579b7 --- /dev/null +++ b/lib/core/enumerable.rb @@ -0,0 +1,33 @@ +# The {Enumerable} module. +module Enumerable + + # Returns an array containing the items in the receiver. + # + # @example + # + # (1..4).to_a # => [1, 2, 3, 4] + # [1, 2, 3].to_a # => [1, 2, 3] + # + # @return [Array] + def to_a + ary = [] + each { |arg| `ary.push(arg);` } + ary + end + + alias_method :entries, :to_a + + def collect(&block) + raise "Enumerable#collect no block given" unless block_given? + `var result = [];` + + each do |*args| + `result.push(#{block.call *args});` + end + + `return result;` + end + + alias_method :map, :collect +end + diff --git a/lib/core/error.rb b/lib/core/error.rb new file mode 100644 index 0000000000..84695bd451 --- /dev/null +++ b/lib/core/error.rb @@ -0,0 +1,78 @@ +# Instances of {Exception} and its subclasses are used to hold error +# information between `raise` calls, `begin` blocks and `rescue` statements. +# `Exceptions` will hold an optional `message`, which is a description +# of the error that occured. The exception `type` is also useful, and is +# just the name of the class that is a decendant of `Exception`. +# Subclasses are generally made of `StandardError`, but a subclass of +# any core `Exception` class is valid. +# +# Implementation details +# ---------------------- +# +# Inside opal, exceptions are instances of the native javascript `Error` +# objects. This allows an efficient means to "piggy-back" the javascript +# try/catch/finally statements, as well as the `throw` statement for +# actually raising exceptions. +# +# Subclasses of `Exception` can also be used on top of `Error`, and the +# correct class and method tables for these subclasses are set in +# {.allocate}. +# +# Exceptions cannot be altered once created, so their message is +# permanently fixed. To improve debugging opal, once an exception it +# {#initialize}, the native error has its `.message` property set to a +# descriptive name with the format `ErrorClassName: error_message`. This +# helps to observe top level error statements appearing in debug tools. +# The original error message may be received using the standard +# {#message} or {#to_s} methods. +# +# Accessing the `backtrace` of an exception is platform dependant. It is +# fully supported on the server side v8 context, but differs in the +# browser context as some browsers have better support than others. The +# backtrace should not be relied on, and is supported purely on a +# platform to platform basis. +class Exception + + # We also need to set err.$m to the right method table incase a subclass adds + # custom methods.. just get this from the klass: self. + def self.allocate + `var err = new Error(); + err.$klass = self; + err.$m = self.$m_tbl; + return err;` + end + + def initialize(message = '') + @message = message + `return self.message = self.$klass.__classid__ + ': ' + message;` + end + + def message + @message || `self.message` + end + + def inspect + `return "#<" + self.$klass.__classid__ + ": '" + #{@message} + "'>";` + end + + def to_s + @message + end +end + +class StandardError < Exception; end +class RuntimeError < Exception; end +class LocalJumpError < StandardError; end +class TypeError < StandardError; end + +class NameError < StandardError; end +class NoMethodError < NameError; end +class ArgumentError < StandardError; end + +class ScriptError < Exception; end +class LoadError < ScriptError; end + +class IndexError < StandardError; end +class KeyError < IndexError; end +class RangeError < StandardError; end + diff --git a/lib/core/false_class.rb b/lib/core/false_class.rb new file mode 100644 index 0000000000..58dc068283 --- /dev/null +++ b/lib/core/false_class.rb @@ -0,0 +1,81 @@ +# Instances of `FalseClass` represent logically false values. There may +# only be one instance of `FalseClass`, which is the global value +# `false`. Attempts to create a new instance will yield an error. +# `FalseClass` provides methods to perform logical operations with other +# ruby objects. +# +# Implementation details +# ---------------------- +# +# Due to the way messages are passed inside opal, `false` is not +# actually toll-free bridged onto the native javascript `false` value. +# In javascript, `false` and `true` are both instances of the Boolean +# type, which means they would need to share the same method_table in +# opal, which would remove their ability to be true instances of Rubys' +# `TrueClass` or `FalseClass`. +# +# As javascripts `false` is not actually the value used in opal, passing +# the native `false` value will cause errors when messages are sent to +# it. Within a file directly loaded by opal, `Qfalse` is a free variable +# that points to the actualy ruby instance of this class. This variable +# may be passed around freely. +class FalseClass + + # Returns a string representation of `false`, which is simply + # `"false"`. + # + # @example + # + # false.to_s # => "false" + # + # @return [String] + def to_s + "false" + end + + # And; This always returns `false`. + # + # @example + # + # false & true # => false + # false & nil # => false + # false & false # => false + # + # @return [false] + def &(other) + false + end + + # Or; If `other` is `false` or `nil`, returns `false`, otherwise + # returns `true`. + # + # @example + # + # false & false # => false + # false & nil # => false + # false & true # => true + # false & [1, 2, 3] # => true + # + # @return [true, false] + def |(other) + `return other.$r ? Qtrue : Qfalse;` + end + + # Exclusive Or; If `other` is `false` or `nil`, then it returns + # `false`, otherwise returns `true`. + # + # @example + # + # false & false # => false + # false & nil # => false + # false & true # => true + # false & [1, 2, 3] # => true + # + # @return [true, false] + def ^(other) + `return other.$r ? Qtrue : Qfalse;` + end +end + +FALSE = false + diff --git a/lib/core/file.rb b/lib/core/file.rb new file mode 100644 index 0000000000..cb9ac55e07 --- /dev/null +++ b/lib/core/file.rb @@ -0,0 +1,62 @@ + +class File + # Use either the browser fs namespace or overriden gem interface. + `var OPAL_FS = VM.opal.fs;` + + # Converts the given `file_name` into its absolute path. The current working + # directory is used as the reference unless the `dir_string` is given, in + # which case that is used. + # + # @param [String] file_name + # @param [String] dir_string + # @return [String] + def self.expand_path(file_name, dir_string = nil) + if dir_string + `return OPAL_FS.expand_path(file_name, dir_string);` + else + `return OPAL_FS.expand_path(file_name);` + end + end + + # Returns a new string constructed by joining the given args with the default + # file separator. + # + # @param [String] str + # @return [String] + def self.join(*str) + `return OPAL_FS.join.apply(OPAL_FS, str);` + end + + # Returns all the components of the given `file_name` except for the last + # one. + # + # @param [String] file_name + # @return [String] + def self.dirname(file_name) + `return OPAL_FS.dirname(file_name);` + end + + # Returns the extension of the given filename. + # + # @param [String] file_name + # @return [String] + def self.extname(file_name) + `return OPAL_FS.extname(file_name);` + end + + # Returns the last path component of the given `file_name`. If a suffix is + # given, and is present in the path, then it is removed. This is useful for + # removing file extensions, for example. + # + # @param [String] file_name + # @param [String] suffix + # @return [String] + def self.basename(file_name, suffix) + `return OPAL_FS.basename(file_name, suffix);` + end + + def self.exist?(path) + `return OPAL_FS.exist_p(path) ? Qtrue : Qfalse;` + end +end + diff --git a/lib/core/hash.rb b/lib/core/hash.rb new file mode 100644 index 0000000000..078e0f2f2c --- /dev/null +++ b/lib/core/hash.rb @@ -0,0 +1,765 @@ +# A `Hash` is a collection of key-value pairs. It is similar to an array except +# that indexing is done via arbitrary keys of any object type, not an integer +# index. Hahses enumerate their values in the order that the corresponding keys +# were inserted. +# +# Hashes have a default value that is returned when accessing keys that do not +# exist in the hash. By default, that valus is `nil`. +# +# Implementation details +# ---------------------- +# +# An Opal hash is actually toll-free bridged to a custom javascript +# prototype called RHash. This is all handled internally and is actually +# just an implementation detail, and is only done for the convenience of +# construction through literals and methods such as {Hash.new}. +# +# Although its syntax is similar to that of an object literal in +# javascript, it is very important to know that they are completely +# different, and a javascript/json object cannot be used in place of a +# ruby hash. All ruby objects require, at minimum, a `.$m` property +# which is their method table. When trying to send a message to a non +# ruby object, like a javascript object, errors will start occuring when +# this method table is not found. +# +# Hash and the JSON gem contain methods for converting native objects into +# `Hash` instances, so those should be used if you need to use objects from +# an external javascript library. +# +# Ruby compatibility +# ------------------ +# +# `Hash` implements the majority of methods from the ruby standard +# library, and those that are not implemented are being added +# constantly. +# +# The `Enumerable` module is not yet implemented in opal, so most of the +# relevant methods used by `Hash` are implemented directly into this class. +# When `Enumerable` gets implemented, the relevant methods will be moved +# back into that module. +class Hash + + # Creates a new hash populated with the given objects. + # + # @return [Hash] + def self.[](*args) + `return VM.H.apply(null, args);` + end + + # Returns a new array populated with the values from `self`. + # + # @example + # + # { :a => 1, :b => 2 }.values + # # => [1, 2] + # + # @return [Array] + def values + `var result = [], length = self.$keys.length; + + for (var i = 0; i < length; i++) { + result.push(self.$assocs[self.$keys[i].$hash()]); + } + + return result;` + end + + # Returns the contents of this hash as a string. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # # => "{ \"a\" => 100, \"b\" => 200 }" + # + # @return [String] + def inspect + `var description = [], key, value; + + for (var i = 0; i < self.$keys.length; i++) { + key = self.$keys[i]; + value = self.$assocs[key.$hash()]; + description.push(#{`key`.inspect} + '=>' + #{`value`.inspect}); + } + + return '{' + description.join(', ') + '}';` + end + + # Returns a simple string representation of the hash's keys and values. + # + # @return [String] + def to_s + `var description = [], key, value; + + for (var i = 0; i < self.$keys.length; i++) { + key = self.$keys[i]; + value = self.$assocs[key.$hash()]; + description.push(#{`key`.inspect} + #{`value`.inspect}); + } + + return description.join('');` + end + + # Yields the block once for each key in `self`, passing the key-value pair + # as parameters. + # + # @example + # + # { 'a' => 100, 'b' => 200 }.each { |k, v| puts "#{k} is #{v}" } + # # => "a is 100" + # # => "b is 200" + # + # @return [Hash] returns the receiver + def each + `var keys = self.$keys, values = self.$assocs, length = keys.length, key; + + for (var i = 0; i < length; i++) { + try { + key = keys[i]; + #{yield `key`, `values[key.$hash()]`}; + } catch (e) { + switch (e.$keyword) { + case 2: + return e['@exit_value']; + default: + throw e; + } + } + } + + return self;` + end + + # Searches through the hash comparing `obj` with the key ysing ==. Returns the + # key-value pair (two element array) or nil if no match is found. + # + # @example + # + # h = { 'a' => [1, 2, 3], 'b' => [4, 5, 6] } + # h.assoc 'a' + # # => ['a', [1, 2, 3]] + # h.assoc 'c' + # # => nil + # + # @param [Object] obj key to search for + # @return [Array, nil] result or nil + def assoc(obj) + `var key, keys = self.$keys, length = keys.length; + + for (var i = 0; i < length; i++) { + key = keys[i]; + if (#{`key` == obj}.$r) { + return [key, self.$assocs[key.$hash()]]; + } + } + + return nil;` + end + + # Equality - Two hashes are equal if they each contain the same number of keys + # and if each key-value paid is equal, accordind to {BasicObject#==}, to the + # corresponding elements in the other hash. + # + # @example + # + # h1 = { 'a' => 1, 'c' => 2 } + # h2 = { 7 => 35, 'c' => 2, 'a' => 1 } + # h3 = { 'a' => 1, 'c' => 2, 7 => 35 } + # h4 = { 'a' => 1, 'd' => 2, 'f' => 35 } + # + # h1 == h2 # => false + # h2 == h3 # => true + # h3 == h4 # => false + # + # @param [Hash] other the hash to compare with + # @return [true, false] + def ==(other) + `if (self === other) return Qtrue; + if (!other.$keys || !other.$assocs) return Qfalse; + if (self.$keys.length != other.$keys.length) return Qfalse; + + for (var i = 0; i < self.$keys.length; i++) { + var key1 = self.$keys[i], assoc1 = key1.$hash(); + + if (!other.$assocs.hasOwnProperty(assoc1)) + return Qfalse; + + var assoc2 = other.$assocs[assoc1]; + + if (!#{`self.$assocs[assoc1]` == `assoc2`}.$r) + return Qfalse; + } + + return Qtrue;` + end + + # Element reference - retrieves the `value` object corresponding to the `key` + # object. If not found, returns the default value. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h['a'] + # # => 100 + # h['c'] + # # => nil + # + # @param [Object] key the key to look for + # @return [Object] result or default value + def [](key) + `var assoc = key.$hash(); + + if (self.$assocs.hasOwnProperty(assoc)) + return self.$assocs[assoc]; + + return self.$default;` + end + + # Element assignment - Associates the value given by 'value' with the key + # given by 'key'. `key` should not have its value changed while it is used as + # a key. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h['a'] = 9 + # h['c'] = 4 + # h + # # => { 'a' => 9, 'b' => 200, 'c' => 4 } + # + # @param [Object] key the key for hash + # @param [Object] value the value for the key + # @return [Object] returns the set value + def []=(key, value) + `var assoc = key.$hash(); + + if (!self.$assocs.hasOwnProperty(assoc)) + self.$keys.push(key); + + return self.$assocs[assoc] = value;` + end + + # Remove all key-value pairs from `self`. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h.clear + # # => {} + # + # @return [Hash] + def clear + `self.$keys = []; + self.$assocs = {}; + + return self;` + end + + # Returns the default value for the hash. + def default + `return self.$default;` + end + + # Sets the default value - the value returned when a key does not exist. + # + # @param [Object] obj the new default + # @return [Object] returns the new default + def default=(obj) + `return self.$default = obj;` + end + + # Deletes and returns a key-value pair from self whose key is equal to `key`. + # If the key is not found, returns the default value. If the optional code + # block is given and the key is not found, pass in the key and return the + # result of the block. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h.delete 'a' + # # => 100 + # h.delete 'z' + # # => nil + # + # @param [Object] key the key to delete + # #return [Object] returns value or default value + def delete(key) + `var assoc = key.$hash(); + + if (self.$assocs.hasOwnProperty(assoc)) { + var ret = self.$assocs[assoc]; + delete self.$assocs[assoc]; + self.$keys.splice(self.$keys.indexOf(key), 1); + return ret; + } + + return self.$default;` + end + + # Deletes every key-value pair from `self` for which the block evaluates to + # `true`. + # + # @example + # + # h = { 'a' => 100, 'b' => 200, 'c' => 300 } + # h.delete_if { |k, v| key >= 'b' } + # # => { 'a' => 100 } + # + # @return [Hash] returns the receiver + def delete_if + `var key, value; + + for (var i = 0; i < self.$keys.length; i++) { + try { + key = self.$keys[i]; + value = self.$assocs[key.$hash()]; + + if (#{yield `key`, `value`}.$r) { + delete self.$assocs[key.$hash()]; + self.$keys.splice(i, 1); + i--; + } + } catch (e) { + switch (e.$keyword) { + case 2: + return e['@exit_value']; + default: + throw e; + } + } + } + + return self;` + end + + # Yields the block once for each key in `self`, passing the key as a param. + # + # @example + # + # h = { 'a' => 100, 'b' => 200, 'c' => 300 } + # h.each_key { |key| puts key } + # # => 'a' + # # => 'b' + # # => 'c' + # + # @return [Hash] returns receiver + def each_key + `var key; + + for (var i = 0; i < self.$keys.length; i++) { + try { + key = self.$keys[i]; + #{yield `key`}; + } catch (e) { + switch (e.$keyword) { + case 2: + return e['@exit_value']; + default: + throw e; + } + } + } + + return self;` + end + + # Yields the block once for each key in self, passing the associated value + # as a param. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h.each_value { |a| puts a } + # # => 100 + # # => 200 + # + # @return [Hash] returns the receiver + def each_value + `var val; + + for (var i = 0; i < self.$keys.length; i++) { + try { + val = self.$assocs[self.$keys[i].$hash()]; + #{yield `val`}; + } catch (e) { + switch (e.$keyword) { + case 2: + return e['@exit_value']; + default: + throw e; + } + } + } + + return self;` + end + + # Returns `true` if `self` contains no key-value pairs, `false` otherwise. + # + # @example + # + # {}.empty? + # # => true + # + # @return [true, false] + def empty? + `return self.$keys.length == 0 ? Qtrue : Qfalse;` + end + + # Returns a value from the hash for the given key. If the key can't be found, + # there are several options; with no other argument, it will raise a + # KeyError exception; if default is given, then that will be returned, if the + # optional code block if specified, then that will be run and its value + # returned. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h.fetch 'a' # => 100 + # h.fetch 'z', 'wow' # => 'wow' + # h.fetch 'z' # => KeyError + # + # @param [Object] key the key to lookup + # @param [Object] defaults the default value to return + # @return [Object] value from hash + def fetch(key, defaults = `undefined`) + `var value = self.$assocs[key.$hash()]; + + if (value != undefined) + return value; + else if (defaults == undefined) + rb_raise('KeyError: key not found'); + else + return defaults;` + end + + # Returns a new array that is a one dimensional flattening of this hash. That + # is, for every key or value that is an array, extraxt its elements into the + # new array. Unlike {Array#flatten}, this method does not flatten + # recursively by default. The optional `level` argument determines the level + # of recursion to flatten. + # + # @example + # + # a = { 1 => 'one', 2 => [2, 'two'], 3 => 'three' } + # a.flatten + # # => [1, 'one', 2, [2, 'two'], 3, 'three'] + # a.flatten(2) + # # => [1, 'one', 2, 2, 'two', 3, 'three'] + # + # @param [Numeric] level the level to flatten until + # @return [Array] flattened hash + def flatten(level = 1) + `var result = [], key, value; + + for (var i = 0; i < self.$keys.length; i++) { + key = self.$keys[i]; + value = self.$assocs[key.$hash()]; + result.push(key); + + if (value instanceof Array) { + if (level == 1) { + result.push(value); + } else { + var tmp = #{`value`.flatten `level - 1`}; + result = result.concat(tmp); + } + } else { + result.push(value); + } + } + + return result;` + end + + # Returns `true` if the given `key` is present in `self`. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h.has_key? 'a' + # # => true + # h.has_key? 'c' + # # => false + # + # @param [Object] key the key to check + # @return [true, false] + def has_key?(key) + `if (self.$assocs.hasOwnProperty(key.$hash())) + return Qtrue; + + return Qfalse;` + end + + # Returns `true` if the given `value` is present for some key in `self`. + # + # @example + # + # h = { 'a' => 100 } + # h.has_value? 100 + # # => true + # h.has_value? 2020 + # # => false + # + # @param [Object] value the value to check for + # @return [true, false] + def has_value?(value) + `var key, value; + + for (var i = 0; i < self.$keys.length; i++) { + key = self.$keys[i]; + val = self.$assocs[key.$hash()]; + + if (#{`value` == `val`}.$r) + return Qtrue; + } + + return Qfalse;` + end + + # Replaces the contents of `self` with the contents of `other`. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h.replace({ 'c' => 200, 'd' => 300 }) + # # => { 'c' => 200, 'd' => 300 } + # + # @param [Hash] other hash to replace with + # @return [Hash] returns receiver + def replace(other) + `self.$keys = []; self.$assocs = {}; + + for (var i = 0; i < other.$keys.length; i++) { + var key = other.$keys[i]; + var val = other.$assocs[key.$hash()]; + self.$keys.push(key); + self.$assocs[key.$hash()] = val; + } + + return self;` + end + + # Returns a new hash created by using `self`'s vales as keys, and keys as + # values. + # + # @example + # + # h = { 'n' => 100, 'm' => 100, 'y' => 300 } + # h.invert + # # => { 100 => 'm', 300 => 'y' } + # + # @return [Hash] inverted hash + def invert + + end + + # Returns the key for the given value. If not found, returns `nil`. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h.key 200 + # # => 'b' + # h.key 300 + # # => nil + # + # @param [Object] value the value to check for + # @return [Object] key or nil + def key(value) + `var key, val; + + for (var i = 0; i < self.$keys.length; i++) { + key = self.$keys[i]; + val = self.$assocs[key.$hash()]; + + if (#{`value` == `val`}.$r) { + return key; + } + } + + return nil;` + end + + # Returns a new array populated with the keys from this hash. See also + # {#values}. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h.keys + # # => ['a', 'b'] + # + # @return [Array] + def keys + `return self.$keys.slice(0);` + end + + # Returns the number of key-value pairs in the hash. + # + # @example + # + # h = { 'a' => 100, 'b' => 200 } + # h.length + # # => 2 + # + # @return [Numeric] + def length + `return self.$keys.length;` + end + + # Returns a new hash containing the contents of `other` and `self`. If no + # block is specified, the value for the entries with duplicate keys will be + # that of `other`. Otherwise the value for each duplicate key is determined + # by calling the block with the key and its value in self, and its value in + # other. + # + # @example + # + # h1 = { 'a' => 100, 'b' => 200 } + # h2 = { 'b' => 300, 'c' => 400 } + # h1.merge h2 + # # => {'a' => 100, 'b' => 300, 'c' => 400 } + # h1 + # # => {'a' => 100, 'b' => 200} + # + # @param [Hash] other hash to merge with + # #return [Hash] returns new hash + def merge(other) + `var result = $opal.H() , key, val; + + for (var i = 0; i < self.$keys.length; i++) { + key = self.$keys[i], val = self.$assocs[key.$hash()]; + + result.$keys.push(key); + result.$assocs[key.$hash()] = val; + } + + for (var i = 0; i < other.$keys.length; i++) { + key = other.$keys[i], val = other.$assocs[key.$hash()]; + + if (!result.$assocs.hasOwnProperty(key.$hash())) { + result.$keys.push(key); + } + + result.$assocs[key.$hash()] = val; + } + + return result;` + end + + # Merges the contents of `other` into `self`. If no block is given, entries + # with duplicate keys are overwritten with values from `other`. + # + # @example + # + # h1 = { 'a' => 100, 'b' => 200 } + # h2 = { 'b' => 300, 'c' => 400 } + # h1.merge! h2 + # # => { 'a' => 100, 'b' => 300, 'c' => 400 } + # h1 + # # => { 'a' => 100, 'b' => 300, 'c' => 400 } + # + # @param [Hash] other + # @return [Hash] + def merge!(other) + `var key, val; + + for (var i = 0; i < other.$keys.length; i++) { + key = other.$keys[i]; + val = other.$assocs[key.$hash()]; + + if (!self.$assocs.hasOwnProperty(key.$hash())) { + self.$keys.push(key); + } + + self.$assocs[key.$hash()] = val; + } + + return self;` + end + + # Searches through the hash comparing `obj` with the value using ==. Returns + # the first key-value pair, as an array, that matches. + # + # @example + # + # a = { 1 => 'one', 2 => 'two', 3 => 'three' } + # a.rassoc 'two' + # # => [2, 'two'] + # a.rassoc 'four' + # # => nil + # + # @param [Object] obj object to check + # @return [Array] + def rassoc(obj) + `var key, val; + + for (var i = 0; i < self.$keys.length; i++) { + key = self.$keys[i]; + val = self.$assocs[key.$hash()]; + + if (#{`val` == obj}.$r) + return [key, val]; + } + + return nil;` + end + + # Removes a key-value pair from the hash and returns it as a two item array. + # Returns the default value if the hash is empty. + # + # @example + # + # h = { 'a' => 1, 'b' => 2 } + # h.shift + # # => ['a', 1] + # h + # # => { 'b' => 2 } + # {}.shift + # # => nil + # + # @return [Array, Object] + def shift + `var key, val; + + if (self.$keys.length > 0) { + key = self.$keys[0]; + val = self.$assocs[key.$hash()]; + + self.$keys.shift(); + delete self.$assocs[key.$hash()]; + return [key, val]; + } + + return self.$default;` + end + + # Convert self into a nested array of `[key, value]` arrays. + # + # @example + # + # h = { 'a' => 1, 'b' => 2, 'c' => 3 } + # h.to_a + # # => [['a', 1], ['b', 2], ['c', 3]] + # + # @return [Array] + def to_a + `var result = [], key, value; + + for (var i = 0; i < self.$keys.length; i++) { + key = self.$keys[i]; + value = self.$assocs[key.$hash()]; + result.push([key, value]); + } + + return result;` + end + + # Returns self. + # + # @return [Hash] + def to_hash + self + end +end + diff --git a/lib/core/kernel.rb b/lib/core/kernel.rb new file mode 100644 index 0000000000..2382c85568 --- /dev/null +++ b/lib/core/kernel.rb @@ -0,0 +1,248 @@ +# The {Kernel} module is directly included into {Object} and provides a +# lot of the core object functionality. It is not, however, included in +# {BasicObject}. +module Kernel + + + def instance_variable_defined?(name) + `name = #{name.to_s}; + return self[name] == undefined ? Qfalse : Qtrue;` + end + + def instance_variable_get(name) + `name = #{name.to_s}; + return self[name] == undefined ? nil : self[name];` + end + + def instance_variable_set(name, value) + `name = #{name.to_s}; + return self[name] = value;` + end + + # Returns `true` if a block was given to the current method, `false` + # otherwise. + # + # @NOTE: In opal, this is actually a fake method. block_given? is inlined + # for efficiency and implementation details. + # + # @return [true, false] + def block_given? + false + end + + def to_a + [self] + end + + def tap + raise LocalJumpError, "no block given" unless block_given? + yield self + self + end + + def kind_of?(klass) + `var search = self.$klass; + + while (search) { + if (search == klass) { + return Qtrue; + } + + search = search.$super; + } + + return Qfalse;` + end + + def is_a?(klass) + kind_of? klass + end + + def nil? + false + end + + # Returns `true` if the method with the given id exists on the receiver, + # `false` otherwise. + # + # Implementation Details + # ---------------------- + # Opals' internals are constructed so that when a method is initially called, + # a fake method is created on the root basic object, so that any subsequent + # calls to that method on an object that has not defined it, will yield a + # method_missing behaviour. For this reason, fake methods are tagged with a + # `.$rbMM` property so that they will not be counted when this method checks + # if a given method has been defined. + # + # @param [String, Symbol] method_id + # @return [Boolean] + def respond_to?(method_id) + `var method = self.$m[#{`method_id`.to_s}]; + + if (method && !method.$rbMM) { + return Qtrue; + } + + return Qfalse;` + end + + def ===(other) + self == other + end + + def send(method_id, *args, &block) + `args.unshift(self); + +var method = self.$m[#{method_id.to_s}]; + + if ($block.f == arguments.callee) { + $block.f = method; + } +return method.apply(self, args); + ` + end + + def class + `return VM.class_real(self.$klass);` + end + + # Returns a random number. If max is `nil`, then the result is 0. Otherwise + # returns a random number between 0 and max. + # + # @example + # + # rand # => 0.918378392234 + # rand # => 0.283842929289 + # rand 10 # => 9 + # rand 100 # => 21 + # + # @param [Numeric] max + # @return [Numeric] + def rand(max = `undefined`) + `if (max != undefined) + return Math.floor(Math.random() * max); + else + return Math.random();` + end + + def __id__ + `return self.$hash();` + end + + def object_id + `return self.$hash();` + end + + # Returns a simple string representation of the receiver object. The id shown in the string + # is not just the object_id, but it is mangled to resemble the format output by ruby, which + # is basically a hex number. + # + # FIXME: proper hex output needed + def to_s + "#<#{`VM.class_real(self.$klass)`}:0x#{`(self.$hash() * 400487).toString(16)`}>" + end + + def inspect + to_s + end + + def const_set(name, value) + `return rb_const_set(VM.class_real(self.$klass), name, value);` + end + + def const_defined?(name) + false + end + + def =~(obj) + nil + end + + def extend(mod) + `VM.extend_module(self, mod);` + nil + end + + # @group Private methods + + private + + # Raises an exception. If given a string, this method will raise a + # RuntimeError with the given string as a message. Otherwise, if the first + # parameter is a subclass of Exception, then the method will raise a new + # instance of the given exception class with the string as a message, if it + # exists, or a fdefault message otherwise. + # + # @example String message + # + # raise "some error" + # # => RuntimeError: some error + # + # @example Exception subclass + # + # raise StandardError, "something went wrong" + # # => StandardError: something went wrong + # + # @param [Exception, String] exception + # @param [String] + # @return [nil] + def raise(exception, string = nil) + `var msg = nil, exc; + + if (typeof exception == 'string') { + msg = exception; + exc = #{RuntimeError.new `msg`}; + } else if (#{`exception`.kind_of? Exception}.$r) { + exc = exception; + } else { + if (string != nil) msg = string; + exc = #{`exception`.new `msg`}; + } + VM.raise_exc(exc);` + end + + # def fail(exception, string = nil) + # raise exception, string + # end + alias_method :fail, :raise + + # Repeatedly executes the given block. + # + # @example + # + # loop do + # puts "this will infinetly repeat" + # end + # + # @return [Object] returns the receiver. + def loop + `while (true) { + #{yield}; + } + + return self;` + end + + # Simple equivalent to `Proc.new`. Returns a new proc from the given block. + # + # @example + # + # proc { puts "a" } + # # => # + # + # @return [Proc] + def proc(&block) + raise ArgumentError, + "tried to create Proc object without a block" unless block_given? + block + end + + def lambda(&block) + raise ArgumentError, + "tried to create Proc object without a block" unless block_given? + `return VM.lambda(block);` + end + + # @endgroup +end + diff --git a/lib/core/match_data.rb b/lib/core/match_data.rb new file mode 100644 index 0000000000..931da7f3e6 --- /dev/null +++ b/lib/core/match_data.rb @@ -0,0 +1,33 @@ +class MatchData + + def inspect + "#" + end + + def to_s + `return self.$data[0];` + end + + def length + `return self.$data.length;` + end + + def size + `return self.$data.length;` + end + + def to_a + `return [].slice.call(self.$data, 0);` + end + + def [](index) + `var length = self.$data.length; + + if (index < 0) index += length; + + if (index >= length || index < 0) return nil; + + return self.$data[index];` + end +end + diff --git a/lib/core/module.rb b/lib/core/module.rb new file mode 100644 index 0000000000..ee9404ecdc --- /dev/null +++ b/lib/core/module.rb @@ -0,0 +1,86 @@ +# Implements the core functionality of modules. This is inherited from +# by instances of {Class}, so these methods are also available to +# classes. +class Module + + def name + `return self.__classid__;` + end + + def ===(obj) + obj.kind_of? self + end + + def define_method(method_id, &block) + raise LocalJumpError, "no block given" unless block_given? + `VM.define_method(self, #{method_id.to_s}, block)` + nil + end + + def attr_accessor(*attrs) + attr_reader *attrs + attr_writer *attrs + end + + def attr_reader(*attrs) + `for (var i = 0; i < attrs.length; i++) { + var attr = attrs[i]; + var method_id = #{`attr`.to_s}; + + VM.define_method(self, method_id, + new Function('self', 'var iv = self["@' + method_id + '"]; return iv === undefined ? nil : iv;')); + + } + + return nil;` + end + + def attr_writer(*attrs) + `for (var i = 0; i < attrs.length; i++) { + var attr = attrs[i]; + var method_id = #{`attr`.to_s}; + + VM.define_method(self, method_id + '=', + new Function('self', 'val', 'return self["@' + method_id + '"] = val;')); + + } + + return nil;` + end + + def alias_method(new_name, old_name) + `VM.alias_method(self, new_name, old_name);` + self + end + + def to_s + `return self.__classid__;` + end + + def const_set(id, value) + `return rb_vm_cs(self, #{id.to_s}, value);` + end + + def class_eval(str = nil, &block) + if block_given? + `block(self)` + else + raise "need to compile str" + end + end + + def module_eval(str = nil, &block) + class_eval str, &block + end + + + def protected + self + end + + def extend(mod) + `VM.extend_module(self, mod)` + nil + end +end + diff --git a/lib/core/nil_class.rb b/lib/core/nil_class.rb new file mode 100644 index 0000000000..0ca52c1bb2 --- /dev/null +++ b/lib/core/nil_class.rb @@ -0,0 +1,54 @@ +# `NilClass` has a single instance `nil`. No more instances of this +# class can be created, and attempts to do so will yield an error. +# +# Implementation details +# ---------------------- +# +# `nil` is an actual ruby object, and is not just a reference to the +# native `null` or `undefined` values in javascript. Sending messages to +# `nil` in ruby is a very useful feature of ruby, and this would not be +# possible in opal if `nil` was just the `null` or `undefined` value. +# +# To access `nil` from javascript, `Qnil` points to this instance and is +# available in both ruby and javascript sources loaded by opal. +class NilClass + + def to_i + 0 + end + + def to_f + 0.0 + end + + def to_s + "" + end + + def to_a + [] + end + + def inspect + "nil" + end + + def nil? + true + end + + def &(other) + false + end + + def |(other) + `return other.$r ? Qtrue : Qfalse;` + end + + def ^(other) + `return other.$r ? Qtrue : Qfalse;` + end +end + +NIL = nil + diff --git a/lib/core/numeric.rb b/lib/core/numeric.rb new file mode 100644 index 0000000000..fc1d710e00 --- /dev/null +++ b/lib/core/numeric.rb @@ -0,0 +1,411 @@ +# Numeric objects represent numbers in opal. Unlike ruby, this class is +# used to represent both floats and integers, and there is currently no +# class representing bignums. Numeric values may only be made using +# their literal representations: +# +# 1 # => 1 +# 2.0 # => 2 +# 0.05 # => 0.05 +# +# Implementation details +# ---------------------- +# +# Opal numbers are toll-free bridged to native javascript numbers so +# that anywhere a ruby number is expected, a native javascript number +# may be used in its place. Javascript only has a single class/prototype +# to represent numbers, meaning that all integers and floats contain the +# same prototype. For this reason, Opal follows suit and implements all +# numbers as a direct instance of {Numeric}. +# +# Floats and Integers could be truley represented using wrappers, but +# this would **dramatically** increase performance overhead at the very +# core parts of the opal runtime. This overhead is too great, and the +# benefits would be too few. +# +# Ruby compatibility +# ------------------ +# +# As discussed, {Numeric} is the only class used to represent numbers in +# opal. Most of the useful methods from `Fixnum`, `Integer` and `Float` +# are implemented on this class. +# +# It is also important to note that there is no distinguishment between +# floats and integers, so that, `1` and `1.0` are exactly equal. +# +# Custom subclasses of Numeric may be used so that a numeric literal is +# passed in. This differs from the ruby approach of number inheritance, +# but does allow for certain circumstances where a subclass of number +# might be useful. Opal does not try to implement `Integer` or `Float` +# for these purposes, but it is easily done. This approach will still +# not allow for literals to be used to make these subclass instances. +class Numeric + # Unary Plus - Returns the receivers value + # + # @example + # + # +5 + # # => 5 + # + # @return [Numeric] receiver + def +@ + `return self;` + end + + # Unary minus - returns the receiver's value, negated. + # + # @example + # + # -5 + # # => -5 + # + # @return [Numeric] result + def -@ + `return -self;` + end + + # Returns `self` modulo `other`. See `divmod` for more information. + # + # @param [Numeric] other number to use for module + # @return [Numeric] result + def %(other) + `return self % other;` + end + + def modulo(other) + `return self % other;` + end + + # Bitwise AND. + # + # @param [Numeric] other numeric to AND with + # @return [Numeric] + def &(num2) + `return self & num2;` + end + + # Performs multiplication + # + # @param [Numeric] other number to multiply with + # @return [Numeric] + def *(other) + `return self * other;` + end + + # Raises `self` to the power of `other`. + # + # @param [Numeric] other number to raise to + # @return [Numeric] + def **(other) + `return Math.pow(self, other);` + end + + # Performs addition. + # + # @param [Numeric] other number to add + # @return [Numeric] + def +(other) + `return self + other;` + end + + # Performs subtraction + # + # @param [Numeric] other number to subtract + # @return [Numeric] + def -(other) + `return self - other;` + end + + # Performs division + # + # @param [Numeric] other number to divide by + # @return [Numeric] + def /(other) + `return self / other;` + end + + # Returns `true` if the value of `self` is less than that or `other`, `false` + # otherwise. + # + # @param [Numeric] other number to compare + # @return [true, false] result of comparison + def <(other) + `return self < other ? Qtrue : Qfalse;` + end + + # Returns `true` if the value of `self` is less than or equal to `other`, + # `false` otherwise. + # + # @param [Numeric] other number to comapre + # @return [true, false] result of comparison + def <=(other) + `return self <= other ? Qtrue : Qfalse;` + end + + # Returns `true` if the value of `self` is greater than `other`, `false` + # otherwise. + # + # @param [Numeric] other number to compare with + # @return [true, false] result of comparison + def >(other) + `return self > other ? Qtrue : Qfalse;` + end + + # Returns `true` if `self` is greater than or equal to `other`, `false` + # otherwise. + # + # @param [Numeric] other number to compare with + # @return [true, false] result of comparison + def >=(other) + `return self >= other ? Qtrue : Qfalse;` + end + + # Shift `self` left by `count` positions. + # + # @param [Numeric] count number to shift + # @return [Numeric] + def <<(count) + `return self << count;` + end + + # Shifts 'self' right by `count` positions. + # + # @param [Numeric] count number to shift + # @return [Numeric] + def >>(count) + `return self >> count;` + end + + # Comparison - Returns '-1', '0', '1' or nil depending on whether `self` is + # less than, equal to or greater than `other`. + # + # @param [Numeric] other number to compare with + # @return [Number, nil] + def <=>(other) + `if (typeof other != 'number') return nil; + else if (self < other) return -1; + else if (self > other) return 1; + return 0;` + end + + # Returns `true` if `self` equals `other` numerically, `false` otherwise. + # + # @param [Numeric] other number to compare + # @return [true, false] + def ==(other) + `return self.valueOf() === other.valueOf() ? Qtrue : Qfalse;` + end + + # Bitwise EXCLUSIVE OR. + # + # @param [Numeric] other number to XOR with + # @return [Numeric] + def ^(other) + `return self ^ other;` + end + + # Returns the absolute value of `self`. + # + # @example + # + # -1234.abs + # # => 1234 + # 1234.abs + # # => 1234 + # + # @return [Numeric] + def abs + `return Math.abs(self);` + end + + def magnitude + `return Math.abs(self);` + end + + # Returns `true` if self is even, `false` otherwise. + # + # @return [true, false] + def even? + `return (self % 2 == 0) ? Qtrue : Qfalse;` + end + + # Returns `true` if self is odd, `false` otherwise. + # + # @return [true, false] + def odd? + `return (self % 2 == 0) ? Qfalse : Qtrue;` + end + + # Returns the number equal to `self` + 1. + # + # @example + # + # 1.next + # # => 2 + # (-1).next + # # => 0 + # + # @return [Numeric] + def succ + `return self + 1;` + end + + def next + `return self + 1;` + end + + # Returns the number equal to `self` - 1 + # + # @example + # + # 1.pred + # # => 0 + # (-1).pred + # # => -2 + # + # @return [Numeric] + def pred + `return self - 1;` + end + + # Iterates the block, passing integer values from `self` upto and including + # `finish`. + # + # @example + # + # 5.upto(10) { |i| puts i } + # # => 5 + # # => 6 + # # => 7 + # # => 8 + # # => 9 + # # => 10 + # + # @param [Numeric] finish where to stop iteration + # @return [Numeric] returns the receiver + def upto(finish) + `for (var i = self; i <= finish; i++) { + #{yield `i`}; + } + + return self;` + end + + # Iterates the block, passing decreasing values from `self` downto and + # including `finish`. + # + # @example + # + # 5.downto(1) { |x| puts x } + # # => 5 + # # => 4 + # # => 3 + # # => 2 + # # => 1 + # + # @param [Numeric] finish where to stop iteration + # @return [Numeric] returns the receiver + def downto(finish) + `for (var i = self; i >= finish; i--) { + #{yield `i`}; + } + + return self;` + end + + # Iterates the block `self` number of times, passing values in the range 0 to + # `self` - 1. + # + # @example + # + # 5.times { |x| puts x } + # # => 0 + # # => 1 + # # => 2 + # # => 3 + # # => 4 + # + # @return [Numeric] returns the receiver + def times + raise "no block given" unless block_given? + `for (var i = 0; i < self; i++) { + #{yield `i`}; + } + + return self;` + end + + # Bitwise OR. + # + # @param [Numeric] other number to OR with + # @return [Numeric] + def |(other) + `return self | other;` + end + + # Returns `true` if `self` is zero, `false` otherwise. + # + # @return [true, false] + def zero? + `return self == 0 ? Qtrue : Qfalse;` + end + + # Returns the receiver if it is not zero, `nil` otherwise + # + # @return [Numeric, nil] + def nonzero? + `return self == 0 ? nil : self;` + end + + # One's complement: returns a number where each bit is flipped. + # + # @return [Numeric] + def ~ + `return ~self;` + end + + # Returns the smallest integer greater than or equal to `num`. + # + # @example + # + # 1.ceil # => 1 + # 1.2.ceil # => 2 + # (-1.2).ceil # => -1 + # (-1.0).ceil # => -1 + # + # @return [Numeric] + def ceil + `return Math.ceil(self);` + end + + # Returns the largest integer less than or equal to `self`. + # + # @example + # + # 1.floor # => 1 + # (-1).floor # => -1 + # + # @return [Numeric] + def floor + `return Math.floor(self);` + end + + # Returns `true` if self is an ineteger, `false` otherwise. + # + # @return [true, false] + def integer? + `return self % 1 == 0 ? Qtrue : Qfalse;` + end + + def inspect + `return self.toString();` + end + + def to_s + `return self.toString();` + end + + def to_i + `return parseInt(self);` + end +end + diff --git a/lib/core/object.rb b/lib/core/object.rb new file mode 100644 index 0000000000..f132a0f7e7 --- /dev/null +++ b/lib/core/object.rb @@ -0,0 +1,6 @@ +# Core object in hierarchy. Most of the implementation of this core +# object are implemented in {Kernel}. +class Object < BasicObject + +end + diff --git a/lib/core/proc.rb b/lib/core/proc.rb new file mode 100644 index 0000000000..c12c9a778d --- /dev/null +++ b/lib/core/proc.rb @@ -0,0 +1,56 @@ +# `Proc` objects are blocks of code that can also be bound to local +# variables in their defined scope. When called, a proc will maintain +# its `self` value, and still have the ability to access variables +# defined within the same scope. A proc may also be called in another +# context and have its `self` value tempararily adjusted. +# +# Creation of procs may be done by passing a block into the {Proc.new} +# constructor, or the {Kernel} method {Kernel#proc}: +# +# a = Proc.new { 14 } # => # +# b = proc { 42 + a.call } # => # +# +# a.call # => 14 +# b.call # => 56 +# +# Implementation details +# ---------------------- +# +# Due to their obvious similarities in functionality, a proc instance is +# simply a native javascript function allowing it to maintain access to +# variables in its outer scope, and to have its `self` value changed on +# demand. +# +# When a proc is defined, its `self` value is stored on the function +# instance itself as a `.$self` property, so when the proc is called in +# future, this is the default value passed as the self property. This +# also means that every function used in the same context as opal may be +# used as a `Proc` meaning the transition back and forth between ruby +# and javascript contexts is easy. +class Proc + + def self.new(&block) + raise ArgumentError, + "tried to create Proc object without a block" unless block_given? + + block + end + + def to_proc + self + end + + def call(*args) + `args.unshift(self.$self); + return self.apply(self.$self, args);` + end + + def to_s + `return "#";` + end + + def lambda? + `return self.$lambda ? Qtrue : Qfalse;` + end +end + diff --git a/lib/core/range.rb b/lib/core/range.rb new file mode 100644 index 0000000000..77228358fd --- /dev/null +++ b/lib/core/range.rb @@ -0,0 +1,17 @@ +class Range + + def to_s + `var str = #{`self.$beg`.to_s}; + var str2 = #{`self.$end`.to_s}; + var join = self.$exc ? '...' : '..'; + return str + join + str2;` + end + + def inspect + `var str = #{`self.$beg`.inspect}; + var str2 = #{`self.$end`.inspect}; + var join = self.$exc ? '...' : '..'; + return str + join + str2;` + end +end + diff --git a/lib/core/regexp.rb b/lib/core/regexp.rb new file mode 100644 index 0000000000..db1e0e0ef3 --- /dev/null +++ b/lib/core/regexp.rb @@ -0,0 +1,61 @@ +# A `Regexp` holds a regular expression, that can be used to match +# against strings. Regexps may be created as literals, or using the +# {Regexp.new} method: +# +# /abc/ # => /abc/ +# Regexp.new '[a-z]' # => /[a-z]/ +# +# Implementation details +# ---------------------- +# +# Instances of {Regexp} are toll-free bridged to native javascript +# regular expressions. This means that javascript regexp instances may +# be passed directly into ruby methods that expect a regexp instance. +# +# Due to the limitations of some browser engines, regexps from ruby are +# not always compatible with the target browser javascript engine. +# Compatibility differences change between engines, so reading up on a +# particular browsers documentation might point to differences +# discovered. The majority of regexp syntax is typically the same. +class Regexp + + def inspect + `return self.toString();` + end + + def to_s + `return self.source;` + end + + def ==(other) + `return self.toString() === other.toString() ? Qtrue : Qfalse;` + end + + def eql?(other) + self == other + end + + # Match - matches the regular expression against the given string. If + # the string matches, the index of the match is returned. Otherwise, + # `nil` is returned to imply no match. + # + # @param [String] str The string to match + # @return [Numeric, nil] + def =~(str) + `var result = self.exec(str); + VM.X = result; + + if (result) { + return result.index; + } + else { + return nil; + }` + end + + def match(pattern) + self =~ pattern + $~ + end +end + diff --git a/lib/core/string.rb b/lib/core/string.rb new file mode 100644 index 0000000000..d78d00b522 --- /dev/null +++ b/lib/core/string.rb @@ -0,0 +1,375 @@ +# -*- encoding: utf-8 -*- + +# String objects holds a sequence of bytes, typically representing +# characters. Strings may be constructed by using methods like +# {String.new} or literals, like the following: +# +# String.new("foo") # => "foo" +# "bar" # => "bar" +# +# Strings in Opal are immutable; which means that their contents cannot +# be changed. This means that a lot of methods like `strip!` are not +# present, and will yield a `NoMethodError`. Thier immutable +# counterparts are still available, which typically just return a new +# string. +# +# Implementation details +# ---------------------- +# +# Ruby strings are toll-free bridged to native javascript strings, +# meaning that anywhere that a ruby string is required, a normal +# javascript string may be passed. This dramatically improves the +# performance of Opal due to a lower overhead in allocating strings as +# well as the ability to used functions of the String prototype to +# perform many of the core ruby methods. +# +# It is due to this limitation that strings are immutable. Javascript +# strings are immutable too, which limits what can be done with them in +# regards to Ruby methods. +# +# Ruby compatibility +# ------------------ +# +# As discussed, {String} instances are immutable so they do not +# implement any of the self mutable methods found in the ruby core +# library. Most of these methods have their relative immutable +# implementations, or alternative methods to take their place. +# +# Custom subclasses of {String} can be used, and are constructed in the +# {.new} method. To due opals internals, a regular string is constructed +# using `new String(string_content)`, and its class and method table +# simply pointed at the custom subclass. As these custom subclasses are +# simply javascript strings as well, they are also limited to being +# immutable. This is because they share the same internal structre as +# regular {String} instances. +# +# String instances will never actually have their {.allocate} methods +# called. Due to the way opal bridges strings to javascript, when a new +# string is constructed, its value must be know. This is not possible in +# `allocate` as the value is not passed. Therefore the creation of +# strings (including subclasses) is done in {.new} where the string +# value is passed as an argument. +# +# Finally, strings do not currently include the `Comparable` module, as +# it is not yet implemented. The main methods used by {String} from this +# module are implemented directly as String methods. When `Comparable` +# is implemented, these methods will be moved back to the module. +class String + + def self.new(str = "") + `var result = new String(str); + result.$klass = self; + result.$m = self.$m_tbl; + return result;` + end + + # Copy - returns a new string containing `count` copies of the receiver. + # + # @example + # + # 'Ho! ' * 3 + # # => 'Ho! Ho! Ho! ' + # + # @param [Numeric] count number of copies + # @return [String] + def *(count) + `var result = []; + + for (var i = 0; i < count; i++) { + result.push(self); + } + + return result.join('');` + end + + # Concatenation - Returns a new string containing `other` concatenated onto + # `self`. + # + # @example + # + # 'Hello from ' + self.to_s + # # => 'Hello from main' + # + # @param [String] other string to concatenate + # @return [String] + def +(other) + `return self + other;` + end + + # Returns a copy of `self` with the first character converted to uppercase and + # the remaining to lowercase. + # + # @example + # + # 'hello'.capitalize + # # => 'Hello' + # 'HELLO'.capitalize + # # => 'Hello' + # '123ABC'.capitalize + # # => '123abc' + # + # @return [String] + def capitalize + `return self.charAt(0).toUpperCase() + self.substr(1).toLowerCase();` + end + + # Returns a copy of `self` with all uppercase letters replaces with their + # lowercase counterparts. + # + # @example + # + # 'hELLo'.downcase + # # => 'hello' + # + # @return [String] + def downcase + `return self.toLowerCase();` + end + + # Returns a printable version of `self`, surrounded with quotation marks, with + # all special characters escaped. + # + # @example + # + # str = "hello" + # str.inspect + # # => "\"hello\"" + # + # @return [String] + def inspect + `/* borrowed from json2.js, see file for license */ + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + + meta = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + + escapable.lastIndex = 0; + + return escapable.test(self) ? '"' + self.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' ? c : + '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + self + '"';` + end + + # Returns the number of characters in `self`. + # + # @return [Numeric] + def length + `return self.length;` + end + + # Returns the corresponding symbol for the receiver. + # + # @example + # + # "koala".to_sym # => :Koala + # 'cat'.to_sym # => :cat + # '@cat'.to_sym # => :@cat + # + # This can also be used to create symbols that cannot be created using the + # :xxxx notation. + # + # @return [Symbol] + def to_sym + `return VM.Y(self);` + end + + def intern + `return VM.Y(self);` + end + + # Returns a new string with the characters from `self` in reverse order. + # + # @example + # + # 'stressed'.reverse + # # => 'desserts' + # + # @return [String] + def reverse + `return self.split('').reverse().join('');` + end + + def sub(pattern) + `return self.replace(pattern, function(str) { + return #{yield `str`}; + });` + end + + def gsub(pattern) + `var r = pattern.toString(); + r = r.substr(1, r.lastIndexOf('/') - 1); + r = new RegExp(r, 'g'); + return self.replace(pattern, function(str) { + return #{yield `str`}; + });` + end + + def slice(start, finish = nil) + `return self.substr(start, finish);` + end + + def split(split) + `return self.split(split);` + end + + # Comparison - returns -1 if `other` is greater than, 0 if `other` is equal to + # or 1 if `other` is less than `self. Returns nil if `other` is not a string. + # + # @example + # + # 'abcdef' <=> 'abcde' # => 1 + # 'abcdef' <=> 'abcdef' # => 0 + # 'abcdef' <=> 'abcdefg' # => -1 + # 'abcdef' <=> 'ABCDEF' # => 1 + # + # @param [String] other string to compare + # @return [-1, 0, 1, nil] result + def <=>(other) + `if (typeof other != 'string') return nil; + else if (self > other) return 1; + else if (self < other) return -1; + return 0;` + end + + # Equality - if other is not a string, returns false. Otherwise, returns true + # if self <=> other returns zero. + # + # @param [String] other string to compare + # @return [true, false] + def ==(other) + `return self.valueOf() === other.valueOf() ? Qtrue : Qfalse;` + end + + # Match - if obj is a Regexp, then uses it to match against self, returning + # nil if there is no match, or the index of the match location otherwise. If + # obj is not a regexp, then it calls =~ on it, using the receiver as an + # argument + # + # TODO passing a non regexp is not currently supported + # + # @param [Regexp, Objec] obj + # @return [Numeric, nil] + def =~(obj) + `if (obj.$flags & VM.T_STRING) { + VM.raise(VM.TypeError, "type mismatch: String given"); + }` + + obj =~ self + end + + # Case-insensitive version of {#<=>} + # + # @example + # + # 'abcdef'.casecmp 'abcde' # => 1 + # 'aBcDeF'.casecmp 'abcdef' # => 0 + # 'abcdef'.casecmp 'aBcdEFg' # => -1 + # + # @param [String] other string to compare + # @return [-1, 0, 1, nil] + def casecmp(other) + `if (typeof other != 'string') return nil; + var a = self.toLowerCase(), b = other.toLowerCase(); + if (a > b) return 1; + else if (a < b) return -1; + return 0;` + end + + # Returns `true` if self has a length of zero. + # + # @example + # + # 'hello'.empty? + # # => false + # ''.empty? + # # => true + # + # @return [true, false] + def empty? + `return self.length == 0 ? Qtrue : Qfalse;` + end + + # Returns true is self ends with the given suffix. + # + # @example + # + # 'hello'.end_with? 'lo' + # # => true + # + # @param [String] suffix the suffix to check + # @return [true, false] + def end_with?(suffix) + `if (self.lastIndexOf(suffix) == self.length - suffix.length) { + return Qtrue; + } + + return Qfalse;` + end + + # Two strings are equal if they have the same length and content. + # + # @param [String] other string to compare + # @return [true, false] + def eql?(other) + `return self == other ? Qtrue : Qfalse;` + end + + # Returns true if self contains the given string `other`. + # + # @example + # + # 'hello'.include? 'lo' # => true + # 'hello'.include? 'ol' # => false + # + # @param [String] other string to check for + # @return [true, false] + def include?(other) + `return self.indexOf(other) == -1 ? Qfalse : Qtrue;` + end + + # Returns the index of the first occurance of the given `substr` or pattern in + # self. Returns `nil` if not found. If the second param is present then it + # specifies the index of self to begin searching. + # + # **TODO** regexp and offsets not yet implemented. + # + # @example + # + # 'hello'.index 'e' # => 1 + # 'hello'.index 'lo' # => 3 + # 'hello'.index 'a' # => nil + # + # @param [String] substr string to check for + # @return [Numeric, nil] + def index(substr) + `var res = self.indexOf(substr); + + return res == -1 ? nil : res;` + end + + # Returns a copy of self with leading whitespace removed. + # + # @example + # + # ' hello '.lstrip + # # => 'hello ' + # 'hello'.lstrip + # # => 'hello' + # + # @return [String] + def lstrip + `return self.replace(/^\s*/, '');` + end +end + diff --git a/lib/core/symbol.rb b/lib/core/symbol.rb new file mode 100644 index 0000000000..4dffd5836e --- /dev/null +++ b/lib/core/symbol.rb @@ -0,0 +1,38 @@ +# `Symbols` are used to represent names and can often be used in place +# of enum variables used in other languages. Symbols can be constructed +# using their literal syntax or one of the various `to_sym` methods +# found in the standard library: +# +# :some_symbol # => :some_symbol +# "a_string".to_sym # => :a_string +# +# It is important to note that regardless of the context that created +# them, two symbols with the same name will always be the exact same +# object. The opal runtime guarantees this as it creates them. If one +# exists already with the required name, it will be returned instead of +# creating a new one. +# +# Implementation details +# ---------------------- +# +# Internally, symbols are just javascript strings. They are constructed +# with the javascript `new String(symbol_name)` syntax. Once created, +# they have their class and method tables altered to point towards the +# {Symbol} class. This avoids them conflicting with regular strings. +# +# Symbols are implemented as strings for performance. They are only +# created once per name, so past the initial creation phase, which +# happends just the once, they perform as quickly as just passig them +# between method calls, and as strings their native prototype offers all +# the required functionality needed by the class. +class Symbol + + def inspect + `return ':' + self.toString();` + end + + def to_sym + self + end +end + diff --git a/lib/core/top_self.rb b/lib/core/top_self.rb new file mode 100644 index 0000000000..905d870a16 --- /dev/null +++ b/lib/core/top_self.rb @@ -0,0 +1,8 @@ +def self.to_s + "main" +end + +def self.include(mod) + Object.include mod +end + diff --git a/lib/core/true_class.rb b/lib/core/true_class.rb new file mode 100644 index 0000000000..8f7b0a200f --- /dev/null +++ b/lib/core/true_class.rb @@ -0,0 +1,41 @@ +# Instances of `TrueClass` represent logically true values. There may +# only be one instance of `TrueClass`, which is the global value +# `true`. Attempts to create a new instance will yield an error. +# `TrueClass` provides methods to perform logical operations with other +# ruby objects. +# +# Implementation details +# ---------------------- +# +# Due to the way messages are passed inside opal, `true` is not +# actually toll-free bridged onto the native javascript `true` value. +# In javascript, `true` and `true` are both instances of the Boolean +# type, which means they would need to share the same method_table in +# opal, which would remove their ability to be true instances of Rubys' +# `TrueClass` or `FalseClass`. +# +# As javascripts `true` is not actually the value used in opal, passing +# the native `true` value will cause errors when messages are sent to +# it. Within a file directly loaded by opal, `Qtrue` is a free variable +# that points to the actualy ruby instance of this class. This variable +# may be passed around freely. +class TrueClass + def to_s + "true" + end + + def &(other) + `return other.$r ? Qtrue : Qfalse;` + end + + def |(other) + true + end + + def ^(other) + `return other.$r ? Qfalse : Qtrue;` + end +end + +TRUE = true + diff --git a/spec/core/array/append_spec.rb b/spec/core/array/append_spec.rb new file mode 100644 index 0000000000..28fd51818b --- /dev/null +++ b/spec/core/array/append_spec.rb @@ -0,0 +1,31 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Array#<<" do + it "pushes the object onto the end of the array" do + ([1, 2] << "c" << "d" << [3, 4]).should == [1, 2, "c", "d", [3, 4]] + end + + it "returns self to allow chaining" do + a = [] + b = a + (a << 1).should == b + (a << 2 << 3).should == b + end + + it "correctly resizes the Array" do + a = [] + a.size.should == 0 + a << :foo + a.size.should == 1 + a << :bar << :baz + a.size.should == 3 + + a = [1, 2, 3] + a.shift + a.shift + a.shift + a << :foo + a.should == [:foo] + end +end + diff --git a/spec/core/array/assoc_spec.rb b/spec/core/array/assoc_spec.rb new file mode 100644 index 0000000000..2bf320dee6 --- /dev/null +++ b/spec/core/array/assoc_spec.rb @@ -0,0 +1,29 @@ + +describe "Array#assoc" do + it "returns the first array whose 1st item is == obj or nil" do + s1 = ["colors", "red", "blue", "green"] + s2 = [:letters, "a", "b", "c"] + s3 = [4] + s4 = ["colors", "cyan", "yellow", "magenda"] + s5 = [:letters, "a", "i", "u"] + s_nil = [nil, nil] + a = [s1, s2, s3, s4, s5, s_nil] + a.assoc(s1.first).should == s1 + a.assoc(s2.first).should == s2 + a.assoc(s3.first).should == s3 + a.assoc(s4.first).should == s1 + a.assoc(s5.first).should == s2 + a.assoc(s_nil.first).should == s_nil + a.assoc(4).should == s3 + a.assoc("key not in array").should == nil + end + + it "ignores any non-Array elements" do + [1, 2, 3].assoc(2).should == nil + s1 = [4] + s2 = [5, 4, 3] + a = ["foo", [], s1, s2, nil, []] + a.assoc(s1.first).should == s1 + a.assoc(s2.first).should == s2 + end +end diff --git a/spec/core/array/at_spec.rb b/spec/core/array/at_spec.rb new file mode 100644 index 0000000000..3a41750467 --- /dev/null +++ b/spec/core/array/at_spec.rb @@ -0,0 +1,37 @@ + +describe "Array#at" do + it "returns the (n+1)'th element for the passed index n" do + a = [1, 2, 3, 4, 5, 6] + a.at(0).should == 1 + a.at(1).should == 2 + a.at(5).should == 6 + end + + it "returns nil if the given index is greater than or equal to the array's length" do + a = [1, 2, 3, 4, 5, 6] + a.at(6).should == nil + a.at(7).should == nil + end + + it "returns the (-n)'th element from the last, for the given negative index n" do + a = [1, 2, 3, 4, 5, 6] + a.at(-1).should == 6 + a.at(-2).should == 5 + a.at(-6).should == 1 + end + + it "returns nil if the given index is less than -len, where len is the length of the array" do + a = [1, 2, 3, 4, 5, 6] + a.at(-7).should == nil + a.at(-8).should == nil + end + + it "does not extend the array unless the given index is out of range" do + a = [1, 2, 3, 4, 5, 6] + a.length.should == 6 + a.at(100) + a.length.should == 6 + a.at(-100) + a.length.should == 6 + end +end diff --git a/spec/core/array/clear_spec.rb b/spec/core/array/clear_spec.rb new file mode 100644 index 0000000000..71a060cc8a --- /dev/null +++ b/spec/core/array/clear_spec.rb @@ -0,0 +1,22 @@ + +describe "Array#clear" do + it "removes all elements" do + a = [1, 2, 3, 4] + a.clear + # # a.clear.should == a + a.should == [] + end + + it "returns self" do + a = [1] + oid = a.object_id + a.clear.object_id.should == oid + end + + it "leaves the Array empty" do + a = [1] + a.clear + a.empty?.should == true + a.size.should == 0 + end +end diff --git a/spec/core/array/collect_bang_spec.rb b/spec/core/array/collect_bang_spec.rb new file mode 100644 index 0000000000..8b5d353d6d --- /dev/null +++ b/spec/core/array/collect_bang_spec.rb @@ -0,0 +1,27 @@ + +describe "Array#collect!" do + it "replaces each element with the value returned by block" do + a = [7, 9, 3, 5] + a.collect! { |i| i - 1 } + a.should == [6, 8, 2, 4] + end + + it "returns self" do + a = [1, 2, 3, 4, 5] + b = a.collect! { |i| i + 1 } + a.object_id.should == b.object_id + end + + it "returns the evaluated value of block but its contents is partially modified, if it broke in the block" do + a = ['a', 'b', 'c', 'd'] + b = a.collect! do |i| + if i == 'c' + # break 0 + else + i + '!' + end + end + b.should == 0 + a.should == ['a!', 'b!', 'c', 'd'] + end +end diff --git a/spec/core/array/collect_spec.rb b/spec/core/array/collect_spec.rb new file mode 100644 index 0000000000..6f11e20925 --- /dev/null +++ b/spec/core/array/collect_spec.rb @@ -0,0 +1,27 @@ + +describe "Array#collect" do + it "returns a copy of array with each element replaced by the value returned by block" do + a = ['a', 'b', 'c', 'd'] + b = a.collect { |i| i + '!'} + b.should == ['a!', 'b!', 'c!', 'd!'] + # a.object_id.should_not == a.object_id + end + + it "does not change self" do + a = ['a', 'b', 'c', 'd'] + b = a.collect { |i| i + '!' } + a.should == ['a', 'b', 'c', 'd'] + end + + it "returns the evaluated value of the block if it broke in the block" do + a = ['a', 'b', 'c', 'd'] + b = a.collect do |i| + if i == 'c' + # break 0 + else + i + '!' + end + end + b.should == 0 + end +end diff --git a/spec/core/array/compact_spec.rb b/spec/core/array/compact_spec.rb new file mode 100644 index 0000000000..4d03e0193f --- /dev/null +++ b/spec/core/array/compact_spec.rb @@ -0,0 +1,41 @@ + +describe "Array#compact" do + it "returns a copy of array with all nil elements removed" do + a = [1, 2, 4] + a.compact.should == [1, 2, 4] + a = [1, nil, 2, 4] + a.compact.should == [1, 2, 4] + a = [1, 2, 4, nil] + a.compact.should == [1, 2, 4] + a = [nil, 1, 2, 4] + a.compact.should == [1, 2, 4] + end + + it "does not return self" do + a = [1, 2, 3] + a.compact.object_id.should_not == a.object_id + end +end + +describe "Array#compact!" do + it "removes all nil elements" do + a = ['a', nil, 'b', false, 'c'] + a.compact!.object_id.should == a.object_id + a.should == ['a', 'b', false, 'c'] + a = [nil, 'a', 'b', false, 'c'] + a.compact!.object_id.should == a.object_id + a.should == ['a', 'b', false, 'c'] + a = ['a', 'b', false, 'c', nil] + a.compact!.object_id.should == a.object_id + a.should == ['a', 'b', false, 'c'] + end + + it "returns self if some nil elements are removed" do + a = ['a', nil, 'b', false, 'c'] + a.compact!.object_id.should == a.object_id + end + + it "returns nil if there are no nil elements to remove" do + [1, 2, false, 3].compact!.should == nil + end +end \ No newline at end of file diff --git a/spec/core/array/concat_spec.rb b/spec/core/array/concat_spec.rb new file mode 100644 index 0000000000..9ffad702ad --- /dev/null +++ b/spec/core/array/concat_spec.rb @@ -0,0 +1,15 @@ + +describe "Array#concat" do + it "appends the elements in the other array" do + ary = [1, 2, 3] + ary.concat([9, 10, 11]) + ary.should == [1, 2, 3, 9, 10, 11] + ary.concat [] + ary.should == [1, 2, 3, 9, 10, 11] + end + + it "does not loop endlessly when argument is self" do + ary = ["x", "y"] + ary.concat(ary).should == ["x", "y", "x", "y"] + end +end diff --git a/spec/core/array/constructor_spec.rb b/spec/core/array/constructor_spec.rb new file mode 100644 index 0000000000..a5bdc0110a --- /dev/null +++ b/spec/core/array/constructor_spec.rb @@ -0,0 +1,14 @@ + +describe "Array.[]" do + it "returns a new array populated with the given elements" do + obj = Object.new + Array.[](5, true, nil, 'a', "Ruby", obj).should == [5, true, nil, 'a', "Ruby", obj] + end +end + +describe "Array[]" do + it "is a synonym for .[]" do + obj = Object.new + Array[5, true, nil, 'a', "Ruby", obj].should == [5, true, nil, 'a', "Ruby", obj] + end +end diff --git a/spec/core/array/each_spec.rb b/spec/core/array/each_spec.rb new file mode 100644 index 0000000000..b76e9b4169 --- /dev/null +++ b/spec/core/array/each_spec.rb @@ -0,0 +1,9 @@ + +describe "Array#each" do + it "yields each element to the block" do + a = [] + x = [1, 2, 3] + x.each { |item| a << item }.should == x + a.should == [1, 2, 3] + end +end diff --git a/spec/core/array/element_reference_spec.rb b/spec/core/array/element_reference_spec.rb new file mode 100644 index 0000000000..6dd23b1e65 --- /dev/null +++ b/spec/core/array/element_reference_spec.rb @@ -0,0 +1,4 @@ + +describe "Array#[]" do + +end diff --git a/spec/core/array/first_spec.rb b/spec/core/array/first_spec.rb new file mode 100644 index 0000000000..e5aadb7feb --- /dev/null +++ b/spec/core/array/first_spec.rb @@ -0,0 +1,35 @@ + +describe "Array#first" do + it "returns the first element" do + # %W{a b c}.first.should == 'a' + [nil].first.should == nil + end + + it "returns nil if self is empty" do + [].first.should == nil + end + + it "returns the first count elements if given a count" do + [true, false, true, nil, false].first(2).should == [true, false] + end + + it "returns an empty array when passed count on an empty array" do + [].first(0).should == [] + [].first(1).should == [] + [].first(2).should == [] + end + + it "returns an empty array when passed count == 0" do + [1, 2, 3, 4, 5].first(0).should == [] + end + + it "returns an array containing the first element when passed count == 1" do + [1, 2, 3, 4, 5].first(1).should == [1] + end + + it "raises an argument error when count is negative" + + it "returns the entire array when count > length" do + [1, 2, 3, 4, 5, 6, 7, 8, 9].first(10).should == [1, 2, 3, 4, 5, 6, 7, 8, 9] + end +end diff --git a/spec/core/array/include_spec.rb b/spec/core/array/include_spec.rb new file mode 100644 index 0000000000..2175f36f83 --- /dev/null +++ b/spec/core/array/include_spec.rb @@ -0,0 +1,9 @@ + +describe "Array#include?" do + it "returns true if object is present, false otherwise" do + [1, 2, "a", "b"].include?("c").should == false + [1, 2, "a", "b"].include?("a").should == true + end + + it "determines presence by using element == obj" +end diff --git a/spec/core/array/join_spec.rb b/spec/core/array/join_spec.rb new file mode 100644 index 0000000000..0aa1cec51e --- /dev/null +++ b/spec/core/array/join_spec.rb @@ -0,0 +1,6 @@ +describe "Array#join" do + it "returns an empty string if the Array is empty" do + a = Array.new + a.join(':').should == "" + end +end diff --git a/spec/core/array/last_spec.rb b/spec/core/array/last_spec.rb new file mode 100644 index 0000000000..fee694d66f --- /dev/null +++ b/spec/core/array/last_spec.rb @@ -0,0 +1,51 @@ + +describe "Array#last" do + it "returns the last element" do + [1, 1, 1, 1, 2].last.should == 2 + end + + it "returns nil if self is empty" do + [].last.should == nil + end + + it "returns the last count elements if given a count" do + [1, 2, 3, 4, 5, 9].last(3).should == [4, 5, 9] + end + + it "returns an empty array when passeed a count on an empty array" do + [].last(0).should == [] + [].last(1).should == [] + end + + it "returns an empty array when count == 0" do + [1, 2, 3, 4, 5].last(0).should == [] + end + + it "returns an array containing the last element when passed count == 1" do + [1, 2, 3, 4, 5].last(1).should == [5] + end + + it "returns the entire array when count > length" do + [1, 2, 3, 4, 5, 9].last(10).should == [1, 2, 3, 4, 5, 9] + end + + it "returns an array which is independant to the original when passed count" do + ary = [1, 2, 3, 4, 5] + ary.last(0).replace([1, 2]) + ary.should == [1, 2, 3, 4, 5] + ary.last(1).replace([1, 2]) + ary.should == [1, 2, 3, 4, 5] + ary.last(6).replace([1, 2]) + ary.should == [1, 2, 3, 4, 5] + end + + it "is not destructive" do + a = [1, 2, 3] + a.last + a.should == [1, 2, 3] + a.last(2) + a.should == [1, 2, 3] + a.last(3) + a.should == [1, 2, 3] + end +end diff --git a/spec/core/array/length_spec.rb b/spec/core/array/length_spec.rb new file mode 100644 index 0000000000..d9d460df4e --- /dev/null +++ b/spec/core/array/length_spec.rb @@ -0,0 +1,6 @@ +describe "Array#length" do + it "returns the number of elements" do + [].length.should == 0 + [1, 2, 3].length.should == 3 + end +end diff --git a/spec/core/array/map_spec.rb b/spec/core/array/map_spec.rb new file mode 100644 index 0000000000..cfbb2bcb4e --- /dev/null +++ b/spec/core/array/map_spec.rb @@ -0,0 +1,33 @@ + +describe "Array#map" do + it "returns a copy of array with each element replaced by the value returned by block" do + a = ['a', 'b', 'c', 'd'] + b = a.map { |i| i + '!' } + b.should == ['a!', 'b!', 'c!', 'd!'] + + # ignore + # b.object_id.should_not == a.object_id + end + + it "does not return subclass instance" + + it "does not change self" do + a = ['a', 'b', 'c', 'd'] + b = a.map { |i| i + '!' } + a.should == ['a', 'b', 'c', 'd'] + end + + it "returns the evaluated value of the block if it broke in the block" do + a = ['a', 'b', 'c', 'd'] + b = a.map { |i| + if i == 'c' + # break 0 + else + i + '!' + end + } + b.should == 0 + end + + it "returns an enumerator when no block given" +end diff --git a/spec/core/array/reverse_spec.rb b/spec/core/array/reverse_spec.rb new file mode 100644 index 0000000000..5f5282c0fe --- /dev/null +++ b/spec/core/array/reverse_spec.rb @@ -0,0 +1,6 @@ +describe "Array#reverse" do + it "returns a new array with the elements in reverse order" do + [].reverse.should == [] + [1, 3, 5, 2].reverse.should == [2, 5, 3, 1] + end +end \ No newline at end of file diff --git a/spec/core/array/select_spec.rb b/spec/core/array/select_spec.rb new file mode 100644 index 0000000000..5434366f76 --- /dev/null +++ b/spec/core/array/select_spec.rb @@ -0,0 +1,7 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Array#select" do + it "returns a new array of elements for which block is true" do + [1, 3, 4, 5, 6, 9].select { |i| i % ((i + 1) / 2) == 0 }.should == [1, 2, 3] + end +end diff --git a/spec/core/builtin_constants/builtin_constants_spec.rb b/spec/core/builtin_constants/builtin_constants_spec.rb new file mode 100644 index 0000000000..2d1819e78d --- /dev/null +++ b/spec/core/builtin_constants/builtin_constants_spec.rb @@ -0,0 +1,7 @@ + +describe "RUBY_VERSION" do + + it "is a String" do + # RUBY_VERSION.class.should == String + end +end diff --git a/spec/core/false/and_spec.rb b/spec/core/false/and_spec.rb new file mode 100644 index 0000000000..61d374ab82 --- /dev/null +++ b/spec/core/false/and_spec.rb @@ -0,0 +1,10 @@ + +# describe "FalseClass#&" do +# it "returns false" do +# (false & false).should == false +# (false & true).should == false +# (false & nil).should == false +# (false & "").should == false +# (false & 'x').should == false +# end +# end diff --git a/spec/core/false/inspect_spec.rb b/spec/core/false/inspect_spec.rb new file mode 100644 index 0000000000..77db4fde7d --- /dev/null +++ b/spec/core/false/inspect_spec.rb @@ -0,0 +1,6 @@ + +describe "FalseClass#inspect" do + it "returns the string 'false'" do + false.inspect.should == "false" + end +end diff --git a/spec/core/false/or_spec.rb b/spec/core/false/or_spec.rb new file mode 100644 index 0000000000..c1de4ef62a --- /dev/null +++ b/spec/core/false/or_spec.rb @@ -0,0 +1,10 @@ + +describe "FalseClass#|" do + it "returns false if other is nil or false, otherwise true" do + (false | false).should == false + (false | true).should == true + (false | nil).should == false + (false | "").should == true + (false | 'x').should == true + end +end diff --git a/spec/core/false/to_s_spec.rb b/spec/core/false/to_s_spec.rb new file mode 100644 index 0000000000..64793dc4a9 --- /dev/null +++ b/spec/core/false/to_s_spec.rb @@ -0,0 +1,6 @@ + +describe "FalseClass#to_s" do + it "returns the string 'false'" do + false.to_s.should == "false" + end +end diff --git a/spec/core/false/xor_spec.rb b/spec/core/false/xor_spec.rb new file mode 100644 index 0000000000..13d6964174 --- /dev/null +++ b/spec/core/false/xor_spec.rb @@ -0,0 +1,10 @@ + +describe "FalseClass#^" do + it "returns false if other is nil or false, otherwise true" do + (false ^ false).should == false + (false ^ true).should == true + (false ^ nil).should == false + (false ^ "").should == true + (false ^ 'x').should == true + end +end diff --git a/spec/core/file/join_spec.rb b/spec/core/file/join_spec.rb new file mode 100644 index 0000000000..57009629ef --- /dev/null +++ b/spec/core/file/join_spec.rb @@ -0,0 +1,19 @@ + +describe "File.join" do + it "returns an empty string when given no arguments" do + File.join.should == "" + end + + it "when given a single argument returns an equal string" do + # File.join("").should == "" + File.join("usr").should == "usr" + end + + it "joins parts using File::SEPARATOR" do + File.join('usr', 'bin').should == 'usr/bin' + end + + it "supports any number of arguments" do + File.join("a", "b", "c", "d").should == "a/b/c/d" + end +end \ No newline at end of file diff --git a/spec/core/hash/assoc_spec.rb b/spec/core/hash/assoc_spec.rb new file mode 100644 index 0000000000..e6999877c3 --- /dev/null +++ b/spec/core/hash/assoc_spec.rb @@ -0,0 +1,32 @@ +require File.expand_path('../../../spec_helper', __FILE__) + +describe "Hash#assoc" do + # before each + $h = {:apple => :green, :orange => :orange, :grape => :green, :banana => :yellow} + + it "returns an Array if the argument is == to a key of the Hash" do + $h.assoc(:apple).class.should == Array + end + + it "returns a 2-element Array if the argument is == to a key of the Hash" do + $h.assoc(:grape).size.should == 2 + end + + it "sets the first element of the Array to the located key" do + $h.assoc(:banana).first.should == :banana + end + + it "sets the last element of the array to the value of the located key" do + $h.assoc(:banana).last.should == :yellow + end + + it "uses #== to compare the argument to the keys" do + $h[1.0] = :value + 1.should == 1.0 + $h.assoc(1).should == [1.0, :value] + end + + it "returns nil if the argument is not a key of the Hash" do + $h.assoc(:green).should == nil + end +end diff --git a/spec/core/kernel/instance_eval_spec.rb b/spec/core/kernel/instance_eval_spec.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/core/kernel/loop_spec.rb b/spec/core/kernel/loop_spec.rb new file mode 100644 index 0000000000..873e4fb064 --- /dev/null +++ b/spec/core/kernel/loop_spec.rb @@ -0,0 +1,24 @@ + +describe "Kernel.loop" do + # it "calls block until it is terminated by a break" do + # i = 0 + # loop do + # i = i + 1 + # break if i == 10 + # end + + # i.should == 10 + # end + + # it "returns value passed to break" do + # loop do + # break 123 + # end.should == 123 + # end + + # it "returns nil if no value passed to break" do + # loop do + # break + # end.should == nil + # end +end diff --git a/spec/core/kernel/raise_spec.rb b/spec/core/kernel/raise_spec.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/core/module/attr_accessor_spec.rb b/spec/core/module/attr_accessor_spec.rb new file mode 100644 index 0000000000..402ce5c75b --- /dev/null +++ b/spec/core/module/attr_accessor_spec.rb @@ -0,0 +1,28 @@ + +# describe "Module#attr_accessor" do +# it "creates a getter and setter for each given attribute name" do +# class AttrAccessorSpec +# attr_accessor :a, 'b' +# end +# +# o = AttrAccessorSpec.new +# +# ['a', 'b'].each do |x| +# o.respond_to?(x).should == true +# o.respond_to?("#{x}=").should == true +# end +# +# o.a = "a" +# o.a.should == "a" +# +# o.b = "b" +# o.b.should == "b" +# o.a = o.b = nil +# +# o.__send__(:a=, "a") +# o.__send__(:a).should == "a" +# +# o.__send__(:b=, "b") +# o.__send__(:b).should == "b" +# end +# end diff --git a/spec/core/number/lt_spec.rb b/spec/core/number/lt_spec.rb new file mode 100644 index 0000000000..29c92d0d3e --- /dev/null +++ b/spec/core/number/lt_spec.rb @@ -0,0 +1,12 @@ + +describe "Fixnum#<" do + it "returns true if self is less than the given argument" do + (2 < 13).should == true + (-600 < -500).should == true + + (5 < 1).should == false + (5 < 5).should == false + + (5 < 4.999).should == false + end +end diff --git a/spec/core/string/sub_spec.rb b/spec/core/string/sub_spec.rb new file mode 100644 index 0000000000..a97d97a5d6 --- /dev/null +++ b/spec/core/string/sub_spec.rb @@ -0,0 +1,24 @@ +# require File.dirname(__FILE__) + '/../../spec_helper' + +describe "String#sub with pattern, replacement" do + it "returns a copy of self with all occurrences of pattern replaced with replacement" do + "hello".sub(/[aeiou]/, '*').should == "h*llo" + # "hello".sub(//, ".").should == "hello" + end + + it "ignores a block if supplied" do + "food".sub(/f/, "g") { "W" }.should == "good" + end + + it "supports /i for ignoring case" do + "Hello".sub(/h/i, "j").should == "jello" + "hello".sub(/H/i, "j").should == "jello" + end +end + +describe "String#sub with pattern and block" do + it "returns a copy of self with the first occurrences of pattern replaced with the block's return value" do + # "hi".sub(/./) { |s| s + ' ' }.should == "h i" + # "hi!".sub(/(.)(.)/) { |*a| " " }.should == '["hi"]!' + end +end diff --git a/spec/core/true/and_spec.rb b/spec/core/true/and_spec.rb new file mode 100644 index 0000000000..2a84cd83ed --- /dev/null +++ b/spec/core/true/and_spec.rb @@ -0,0 +1,10 @@ + +describe "TrueClass#&" do + it "returns false if other is nil or false, otherwise true" do + # (true & true).should == true + # (true & false).should == false + # (true & nil).should == false + # (true & "").should == true + # (true & 'x').should == true + end +end diff --git a/spec/core/true/inspect_spec.rb b/spec/core/true/inspect_spec.rb new file mode 100644 index 0000000000..ae3343d7c6 --- /dev/null +++ b/spec/core/true/inspect_spec.rb @@ -0,0 +1,6 @@ + +describe "TrueClass#inspect" do + it "returns the string 'true'" do + true.inspect.should == "true" + end +end diff --git a/spec/core/true/or_spec.rb b/spec/core/true/or_spec.rb new file mode 100644 index 0000000000..9d5ef74152 --- /dev/null +++ b/spec/core/true/or_spec.rb @@ -0,0 +1,10 @@ + +describe "TrueClass#|" do + it "returns true" do + (true | true).should == true + (true | false).should == true + (true | nil).should == true + (true | '').should == true + (true | 'x').should == true + end +end diff --git a/spec/core/true/to_s_spec.rb b/spec/core/true/to_s_spec.rb new file mode 100644 index 0000000000..bb93aa4900 --- /dev/null +++ b/spec/core/true/to_s_spec.rb @@ -0,0 +1,6 @@ + +describe "TrueClass#to_s" do + it "returns the string 'true'" do + true.to_s.should == "true" + end +end diff --git a/spec/core/true/xor_spec.rb b/spec/core/true/xor_spec.rb new file mode 100644 index 0000000000..37b8fe1156 --- /dev/null +++ b/spec/core/true/xor_spec.rb @@ -0,0 +1,10 @@ + +describe "TrueClass#^" do + it "returns true if other is nil or false, otherwise false" do + (true ^ true).should == false + (true ^ false).should == true + (true ^ nil).should == true + (true ^ '').should == false + (true ^ 'x').should == false + end +end diff --git a/spec/language/and_spec.rb b/spec/language/and_spec.rb new file mode 100644 index 0000000000..0ad42aec92 --- /dev/null +++ b/spec/language/and_spec.rb @@ -0,0 +1,64 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The '&&' statement" do + + it "short-circuits evaluation at the first condition to be false" do + x = nil + true && false && x = 1 + x.should == nil + end + + it "evaluates to the first condition not to be true" do + ("yes" && 1 && nil && true).should == nil + ("yes" && 1 && false && true).should == false + end + + it "evaluates to the last condition if all are true" do + ("yes" && 1).should == 1 + (1 && "yes").should == "yes" + end + + it "evaluates the full set of chained conditions during assignment" do + x = y = nil + x = 1 && y = 2 + x.should == 2 + end + + it "treats empty expressions as nil" do + (() && true).should == nil + (true && ()).should == nil + (() && ()).should == nil + end +end + +describe "The 'and' statement" do + it "short-circuits evaluation at the first condition to be false" do + x = nil + true and false and x = 1 + x.should == nil + end + + it "evaluates to the first condition not to be true" do + ("yes" and 1 and nil and true).should == nil + ("yes" and 1 and false and true).should == false + end + + it "evaluates to the last condition if all are true" do + ("yes" and 1).should == 1 + (1 and "yes").should == "yes" + end + + it "when used in assignment, evaluates and assigns the expressions individually" do + x = nil + y = nil + x = 1 and y = 2 + x.should == 1 + end + + it "treats empty expressions as nil" do + (() and true).should == nil + (true and ()).should == nil + (() and ()).should == nil + end +end + diff --git a/spec/language/array_spec.rb b/spec/language/array_spec.rb new file mode 100644 index 0000000000..408d5820f7 --- /dev/null +++ b/spec/language/array_spec.rb @@ -0,0 +1,70 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/array', __FILE__) + +describe "Array literals" do + it "[] should return a new array populated with the given elements" do + array = [1, 'a', nil] + array.class.should == Array + array[0].should == 1 + array[1].should == "a" + array[2].should == nil + end + + it "[] treats empty expressions as nil elements" do + array = [0, (), 2, (), 4] + array.class.should == Array + array[0].should == 0 + array[1].should == nil + array[2].should == 2 + array[3].should == nil + array[4].should == 4 + end + + it "[] accepts a literal hash without curly braces as its only parameter" do + ["foo" => :bar, :baz => 42].should == [{ "foo" => :bar, :baz => 42 }] + end +end + +describe "Bareword array literal" do + it "%w() transforms unquoted barewords into an array" do + a = 3 + %w(a #{3+a} 3).should == ['a', '#{3+a}', '3'] + end + + it "%W() transforms unquoted barewords into an array, supporing interpolation" do + a = 3 + %W(a #{3+a} 3).should == ["a", "6", "3"] + end + + it "%W() always treats interpolated expressions as a single word" do + a = "hello world" + %W(a b c #{a} d e).should == ["a", "b", "c", "hello world", "d", "e"] + end + + it "treats consecutive whitespace characters the same as one" do + %W(a b c d).should == ["a", "b", "c", "d"] + %W(hello + world).should == ["hello", "world"] + end + + it "treats whitespace as literals characters when escaped by a backslash" do + %W(a b\ c d e).should == ["a", "b c", "d", "e"] + end + +end + +describe "The unpacking splat operator (*)" do + it "when applied to a literal nested array, unpacks its elements into the containing array" do + [1, 2, *[3, 4, 5]].should == [1, 2, 3, 4, 5] + end + + it "when applied to a nested referenced array, unpacks its elements into the containing array" do + splatted_array = [3, 4, 5] + [1, 2, *splatted_array].should == [1, 2, 3, 4, 5] + end + + it "when applied to a value with no other items in the containing array, coerces the passed value to an array and returns it unchanged" do + splatted_array = [3, 4, 5] + [*splatted_array].should == splatted_array + end +end diff --git a/spec/language/block_spec.rb b/spec/language/block_spec.rb new file mode 100644 index 0000000000..72ec44c1d6 --- /dev/null +++ b/spec/language/block_spec.rb @@ -0,0 +1,63 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/block', __FILE__) + +describe "A block with mismatched arguments" do + it "should fill in unsupplied arguments with nil" do + ret = nil + BlockSpecs::Yield.new.two_args { |one, two, three| ret = [one, two, three] } + ret.should == [1, 2, nil] + end +end + +describe "A block with a 'rest' arg" do + it "collects all of the arguments passed to yield" do + ret = nil + BlockSpecs::Yield.new.splat(1,2,3) {|*args| ret = args} + ret.should == [1,2,3] + end +end + +describe "A block with an anonymous 'rest' arg" do + it "ignores all of the arguments passed to yield" do + ret = [1].each {|*| } + ret.should == [1] + end +end + +describe "A block whose arguments are splatted" do + it "captures the arguments passed to the block in an array" do + a = [] + BlockSpecs::Yield.new.two_args { |*args| a << args } + a.should == [[1, 2]] + end + + it "captures the array passed to the block in an array" do + a = [] + BlockSpecs::Yield.new.two_arg_array { |*args| a << args } + a.should == [[[1, 2]]] + end + + # it "yields the correct arguments in a nested block" do + # a = [] + # BlockSpecs::Yield.new.yield_splat_inside_block { |a1, a2| a << [a1, a2]} + # a.should == [[1, 0], [2, 1]] + # end +end + +describe "Block parameters" do + it "does not override a shadowed variable from the outer scope" do + i = 0 + a = [1, 2, 3] + a.each {|i| ;} + i.should == 0 + end + + it "captures variables from the outer scope" do + a = [1,2,3] + sum = 0 + val = nil + a.each {|val| sum += val} + sum.should == 6 + val.should == nil + end +end diff --git a/spec/language/break_spec.rb b/spec/language/break_spec.rb new file mode 100644 index 0000000000..72b9a25dae --- /dev/null +++ b/spec/language/break_spec.rb @@ -0,0 +1,26 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/break', __FILE__) + +describe "The break statement in a block" do + before :each do + ScratchPad.record [] + @program = BreakSpecs::Block.new + end + + it "returns nil to method invoking the method yielding to the block when not passed an argument" do + @program.break_nil + ScratchPad.recorded.should == [:a, :aa, :b, nil, :d] + end +end + +# describe "Executing break from within a block" do +# +# it "returns from the invoking singleton method" do +# obj = Object.new +# def obj.meth_with_block +# yield +# raise "break didn't break from the singleton method" +# end +# obj.meth_with_block { break :value }.should == :value +# end +# end diff --git a/spec/language/case_spec.rb b/spec/language/case_spec.rb new file mode 100644 index 0000000000..ec406c7ae8 --- /dev/null +++ b/spec/language/case_spec.rb @@ -0,0 +1,103 @@ +# +# describe "The 'case'-construct" do +# it "evaluates the body of the when clause matching the case target expression" do +# case 1 +# when 2; false +# when 1; true +# end.should == true +# end +# +# it "evaluates the body of the when clause whose array expression includes the case target expression" do +# case 2 +# when 3, 4; false +# when 1, 2; true +# end.should == true +# end +# +# it "evaluates the body of the when clause in left-to-right order if it's an array expression" do +# @calls = [] +# def foo; @calls << :foo; end +# def bar; @calls << :bar; end +# +# case true +# when foo, bar; +# end +# +# @calls.should == [:foo, :bar] +# end +# +# it "evaluates the body of the when clause whose range expression includes the case target expression" do +# case 5 +# when 21..30; false +# when 1..20; true +# end.should == true +# end +# +# it "returns nil when no 'then'-bodies are given" do +# case "a" +# when "a" +# when "b" +# end.should == nil +# end +# +# it "evaluates the 'else'-body when no other expression matches" do +# case "c" +# when "a"; 'foo' +# when "b"; 'bar' +# else 'zzz' +# end.should == 'zzz' +# end +# +# it "returns nil when no expression matches and 'else'-body is empty" do +# case "c" +# when "a"; "a" +# when "b"; "b" +# else +# end.should == nil +# end +# +# it "returns 2 when a then body is empty" do +# case Object.new +# when Number then +# 1 +# when String then +# # ok +# else +# 2 +# end.should == 2 +# end +# +# it "returns that statement following 'then'" do +# case "a" +# when "a" then 'foo' +# when "b" then 'bar' +# end.should == 'foo' +# end +# +# it "tests classes with case equality" do +# case "a" +# when String +# 'foo' +# when Symbol +# 'bar' +# end.should == 'foo' +# end +# +# it "tests with matching regexps" do +# case "hello" +# when /abc/; false +# when /^hell/; true +# end.should == true +# end +# +# it "takes a list of values" do +# case 'z' +# when 'a', 'b', 'c', 'd' +# "foo" +# when 'x', 'y', 'z' +# "bar" +# end.should == "bar" +# end +# +# it "takes an expanded array in addition to a list of values" +# end diff --git a/spec/language/def_spec.rb b/spec/language/def_spec.rb new file mode 100644 index 0000000000..7b961625b9 --- /dev/null +++ b/spec/language/def_spec.rb @@ -0,0 +1,21 @@ + +describe "Redefining a method" do + + it "replaces the original method" do + def barFoo; 100; end + barFoo.should == 100 + + def barFoo; 200; end + barFoo.should == 200 + end +end + +describe "A singleton method definition" do + it "can be declared for a local variable" do + a = Object.new + def a.foo + 5 + end + a.foo.should == 5 + end +end diff --git a/spec/language/eigenclass_spec.rb b/spec/language/eigenclass_spec.rb new file mode 100644 index 0000000000..bdc4ef48db --- /dev/null +++ b/spec/language/eigenclass_spec.rb @@ -0,0 +1,69 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "self in an eigenclass body (class << obj)" do + it "is TrueClass for true" do + class << true; self; end.should == TrueClass + end + + it "is FalseClass for false" do + class << false; self; end.should == FalseClass + end + + it "is NilClass for nil" do + class << nil; self; end.should == NilClass + end + + it "raises a TypeError for Fixnum's" do + lambda { class << 1; self; end }.should raise_error(TypeError) + end + + it "raises a TypeError for symbols" do + lambda { class << :symbol; self; end }.should raise_error(TypeError) + end + + it "is a singleton Class instance" do + mock = Object.new + cls = class << mock; self; end + cls.is_a?(Class).should == true + (cls == Object).should == false + mock.is_a?(cls).should == true + end +# +# it "is a Class for classes" +# +# it "inherits from Class for classes" do +# temp = [] +# cls = class << Object; self; end +# sc = cls +# until sc.nil? || sc.superclass == sc +# temp << sc +# sc = sc.superclass +# end +# temp.should include(Class) +# end +end + + +# puts "testing eigenclass etc" +# +# class EigenclassSpecTest +# +# def something=(something) +# puts "setting something to #{something}" +# end +# end +# +# a = EigenclassSpecTest.new +# a.something = 100 +# +# a_class = class << a; self; end +# +# a_class.define_method(:something=) do |value| +# puts "setting something.." +# super value +# puts "did set something" +# end +# +# # `console.log(#{a}['$something=']);` +# +# a.something = 200 diff --git a/spec/language/file_spec.rb b/spec/language/file_spec.rb new file mode 100644 index 0000000000..2a32f86dc0 --- /dev/null +++ b/spec/language/file_spec.rb @@ -0,0 +1,13 @@ +require File.expand_path('../../spec_helper', __FILE__) + +# specs for __FILE__ + +describe "The __FILE__ constant" do + it "equals the current filename" do + File.basename(__FILE__).should == "file_spec.rb" + end + + it "equals (eval) inside an eval" do + # eval("__FILE__").should == "(eval)" + end +end diff --git a/spec/language/fixtures/array.rb b/spec/language/fixtures/array.rb new file mode 100644 index 0000000000..e69de29bb2 diff --git a/spec/language/fixtures/block.rb b/spec/language/fixtures/block.rb new file mode 100644 index 0000000000..44351fa40e --- /dev/null +++ b/spec/language/fixtures/block.rb @@ -0,0 +1,21 @@ +puts "in block fixtures" + +module BlockSpecs + class Yield + def splat(*args) + yield *args + end + + def two_args + yield 1, 2 + end + + def two_arg_array + yield [1, 2] + end + + def yield_splat_inside_block + [1, 2].send(:each_with_index) { |*args| yield(*args) } + end + end +end diff --git a/spec/language/fixtures/break.rb b/spec/language/fixtures/break.rb new file mode 100644 index 0000000000..1ef61d6e8d --- /dev/null +++ b/spec/language/fixtures/break.rb @@ -0,0 +1,41 @@ +module BreakSpecs + class Driver + def initialize(ensures=false) + @ensures = ensures + end + + def note(value) + ScratchPad << value + end + end + + class Block < Driver + def break_nil + note :a + note yielding { + note :b + break + note :c + } + note :d + end + + def break_value + note :a + note yielding { + note :b + break :break + note :c + } + note :d + end + + def yielding + note :aa + note yield + note :bb + end + end + +end + diff --git a/spec/language/fixtures/private.rb b/spec/language/fixtures/private.rb new file mode 100644 index 0000000000..eb666cd8ce --- /dev/null +++ b/spec/language/fixtures/private.rb @@ -0,0 +1,44 @@ +module Private + class A + def foo + "foo" + end + + private + def bar + "bar" + end + end + + class B + def foo + "foo" + end + + private + class C + def baz + "baz" + end + end + + class << self + def public_class_method1; 1; end + private + def private_class_method1; 1; end + end + def self.public_class_method2; 2; end + + def bar + "bar" + end + end + + class F + def foo + "foo" + end + end +end + + diff --git a/spec/language/fixtures/super.rb b/spec/language/fixtures/super.rb new file mode 100644 index 0000000000..d8555fc7d0 --- /dev/null +++ b/spec/language/fixtures/super.rb @@ -0,0 +1,293 @@ +module Super + module S1 + class A + def foo(a) + a << "A#foo" + bar(a) + end + def bar(a) + a << "A#bar" + end + end + class B < A + def foo(a) + a << "B#foo" + super(a) + end + def bar(a) + a << "B#bar" + super(a) + end + end + end + + module S2 + class A + def baz(a) + a << "A#baz" + end + end + class B < A + def foo(a) + a << "B#foo" + baz(a) + end + end + class C < B + def baz(a) + a << "C#baz" + super(a) + end + end + end + + module S3 + class A + def foo(a) + a << "A#foo" + end + def self.foo(a) + a << "A::foo" + end + def self.bar(a) + a << "A::bar" + foo(a) + end + end + class B < A + def self.foo(a) + a << "B::foo" + super(a) + end + def self.bar(a) + a << "B::bar" + super(a) + end + end + end + + module S4 + class A + def foo(a) + a << "A#foo" + end + end + class B < A + def foo(a, b) + a << "B#foo(a,#{b})" + super(a) + end + end + end + + class S5 + def here + :good + end + end + + class S6 < S5 + def under + yield + end + + def here + under { + super + } + end + end + + class S7 < S5 + define_method(:here) { super() } + end + + module MS1 + module ModA + def foo(a) + a << "ModA#foo" + bar(a) + end + def bar(a) + a << "ModA#bar" + end + end + class A + include ModA + end + module ModB + def bar(a) + a << "ModB#bar" + super(a) + end + end + class B < A + def foo(a) + a << "B#foo" + super(a) + end + include ModB + end + end + # + # module MS2 + # class A + # def baz(a) + # a << "A#baz" + # end + # end + # module ModB + # def foo(a) + # a << "ModB#foo" + # baz(a) + # end + # end + # class B < A + # include ModB + # end + # class C < B + # def baz(a) + # a << "C#baz" + # super(a) + # end + # end + # end + # + # module MS3 + # module ModA + # def foo(a) + # a << "ModA#foo" + # end + # def bar(a) + # a << "ModA#bar" + # foo(a) + # end + # end + # class A + # def foo(a) + # a << "A#foo" + # end + # class << self + # include ModA + # end + # end + # class B < A + # def self.foo(a) + # a << "B::foo" + # super(a) + # end + # def self.bar(a) + # a << "B::bar" + # super(a) + # end + # end + # end + # + # module MS4 + # module Layer1 + # def example + # 5 + # end + # end + # + # module Layer2 + # include Layer1 + # def example + # super + # end + # end + # + # class A + # include Layer2 + # public :example + # end + # end + + # class MM_A + # undef_method :is_a? + # end + # + # class MM_B < MM_A + # def is_a?(blah) + # # should fire the method_missing below + # super + # end + # + # def method_missing(*) + # false + # end + # end + # + # class Alias1 + # def name + # [:alias1] + # end + # end + # + # class Alias2 < Alias1 + # def initialize + # @times = 0 + # end + # + # def name + # if @times >= 10 + # raise "runaway super" + # end + # + # @times += 1 + # + # # Use this so that we can see collect all supers that we see. + # # One bug that arises is that we call Alias2#name from Alias2#name + # # as it's superclass. In that case, either we get a runaway recursion + # # super OR we get the return value being [:alias2, :alias2, :alias1] + # # rather than [:alias2, :alias1]. + # # + # # Which one depends on caches and how super is implemented. + # [:alias2] + super + # end + # end + # + # class Alias3 < Alias2 + # alias_method :name3, :name + # # In the method table for Alias3 now should be a special alias entry + # # that references Alias2 and Alias2#name (probably as an object). + # # + # # When name3 is called then, Alias2 (NOT Alias3) is presented as the + # # current module to Alias2#name, so that when super is called, + # # Alias2->superclass is next. + # # + # # Otherwise, Alias2 is next, which is where name was to begin with, + # # causing the wrong #name method to be called. + # end + # + # module AliasWithSuper + # module AS1 + # def foo + # :a + # end + # end + # + # module BS1 + # def foo + # [:b, super] + # end + # end + # + # class Base + # extend AS1 + # extend BS1 + # end + # + # class Trigger < Base + # class << self + # def foo_quux + # foo_baz + # end + # + # alias_method :foo_baz, :foo + # alias_method :foo, :foo_quux + # end + # end + # end + +end diff --git a/spec/language/hash_spec.rb b/spec/language/hash_spec.rb new file mode 100644 index 0000000000..b8180ecdd6 --- /dev/null +++ b/spec/language/hash_spec.rb @@ -0,0 +1,29 @@ + +describe "Hash literal" do + it "{} should return an empty hash" do + {}.size.should == 0 + {}.should == {} + end + + it "{} should return a new hash populated with the given elements" do + h = { :a => 'a', 'b' => 3, 44 => 2.3 } + h.size.should == 3 + h.should == { :a => 'a', 'b' => 3, 44 => 2.3 } + end + + it "treats empty expressions as nils" do + h = {() => ()} + h.keys.should == [nil] + h.values.should == [nil] + + h = {() => :value} + h.keys.should == [nil] + h.values.should == [:value] + h[nil].should == :value + + h = {:key => ()} + h.keys.should == [:key] + h.values.should == [nil] + h[:key].should == nil + end +end diff --git a/spec/language/if_spec.rb b/spec/language/if_spec.rb new file mode 100644 index 0000000000..1846723d6a --- /dev/null +++ b/spec/language/if_spec.rb @@ -0,0 +1,54 @@ +# +# describe "The if expression" do +# it "evaluates body if expression is true" do +# a = [] +# if true +# a << 123 +# end +# a.should == [123] +# end +# +# it "does not evaluate body if expression is false" do +# a = [] +# if false +# a << 123 +# end +# a.should == [] +# end +# +# it "does not evaluate body if expression is empty" do +# a = [] +# if () +# a << 123 +# end +# a.should == [] +# end +# +# it "does not evaluate else body if expression is true" do +# a = [] +# if true +# a << 123 +# else +# a << 456 +# end +# a.should == [123] +# end +# +# it "evaluates only else-body if expression is false" do +# a = [] +# if false +# a << 123 +# else +# a << 456 +# end +# a.should == [456] +# end +# +# it "returns result of then-body evaluation if expression is true" do +# # raise "this causes error: return is injected before if = javascript error" +# # if true +# # 123 +# # end.should == 456 +# end +# end + diff --git a/spec/language/loop_spec.rb b/spec/language/loop_spec.rb new file mode 100644 index 0000000000..59daf255cd --- /dev/null +++ b/spec/language/loop_spec.rb @@ -0,0 +1,11 @@ + +describe "The loop expression" do + # it "repeats the given block until a break is called" do + # outer_loop = 0 + # loop do + # outer_loop = outer_loop + 1 + # break if outer_loop == 10 + # end + # outer_loop.should == 10 + # end +end diff --git a/spec/language/metaclass_spec.rb b/spec/language/metaclass_spec.rb new file mode 100644 index 0000000000..23771e08e2 --- /dev/null +++ b/spec/language/metaclass_spec.rb @@ -0,0 +1,21 @@ +# +# describe "self in a metaclass body (class << obj)" do +# it "is TrueClass for true" do +# class << true; self; end.should == TrueClass +# end +# +# it "is FalseClass for false" do +# class << false; self; end.should == FalseClass +# end +# +# it "is NilClass for nil" do +# class << nil; self; end.should == NilClass +# end +# +# it "raises a TypeError for numbers" +# +# it "raises a TypeError for symbols" +# +# it "is a singleton Class instance" +# +# end diff --git a/spec/language/method_spec.rb b/spec/language/method_spec.rb new file mode 100644 index 0000000000..97405aef30 --- /dev/null +++ b/spec/language/method_spec.rb @@ -0,0 +1,124 @@ + +describe "Calling a method" do + it "with no arguments is ok" do + def fooP0; 100; end + + fooP0.should == 100 + end + + it "with simple required arguments works" do + def fooP1(a); [a]; end + fooP1(1).should == [1] + + def fooP2(a, b); [a, b]; end + fooP2(1, 2).should == [1, 2] + + def fooP3(a,b,c); [a,b,c]; end + fooP3(1,2,3).should == [1,2,3] + + def fooP4(a,b,c,d); [a,b,c,d]; end + fooP4(1,2,3,4).should == [1,2,3,4] + + def fooP5(a,b,c,d,e); [a,b,c,d,e]; end + fooP5(1,2,3,4,5).should == [1,2,3,4,5] + end + + it "works with optional arguments" do + def fooP0O1(a=1); [a]; end + fooP0O1().should == [1] + + def fooP1O1(a, b=1); [a, b]; end + fooP1O1(1).should == [1, 1] + + def fooP2O1(a, b, c=1); [a, b, c]; end + fooP2O1(1, 2).should == [1, 2, 1] + + def fooP3O1(a, b, c, d=1); [a, b, c, d]; end + fooP3O1(1, 2, 3).should == [1, 2, 3, 1] + + def fooP4O1(a, b, c, d, e=1); [a, b, c, d, e]; end + fooP4O1(1, 2, 3, 4).should == [1, 2, 3, 4, 1] + + def fooP0O2(a=1, b=2); [a, b]; end + fooP0O2.should == [1, 2] + end + + it "works with rest arguments" do + def fooP0R(*r); r; end + fooP0R().should == [] + fooP0R(1).should == [1] + fooP0R(1, 2).should == [1, 2] + + def fooP1R(a, *r); [a, r]; end + fooP1R(1).should == [1, []] + fooP1R(1, 2).should == [1, [2]] + + # def fooP0O1R(a=1, *r); [a, r]; end + # fooP0O1R().should == [1, []] + end + + it "with an empty expression is like calling with nil argument" do + def foo(a); a; end + foo(()).should == nil + end + + it "with block as block argument is ok" do + # def foo(a, &b); [a, yield(b)] end + + foo(10) do 200 end.should == [10, 200] + foo(10) { 200 }.should == [10, 200] + end + + it "with block argument converts the block to proc" do + def makeproc(&b) b end + makeproc { "hello" }.call.should == "hello" + makeproc { "hello" }.class.should == Proc + + def proc_caller(&b) b.call end + def enclosing_method + proc_caller { return :break_return_value } + :method_return_value + end + + enclosing_method.should == :break_return_value + end + + it "with same names as existing variables is ok" do + foobar = 100 + + def foobar; 200; end + + foobar.should == 100 + foobar().should == 200 + end + + it "with splat operator * and literal array unpacks params" do + def fooP3(a, b, c); [a, b, c]; end + + fooP3(*[1, 2, 3]).should == [1, 2, 3] + end + + it "with splat operator * and references array unpacks params" do + def fooP3(a,b,c); [a,b,c] end + + a = [1,2,3] + fooP3(*a).should == [1,2,3] + end + + it "without parentheses works" do + def fooP3(a,b,c); [a,b,c] end + + (fooP3 1,2,3).should == [1,2,3] + end + + it "with a space separating method name and parenthesis treats expression in parenthesis as first argument" do + def myfoo(x); x * 2 end + def mybar + # means myfoo((5).to_s) + # NOT (myfoo(5)).to_s + # myfoo (5).to_s + end + + # mybar().should == "55" + end +end diff --git a/spec/language/next_spec.rb b/spec/language/next_spec.rb new file mode 100644 index 0000000000..a31a6b81b5 --- /dev/null +++ b/spec/language/next_spec.rb @@ -0,0 +1,25 @@ + +describe "The next statement from within the block" do + it "ends block execution" do + a = [] + lambda { + a << 1 + # next + a << 2 + }.call + a.should == [1] + end + + it "causes block to return nil if invoked without arguments" do + # lambda { 123; next; 456 }.call.should == nil + end + + it "causes block to return nil if invoked with an empty expression" do + # lambda { next (); 456 }.call.should == nil + end + + it "returns the argument passed" do + # lambda { 123; next 234; 345 }.call.should == 234 + end +end + diff --git a/spec/language/or_spec.rb b/spec/language/or_spec.rb new file mode 100644 index 0000000000..7fadb40d2c --- /dev/null +++ b/spec/language/or_spec.rb @@ -0,0 +1,34 @@ + +describe "The || operator" do + it "evaluates to true if any of its operands are true" do + if false || true || nil + x = true + end + x.should == true + end + + it "evaluates to false if all of its operands are false" do + if false || nil + x = true + end + x.should == nil + end + + it "is evaluated before assignment operators" do + x = nil || true + x.should == true + end + + it "has a lower precedence than the && operator" do + x = 1 || false && x = 2 + x.should == 1 + end + + it "treats empty expressions as nil" do + (() || true).should == true + (() || false).should == false + (true || ()).should == true + (false || ()).should == nil + (() || ()).should == nil + end +end diff --git a/spec/language/private_spec.rb b/spec/language/private_spec.rb new file mode 100644 index 0000000000..9ade9386cb --- /dev/null +++ b/spec/language/private_spec.rb @@ -0,0 +1,38 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/private', __FILE__) + +describe "The private keyword" do + it "marks following methods as being private" do + a = Private::A.new + lambda { a.bar }.should raise_error(NoMethodError) + + b = Private::B.new + lambda { b.bar }.should raise_error(NoMethodError) + end + + it "is overridden when a new class is opened" do + c = Private::B::C.new + c.baz + Private::B.public_class_method1.should == 1 + Private::B.public_class_method2.should == 2 + lambda { Private::B.private_class_method1 }.should raise_error(NoMethodError) + end + + it "is no longer in effect when the class is closed" do + b = Private::B.new + b.foo + end + + it "changes visibility of previously called method" do + f = Private::F.new + f.foo + module Private + class F + private :foo + end + end + lambda { f.foo }.should raise_error(NoMethodError) + end +end + + diff --git a/spec/language/redo_spec.rb b/spec/language/redo_spec.rb new file mode 100644 index 0000000000..2fff9d865a --- /dev/null +++ b/spec/language/redo_spec.rb @@ -0,0 +1,24 @@ + +describe "The redo statement" do + it "restarts block execution if used within block" do + a = [] + lambda { + a << 1 + # redo if a.size < 2 + a << 2 + }.call + a.should == [1, 1, 2] + end + + it "re-executes the closest loop" + + it "re-executes the last step in enumeration" do + list = [] + [1,2,3].each do |x| + list << x + # break if list.size == 6 + # redo if x == 3 + end + list.should == [1,2,3,3,3,3] + end +end diff --git a/spec/language/regexp_spec.rb b/spec/language/regexp_spec.rb new file mode 100644 index 0000000000..f5254879e0 --- /dev/null +++ b/spec/language/regexp_spec.rb @@ -0,0 +1,26 @@ + +describe "Regexp literals" do + + # =========================================================================== + # = Back-refs = + # =========================================================================== + + it "saves match data in the $~ pseudo-global variable" do + "hello" =~ /l+/ + $~.to_a.should == ["ll"] + end + + it "saves captures in numbered $[1-9] variables" do + "1234567890" =~ /(1)(2)(3)(4)(5)(6)(7)(8)(9)(0)/ + $~.to_a.should == ["1234567890", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"] + $1.should == "1" + $2.should == "2" + $3.should == "3" + $4.should == "4" + $5.should == "5" + $6.should == "6" + $7.should == "7" + $8.should == "8" + $9.should == "9" + end +end diff --git a/spec/language/rescue_spec.rb b/spec/language/rescue_spec.rb new file mode 100644 index 0000000000..4643a39183 --- /dev/null +++ b/spec/language/rescue_spec.rb @@ -0,0 +1,20 @@ + +# class SpecificExampleException < StandardError; end + +# class OtherCustomException < StandardError; end + +# def exception_list +# [SpecificExampleException, OtherCustomException] +# end + +# describe "The rescue keyword" do +# it "can be used to handle a specific exception" + +# it "can capture the raised exception in a local variable" do +# begin +# raise SpecificExampleException, "some text" +# rescue SpecificExampleException => e +# e.message.should == "some text" +# end +# end +# end diff --git a/spec/language/return_spec.rb b/spec/language/return_spec.rb new file mode 100644 index 0000000000..fd33f9ce49 --- /dev/null +++ b/spec/language/return_spec.rb @@ -0,0 +1,47 @@ + +describe "The return keyword" do + it "returns any object directly" do + def r; return 1; end + r().should == 1 + end + + it "returns an single element array directly" do + def r; return[1]; end + r().should == [1] + end + + it "returns an multi element array directly" do + def r; return [1, 2]; end + r().should == [1, 2] + end + + it "returns nil be default" do + def r; return; end + r().should == nil + end + + # describe "within a begin" do + # it "executes ensure before re" + # end + + describe "within a block" do + it "raises a LocalJumpError if there is no lexicaly enclosing method" #do + # def f; yield; end + # lambda { f { return 5 } } + # end + + it "causes lambda to return nil if invoked without any arguments" do + lambda { return; 456 }.call.should == nil + end + + end + + describe "within two blocks" do + it "causes the method that lexically encloses the block to return" do + def f + 1.times { 1.times { return true }; false }; false + end + f.should == true + end + end +end diff --git a/spec/language/string_spec.rb b/spec/language/string_spec.rb new file mode 100644 index 0000000000..368e92d22f --- /dev/null +++ b/spec/language/string_spec.rb @@ -0,0 +1,25 @@ + +# describe "Ruby character strings" do +# it "don't get interpolated when put in single quotes" do +# @ip = 'xxx' +# '#{@ip}'.should == '#{@ip}' +# end + +# it 'get interpolated with #{} when put in double quotes' do +# @ip = 'xxx' +# "#{@ip}".should == "xxx" +# end + +# it "interpolate instance variables just with the # character" #do +# # @ip = "xxx" +# # "#@ip".should == "xxx" +# # end + +# it "interpolate global variables just with the # character" + +# it "interpolate class variables with just the # character" + +# it "allow underscore as part of a variable name in a simple interpolation" + +# it "have characters [.(=?!# end simple # interpolation" +# end diff --git a/spec/language/super_spec.rb b/spec/language/super_spec.rb new file mode 100644 index 0000000000..01ee803314 --- /dev/null +++ b/spec/language/super_spec.rb @@ -0,0 +1,33 @@ +require File.expand_path('../../spec_helper', __FILE__) +require File.expand_path('../fixtures/super', __FILE__) + +describe "The super keyword" do + + it "calls the method on the calling class" do + Super::S1::A.new.foo([]).should == ["A#foo", "A#bar"] + Super::S1::A.new.bar([]).should == ["A#bar"] + Super::S1::B.new.foo([]).should == ["B#foo","A#foo","B#bar","A#bar"] + Super::S1::B.new.bar([]).should == ["B#bar", "A#bar"] + end + + it "searches the full inheritence chain" do + Super::S2::B.new.foo([]).should == ["B#foo", "A#baz"] + Super::S2::B.new.baz([]).should == ["A#baz"] + Super::S2::C.new.foo([]).should == ["B#foo","C#baz","A#baz"] + Super::S2::C.new.baz([]).should == ["C#baz","A#baz"] + end + + it "searches class methods" do + Super::S3::A.new.foo([]).should == ["A#foo"] + Super::S3::A.foo([]).should == ["A::foo"] + Super::S3::A.bar([]).should == ["A::bar","A::foo"] + Super::S3::B.new.foo([]).should == ["A#foo"] + Super::S3::B.foo([]).should == ["B::foo","A::foo"] + Super::S3::B.bar([]).should == ["B::bar","A::bar","B::foo","A::foo"] + end + + it "calls the method on the calling class including modules" do + Super::MS1::A.new.foo([]).should == ["ModA#foo", "ModA#bar"] + end +end + diff --git a/spec/language/until_spec.rb b/spec/language/until_spec.rb new file mode 100644 index 0000000000..6318d663fc --- /dev/null +++ b/spec/language/until_spec.rb @@ -0,0 +1,157 @@ + +# describe "The until expression" do +# it "runs while the expression is false" do +# i = 0 +# until i > 9 +# i = i + 1 +# end + +# i.should == 10 +# end + +# it "optionally takes a 'do' after the expression" do +# i = 0 +# until i > 9 do +# i = i + 1 +# end + +# i.should == 10 +# end + +# it "allows body begin on the same line if do is used" do +# i = 0 +# until i > 9 do i = i + 1 +# end + +# i.should == 10 +# end + +# it "executes code in containing variable scope" do +# i = 0 +# until i == 1 +# a = 123 +# i = 1 +# end + +# a.should == 123 +# end + +# it "executes code in containing variable scope with 'do'" do +# i = 0 +# until i == 1 do +# a = 123 +# i = 1 +# end + +# a.should == 123 +# end + +# it "returns nil if ended when condition became true" do +# i = 0 +# until i > 9 +# i = i + 1 +# end.should == nil +# end + +# it "evaluates the body if expression is empty" do +# a = [] +# until () +# a << :body_evaluated +# break +# end +# a.should == [:body_evaluated] +# end + +# it "stops running body if interrupted by break" do +# i = 0 +# until i > 9 +# i = i + 1 +# break if i > 5 +# end +# i.should == 6 +# end + +# it "returns value passed to break if interrupted by break" do +# until false +# break 123 +# end.should == 123 +# end + +# it "returns nil if interrupted by break with no arguments" do +# until false +# break +# end.should == nil +# end + +# it "skips to end of body with next" do +# a = [] +# i = 0 +# until (i = i + 1) >= 5 +# next if i == 3 +# a << i +# end + +# a.should == [1, 2, 4] +# end + +# it "restarts the current iteration without reevaluating condition with redo" do +# a = [] +# i = 0 +# j = 0 +# until (i = i + 1) >= 3 +# a << i +# j = j + 1 +# redo if j < 3 +# end +# a.should == [1, 1, 1, 2] +# end +# end + +# # describe "The until modifier" do +# # it "runs preceding statement while the condition is false" do +# # i = 0 +# # i = i + 1 until i > 9 +# # i.should == 10 +# # end +# # +# # it "evaluates condition before statement execution" do +# # a = [] +# # i = 0 +# # a << i until (i = i + 1) >= 3 +# # a.should == [1, 2] +# # end +# # +# # it "does not run preceding statement if the condition is true" do +# # i = 0 +# # i = i + 1 until true +# # i.should == 0 +# # end +# # +# # it "returns nil if ended when condition became true" do +# # i = 0 +# # (i = i + 1 until i > 9).should == nil +# # end +# # +# # it "returns value passed to break if interrupted by break" do +# # (break 123 until false).should == 123 +# # end +# # +# # it "returns nil if interrupted by break with no arguments" do +# # (break until false).should == nil +# # end +# # +# # it "skips to end of body with next" do +# # i = 0 +# # j = 0 +# # (i = i + 1) == 3 ? next : j = j + i until i > 10 +# # j.should == 63 +# # end +# # +# # it "restarts the current iteration without reevaluating condition with redo" do +# # i = 0 +# # j = 0 +# # (i = i + 1) == 4 ? redo : j = j + i until (i = i + 1) > 10 +# # j.should == 34 +# # end +# # end + diff --git a/spec/language/variables_spec.rb b/spec/language/variables_spec.rb new file mode 100644 index 0000000000..bd1589ccd1 --- /dev/null +++ b/spec/language/variables_spec.rb @@ -0,0 +1,155 @@ + +describe "Operator assignment 'var op= expr'" do + it "is equivalent to 'var = var op expr'" do + x = 13 + (x += 5).should == 18 + x.should == 18 + + x = 17 + (x -= 11).should == 6 + x.should == 6 + + x = 2 + (x *= 5).should == 10 + x.should == 10 + + # x = 36 + # (x /= 9).should == 4 + # x.should == 4 + + # x = 23 + # (x %= 5).should == 3 + # x.should == 3 + # (x %= 3).should == 0 + # x.should == 0 + + # x = 2 + # (x **= 3).should == 8 + # x.should == 8 + + # x = 4 + # (x |= 3).should == 7 + # x.should == 7 + # (x |= 4).should == 7 + # x.should == 7 + + # x = 6 + # (x &= 3).should == 2 + # x.should == 2 + # (x &= 4).should == 0 + # x.should == 0 + + # x = 2 + # (x ^= 3).should == 1 + # x.should == 1 + # (x ^= 4).should == 5 + # x.should == 5 + + # x = 17 + # (x <<= 3).should == 136 + # x.should == 136 + + # x = 5 + # (x >>= 1).should == 2 + # x.should == 2 + + x = nil + (x ||= 17).should == 17 + x.should == 17 + (x ||= 2).should == 17 + x.should == 17 + + # x = false + # (x &&= true).should == false + # x.should == false + # (x &&= false).should == false + # x.should == false + # x = true + # (x &&= true).should == true + # x.should == true + # (x &&= false).should == false + # x.should == false + end +end + +describe "Operator assignment 'obj[idx] op= expr'" do + it "is equivalent to 'obj[idx] = obj[idx] op expr'" do + x = [2, 13, 7] + (x[1] += 5).should == 18 + x.should == [2, 18, 7] + + x = [17, 6] + (x[0] -= 11).should == 6 + x.should == [6, 6] + end +end + +describe "Single assignment" do + it "Assignment does not modify the lhs, it reassigns its reference" do + a = 'Foobar' + b = a + b = 'Bazquux' + a.should == 'Foobar' + b.should == 'Bazquux' + end + + it "Assignment does not copy the object being assigned, just creates a new reference to it" do + a = [] + b = a + b << 1 + a.should == [1] + end + + it "If rhs has multiple arguments, lhs becomes an Array of them" do + a = 1, 2, 3 + a.should == [1, 2, 3] + + a = 1, (), 3 + a.should == [1, nil, 3] + end +end + +describe "Multiple assignment without grouping or splatting" do + it "an equal number of arguments on lhs and rhs assigns positionally" do + a, b, c, d = 1, 2, 3, 4 + a.should == 1 + b.should == 2 + c.should == 3 + d.should == 4 + end + + it "If rhs has too few arguments, the missing ones on lhs are assigned nil" do + a, b, c = 1, 2 + a.should == 1 + b.should == 2 + c.should == nil + end + + it "If rhs has too many arguments, the extra ones are silently not assigned anywhere" do + a, b = 1, 2, 3 + a.should == 1 + b.should == 2 + end +end + +describe "Multiple assignments with splats" do + it "* on the lhs collects all parameters from its position onwards as an Array or an empty array" do + a, *b = 1, 2 + c, *d = 1 + e, *f = 1, 2, 3 + g, *h = 1, [2, 3] + # i + # j + # k + + a.should == 1 + b.should == [2] + c.should == 1 + d.should == [] + e.should == 1 + f.should == [2, 3] + g.should == 1 + h.should == [[2, 3]] + end +end + diff --git a/spec/language/while_spec.rb b/spec/language/while_spec.rb new file mode 100644 index 0000000000..31ba5fc39a --- /dev/null +++ b/spec/language/while_spec.rb @@ -0,0 +1,164 @@ +require File.expand_path('../../spec_helper', __FILE__) + +describe "The while expression" do + it "runs while the expression is true" do + i = 0 + while i < 3 + i += 1 + end + + i.should == 3 + end + + it "optionally takes a 'do' after the expression" do + i = 0 + while i < 3 do + i += 1 + end + + i.should == 3 + end + + it "allows body begin on the same line if do is used" do + i = 0 + while i < 3 do i += 1 + end + + i.should == 3 + end + + it "executes code in containing variable scope" do + i = 0 + while i != 1 + a = 123 + i = 1 + end + + a.should == 123 + end + + it "executes code in containing variable scope with 'do'" do + i = 0 + while i != 1 do + a = 123 + i = 1 + end + + a.should == 123 + end + + it "returns nil if ended when condition became false" do + i = 0 + while i < 3 + i += 1 + end.should == nil + end + + it "does not evaluate the body if expression is empty" do + a = [] + while () + a << :body_evaluated + end + a.should == [] + end + + it "stops running body if interrupted by break" do + i = 0 + while i < 10 + i = i + 1 + break if i > 5 + end + + i.should == 6 + end + + it "returns value passed to break if interrupted by break" do + while true + break 123 + end.should == 123 + end + + it "returns nil if interrupted by break with no arguments" do + while true + break + end.should == nil + end + + it "skips to end of body with next" do + a = [] + i = 0 + while (i += 1) < 5 + next if i == 3 + a << i + end + + a.should == [1, 2, 4] + end + + it "restarts the current iteration without reevaluating condition with redo" do + a = [] + i = 0 + j = 0 + while (i = i + 1) < 3 + a << i + j = j + 1 + # redo if j < 3 + end + + a.should == [1, 1, 1, 2] + end +end + +# describe "The while modifier" do +# it "runs preceding statement while the condition is true" do +# i = 0 +# i = i + 1 while i < 3 +# i.should == 3 +# end + +# it "evaluates condition before statement execution" do +# a = [] +# i = 0 +# a << i while (i = i + 1) < 3 +# a.should == [1, 2] +# end + +# it "does not run preceding statement if the condition is false" do +# i = 0 +# i = i + 1 while false +# i.should == 0 +# end + +# it "does not run preceding statement if the condition is empty" do +# i = 0 +# i = i + 1 while () +# i.should == 0 +# end + +# it "returns nil if ended when condition became false" do +# i = 0 +# (i = i + 1 while i < 10).should == nil +# end + +# it "returns value passed to break if interrupetd by break" do +# (break 123 while true).should == 123 +# end + +# it "returns nil if interrupted by break with no arguments" do +# (break while true).should == nil +# end + +# it "skips to end of body with next" do +# i = 0 +# j = 0 +# (( i = i + 1) == 3 ? next : j = j + i) while i <= 10 +# j.should == 63 +# end + +# it "restarts the current iteration without reevaluating condition with redo" do +# i = 0 +# j = 0 +# # (i = i + 1) == 4 ? redo : j = j + i while (i = i + 1) <= 10 +# j.should == 34 +# end +# end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000000..07495eb536 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,5 @@ +# Usually do this to require our libs, but as we are in opal, these are already +# included. +# require File.join(File.dirname(__FILE__), '..', 'lib', 'opal.rb') + +puts "in spec helper"