Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial commit

  • Loading branch information...
commit 387feedaed20bb4cb88038db63ef7a42f0b221d4 0 parents
@matthewd authored
3  .gitignore
@@ -0,0 +1,3 @@
+pkg/*
+*.gem
+.bundle
4 Gemfile
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in capuchin.gemspec
+gemspec
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2010 Matthew Draper. All rights reserved.
+
+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.
35 README.markdown
@@ -0,0 +1,35 @@
+# Capuchin
+
+A JavaScript implementation, running on the Rubinius VM.
+
+
+## Usage
+
+Given `eg/nest.js`:
+
+ function a(n) {
+ n = n + 4;
+ function b(n) {
+ n = n + 5;
+ return n * 2;
+ }
+ return n + b(7);
+ }
+
+ print(a(3));
+
+Run:
+
+ bin/capuchin eg/nest.js
+
+And you'll see:
+
+ (Loads of debug information that I haven't turned off yet, and...)
+ 31
+
+(Which isn't very exciting, but is correct, and demonstrates that the
+inner `n` parameter correctly shadows the outer.)
+
+Alternatively, run something more interesting, and then fix whatever
+breaks.
+
2  Rakefile
@@ -0,0 +1,2 @@
+require 'bundler'
+Bundler::GemHelper.install_tasks
61 bin/capuchin
@@ -0,0 +1,61 @@
+#!/usr/bin/env ruby
+
+$: << File.join(File.dirname(File.dirname(__FILE__)), "lib")
+
+require "capuchin"
+
+filename = File.exist?(ARGV.first) ? ARGV.shift : 'eg/math.js'
+parser = RKelly::Parser.new
+class << parser
+ # A mostly-futile attempt to get the parser to tell us why it fails
+ def yyerror; yyabort; end
+ def yyabort
+ raise ParseError, sprintf("\nparse error on value %s (%s)",
+ @racc_val.inspect, token_to_str(@racc_t) || '?')
+ end
+end
+ast = parser.parse(File.read(filename))
+
+g = Capuchin::Generator.new
+scope = Capuchin::Visitor::Scope.new(nil)
+visitor = Capuchin::Visitor.new(g, scope)
+
+Rubinius::AST::AsciiGrapher.new(ast, RKelly::Nodes::Node).print
+
+code = Object.new
+
+g.name = :call
+g.file = filename.intern
+g.set_line 1
+
+g.required_args = 0
+g.total_args = 0
+g.splat_index = nil
+
+g.local_count = 0
+g.local_names = []
+
+ast.accept(Capuchin::Visitor::DeclScanner.new(scope))
+scope.append_method_definitions g, visitor
+ast.accept(visitor)
+
+g.push_nil
+g.ret
+g.close
+
+g.local_count = g.state.scope.local_count
+g.local_names = g.state.scope.local_names
+
+g.encode
+cm = g.package ::Rubinius::CompiledMethod
+#puts cm.decode #if $DEBUG
+
+p = Rubinius::Compiler::MethodPrinter.new
+p.bytecode = true
+p.print_method(cm)
+
+ss = ::Rubinius::StaticScope.new Object
+::Rubinius.attach_method g.name, cm, ss, code
+
+code.send(g.name)
+
21 capuchin.gemspec
@@ -0,0 +1,21 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "capuchin/version"
+
+Gem::Specification.new do |s|
+ s.name = "capuchin"
+ s.version = Capuchin::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.authors = ["Matthew Draper"]
+ s.email = ["matthew@trebex.net"]
+ s.homepage = ""
+ s.summary = %q{JavaScript on Rubinius}
+ s.description = %q{A JavaScript implemention on the Rubinus VM.}
+
+ s.add_dependency "rkelly", "~> 1.0.1"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+end
9 eg/cond.js
@@ -0,0 +1,9 @@
+
+if (!"!") {
+ print('yes');
+} else {
+ print('no');
+}
+
+print(!!print);
+
12 eg/math.js
@@ -0,0 +1,12 @@
+
+var a = 2;
+var b = 3;
+
+var x = function(n) { var b = a * n; return b; };
+
+var c = x(b);
+
+print(a);
+print(b);
+print(c);
+
12 eg/nest.js
@@ -0,0 +1,12 @@
+
+function a(n) {
+ n = n + 4;
+ function b(n) {
+ n = n + 5;
+ return n * 2;
+ }
+ return n + b(7);
+}
+
+print(a(3));
+
16 eg/obj.js
@@ -0,0 +1,16 @@
+
+var o = { a: 1, b: 2, c: 3, x: function() { p(this); print('xx'); } };
+print(o.a);
+o.d = 4;
+print(o.d);
+
+p(o);
+
+o.x();
+
+var z = o.x;
+
+z();
+
+z.call('foo');
+
827 eg/richards.js
@@ -0,0 +1,827 @@
+
+// Copyright 2008 the V8 project authors. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// Simple framework for running the benchmark suites and
+// computing a score based on the timing measurements.
+
+
+// A benchmark has a name (string) and a function that will be run to
+// do the performance measurement. The optional setup and tearDown
+// arguments are functions that will be invoked before and after
+// running the benchmark, but the running time of these functions will
+// not be accounted for in the benchmark score.
+function Benchmark(name, run, setup, tearDown) {
+ this.name = name;
+ this.run = run;
+ this.Setup = setup ? setup : function() { };
+ this.TearDown = tearDown ? tearDown : function() { };
+}
+
+
+// Benchmark results hold the benchmark and the measured time used to
+// run the benchmark. The benchmark score is computed later once a
+// full benchmark suite has run to completion.
+function BenchmarkResult(benchmark, time) {
+ this.benchmark = benchmark;
+ this.time = time;
+}
+
+
+// Automatically convert results to numbers. Used by the geometric
+// mean computation.
+BenchmarkResult.prototype.valueOf = function() {
+ return this.time;
+};
+
+
+// Suites of benchmarks consist of a name and the set of benchmarks in
+// addition to the reference timing that the final score will be based
+// on. This way, all scores are relative to a reference run and higher
+// scores implies better performance.
+function BenchmarkSuite(name, reference, benchmarks) {
+ this.name = name;
+ this.reference = reference;
+ this.benchmarks = benchmarks;
+ BenchmarkSuite.suites.push(this);
+}
+
+
+// Keep track of all declared benchmark suites.
+BenchmarkSuite.suites = [];
+
+
+// Scores are not comparable across versions. Bump the version if
+// you're making changes that will affect that scores, e.g. if you add
+// a new benchmark or change an existing one.
+BenchmarkSuite.version = '5';
+
+
+// To make the benchmark results predictable, we replace Math.random
+// with a 100% deterministic alternative.
+Math.random = (function() {
+ var seed = 49734321;
+ return function() {
+ // Robert Jenkins' 32 bit integer hash function.
+ seed = ((seed + 0x7ed55d16) + (seed << 12)) & 0xffffffff;
+ seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
+ seed = ((seed + 0x165667b1) + (seed << 5)) & 0xffffffff;
+ seed = ((seed + 0xd3a2646c) ^ (seed << 9)) & 0xffffffff;
+ seed = ((seed + 0xfd7046c5) + (seed << 3)) & 0xffffffff;
+ seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
+ return (seed & 0xfffffff) / 0x10000000;
+ };
+})();
+
+
+// Runs all registered benchmark suites and optionally yields between
+// each individual benchmark to avoid running for too long in the
+// context of browsers. Once done, the final score is reported to the
+// runner.
+BenchmarkSuite.RunSuites = function(runner) {
+ var continuation = null;
+ var suites = BenchmarkSuite.suites;
+ var length = suites.length;
+ BenchmarkSuite.scores = [];
+ var index = 0;
+ function RunStep() {
+ while (continuation || index < length) {
+ if (continuation) {
+ continuation = continuation();
+ } else {
+ var suite = suites[index++];
+ if (runner.NotifyStart) runner.NotifyStart(suite.name);
+ continuation = suite.RunStep(runner);
+ }
+ if (continuation && typeof window != 'undefined' && window.setTimeout) {
+ window.setTimeout(RunStep, 25);
+ return;
+ }
+ }
+ if (runner.NotifyScore) {
+ var score = BenchmarkSuite.GeometricMean(BenchmarkSuite.scores);
+ var formatted = BenchmarkSuite.FormatScore(100 * score);
+ runner.NotifyScore(formatted);
+ }
+ }
+ RunStep();
+};
+
+
+// Counts the total number of registered benchmarks. Useful for
+// showing progress as a percentage.
+BenchmarkSuite.CountBenchmarks = function() {
+ var result = 0;
+ var suites = BenchmarkSuite.suites;
+ for (var i = 0; i < suites.length; i++) {
+ result = result + suites[i].benchmarks.length;
+ }
+ return result;
+};
+
+
+// Computes the geometric mean of a set of numbers.
+BenchmarkSuite.GeometricMean = function(numbers) {
+ var log = 0;
+ for (var i = 0; i < numbers.length; i++) {
+ log = log + Math.log(numbers[i]);
+ }
+ return Math.pow(Math.E, log / numbers.length);
+};
+
+
+// Converts a score value to a string with at least three significant
+// digits.
+BenchmarkSuite.FormatScore = function(value) {
+ if (value > 100) {
+ return value.toFixed(0);
+ } else {
+ return value.toPrecision(3);
+ }
+};
+
+// Notifies the runner that we're done running a single benchmark in
+// the benchmark suite. This can be useful to report progress.
+BenchmarkSuite.prototype.NotifyStep = function(result) {
+ this.results.push(result);
+ if (this.runner.NotifyStep) this.runner.NotifyStep(result.benchmark.name);
+};
+
+
+// Notifies the runner that we're done with running a suite and that
+// we have a result which can be reported to the user if needed.
+BenchmarkSuite.prototype.NotifyResult = function() {
+ var mean = BenchmarkSuite.GeometricMean(this.results);
+ var score = this.reference / mean;
+ BenchmarkSuite.scores.push(score);
+ if (this.runner.NotifyResult) {
+ var formatted = BenchmarkSuite.FormatScore(100 * score);
+ this.runner.NotifyResult(this.name, formatted);
+ }
+};
+
+
+// Notifies the runner that running a benchmark resulted in an error.
+BenchmarkSuite.prototype.NotifyError = function(error) {
+ if (this.runner.NotifyError) {
+ this.runner.NotifyError(this.name, error);
+ }
+ if (this.runner.NotifyStep) {
+ this.runner.NotifyStep(this.name);
+ }
+};
+
+
+// Runs a single benchmark for at least a second and computes the
+// average time it takes to run a single iteration.
+BenchmarkSuite.prototype.RunSingleBenchmark = function(benchmark) {
+ var elapsed = 0;
+ var start = new Date();
+ for (var n = 0; elapsed < 1000; n++) {
+ benchmark.run();
+ elapsed = new Date() - start;
+ }
+ var usec = (elapsed * 1000) / n;
+ this.NotifyStep(new BenchmarkResult(benchmark, usec));
+};
+
+
+// This function starts running a suite, but stops between each
+// individual benchmark in the suite and returns a continuation
+// function which can be invoked to run the next benchmark. Once the
+// last benchmark has been executed, null is returned.
+BenchmarkSuite.prototype.RunStep = function(runner) {
+ this.results = [];
+ this.runner = runner;
+ var length = this.benchmarks.length;
+ var index = 0;
+ var suite = this;
+
+ // Run the setup, the actual benchmark, and the tear down in three
+ // separate steps to allow the framework to yield between any of the
+ // steps.
+
+ function RunNextSetup() {
+ if (index < length) {
+ try {
+ suite.benchmarks[index].Setup();
+ } catch (e) {
+ suite.NotifyError(e);
+ return null;
+ }
+ return RunNextBenchmark;
+ }
+ suite.NotifyResult();
+ return null;
+ }
+
+ function RunNextBenchmark() {
+ try {
+ suite.RunSingleBenchmark(suite.benchmarks[index]);
+ } catch (e) {
+ suite.NotifyError(e);
+ return null;
+ }
+ return RunNextTearDown;
+ }
+
+ function RunNextTearDown() {
+ try {
+ suite.benchmarks[index++].TearDown();
+ } catch (e) {
+ suite.NotifyError(e);
+ return null;
+ }
+ return RunNextSetup;
+ }
+
+ // Start out running the setup.
+ return RunNextSetup();
+};
+
+
+
+
+// Copyright 2007 Google Inc. All rights reserved.
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following
+// disclaimer in the documentation and/or other materials provided
+// with the distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived
+// from this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+// This is a JavaScript implementation of the Richards
+// benchmark from:
+//
+// http://www.cl.cam.ac.uk/~mr10/Bench.html
+//
+// The benchmark was originally implemented in BCPL by
+// Martin Richards.
+
+
+var Richards = new BenchmarkSuite('Richards', 34886, [
+ new Benchmark("Richards", runRichards)
+]);
+
+
+/**
+ * The Richards benchmark simulates the task dispatcher of an
+ * operating system.
+ **/
+function runRichards() {
+ var scheduler = new Scheduler();
+ scheduler.addIdleTask(ID_IDLE, 0, null, COUNT);
+
+ var queue = new Packet(null, ID_WORKER, KIND_WORK);
+ queue = new Packet(queue, ID_WORKER, KIND_WORK);
+ scheduler.addWorkerTask(ID_WORKER, 1000, queue);
+
+ queue = new Packet(null, ID_DEVICE_A, KIND_DEVICE);
+ queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE);
+ queue = new Packet(queue, ID_DEVICE_A, KIND_DEVICE);
+ scheduler.addHandlerTask(ID_HANDLER_A, 2000, queue);
+
+ queue = new Packet(null, ID_DEVICE_B, KIND_DEVICE);
+ queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE);
+ queue = new Packet(queue, ID_DEVICE_B, KIND_DEVICE);
+ scheduler.addHandlerTask(ID_HANDLER_B, 3000, queue);
+
+ scheduler.addDeviceTask(ID_DEVICE_A, 4000, null);
+
+ scheduler.addDeviceTask(ID_DEVICE_B, 5000, null);
+
+ scheduler.schedule();
+
+ if (scheduler.queueCount != EXPECTED_QUEUE_COUNT ||
+ scheduler.holdCount != EXPECTED_HOLD_COUNT) {
+ var msg =
+ "Error during execution: queueCount = " + scheduler.queueCount +
+ ", holdCount = " + scheduler.holdCount + ".";
+ print(msg);
+ }
+}
+
+var COUNT = 1000;
+
+/**
+ * These two constants specify how many times a packet is queued and
+ * how many times a task is put on hold in a correct run of richards.
+ * They don't have any meaning a such but are characteristic of a
+ * correct run so if the actual queue or hold count is different from
+ * the expected there must be a bug in the implementation.
+ **/
+var EXPECTED_QUEUE_COUNT = 2322;
+var EXPECTED_HOLD_COUNT = 928;
+
+
+/**
+ * A scheduler can be used to schedule a set of tasks based on their relative
+ * priorities. Scheduling is done by maintaining a list of task control blocks
+ * which holds tasks and the data queue they are processing.
+ * @constructor
+ */
+function Scheduler() {
+ this.queueCount = 0;
+ this.holdCount = 0;
+ this.blocks = new Array(NUMBER_OF_IDS);
+ this.list = null;
+ this.currentTcb = null;
+ this.currentId = null;
+}
+
+var ID_IDLE = 0;
+var ID_WORKER = 1;
+var ID_HANDLER_A = 2;
+var ID_HANDLER_B = 3;
+var ID_DEVICE_A = 4;
+var ID_DEVICE_B = 5;
+var NUMBER_OF_IDS = 6;
+
+var KIND_DEVICE = 0;
+var KIND_WORK = 1;
+
+/**
+ * Add an idle task to this scheduler.
+ * @param {int} id the identity of the task
+ * @param {int} priority the task's priority
+ * @param {Packet} queue the queue of work to be processed by the task
+ * @param {int} count the number of times to schedule the task
+ */
+Scheduler.prototype.addIdleTask = function (id, priority, queue, count) {
+ this.addRunningTask(id, priority, queue, new IdleTask(this, 1, count));
+};
+
+/**
+ * Add a work task to this scheduler.
+ * @param {int} id the identity of the task
+ * @param {int} priority the task's priority
+ * @param {Packet} queue the queue of work to be processed by the task
+ */
+Scheduler.prototype.addWorkerTask = function (id, priority, queue) {
+ this.addTask(id, priority, queue, new WorkerTask(this, ID_HANDLER_A, 0));
+};
+
+/**
+ * Add a handler task to this scheduler.
+ * @param {int} id the identity of the task
+ * @param {int} priority the task's priority
+ * @param {Packet} queue the queue of work to be processed by the task
+ */
+Scheduler.prototype.addHandlerTask = function (id, priority, queue) {
+ this.addTask(id, priority, queue, new HandlerTask(this));
+};
+
+/**
+ * Add a handler task to this scheduler.
+ * @param {int} id the identity of the task
+ * @param {int} priority the task's priority
+ * @param {Packet} queue the queue of work to be processed by the task
+ */
+Scheduler.prototype.addDeviceTask = function (id, priority, queue) {
+ this.addTask(id, priority, queue, new DeviceTask(this));
+};
+
+/**
+ * Add the specified task and mark it as running.
+ * @param {int} id the identity of the task
+ * @param {int} priority the task's priority
+ * @param {Packet} queue the queue of work to be processed by the task
+ * @param {Task} task the task to add
+ */
+Scheduler.prototype.addRunningTask = function (id, priority, queue, task) {
+ this.addTask(id, priority, queue, task);
+ this.currentTcb.setRunning();
+};
+
+/**
+ * Add the specified task to this scheduler.
+ * @param {int} id the identity of the task
+ * @param {int} priority the task's priority
+ * @param {Packet} queue the queue of work to be processed by the task
+ * @param {Task} task the task to add
+ */
+Scheduler.prototype.addTask = function (id, priority, queue, task) {
+ this.currentTcb = new TaskControlBlock(this.list, id, priority, queue, task);
+ this.list = this.currentTcb;
+ this.blocks[id] = this.currentTcb;
+};
+
+/**
+ * Execute the tasks managed by this scheduler.
+ */
+Scheduler.prototype.schedule = function () {
+ this.currentTcb = this.list;
+ while (this.currentTcb != null) {
+ if (this.currentTcb.isHeldOrSuspended()) {
+ this.currentTcb = this.currentTcb.link;
+ } else {
+ this.currentId = this.currentTcb.id;
+ this.currentTcb = this.currentTcb.run();
+ }
+ }
+};
+
+/**
+ * Release a task that is currently blocked and return the next block to run.
+ * @param {int} id the id of the task to suspend
+ */
+Scheduler.prototype.release = function (id) {
+ var tcb = this.blocks[id];
+ if (tcb == null) return tcb;
+ tcb.markAsNotHeld();
+ if (tcb.priority > this.currentTcb.priority) {
+ return tcb;
+ } else {
+ return this.currentTcb;
+ }
+};
+
+/**
+ * Block the currently executing task and return the next task control block
+ * to run. The blocked task will not be made runnable until it is explicitly
+ * released, even if new work is added to it.
+ */
+Scheduler.prototype.holdCurrent = function () {
+ this.holdCount++;
+ this.currentTcb.markAsHeld();
+ return this.currentTcb.link;
+};
+
+/**
+ * Suspend the currently executing task and return the next task control block
+ * to run. If new work is added to the suspended task it will be made runnable.
+ */
+Scheduler.prototype.suspendCurrent = function () {
+ this.currentTcb.markAsSuspended();
+ return this.currentTcb;
+};
+
+/**
+ * Add the specified packet to the end of the worklist used by the task
+ * associated with the packet and make the task runnable if it is currently
+ * suspended.
+ * @param {Packet} packet the packet to add
+ */
+Scheduler.prototype.queue = function (packet) {
+ var t = this.blocks[packet.id];
+ if (t == null) return t;
+ this.queueCount++;
+ packet.link = null;
+ packet.id = this.currentId;
+ return t.checkPriorityAdd(this.currentTcb, packet);
+};
+
+/**
+ * A task control block manages a task and the queue of work packages associated
+ * with it.
+ * @param {TaskControlBlock} link the preceding block in the linked block list
+ * @param {int} id the id of this block
+ * @param {int} priority the priority of this block
+ * @param {Packet} queue the queue of packages to be processed by the task
+ * @param {Task} task the task
+ * @constructor
+ */
+function TaskControlBlock(link, id, priority, queue, task) {
+ this.link = link;
+ this.id = id;
+ this.priority = priority;
+ this.queue = queue;
+ this.task = task;
+ if (queue == null) {
+ this.state = STATE_SUSPENDED;
+ } else {
+ this.state = STATE_SUSPENDED_RUNNABLE;
+ }
+}
+
+/**
+ * The task is running and is currently scheduled.
+ */
+var STATE_RUNNING = 0;
+
+/**
+ * The task has packets left to process.
+ */
+var STATE_RUNNABLE = 1;
+
+/**
+ * The task is not currently running. The task is not blocked as such and may
+* be started by the scheduler.
+ */
+var STATE_SUSPENDED = 2;
+
+/**
+ * The task is blocked and cannot be run until it is explicitly released.
+ */
+var STATE_HELD = 4;
+
+var STATE_SUSPENDED_RUNNABLE = STATE_SUSPENDED | STATE_RUNNABLE;
+var STATE_NOT_HELD = ~STATE_HELD;
+
+TaskControlBlock.prototype.setRunning = function () {
+ this.state = STATE_RUNNING;
+};
+
+TaskControlBlock.prototype.markAsNotHeld = function () {
+ this.state = this.state & STATE_NOT_HELD;
+};
+
+TaskControlBlock.prototype.markAsHeld = function () {
+ this.state = this.state | STATE_HELD;
+};
+
+TaskControlBlock.prototype.isHeldOrSuspended = function () {
+ return (this.state & STATE_HELD) != 0 || (this.state == STATE_SUSPENDED);
+};
+
+TaskControlBlock.prototype.markAsSuspended = function () {
+ this.state = this.state | STATE_SUSPENDED;
+};
+
+TaskControlBlock.prototype.markAsRunnable = function () {
+ this.state = this.state | STATE_RUNNABLE;
+};
+
+/**
+ * Runs this task, if it is ready to be run, and returns the next task to run.
+ */
+TaskControlBlock.prototype.run = function () {
+ var packet;
+ if (this.state == STATE_SUSPENDED_RUNNABLE) {
+ packet = this.queue;
+ this.queue = packet.link;
+ if (this.queue == null) {
+ this.state = STATE_RUNNING;
+ } else {
+ this.state = STATE_RUNNABLE;
+ }
+ } else {
+ packet = null;
+ }
+ return this.task.run(packet);
+};
+
+/**
+ * Adds a packet to the worklist of this block's task, marks this as runnable if
+ * necessary, and returns the next runnable object to run (the one
+ * with the highest priority).
+ */
+TaskControlBlock.prototype.checkPriorityAdd = function (task, packet) {
+ if (this.queue == null) {
+ this.queue = packet;
+ this.markAsRunnable();
+ if (this.priority > task.priority) return this;
+ } else {
+ this.queue = packet.addTo(this.queue);
+ }
+ return task;
+};
+
+TaskControlBlock.prototype.toString = function () {
+ return "tcb { " + this.task + "@" + this.state + " }";
+};
+
+/**
+ * An idle task doesn't do any work itself but cycles control between the two
+ * device tasks.
+ * @param {Scheduler} scheduler the scheduler that manages this task
+ * @param {int} v1 a seed value that controls how the device tasks are scheduled
+ * @param {int} count the number of times this task should be scheduled
+ * @constructor
+ */
+function IdleTask(scheduler, v1, count) {
+ this.scheduler = scheduler;
+ this.v1 = v1;
+ this.count = count;
+}
+
+IdleTask.prototype.run = function (packet) {
+ this.count--;
+ if (this.count == 0) return this.scheduler.holdCurrent();
+ if ((this.v1 & 1) == 0) {
+ this.v1 = this.v1 >> 1;
+ return this.scheduler.release(ID_DEVICE_A);
+ } else {
+ this.v1 = (this.v1 >> 1) ^ 0xD008;
+ return this.scheduler.release(ID_DEVICE_B);
+ }
+};
+
+IdleTask.prototype.toString = function () {
+ return "IdleTask";
+};
+
+/**
+ * A task that suspends itself after each time it has been run to simulate
+ * waiting for data from an external device.
+ * @param {Scheduler} scheduler the scheduler that manages this task
+ * @constructor
+ */
+function DeviceTask(scheduler) {
+ this.scheduler = scheduler;
+ this.v1 = null;
+}
+
+DeviceTask.prototype.run = function (packet) {
+ if (packet == null) {
+ if (this.v1 == null) return this.scheduler.suspendCurrent();
+ var v = this.v1;
+ this.v1 = null;
+ return this.scheduler.queue(v);
+ } else {
+ this.v1 = packet;
+ return this.scheduler.holdCurrent();
+ }
+};
+
+DeviceTask.prototype.toString = function () {
+ return "DeviceTask";
+};
+
+/**
+ * A task that manipulates work packets.
+ * @param {Scheduler} scheduler the scheduler that manages this task
+ * @param {int} v1 a seed used to specify how work packets are manipulated
+ * @param {int} v2 another seed used to specify how work packets are manipulated
+ * @constructor
+ */
+function WorkerTask(scheduler, v1, v2) {
+ this.scheduler = scheduler;
+ this.v1 = v1;
+ this.v2 = v2;
+}
+
+WorkerTask.prototype.run = function (packet) {
+ if (packet == null) {
+ return this.scheduler.suspendCurrent();
+ } else {
+ if (this.v1 == ID_HANDLER_A) {
+ this.v1 = ID_HANDLER_B;
+ } else {
+ this.v1 = ID_HANDLER_A;
+ }
+ packet.id = this.v1;
+ packet.a1 = 0;
+ for (var i = 0; i < DATA_SIZE; i++) {
+ this.v2++;
+ if (this.v2 > 26) this.v2 = 1;
+ packet.a2[i] = this.v2;
+ }
+ return this.scheduler.queue(packet);
+ }
+};
+
+WorkerTask.prototype.toString = function () {
+ return "WorkerTask";
+};
+
+/**
+ * A task that manipulates work packets and then suspends itself.
+ * @param {Scheduler} scheduler the scheduler that manages this task
+ * @constructor
+ */
+function HandlerTask(scheduler) {
+ this.scheduler = scheduler;
+ this.v1 = null;
+ this.v2 = null;
+}
+
+HandlerTask.prototype.run = function (packet) {
+ if (packet != null) {
+ if (packet.kind == KIND_WORK) {
+ this.v1 = packet.addTo(this.v1);
+ } else {
+ this.v2 = packet.addTo(this.v2);
+ }
+ }
+ if (this.v1 != null) {
+ var count = this.v1.a1;
+ var v;
+ if (count < DATA_SIZE) {
+ if (this.v2 != null) {
+ v = this.v2;
+ this.v2 = this.v2.link;
+ v.a1 = this.v1.a2[count];
+ this.v1.a1 = count + 1;
+ return this.scheduler.queue(v);
+ }
+ } else {
+ v = this.v1;
+ this.v1 = this.v1.link;
+ return this.scheduler.queue(v);
+ }
+ }
+ return this.scheduler.suspendCurrent();
+};
+
+HandlerTask.prototype.toString = function () {
+ return "HandlerTask";
+};
+
+/* --- *
+ * P a c k e t
+ * --- */
+
+var DATA_SIZE = 4;
+
+/**
+ * A simple package of data that is manipulated by the tasks. The exact layout
+ * of the payload data carried by a packet is not importaint, and neither is the
+ * nature of the work performed on packets by the tasks.
+ *
+ * Besides carrying data, packets form linked lists and are hence used both as
+ * data and worklists.
+ * @param {Packet} link the tail of the linked list of packets
+ * @param {int} id an ID for this packet
+ * @param {int} kind the type of this packet
+ * @constructor
+ */
+function Packet(link, id, kind) {
+ this.link = link;
+ this.id = id;
+ this.kind = kind;
+ this.a1 = 0;
+ this.a2 = new Array(DATA_SIZE);
+}
+
+/**
+ * Add this packet to the end of a worklist, and return the worklist.
+ * @param {Packet} queue the worklist to add this packet to
+ */
+Packet.prototype.addTo = function (queue) {
+ this.link = null;
+ if (queue == null) return this;
+ var peek, next = queue;
+ while ((peek = next.link) != null)
+ next = peek;
+ next.link = this;
+ return queue;
+};
+
+Packet.prototype.toString = function () {
+ return "Packet";
+};
+
+
+
+function PrintResult(name, result) {
+ print(name + ': ' + result);
+}
+
+
+function PrintScore(score) {
+ print('----');
+ print('Score: ' + score);
+}
+
+
+for (var ii = 0; ii < 10; ii++) {
+BenchmarkSuite.RunSuites({ NotifyResult: PrintResult,
+ NotifyScore: PrintScore });
+}
+
7 lib/capuchin.rb
@@ -0,0 +1,7 @@
+module Capuchin
+end
+
+require "rkelly"
+require "capuchin/kernel"
+require "capuchin/generator"
+require "capuchin/visitor"
35 lib/capuchin/generator.rb
@@ -0,0 +1,35 @@
+
+class Capuchin::Generator < Rubinius::Generator
+ def giz(label)
+ not_easy = new_label
+
+ dup
+ git not_easy
+ pop
+ goto label
+
+ not_easy.set!
+ send :js_truthy?, 0
+ gif label
+ end
+ def gnz(label)
+ do_pop = new_label
+ the_end = new_label
+
+ dup
+ gif do_pop
+ send :js_truthy?, 0
+ gif the_end
+ goto label
+
+ do_pop.set!
+ pop
+ the_end.set!
+ end
+
+ #def encode
+ # @iseq = Rubinius::InstructionSequence.new @stream.to_tuple
+ # @generators.each {|x| @literals[x].encode }
+ #end
+end
+
322 lib/capuchin/kernel.rb
@@ -0,0 +1,322 @@
+
+module JSOpen
+ def js_hash; @js_hash ||= {}; end
+ def _js_key?(k); js_hash.key?(k); end
+ def _js_get(k); js_hash[k]; end
+ def _js_set(k,v); js_hash[k] = v; end
+end
+class Hash
+ def _js_key?(k); key?(k); end
+ def _js_get(k); self[k]; end
+ def _js_set(k,v); self[k] = v; end
+end
+
+module Kernel
+ def js_value; js_invoke(:valueOf); end
+ def js_div(n); self / n; end
+ def js_add(other)
+ if Numeric === self && Numeric === other
+ self + other
+ else
+ "#{self}#{other}"
+ end
+ end
+ def js_key; to_s.intern; end
+ def js_truthy?; self; end
+ def js_typeof; 'object'; end
+ def js_equal(v); self == v; end
+ def js_strictly_equal(v); eql?(v); end
+ def js_respond_to?(name); respond_to?("js:#{name}"); end
+ def js_in(k)
+ if respond_to?(:_js_get)
+ if respond_to?(:_js_key?) && _js_key?(name)
+ return true
+ elsif self._js_get(name)
+ return true
+ end
+ end
+
+ js_respond_to?(k)
+ end
+ def js_get(name)
+ if respond_to?(:"js:get:#{name}")
+ return send(:"js:get:#{name}")
+ end
+
+ if respond_to?(:_js_get)
+ if respond_to?(:_js_key?) && _js_key?(name)
+ return self._js_get(name)
+ elsif v = self._js_get(name)
+ return v
+ end
+ end
+
+ if js_respond_to?(name)
+ meth = method("js:#{name}")
+ if meth.owner < self.class
+ # The method belongs to the object's metaclass
+ (class << self; @js_methods || {}; end)["js:#{name}".to_sym] || meth
+ else
+ meth
+ end
+ end
+ end
+ def js_set(name, value)
+ if respond_to?(:"js:set:#{name}")
+ return send(:"js:set:#{name}", value)
+ end
+
+ if UnboundMethod === value || Capuchin::Function === value
+ (class << self; self; end).send(:_js_define_method, "js:#{name}") do |*args|
+ instance_exec(*args, &value)
+ end
+ return value
+ end
+ self._js_set(name, value)
+ end
+ def js_invoke(name, *args)
+ if !js_respond_to?(name) && f = js_get(name)
+ f.js_call(self, *args)
+ else
+ send("js:#{name}", *args)
+ end
+ end
+end
+class Capuchin::Proto
+ include JSOpen
+end
+class Capuchin::Function
+ include JSOpen
+ def initialize(name=nil, object={}, mod=nil, &block)
+ @name = name
+ @block = block || lambda {}
+ @proto = Capuchin::Proto.new
+ @module = mod
+ self._js_set(:prototype, @proto)
+ object.each do |k,v|
+ _js_set(k, v)
+ end
+ end
+end
+class Class
+ def js_new(*args); new(*args); end
+ def js_instance_of(v); self === v; end
+ def js_expose_method(*names)
+ names.each do |name|
+ alias_method "js:#{name}", name
+ end
+ end
+ def js_expose_attr(*names)
+ names.each do |name|
+ alias_method "js:get:#{name}", name
+ if method_defined?("#{name}=")
+ alias_method "js:set:#{name}", "#{name}="
+ end
+ end
+ end
+ def js_def(name, &block)
+ _js_define_method("js:#{name}", &block)
+ end
+ def js_attr(name, &block)
+ name = name.to_s.dup
+ if name.sub!(/=$/, '')
+ _js_define_method("js:set:#{name}", &block)
+ else
+ _js_define_method("js:get:#{name}", &block)
+ end
+ end
+ def _js_define_method(name, &block)
+ (@js_methods ||= {})[name.to_sym] = Capuchin::Function.new(&block)
+ define_method(name, &block)
+ end
+end
+
+class Array
+ js_attr :length do
+ size
+ end
+ js_expose_method :push
+
+ def _js_get(k); Fixnum === k ? self[k] : nil; end
+ def _js_set(k,v); self[k] = v; end
+
+ def self.js_new(*args)
+ new(*args)
+ end
+end
+class Integer
+ def js_div(n); n == 0 ? self.to_f / n : self / n; end
+end
+class Fixnum
+ def js_key; self; end
+ def js_truthy?; 0 != self; end
+ def js_typeof; 'number'; end
+end
+class Float
+ def js_typeof; 'number'; end
+ js_def :toFixed do |x|
+ self.to_i
+ end
+ js_def :valueOf do
+ self
+ end
+ js_def :toPrecision do |digits|
+ "%.#{digits}f" % self
+ end
+end
+class Symbol
+ def js_key; self; end
+end
+class String
+ def js_key; intern; end
+ def js_truthy?; size > 0; end
+ def js_typeof; 'string'; end
+end
+class TrueClass
+ def js_typeof; 'boolean'; end
+end
+class FalseClass
+ def js_typeof; 'boolean'; end
+end
+class Method
+ include JSOpen
+ def js_prototype
+ @proto || (
+ @proto = Capuchin::Proto.new
+ self._js_set(:prototype, @proto)
+ )
+ end
+ def js_typeof; 'function'; end
+ def js_call(target, *args)
+ if receiver.eql? target
+ call(*args)
+ else
+ unbind.bind(target).call(*args)
+ end
+ end
+ def js_apply(target, args)
+ js_call(target, *args)
+ end
+ js_def :call do |this, *args|
+ js_call(this, *args)
+ end
+ js_def :apply do |this, args|
+ js_call(this, *args)
+ end
+ def js_new(*args)
+ o = Capuchin::Obj.new(self, js_prototype)
+ js_call(o, *args)
+ o
+ end
+end
+class UnboundMethod
+ include JSOpen
+ def js_prototype
+ @proto || (
+ @proto = Capuchin::Proto.new
+ self._js_set(:prototype, @proto)
+ )
+ end
+ def js_typeof; 'function'; end
+ def js_call(target, *args)
+ bind(target).call(*args)
+ end
+ def js_apply(target, args)
+ js_call(target, *args)
+ end
+ js_def :call do |this, *args|
+ js_call(this, *args)
+ end
+ js_def :apply do |this, args|
+ js_call(this, *args)
+ end
+ def js_new(*args)
+ o = Capuchin::Obj.new(self, js_prototype)
+ js_call(o, *args)
+ o
+ end
+end
+
+class Capuchin::Obj
+ include JSOpen
+ def initialize(constructor, proto)
+ self._js_set(:constructor, constructor)
+ @__proto__ = proto
+ end
+ def js_get(name)
+ super || @__proto__.js_get(name)
+ end
+ def js_in(k)
+ super || @__proto__.js_in(k)
+ end
+ def js_invoke(name, *args)
+ if js_respond_to?(name)
+ send("js:#{name}", *args)
+ elsif f = js_get(name)
+ f.js_call(self, *args)
+ elsif f = @__proto__.js_get(name)
+ f.js_call(self, *args)
+ else
+ # Do the send anyway, for the exception
+ send("js:#{name}", *args)
+ end
+ end
+ def to_f
+ js_value.to_f
+ end
+ def to_i
+ js_value.to_i
+ end
+ def to_s
+ js_value.to_s
+ end
+ def to_str
+ js_value.to_s
+ end
+end
+class Capuchin::Function
+ def call(*args)
+ js_call(nil, *args)
+ end
+ js_def :call do |this, *args|
+ js_call(this, *args)
+ end
+ js_def :apply do |this, args|
+ js_call(this, *args)
+ end
+ def js_typeof; 'function'; end
+ def js_call(target, *args)
+ target.instance_exec(*args, &@block)
+ end
+ def js_apply(target, args)
+ js_call(target, *args)
+ end
+ def js_new(*args)
+ o = Capuchin::Obj.new(self, @proto)
+ js_call(o, *args)
+ o.extend(@module) if @module
+ o
+ end
+ def to_proc
+ @block
+ end
+end
+
+module Capuchin::DateMethods
+ attr :t
+ def -(other)
+ (t - other.t) * 1000
+ end
+end
+
+Capuchin::Globals = Rubinius::LookupTable.new
+Capuchin::Globals[:Array] = Array
+Capuchin::Globals[:Date] = Capuchin::Function.new('Date', {}, Capuchin::DateMethods) {|| @t = Time.new }
+Capuchin::Globals[:print] = Capuchin::Function.new {|x| puts x }
+Capuchin::Globals[:p] = Capuchin::Function.new {|x| p [x, x.methods.grep(/^js:/)] }
+Capuchin::Globals[:Math] = {
+ :log => Capuchin::Function.new {|n| Math.log(n.js_value) },
+ :pow => Capuchin::Function.new {|a,b| a.js_value ** b.js_value },
+ :E => Math::E,
+}
+
3  lib/capuchin/version.rb
@@ -0,0 +1,3 @@
+module Capuchin
+ VERSION = "0.5.0"
+end
621 lib/capuchin/visitor.rb
@@ -0,0 +1,621 @@
+
+class Capuchin::Visitor < RKelly::Visitors::Visitor
+ class DeclScanner < RKelly::Visitors::Visitor
+ def initialize(scope)
+ @scope = scope
+ end
+
+ def visit_VarDeclNode(o)
+ if var = @scope.variables[o.name.to_sym]
+ raise "Duplicate variable #{o.name}?"
+ else
+ var = @scope.new_local(o.name.to_sym)
+ end
+
+ # Scan the value
+ accept o.value if o.value
+ end
+ def visit_FunctionDeclNode(o)
+ if var = @scope.variables[o.value.to_sym]
+ raise "Duplicate variable #{o.value}?"
+ else
+ var = @scope.new_local(o.value.to_sym)
+ end
+
+ @scope.add_method do |g,v|
+ g.set_line o.line
+ g.push_const :Capuchin
+ g.find_const :Function
+ g.push_literal o.value.to_sym
+ g.create_block v.compile_method(o.line, o.value, o.arguments.map {|p| p.value }, o.function_body)
+ g.send_with_block :new, 1
+
+ var.reference.set_bytecode(g)
+ g.pop
+ end
+ end
+ def visit_FunctionExprNode(o)
+ # Don't scan function_body; it's a new scope
+ end
+ end
+
+ class Scope
+ include Rubinius::Compiler::LocalVariables
+ def initialize(parent)
+ @parent = parent
+ @methods = []
+ end
+
+ def new_local(name)
+ variable = Rubinius::Compiler::LocalVariable.new allocate_slot
+ variables[name] = variable
+ end
+ def new_nested_local(name)
+ new_local(name).nested_reference
+ end
+
+ def add_method(&block)
+ @methods << block
+ end
+ def append_method_definitions(g, v)
+ @methods.each do |defn|
+ defn.call(g, v)
+ end
+ end
+
+ def search_local(name)
+ if variable = variables[name]
+ variable.nested_reference
+ elsif @parent && reference = @parent.search_local(name)
+ reference.depth += 1
+ reference
+ end
+ end
+ end
+
+ def initialize(g, scope=nil)
+ @g = g
+ @scope = scope || Scope.new(nil)
+ @g.push_state scope if scope
+ end
+ def with_scope(scope=Scope.new(@scope))
+ old_scope, @scope = @scope, scope
+ @g.push_state @scope
+ yield
+ ensure
+ @g.pop_state
+ @scope = old_scope
+ end
+
+ def visit_TrueNode(o)
+ @g.set_line o.line
+ @g.push_true
+ end
+ def visit_FalseNode(o)
+ @g.set_line o.line
+ @g.push_false
+ end
+ def visit_NullNode(o)
+ @g.set_line o.line
+ @g.push_nil
+ end
+ def visit_ThisNode(o)
+ @g.set_line o.line
+ @g.push_self
+ end
+ def visit_FunctionExprNode(o)
+ @g.set_line o.line
+ @g.push_const :Capuchin
+ @g.find_const :Function
+ @g.push_literal o.value.to_sym
+ @g.create_block compile_method(o.line, o.value, o.arguments.map {|p| p.value }, o.function_body)
+ @g.send_with_block :new, 1
+ end
+ def visit_FunctionDeclNode(o)
+ # The entire definition was pulled up by DeclScanner; nothing to do
+ # now.
+ end
+
+ def new_generator(name, arguments=[])
+ meth = @g.class.new
+ meth.name = name.to_sym
+ meth.file = @g.file
+
+ meth.required_args = #0
+ meth.total_args = arguments.size
+
+ meth
+ end
+ def new_visitor(g)
+ self.class.new(g)
+ end
+ def compile_method(line, name, arguments, body)
+ meth = new_generator(name, arguments)
+
+ v = new_visitor(meth)
+
+ v.with_scope(Scope.new(@scope)) do
+ #meth.state.push_super self
+
+ meth.state.push_name name
+
+ meth.set_line line
+
+ if true
+ # We use "block-style" arguments; our parameters are in an
+ # array-like object on the stack
+ meth.cast_for_splat_block_arg
+
+ # TODO: Pre-scan should identify whether 'arguments' is used, and
+ # we should only bother with this if it is.
+ meth.dup
+ meth.send :dup, 0
+ var = meth.state.scope.new_local(:arguments)
+ var.reference.set_bytecode(meth)
+ meth.pop
+
+ arguments.each do |a|
+ var = meth.state.scope.new_local(a.to_sym)
+ meth.shift_array
+ var.reference.set_bytecode(meth)
+ meth.pop
+ end
+ meth.pop
+ else
+ arguments.each do |a|
+ meth.state.scope.new_local(a.to_sym)
+ end
+ end
+
+ # Marker between arg processing and function body, for quick scanning of
+ # the generated bytecode
+ meth.noop
+
+ body.accept DeclScanner.new(meth.state.scope)
+ meth.state.scope.append_method_definitions meth, v
+ body.accept v
+
+ meth.state.pop_name
+
+ meth.local_count = meth.state.scope.local_count
+ meth.local_names = meth.state.scope.local_names
+
+ meth.push_nil
+ meth.ret
+ meth.close
+ end
+
+ meth
+ end
+
+ def visit_VarDeclNode(o)
+ @g.set_line o.line
+
+ var = @g.state.scope.variables[o.name.to_sym]
+ if o.value
+ accept o.value
+ var.reference.set_bytecode(@g)
+ @g.pop
+ end
+ end
+ def visit_ResolveNode(o)
+ @g.set_line o.line
+ if ref = @g.state.scope.search_local(o.value.to_sym)
+ ref.get_bytecode(@g)
+ else
+ @g.push_const :Capuchin
+ @g.find_const :Globals
+ @g.push_literal o.value.to_sym
+ @g.send :[], 1
+ end
+ end
+ def visit_ExpressionStatementNode(o)
+ @g.set_line o.line
+ accept o.value
+ @g.pop
+ end
+ def visit_ArrayNode(o)
+p :ArrayNode
+ @g.set_line o.line
+ o.value.each do |entry|
+ accept entry
+ end
+p o.value.size
+ @g.make_array o.value.size
+ end
+ def visit_DotAccessorNode(o)
+ @g.set_line o.line
+ accept o.value
+ @g.push_literal o.accessor.to_sym
+ @g.send :js_get, 1
+ end
+ def visit_BracketAccessorNode(o)
+ @g.set_line o.line
+ accept o.value
+ accept o.accessor
+ @g.send :js_key, 0
+ @g.send :js_get, 1
+ end
+ def visit_StringNode(o)
+ @g.set_line o.line
+ str = o.value[1, o.value.size - 2]
+ #str.gsub # FIXME: Escapes: \\, \", \n, \u..., \0..
+ @g.push_literal str
+ end
+ def visit_NumberNode(o)
+ @g.set_line o.line
+ @g.push_literal o.value
+ end
+ def visit_ObjectLiteralNode(o)
+ @g.push_const :Hash
+ @g.send :new, 0
+ o.value.each do |prop|
+ accept prop
+ end
+ end
+ def visit_PropertyNode(o)
+ @g.dup
+ @g.push_literal o.name.to_sym
+ accept o.value
+ @g.send :[]=, 2
+ @g.pop
+ end
+ def visit_IfNode(o)
+ @g.set_line o.line
+ accept o.conditions
+ after = @g.new_label
+ if o.else
+ alternate = @g.new_label
+ @g.giz alternate
+ accept o.value
+ @g.goto after
+ alternate.set!
+ accept o.else
+ else
+ @g.giz after
+ accept o.value
+ end
+ after.set!
+ end
+ def visit_TypeOfNode(o)
+ @g.set_line o.line
+ accept o.value
+ @g.send :js_typeof, 0
+ end
+ def visit_VoidNode(o)
+ @g.set_line o.line
+ accept o.value
+ @g.pop
+ @g.push_nil
+ end
+ def visit_CommaNode(o)
+ @g.set_line o.line
+ accept o.left
+ @g.pop
+ accept o.value
+ end
+ def visit_NewExprNode(o)
+ @g.set_line o.line
+ accept o.value
+ args = o.arguments.value
+ args.each do |arg|
+ accept arg
+ end
+ @g.send :js_new, args.size
+ end
+ def visit_ForInNode(o)
+ raise NotImplementedError, "for .. in"
+ end
+ def visit_BreakNode(o)
+ raise NotImplementedError, "break"
+ end
+ def visit_ContinueNode(o)
+ raise NotImplementedError, "continue"
+ end
+
+ def visit_ForNode(o)
+ @g.set_line o.line
+ accept o.init
+
+ top = @g.new_label
+ done = @g.new_label
+
+ top.set!
+ accept o.test
+ @g.giz done
+
+ accept o.value
+ accept o.counter
+ @g.pop
+ @g.goto top
+
+ done.set!
+ end
+ def visit_DoWhileNode(o)
+ @g.set_line o.line
+ again = @g.new_label
+
+ again.set!
+ accept o.left
+ accept o.value
+ @g.giz again
+ end
+ def visit_WhileNode(o)
+ @g.set_line o.line
+ again = @g.new_label
+ nope = @g.new_label
+
+ again.set!
+ accept o.left
+ @g.giz nope
+
+ accept o.value
+ @g.goto again
+
+ nope.set!
+ end
+
+ def visit_TryNode(o)
+ # FIXME: Ignores catch
+ accept o.value
+ accept o.finally_block if o.finally_block
+ end
+
+ def visit_ReturnNode(o)
+ @g.set_line o.line
+
+ if o.value
+ accept o.value
+ else
+ @g.push_nil
+ end
+ @g.ret
+ end
+
+ def visit_PrefixNode(o)
+ # FIXME: Double-evaluates operand
+ accept o.operand
+ @g.meta_push_1
+ case o.value
+ when '++'
+ @g.meta_send_op_plus @g.find_literal(:+)
+ when '--'
+ @g.meta_send_op_minus @g.find_literal(:-)
+ end
+ assign_to o.operand
+ end
+ def visit_PostfixNode(o)
+ # FIXME: Double-evaluates operand
+ accept o.operand
+ @g.dup
+ @g.meta_push_1
+ case o.value
+ when '++'
+ @g.meta_send_op_plus @g.find_literal(:+)
+ when '--'
+ @g.meta_send_op_minus @g.find_literal(:-)
+ end
+ assign_to o.operand
+ @g.pop
+ end
+
+ [
+ [:Add, :js_add],
+ [:BitAnd, '&'],
+ [:BitOr, '|'],
+ [:BitXOr, '^'],
+ [:Divide, :js_div],
+ [:LeftShift, '<<'],
+ [:Modulus, '%'],
+ [:Multiply, '*'],
+ [:RightShift, '>>'],
+ [:Subtract, '-'],
+ ].each do |name,op|
+ define_method(:"visit_#{name}Node") do |o|
+ @g.set_line o.line
+ accept o.left
+ accept o.value
+ @g.send op.to_sym, 1
+ end
+ end
+
+ def visit_LogicalNotNode(o)
+ @g.set_line o.line
+ a = @g.new_label
+ b = @g.new_label
+ accept o.value
+ @g.gnz a
+ @g.push_true
+ @g.goto b
+ a.set!
+ @g.push_false
+ b.set!
+ end
+ def visit_LogicalAndNode(o)
+ @g.set_line o.line
+ done = @g.new_label
+ accept o.left
+ @g.dup
+ @g.giz done
+ @g.pop
+ accept o.value
+ done.set!
+ end
+ def visit_LogicalOrNode(o)
+ @g.set_line o.line
+ done = @g.new_label
+ accept o.left
+ @g.dup
+ @g.gnz done
+ @g.pop
+ accept o.value
+ done.set!
+ end
+
+ [
+ [:Greater, '>'],
+ [:GreaterOrEqual, '>='],
+ [:Less, '<'],
+ [:LessOrEqual, '<='],
+ ].each do |name,op|
+ define_method(:"visit_#{name}Node") do |o|
+ @g.set_line o.line
+ accept o.left
+ accept o.value
+ @g.send op.to_sym, 1
+ end
+ end
+
+ def visit_EqualNode(o)
+ @g.set_line o.line
+ accept o.left
+ accept o.value
+ @g.send :js_equal, 1
+ end
+ def visit_StrictEqualNode(o)
+ @g.set_line o.line
+ accept o.left
+ accept o.value
+ @g.send :js_strict_equal, 1
+ end
+ def visit_NotEqualNode(o)
+ @g.set_line o.line
+ accept o.left
+ accept o.value
+ @g.send :js_equal, 1
+
+ alt = @g.new_label
+ done = @g.new_label
+ @g.git alt
+ @g.push_true
+ @g.goto done
+
+ alt.set!
+ @g.push_false
+ done.set!
+ end
+ def visit_NotStrictEqualNode(o)
+ @g.set_line o.line
+ accept o.left
+ accept o.value
+ @g.send :js_strict_equal, 1
+
+ alt = @g.new_label
+ done = @g.new_label
+ @g.git alt
+ @g.push_true
+ @g.goto done
+
+ alt.set!
+ @g.push_false
+ done.set!
+ end
+
+ def visit_BitwiseNotNode(o)
+ @g.set_line o.line
+ accept o.value
+ @g.send :~, 0
+ end
+
+ def visit_InstanceOfNode(o)
+ @g.set_line o.line
+ accept o.left
+ accept o.value
+ @g.swap
+ @g.send :js_instance_of, 1
+ end
+ def visit_InNode(o)
+ @g.set_line o.line
+ accept o.left
+ accept o.value
+ @g.swap
+ @g.send :js_in, 1
+ end
+
+ def visit_FunctionCallNode(o)
+ callee = o.value
+ args = o.arguments.value
+
+ case callee
+ when RKelly::Nodes::DotAccessorNode
+ accept callee.value
+ @g.push_literal callee.accessor.to_sym
+ args.each do |arg|
+ accept arg
+ end
+ @g.send :js_invoke, args.size + 1
+
+ when RKelly::Nodes::BracketAccessorNode
+ accept callee.value
+ accept callee.left
+ @g.send :js_key, 0
+ args.each do |arg|
+ accept arg
+ end
+ @g.send :js_invoke, args.size + 1
+
+ else
+ accept callee
+ args.each do |arg|
+ accept arg
+ end
+ @g.send :call, args.size
+ end
+ end
+ def assign_to(o)
+ @g.set_line o.line
+ case o
+ when RKelly::Nodes::ResolveNode
+ if ref = @g.state.scope.search_local(o.value.to_sym)
+ ref.set_bytecode(@g)
+ else
+ @g.push_const :Capuchin
+ @g.find_const :Globals
+ @g.swap
+ @g.push_literal o.value.to_sym
+ @g.swap
+ @g.send :[]=, 2
+ end
+ when RKelly::Nodes::DotAccessorNode
+ accept o.value
+ @g.swap
+ @g.push_literal o.accessor.to_sym
+ @g.swap
+ @g.send :js_set, 2
+ when RKelly::Nodes::BracketAccessorNode
+ accept o.value
+ @g.swap
+ accept o.accessor
+ @g.send :js_key, 0
+ @g.swap
+ @g.send :js_set, 2
+ else
+ raise "Don't know how to assign to #{o.class}"
+ end
+ end
+ def visit_OpEqualNode(o)
+ accept o.value
+ assign_to o.left
+ end
+
+ [
+ [:OpAndEqual, '&='],
+ [:OpDivideEqual, '/='],
+ [:OpLShiftEqual, '<<='],
+ [:OpMinusEqual, '-='],
+ [:OpModEqual, '%='],
+ [:OpMultiplyEqual, '*='],
+ [:OpOrEqual, '|='],
+ [:OpPlusEqual, '+='],
+ [:OpRShiftEqual, '>>='],
+ [:OpURShiftEqual, '>>>='],
+ [:OpXOrEqual, '^='],
+ ].each do |name,op|
+ define_method(:"visit_#{name}Node") do |o|
+ # Need to address double-evaluation of o.left
+ raise NotImplementedError, "#{name}Node"
+ "#{o.left.accept(self)} #{op} #{o.value.accept(self)}"
+ end
+ end
+end
+
Please sign in to comment.
Something went wrong with that request. Please try again.