Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Kick it off with a bang.

  • Loading branch information...
commit 5217e9a6303be3c64f812e90698d4f88a5364b92 0 parents
Brian Shirai authored April 30, 2011
5  .gitignore
... ...
@@ -0,0 +1,5 @@
  1
+*.rbc
  2
+*.gem
  3
+.bundle
  4
+Gemfile.lock
  5
+pkg/*
7  Gemfile
... ...
@@ -0,0 +1,7 @@
  1
+source "http://rubygems.org"
  2
+
  3
+gemspec
  4
+
  5
+gem "rake",   ">= 0.8.7"
  6
+gem "kpeg",   "~> 0.8.2"
  7
+gem "mspec",  "~> 1.5.17"
22  LICENSE
... ...
@@ -0,0 +1,22 @@
  1
+Copyright (c) 2011 Brian Ford. All rights reserved.
  2
+
  3
+Permission is hereby granted, free of charge, to any person
  4
+obtaining a copy of this software and associated documentation
  5
+files (the "Software"), to deal in the Software without
  6
+restriction, including without limitation the rights to use,
  7
+copy, modify, merge, publish, distribute, sublicense, and/or sell
  8
+copies of the Software, and to permit persons to whom the
  9
+Software is furnished to do so, subject to the following
  10
+conditions:
  11
+
  12
+The above copyright notice and this permission notice shall be
  13
+included in all copies or substantial portions of the Software.
  14
+
  15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  17
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  19
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  20
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  21
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  22
+OTHER DEALINGS IN THE SOFTWARE.
77  README
... ...
@@ -0,0 +1,77 @@
  1
+A native implementation of CoffeeScript [1] that runs on the Rubinius VM [2].
  2
+
  3
+Q. Whence comes the name Poetics?
  4
+A. Poetics is a partial anagram of the word CoffeeScript. It is also a nod to
  5
+   Jeremy Ashkenas, the author of CoffeeScript, and his interest in code as
  6
+   literature.
  7
+
  8
+Q. Why a native implementation?
  9
+A. CoffeeScript is an interesting language in its own right. Rather than
  10
+   merely being a syntax layer on top of Javascript, and bound to expressing
  11
+   its semantics in those of Javascript, it deserves its own implementation.
  12
+   Many of the reasons to use CoffeeScript in Node.js would also apply to
  13
+   using this native implementation.
  14
+
  15
+
  16
+Installation
  17
+
  18
+First, install Rubinius:
  19
+
  20
+  1. Using RVM (http://rvm.beginrescueend.com).
  21
+
  22
+    rvm install rbx
  23
+
  24
+  2. Or directly (http://rubini.us/doc/en/what-is-rubinius/). 
  25
+
  26
+Second, install Poetics:
  27
+
  28
+    rbx -S gem install poetics
  29
+
  30
+
  31
+Running Poetics
  32
+
  33
+Poetics provides a REPL for exploratory programming or runs CoffeeScript
  34
+scripts.
  35
+
  36
+    rbx -S poetics -h
  37
+
  38
+
  39
+Getting Help
  40
+
  41
+Poetics is a work in progress. If you encounter trouble, please file an issue
  42
+at https://github.com/brixen/poetics/issues
  43
+
  44
+
  45
+Contributing
  46
+
  47
+If you find Poetics interesting and would like to help out, fork the project
  48
+on Github and submit a pull request.
  49
+
  50
+
  51
+People
  52
+
  53
+Poetics was created by Brian Ford (brixen) to force him to really learn
  54
+Javascript and CoffeeScript.
  55
+
  56
+<add your name here>
  57
+
  58
+
  59
+License
  60
+
  61
+Poetics is MIT licensed. See the LICENSE file.
  62
+
  63
+
  64
+Credits
  65
+
  66
+Jeremy has created an very interesting language in CoffeeScript. This
  67
+implementation steals bits and pieces from other projects:
  68
+
  69
+Rubinius (https://github.com/evanphx/rubinius/)
  70
+KPeg (https://github.com/evanphx/kpeg/)
  71
+Atomy (https://github.com/vito/atomy/)
  72
+Poison (https://github.com/brixen/poison/)
  73
+Talon (https://github.com/evanphx/talon/)
  74
+
  75
+
  76
+[1] CoffeeScript (http://jashkenas.github.com/coffee-script/)
  77
+[2] Rubinius (http://rubini.us)
21  Rakefile
... ...
@@ -0,0 +1,21 @@
  1
+require 'bundler'
  2
+Bundler::GemHelper.install_tasks
  3
+
  4
+task :default => :spec
  5
+
  6
+base = File.expand_path '../lib/poetics/parser', __FILE__
  7
+
  8
+grammar = "#{base}/poetics.kpeg"
  9
+parser  = "#{base}/parser.rb"
  10
+
  11
+file parser => grammar do
  12
+  sh "rbx -S kpeg -f -s #{base}/poetics.kpeg -o #{base}/parser.rb"
  13
+end
  14
+
  15
+desc "Convert the grammar description to a parser"
  16
+task :parser => parser
  17
+
  18
+desc "Run the specs (default)"
  19
+task :spec => :parser do
  20
+  sh "mspec spec"
  21
+end
0  bin/poetics
No changes.
3  lib/poetics.rb
... ...
@@ -0,0 +1,3 @@
  1
+require 'poetics/version'
  2
+require 'poetics/syntax'
  3
+require 'poetics/parser'
21  lib/poetics/parser.rb
... ...
@@ -0,0 +1,21 @@
  1
+require 'poetics/parser/parser'
  2
+
  3
+class Poetics::Parser
  4
+  include Poetics::Syntax
  5
+
  6
+  def self.parse_to_sexp(string)
  7
+    parser = new string
  8
+    unless parser.parse
  9
+      parser.raise_error
  10
+    end
  11
+
  12
+    parser.result.to_sexp
  13
+  end
  14
+
  15
+  attr_reader :line, :column
  16
+
  17
+  def position(line, column)
  18
+    @line = line
  19
+    @column = column
  20
+  end
  21
+end
461  lib/poetics/parser/parser.rb
... ...
@@ -0,0 +1,461 @@
  1
+class Poetics::Parser
  2
+# STANDALONE START
  3
+    def setup_parser(str, debug=false)
  4
+      @string = str
  5
+      @pos = 0
  6
+      @memoizations = Hash.new { |h,k| h[k] = {} }
  7
+      @result = nil
  8
+      @failed_rule = nil
  9
+      @failing_rule_offset = -1
  10
+
  11
+      setup_foreign_grammar
  12
+    end
  13
+
  14
+    # This is distinct from setup_parser so that a standalone parser
  15
+    # can redefine #initialize and still have access to the proper
  16
+    # parser setup code.
  17
+    #
  18
+    def initialize(str, debug=false)
  19
+      setup_parser(str, debug)
  20
+    end
  21
+
  22
+    attr_reader :string
  23
+    attr_reader :result, :failing_rule_offset
  24
+    attr_accessor :pos
  25
+
  26
+    # STANDALONE START
  27
+    def current_column(target=pos)
  28
+      if c = string.rindex("\n", target-1)
  29
+        return target - c - 1
  30
+      end
  31
+
  32
+      target + 1
  33
+    end
  34
+
  35
+    def current_line(target=pos)
  36
+      cur_offset = 0
  37
+      cur_line = 0
  38
+
  39
+      string.each_line do |line|
  40
+        cur_line += 1
  41
+        cur_offset += line.size
  42
+        return cur_line if cur_offset >= target
  43
+      end
  44
+
  45
+      -1
  46
+    end
  47
+
  48
+    def lines
  49
+      lines = []
  50
+      string.each_line { |l| lines << l }
  51
+      lines
  52
+    end
  53
+
  54
+    #
  55
+
  56
+    def get_text(start)
  57
+      @string[start..@pos-1]
  58
+    end
  59
+
  60
+    def show_pos
  61
+      width = 10
  62
+      if @pos < width
  63
+        "#{@pos} (\"#{@string[0,@pos]}\" @ \"#{@string[@pos,width]}\")"
  64
+      else
  65
+        "#{@pos} (\"... #{@string[@pos - width, width]}\" @ \"#{@string[@pos,width]}\")"
  66
+      end
  67
+    end
  68
+
  69
+    def failure_info
  70
+      l = current_line @failing_rule_offset
  71
+      c = current_column @failing_rule_offset
  72
+
  73
+      if @failed_rule.kind_of? Symbol
  74
+        info = self.class::Rules[@failed_rule]
  75
+        "line #{l}, column #{c}: failed rule '#{info.name}' = '#{info.rendered}'"
  76
+      else
  77
+        "line #{l}, column #{c}: failed rule '#{@failed_rule}'"
  78
+      end
  79
+    end
  80
+
  81
+    def failure_caret
  82
+      l = current_line @failing_rule_offset
  83
+      c = current_column @failing_rule_offset
  84
+
  85
+      line = lines[l-1]
  86
+      "#{line}\n#{' ' * (c - 1)}^"
  87
+    end
  88
+
  89
+    def failure_character
  90
+      l = current_line @failing_rule_offset
  91
+      c = current_column @failing_rule_offset
  92
+      lines[l-1][c-1, 1]
  93
+    end
  94
+
  95
+    def failure_oneline
  96
+      l = current_line @failing_rule_offset
  97
+      c = current_column @failing_rule_offset
  98
+
  99
+      char = lines[l-1][c-1, 1]
  100
+
  101
+      if @failed_rule.kind_of? Symbol
  102
+        info = self.class::Rules[@failed_rule]
  103
+        "@#{l}:#{c} failed rule '#{info.name}', got '#{char}'"
  104
+      else
  105
+        "@#{l}:#{c} failed rule '#{@failed_rule}', got '#{char}'"
  106
+      end
  107
+    end
  108
+
  109
+    class ParseError < RuntimeError
  110
+    end
  111
+
  112
+    def raise_error
  113
+      raise ParseError, failure_oneline
  114
+    end
  115
+
  116
+    def show_error(io=STDOUT)
  117
+      error_pos = @failing_rule_offset
  118
+      line_no = current_line(error_pos)
  119
+      col_no = current_column(error_pos)
  120
+
  121
+      io.puts "On line #{line_no}, column #{col_no}:"
  122
+
  123
+      if @failed_rule.kind_of? Symbol
  124
+        info = self.class::Rules[@failed_rule]
  125
+        io.puts "Failed to match '#{info.rendered}' (rule '#{info.name}')"
  126
+      else
  127
+        io.puts "Failed to match rule '#{@failed_rule}'"
  128
+      end
  129
+
  130
+      io.puts "Got: #{string[error_pos,1].inspect}"
  131
+      line = lines[line_no-1]
  132
+      io.puts "=> #{line}"
  133
+      io.print(" " * (col_no + 3))
  134
+      io.puts "^"
  135
+    end
  136
+
  137
+    def set_failed_rule(name)
  138
+      if @pos > @failing_rule_offset
  139
+        @failed_rule = name
  140
+        @failing_rule_offset = @pos
  141
+      end
  142
+    end
  143
+
  144
+    attr_reader :failed_rule
  145
+
  146
+    def match_string(str)
  147
+      len = str.size
  148
+      if @string[pos,len] == str
  149
+        @pos += len
  150
+        return str
  151
+      end
  152
+
  153
+      return nil
  154
+    end
  155
+
  156
+    def scan(reg)
  157
+      if m = reg.match(@string[@pos..-1])
  158
+        width = m.end(0)
  159
+        @pos += width
  160
+        return true
  161
+      end
  162
+
  163
+      return nil
  164
+    end
  165
+
  166
+    if "".respond_to? :getbyte
  167
+      def get_byte
  168
+        if @pos >= @string.size
  169
+          return nil
  170
+        end
  171
+
  172
+        s = @string.getbyte @pos
  173
+        @pos += 1
  174
+        s
  175
+      end
  176
+    else
  177
+      def get_byte
  178
+        if @pos >= @string.size
  179
+          return nil
  180
+        end
  181
+
  182
+        s = @string[@pos]
  183
+        @pos += 1
  184
+        s
  185
+      end
  186
+    end
  187
+
  188
+    def parse(rule=nil)
  189
+      if !rule
  190
+        _root ? true : false
  191
+      else
  192
+        # This is not shared with code_generator.rb so this can be standalone
  193
+        method = rule.gsub("-","_hyphen_")
  194
+        __send__("_#{method}") ? true : false
  195
+      end
  196
+    end
  197
+
  198
+    class LeftRecursive
  199
+      def initialize(detected=false)
  200
+        @detected = detected
  201
+      end
  202
+
  203
+      attr_accessor :detected
  204
+    end
  205
+
  206
+    class MemoEntry
  207
+      def initialize(ans, pos)
  208
+        @ans = ans
  209
+        @pos = pos
  210
+        @uses = 1
  211
+        @result = nil
  212
+      end
  213
+
  214
+      attr_reader :ans, :pos, :uses, :result
  215
+
  216
+      def inc!
  217
+        @uses += 1
  218
+      end
  219
+
  220
+      def move!(ans, pos, result)
  221
+        @ans = ans
  222
+        @pos = pos
  223
+        @result = result
  224
+      end
  225
+    end
  226
+
  227
+    def external_invoke(other, rule, *args)
  228
+      old_pos = @pos
  229
+      old_string = @string
  230
+
  231
+      @pos = other.pos
  232
+      @string = other.string
  233
+
  234
+      begin
  235
+        if val = __send__(rule, *args)
  236
+          other.pos = @pos
  237
+        else
  238
+          other.set_failed_rule "#{self.class}##{rule}"
  239
+        end
  240
+        val
  241
+      ensure
  242
+        @pos = old_pos
  243
+        @string = old_string
  244
+      end
  245
+    end
  246
+
  247
+    def apply(rule)
  248
+      if m = @memoizations[rule][@pos]
  249
+        m.inc!
  250
+
  251
+        prev = @pos
  252
+        @pos = m.pos
  253
+        if m.ans.kind_of? LeftRecursive
  254
+          m.ans.detected = true
  255
+          return nil
  256
+        end
  257
+
  258
+        @result = m.result
  259
+
  260
+        return m.ans
  261
+      else
  262
+        lr = LeftRecursive.new(false)
  263
+        m = MemoEntry.new(lr, @pos)
  264
+        @memoizations[rule][@pos] = m
  265
+        start_pos = @pos
  266
+
  267
+        ans = __send__ rule
  268
+
  269
+        m.move! ans, @pos, @result
  270
+
  271
+        # Don't bother trying to grow the left recursion
  272
+        # if it's failing straight away (thus there is no seed)
  273
+        if ans and lr.detected
  274
+          return grow_lr(rule, start_pos, m)
  275
+        else
  276
+          return ans
  277
+        end
  278
+
  279
+        return ans
  280
+      end
  281
+    end
  282
+
  283
+    def grow_lr(rule, start_pos, m)
  284
+      while true
  285
+        @pos = start_pos
  286
+        @result = m.result
  287
+
  288
+        ans = __send__ rule
  289
+        return nil unless ans
  290
+
  291
+        break if @pos <= m.pos
  292
+
  293
+        m.move! ans, @pos, @result
  294
+      end
  295
+
  296
+      @result = m.result
  297
+      @pos = m.pos
  298
+      return m.ans
  299
+    end
  300
+
  301
+    class RuleInfo
  302
+      def initialize(name, rendered)
  303
+        @name = name
  304
+        @rendered = rendered
  305
+      end
  306
+
  307
+      attr_reader :name, :rendered
  308
+    end
  309
+
  310
+    def self.rule_info(name, rendered)
  311
+      RuleInfo.new(name, rendered)
  312
+    end
  313
+
  314
+    #
  315
+  def setup_foreign_grammar; end
  316
+
  317
+  # root = - number? - !.
  318
+  def _root
  319
+
  320
+    _save = self.pos
  321
+    while true # sequence
  322
+      _tmp = apply(:__hyphen_)
  323
+      unless _tmp
  324
+        self.pos = _save
  325
+        break
  326
+      end
  327
+      _save1 = self.pos
  328
+      _tmp = apply(:_number)
  329
+      unless _tmp
  330
+        _tmp = true
  331
+        self.pos = _save1
  332
+      end
  333
+      unless _tmp
  334
+        self.pos = _save
  335
+        break
  336
+      end
  337
+      _tmp = apply(:__hyphen_)
  338
+      unless _tmp
  339
+        self.pos = _save
  340
+        break
  341
+      end
  342
+      _save2 = self.pos
  343
+      _tmp = get_byte
  344
+      _tmp = _tmp ? nil : true
  345
+      self.pos = _save2
  346
+      unless _tmp
  347
+        self.pos = _save
  348
+      end
  349
+      break
  350
+    end # end sequence
  351
+
  352
+    set_failed_rule :_root unless _tmp
  353
+    return _tmp
  354
+  end
  355
+
  356
+  # - = (" " | "\t")*
  357
+  def __hyphen_
  358
+    while true
  359
+
  360
+      _save1 = self.pos
  361
+      while true # choice
  362
+        _tmp = match_string(" ")
  363
+        break if _tmp
  364
+        self.pos = _save1
  365
+        _tmp = match_string("\t")
  366
+        break if _tmp
  367
+        self.pos = _save1
  368
+        break
  369
+      end # end choice
  370
+
  371
+      break unless _tmp
  372
+    end
  373
+    _tmp = true
  374
+    set_failed_rule :__hyphen_ unless _tmp
  375
+    return _tmp
  376
+  end
  377
+
  378
+  # number = position < /\d+/ > {number(text)}
  379
+  def _number
  380
+
  381
+    _save = self.pos
  382
+    while true # sequence
  383
+      _tmp = apply(:_position)
  384
+      unless _tmp
  385
+        self.pos = _save
  386
+        break
  387
+      end
  388
+      _text_start = self.pos
  389
+      _tmp = scan(/\A(?-mix:\d+)/)
  390
+      if _tmp
  391
+        text = get_text(_text_start)
  392
+      end
  393
+      unless _tmp
  394
+        self.pos = _save
  395
+        break
  396
+      end
  397
+      @result = begin; number(text); end
  398
+      _tmp = true
  399
+      unless _tmp
  400
+        self.pos = _save
  401
+      end
  402
+      break
  403
+    end # end sequence
  404
+
  405
+    set_failed_rule :_number unless _tmp
  406
+    return _tmp
  407
+  end
  408
+
  409
+  # line = { current_line }
  410
+  def _line
  411
+    @result = begin;  current_line ; end
  412
+    _tmp = true
  413
+    set_failed_rule :_line unless _tmp
  414
+    return _tmp
  415
+  end
  416
+
  417
+  # column = { current_column }
  418
+  def _column
  419
+    @result = begin;  current_column ; end
  420
+    _tmp = true
  421
+    set_failed_rule :_column unless _tmp
  422
+    return _tmp
  423
+  end
  424
+
  425
+  # position = line:l column:c { position(l, c) }
  426
+  def _position
  427
+
  428
+    _save = self.pos
  429
+    while true # sequence
  430
+      _tmp = apply(:_line)
  431
+      l = @result
  432
+      unless _tmp
  433
+        self.pos = _save
  434
+        break
  435
+      end
  436
+      _tmp = apply(:_column)
  437
+      c = @result
  438
+      unless _tmp
  439
+        self.pos = _save
  440
+        break
  441
+      end
  442
+      @result = begin;  position(l, c) ; end
  443
+      _tmp = true
  444
+      unless _tmp
  445
+        self.pos = _save
  446
+      end
  447
+      break
  448
+    end # end sequence
  449
+
  450
+    set_failed_rule :_position unless _tmp
  451
+    return _tmp
  452
+  end
  453
+
  454
+  Rules = {}
  455
+  Rules[:_root] = rule_info("root", "- number? - !.")
  456
+  Rules[:__hyphen_] = rule_info("-", "(\" \" | \"\\t\")*")
  457
+  Rules[:_number] = rule_info("number", "position < /\\d+/ > {number(text)}")
  458
+  Rules[:_line] = rule_info("line", "{ current_line }")
  459
+  Rules[:_column] = rule_info("column", "{ current_column }")
  460
+  Rules[:_position] = rule_info("position", "line:l column:c { position(l, c) }")
  461
+end
11  lib/poetics/parser/poetics.kpeg
... ...
@@ -0,0 +1,11 @@
  1
+%% name = Poetics::Parser
  2
+
  3
+root      = - number? - !.
  4
+
  5
+-         = (" " | "\t")*
  6
+number    = position < /\d+/ > ~number(text)
  7
+
  8
+# keep track of column and line
  9
+line      = { current_line }
  10
+column    = { current_column }
  11
+position  = line:l column:c { position(l, c) }
3  lib/poetics/syntax.rb
... ...
@@ -0,0 +1,3 @@
  1
+require 'poetics/syntax/node'
  2
+require 'poetics/syntax/literal'
  3
+require 'poetics/syntax/ast'
7  lib/poetics/syntax/ast.rb
... ...
@@ -0,0 +1,7 @@
  1
+module Poetics
  2
+  module Syntax
  3
+    def number(value)
  4
+      Number.new line, column, value
  5
+    end
  6
+  end
  7
+end
16  lib/poetics/syntax/literal.rb
... ...
@@ -0,0 +1,16 @@
  1
+module Poetics
  2
+  module Syntax
  3
+    class Number < Node
  4
+      attr_accessor :value
  5
+
  6
+      def initialize(line, column, value)
  7
+        super
  8
+        @value = value.to_f
  9
+      end
  10
+
  11
+      def to_sexp
  12
+        [:number, value, line, column]
  13
+      end
  14
+    end
  15
+  end
  16
+end
15  lib/poetics/syntax/node.rb
... ...
@@ -0,0 +1,15 @@
  1
+module Poetics
  2
+  module Syntax
  3
+    class Node
  4
+      attr_accessor :line, :column
  5
+
  6
+      def initialize(line, column, *)
  7
+        @line = line
  8
+        @column = column
  9
+      end
  10
+
  11
+      def to_sexp
  12
+      end
  13
+    end
  14
+  end
  15
+end
3  lib/poetics/version.rb
... ...
@@ -0,0 +1,3 @@
  1
+module Poetics
  2
+  VERSION = "0.0.1"
  3
+end
23  poetics.gemspec
... ...
@@ -0,0 +1,23 @@
  1
+# -*- encoding: utf-8 -*-
  2
+$:.push File.expand_path("../lib", __FILE__)
  3
+require "poetics/version"
  4
+
  5
+Gem::Specification.new do |s|
  6
+  s.name        = "poetics"
  7
+  s.version     = Poetics::VERSION
  8
+  s.platform    = Gem::Platform::RUBY
  9
+  s.authors     = ["Brian Ford"]
  10
+  s.email       = ["brixen@gmail.com"]
  11
+  s.homepage    = "https://github.com/brixen/poetics"
  12
+  s.summary     = %q{A native implementation of CoffeeScript on the Rubinius VM}
  13
+  s.description =<<-EOD
  14
+Poetics implements CoffeeScript (http://jashkenas.github.com/coffee-script/)
  15
+directly on the Rubinius VM (http://rubini.us). It includes a REPL for
  16
+exploratory programming, as well as executing CoffeeScript scripts directly.
  17
+  EOD
  18
+
  19
+  s.files         = `git ls-files`.split("\n")
  20
+  s.test_files    = Dir["spec/**/*.rb"]
  21
+  s.executables   = ["poetics"]
  22
+  s.require_paths = ["lib"]
  23
+end
4  spec/custom.rb
... ...
@@ -0,0 +1,4 @@
  1
+require 'spec/custom/runner/relates'
  2
+require 'spec/custom/matchers/parse_as'
  3
+require 'spec/custom/utils/options'
  4
+require 'spec/custom/utils/script'
21  spec/custom/matchers/parse_as.rb
... ...
@@ -0,0 +1,21 @@
  1
+class ParseAsMatcher
  2
+  def initialize(expected)
  3
+    @expected = expected
  4
+  end
  5
+
  6
+  def matches?(actual)
  7
+    @actual = Poetics::Parser.parse_to_sexp actual
  8
+    @actual == @expected
  9
+  end
  10
+
  11
+  def failure_message
  12
+    ["Expected:\n#{@actual.inspect}\n",
  13
+     "to equal:\n#{@expected.inspect}"]
  14
+  end
  15
+end
  16
+
  17
+class Object
  18
+  def parse_as(sexp)
  19
+    ParseAsMatcher.new sexp
  20
+  end
  21
+end
97  spec/custom/runner/relates.rb
... ...
@@ -0,0 +1,97 @@
  1
+# NOTE: Copied from Rubinius
  2
+#
  3
+# SpecDataRelation enables concise specs that involve several different forms
  4
+# of the same data. This is specifically useful for the parser and compiler
  5
+# specs where the output of each stage is essentially related to the input
  6
+# Ruby source. Together with the #relates spec method, it enables specs like:
  7
+#
  8
+#   describe "An If node" do
  9
+#     relates "a if b" do
  10
+#       parse do
  11
+#         # return the expected sexp
  12
+#       end
  13
+#
  14
+#       compile do |g|
  15
+#         # return the expected bytecode
  16
+#       end
  17
+#
  18
+#       jit do |as|
  19
+#         # return the expected asm/machine code
  20
+#       end
  21
+#     end
  22
+#
  23
+#     relates "if a; b; end" do
  24
+#       # ...
  25
+#     end
  26
+#   end
  27
+
  28
+class SpecDataRelation
  29
+  # Provides a simple configurability so that any one or more of the possible
  30
+  # processes can be run. See the custom options in custom/utils/options.rb.
  31
+  def self.enable(process)
  32
+    @processors ||= []
  33
+    @processors << process
  34
+  end
  35
+
  36
+  # Returns true if no process is specifically set or if +process+ is in the
  37
+  # list of enabled processes. In other words, all processes are enabled by
  38
+  # default, or any combination of them may be enabled.
  39
+  def self.enabled?(process)
  40
+    @processors.nil? or @processors.include?(process)
  41
+  end
  42
+
  43
+  def initialize(ruby)
  44
+    @ruby = ruby
  45
+  end
  46
+
  47
+  # Formats the Ruby source code for reabable output in the -fs formatter
  48
+  # option. If the source contains no newline characters, wraps the source in
  49
+  # single quotes to set if off from the rest of the description string. If
  50
+  # the source does contain newline characters, sets the indent level to four
  51
+  # characters.
  52
+  def format(ruby)
  53
+    if /\n/ =~ ruby
  54
+      lines = ruby.rstrip.to_a
  55
+      if /( *)/ =~ lines.first
  56
+        if $1.size > 4
  57
+          dedent = $1.size - 4
  58
+          ruby = lines.map { |l| l[dedent..-1] }.join
  59
+        else
  60
+          indent = " " * (4 - $1.size)
  61
+          ruby = lines.map { |l| "#{indent}#{l}" }.join
  62
+        end
  63
+      end
  64
+      "\n#{ruby}"
  65
+    else
  66
+      "'#{ruby}'"
  67
+    end
  68
+  end
  69
+
  70
+  # Creates spec example blocks if the compile process is enabled.
  71
+  def compile(*plugins, &block)
  72
+    return unless self.class.enabled? :compiler
  73
+
  74
+    ruby = @ruby
  75
+    it "is compiled from #{format ruby}" do
  76
+      generator = Rubinius::TestGenerator.new
  77
+      generator.instance_eval(&block)
  78
+
  79
+      ruby.should compile_as(generator, *plugins)
  80
+    end
  81
+  end
  82
+
  83
+  def parse(&block)
  84
+    return unless self.class.enabled? :parser
  85
+
  86
+    ruby = @ruby
  87
+    it "is parsed from #{format ruby}" do
  88
+      ruby.should parse_as(block.call)
  89
+    end
  90
+  end
  91
+end
  92
+
  93
+class Object
  94
+  def relates(str, &block)
  95
+    SpecDataRelation.new(str).instance_eval(&block)
  96
+  end
  97
+end
21  spec/custom/utils/options.rb
... ...
@@ -0,0 +1,21 @@
  1
+# Custom MSpec options
  2
+#
  3
+class MSpecOptions
  4
+  def compiler
  5
+    # The require is inside the method because this file has to be able to be
  6
+    # loaded in MRI and there are parts of the custom ensemble that are
  7
+    # Rubinius specific (primarily iseq, which could potentially be fixed by
  8
+    # better structuring the compiler).
  9
+    require 'spec/custom/runner/relates'
  10
+
  11
+    on("--compiler", "Run only the compile part of the compiler specs") do
  12
+      SpecDataRelation.enable :compiler
  13
+    end
  14
+  end
  15
+
  16
+  def parser
  17
+    on("--parser", "Run only the parse part of the compiler specs") do
  18
+      SpecDataRelation.enable :parser
  19
+    end
  20
+  end
  21
+end
26  spec/custom/utils/script.rb
... ...
@@ -0,0 +1,26 @@
  1
+# Custom options for mspec-run
  2
+#
  3
+class MSpecRun
  4
+  def custom_options(options)
  5
+    options.compiler
  6
+    options.parser
  7
+  end
  8
+end
  9
+
  10
+# Custom options for mspec-ci
  11
+#
  12
+class MSpecCI
  13
+  def custom_options(options)
  14
+    options.compiler
  15
+    options.parser
  16
+  end
  17
+end
  18
+
  19
+# Custom options for mspec-tag
  20
+#
  21
+class MSpecTag
  22
+  def custom_options(options)
  23
+    options.compiler
  24
+    options.parser
  25
+  end
  26
+end
6  spec/default.mspec
... ...
@@ -0,0 +1,6 @@
  1
+# vim: filetype=ruby
  2
+require 'spec/custom'
  3
+
  4
+class MSpecScript
  5
+  set :target, 'rbx'
  6
+end
3  spec/spec_helper.rb
... ...
@@ -0,0 +1,3 @@
  1
+$: << File.expand_path('../../lib', __FILE__)
  2
+
  3
+require 'poetics'
9  spec/syntax/literal_spec.rb
... ...
@@ -0,0 +1,9 @@
  1
+require 'spec/spec_helper'
  2
+
  3
+describe "The Number node" do
  4
+  relates "42" do
  5
+    parse do
  6
+      [:number, 42.0, 1, 1]
  7
+    end
  8
+  end
  9
+end

0 notes on commit 5217e9a

Please sign in to comment.
Something went wrong with that request. Please try again.