Repository: brixen/poetics
Branch: master
Commit: b382a804e3d3
Files: 26
Total size: 34.8 KB
Directory structure:
gitextract_l4a1w08_/
├── .gitignore
├── Gemfile
├── LICENSE
├── README
├── Rakefile
├── bin/
│ └── poetics
├── lib/
│ ├── poetics/
│ │ ├── library/
│ │ │ └── code_loader.rb
│ │ ├── library.rb
│ │ ├── parser/
│ │ │ ├── parser.rb
│ │ │ └── poetics.kpeg
│ │ ├── parser.rb
│ │ ├── syntax/
│ │ │ ├── ast.rb
│ │ │ ├── literal.rb
│ │ │ └── node.rb
│ │ ├── syntax.rb
│ │ └── version.rb
│ └── poetics.rb
├── poetics.gemspec
└── spec/
├── custom/
│ ├── matchers/
│ │ └── parse_as.rb
│ ├── runner/
│ │ └── relates.rb
│ └── utils/
│ ├── options.rb
│ └── script.rb
├── custom.rb
├── default.mspec
├── spec_helper.rb
└── syntax/
└── literal_spec.rb
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*.rbc
*.gem
.bundle
Gemfile.lock
pkg/*
================================================
FILE: Gemfile
================================================
source "http://rubygems.org"
gemspec
gem "rake", ">= 0.8.7"
gem "kpeg", "~> 0.8.2"
gem "mspec", "~> 1.5.17"
================================================
FILE: LICENSE
================================================
Copyright (c) 2011 Brian Ford. 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.
================================================
FILE: README
================================================
1. What is Poetics?
A native implementation of CoffeeScript [1] that runs on the Rubinius VM [2].
Q. Whence comes the name Poetics?
A. Poetics is a partial anagram of the word CoffeeScript. It is also a nod
to Jeremy Ashkenas, the author of CoffeeScript, and his interest in code
as literature.
Q. Why a native implementation?
A. CoffeeScript is an interesting language in its own right. Rather than
merely being a syntax layer on top of Javascript, and bound to expressing
its semantics in those of Javascript, it deserves its own implementation.
Many of the reasons to use CoffeeScript in Node.js would also apply to
using this native implementation.
2. License
Poetics is MIT licensed. See the LICENSE file.
3. Installation
First, install Rubinius:
1. Using RVM (http://rvm.beginrescueend.com).
rvm install rbx
2. Or directly (http://rubini.us/doc/en/what-is-rubinius/).
Second, install Poetics:
rbx -S gem install poetics
4. Running Poetics
Poetics provides a REPL for exploratory programming or runs CoffeeScript
scripts.
rbx -S poetics -h
The REPL presently just parses code and returns an S-Expression
representation:
$ rbx -S poetics
> 42
[:number, 42.0, 1, 1]
> "hello, y'all"
[:string, "hello, y'all", 1, 1]
>
To exit the REPL hit CTRL + d.
5. Getting Help
Poetics is a work in progress. If you encounter trouble, please file an issue
at https://github.com/brixen/poetics/issues
6. Contributing
If you find Poetics interesting and would like to help out, fork the project
on Github and submit a pull request.
7. People
Poetics was created by Brian Ford (brixen) to force him to really learn
Javascript and CoffeeScript.
<add your name here>
8. Credits
Jeremy has created an very interesting language in CoffeeScript. This
implementation steals bits and pieces from other projects:
* Rubinius (https://github.com/rubinius/rubinius/)
* KPeg (https://github.com/evanphx/kpeg/)
* Atomy (https://github.com/vito/atomy/)
* Poison (https://github.com/brixen/poison/)
* Talon (https://github.com/evanphx/talon/)
[1] CoffeeScript (http://jashkenas.github.com/coffee-script/)
[2] Rubinius (http://rubini.us)
================================================
FILE: Rakefile
================================================
require 'bundler'
Bundler::GemHelper.install_tasks
task :default => :spec
base = File.expand_path '../lib/poetics/parser', __FILE__
grammar = "#{base}/poetics.kpeg"
parser = "#{base}/parser.rb"
file parser => grammar do
sh "rbx -S kpeg -f -s #{base}/poetics.kpeg -o #{base}/parser.rb"
end
desc "Convert the grammar description to a parser"
task :parser => parser
desc "Run the specs (default)"
task :spec => :parser do
sh "mspec spec"
end
================================================
FILE: bin/poetics
================================================
#!/usr/bin/env rbx
#
# vim: filetype=ruby
$:.unshift File.expand_path('../../lib', __FILE__)
require 'readline'
require 'poetics'
class Poetics::Script
HISTORY = File.expand_path "~/.poetics_history"
attr_accessor :prompt
def initialize
@evals = []
@script = nil
@history = []
end
def options(argv=ARGV)
options = Rubinius::Options.new "Usage: poetics [options] [script]", 20
options.on "-", "Read and evaluate code from STDIN" do
@evals << STDIN.read
end
options.on "-c", "FILE", "Check the syntax of FILE" do |f|
begin
begin
Poetics::Parser.parse_file f
rescue => e
e.render
exit 1
end
puts "Syntax OK"
exit 0
end
end
options.on "-e", "CODE", "Execute CODE" do |e|
@evals << e
end
options.on "-v", "--version", "Display the version" do
puts Poetics::COMMAND_VERSION
end
options.on "-h", "--help", "Display this help" do
puts options
exit 0
end
options.doc ""
rest = options.parse(argv)
@script ||= rest.first
end
def evals
return if @evals.empty?
Poetics::CodeLoader.evaluate @evals.join("\n")
end
def script
return unless @script
if File.exists? @script
Poetics::CodeLoader.execute_file @script
else
STDERR.puts "Unable to find '#{@script}' to run"
exit 1
end
end
def load_history
return unless File.exists? HISTORY
File.open HISTORY, "r" do |f|
f.each { |l| Readline::HISTORY << l }
end
end
def save_history
File.open HISTORY, "w" do |f|
@history.last(100).map { |s| f.puts s }
end
end
def record_history(str)
@history << str
end
def start_prompt
@prompt = "> "
end
def continue_prompt
@prompt = ". "
end
def repl
return if @script
load_history
start_prompt
begin
snippet = ""
while str = Readline.readline(prompt)
break if str.nil? and snippet.empty?
next if str.empty?
snippet << str
begin
value = Poetics::CodeLoader.evaluate snippet
p value
record_history snippet
snippet = ""
start_prompt
rescue => e
STDERR.puts e
end
end
ensure
save_history
end
end
def main
options
evals
script
repl
end
end
Poetics::Script.new.main
================================================
FILE: lib/poetics/library/code_loader.rb
================================================
module Poetics
class CodeLoader
def self.evaluate(string)
# We're just parsing for now
Poetics::Parser.parse_to_sexp string
end
def self.execute_file(name)
value = Poetics::Parser.parse_to_sexp IO.read(name)
p value
end
end
end
================================================
FILE: lib/poetics/library.rb
================================================
require 'poetics/library/code_loader'
================================================
FILE: lib/poetics/parser/parser.rb
================================================
class Poetics::Parser
# STANDALONE START
def setup_parser(str, debug=false)
@string = str
@pos = 0
@memoizations = Hash.new { |h,k| h[k] = {} }
@result = nil
@failed_rule = nil
@failing_rule_offset = -1
setup_foreign_grammar
end
# This is distinct from setup_parser so that a standalone parser
# can redefine #initialize and still have access to the proper
# parser setup code.
#
def initialize(str, debug=false)
setup_parser(str, debug)
end
attr_reader :string
attr_reader :failing_rule_offset
attr_accessor :result, :pos
# STANDALONE START
def current_column(target=pos)
if c = string.rindex("\n", target-1)
return target - c - 1
end
target + 1
end
def current_line(target=pos)
cur_offset = 0
cur_line = 0
string.each_line do |line|
cur_line += 1
cur_offset += line.size
return cur_line if cur_offset >= target
end
-1
end
def lines
lines = []
string.each_line { |l| lines << l }
lines
end
#
def get_text(start)
@string[start..@pos-1]
end
def show_pos
width = 10
if @pos < width
"#{@pos} (\"#{@string[0,@pos]}\" @ \"#{@string[@pos,width]}\")"
else
"#{@pos} (\"... #{@string[@pos - width, width]}\" @ \"#{@string[@pos,width]}\")"
end
end
def failure_info
l = current_line @failing_rule_offset
c = current_column @failing_rule_offset
if @failed_rule.kind_of? Symbol
info = self.class::Rules[@failed_rule]
"line #{l}, column #{c}: failed rule '#{info.name}' = '#{info.rendered}'"
else
"line #{l}, column #{c}: failed rule '#{@failed_rule}'"
end
end
def failure_caret
l = current_line @failing_rule_offset
c = current_column @failing_rule_offset
line = lines[l-1]
"#{line}\n#{' ' * (c - 1)}^"
end
def failure_character
l = current_line @failing_rule_offset
c = current_column @failing_rule_offset
lines[l-1][c-1, 1]
end
def failure_oneline
l = current_line @failing_rule_offset
c = current_column @failing_rule_offset
char = lines[l-1][c-1, 1]
if @failed_rule.kind_of? Symbol
info = self.class::Rules[@failed_rule]
"@#{l}:#{c} failed rule '#{info.name}', got '#{char}'"
else
"@#{l}:#{c} failed rule '#{@failed_rule}', got '#{char}'"
end
end
class ParseError < RuntimeError
end
def raise_error
raise ParseError, failure_oneline
end
def show_error(io=STDOUT)
error_pos = @failing_rule_offset
line_no = current_line(error_pos)
col_no = current_column(error_pos)
io.puts "On line #{line_no}, column #{col_no}:"
if @failed_rule.kind_of? Symbol
info = self.class::Rules[@failed_rule]
io.puts "Failed to match '#{info.rendered}' (rule '#{info.name}')"
else
io.puts "Failed to match rule '#{@failed_rule}'"
end
io.puts "Got: #{string[error_pos,1].inspect}"
line = lines[line_no-1]
io.puts "=> #{line}"
io.print(" " * (col_no + 3))
io.puts "^"
end
def set_failed_rule(name)
if @pos > @failing_rule_offset
@failed_rule = name
@failing_rule_offset = @pos
end
end
attr_reader :failed_rule
def match_string(str)
len = str.size
if @string[pos,len] == str
@pos += len
return str
end
return nil
end
def scan(reg)
if m = reg.match(@string[@pos..-1])
width = m.end(0)
@pos += width
return true
end
return nil
end
if "".respond_to? :getbyte
def get_byte
if @pos >= @string.size
return nil
end
s = @string.getbyte @pos
@pos += 1
s
end
else
def get_byte
if @pos >= @string.size
return nil
end
s = @string[@pos]
@pos += 1
s
end
end
def parse(rule=nil)
if !rule
_root ? true : false
else
# This is not shared with code_generator.rb so this can be standalone
method = rule.gsub("-","_hyphen_")
__send__("_#{method}") ? true : false
end
end
class LeftRecursive
def initialize(detected=false)
@detected = detected
end
attr_accessor :detected
end
class MemoEntry
def initialize(ans, pos)
@ans = ans
@pos = pos
@uses = 1
@result = nil
end
attr_reader :ans, :pos, :uses, :result
def inc!
@uses += 1
end
def move!(ans, pos, result)
@ans = ans
@pos = pos
@result = result
end
end
def external_invoke(other, rule, *args)
old_pos = @pos
old_string = @string
@pos = other.pos
@string = other.string
begin
if val = __send__(rule, *args)
other.pos = @pos
other.result = @result
else
other.set_failed_rule "#{self.class}##{rule}"
end
val
ensure
@pos = old_pos
@string = old_string
end
end
def apply_with_args(rule, *args)
memo_key = [rule, args]
if m = @memoizations[memo_key][@pos]
m.inc!
prev = @pos
@pos = m.pos
if m.ans.kind_of? LeftRecursive
m.ans.detected = true
return nil
end
@result = m.result
return m.ans
else
lr = LeftRecursive.new(false)
m = MemoEntry.new(lr, @pos)
@memoizations[memo_key][@pos] = m
start_pos = @pos
ans = __send__ rule, *args
m.move! ans, @pos, @result
# Don't bother trying to grow the left recursion
# if it's failing straight away (thus there is no seed)
if ans and lr.detected
return grow_lr(rule, args, start_pos, m)
else
return ans
end
return ans
end
end
def apply(rule)
if m = @memoizations[rule][@pos]
m.inc!
prev = @pos
@pos = m.pos
if m.ans.kind_of? LeftRecursive
m.ans.detected = true
return nil
end
@result = m.result
return m.ans
else
lr = LeftRecursive.new(false)
m = MemoEntry.new(lr, @pos)
@memoizations[rule][@pos] = m
start_pos = @pos
ans = __send__ rule
m.move! ans, @pos, @result
# Don't bother trying to grow the left recursion
# if it's failing straight away (thus there is no seed)
if ans and lr.detected
return grow_lr(rule, nil, start_pos, m)
else
return ans
end
return ans
end
end
def grow_lr(rule, args, start_pos, m)
while true
@pos = start_pos
@result = m.result
if args
ans = __send__ rule, *args
else
ans = __send__ rule
end
return nil unless ans
break if @pos <= m.pos
m.move! ans, @pos, @result
end
@result = m.result
@pos = m.pos
return m.ans
end
class RuleInfo
def initialize(name, rendered)
@name = name
@rendered = rendered
end
attr_reader :name, :rendered
end
def self.rule_info(name, rendered)
RuleInfo.new(name, rendered)
end
#
def setup_foreign_grammar; end
# root = - value? - end
def _root
_save = self.pos
while true # sequence
_tmp = apply(:__hyphen_)
unless _tmp
self.pos = _save
break
end
_save1 = self.pos
_tmp = apply(:_value)
unless _tmp
_tmp = true
self.pos = _save1
end
unless _tmp
self.pos = _save
break
end
_tmp = apply(:__hyphen_)
unless _tmp
self.pos = _save
break
end
_tmp = apply(:_end)
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_root unless _tmp
return _tmp
end
# end = !.
def _end
_save = self.pos
_tmp = get_byte
_tmp = _tmp ? nil : true
self.pos = _save
set_failed_rule :_end unless _tmp
return _tmp
end
# - = (" " | "\t" | "\n")*
def __hyphen_
while true
_save1 = self.pos
while true # choice
_tmp = match_string(" ")
break if _tmp
self.pos = _save1
_tmp = match_string("\t")
break if _tmp
self.pos = _save1
_tmp = match_string("\n")
break if _tmp
self.pos = _save1
break
end # end choice
break unless _tmp
end
_tmp = true
set_failed_rule :__hyphen_ unless _tmp
return _tmp
end
# value = (string | number | boolean)
def _value
_save = self.pos
while true # choice
_tmp = apply(:_string)
break if _tmp
self.pos = _save
_tmp = apply(:_number)
break if _tmp
self.pos = _save
_tmp = apply(:_boolean)
break if _tmp
self.pos = _save
break
end # end choice
set_failed_rule :_value unless _tmp
return _tmp
end
# boolean = position (true | false | null | undefined)
def _boolean
_save = self.pos
while true # sequence
_tmp = apply(:_position)
unless _tmp
self.pos = _save
break
end
_save1 = self.pos
while true # choice
_tmp = apply(:_true)
break if _tmp
self.pos = _save1
_tmp = apply(:_false)
break if _tmp
self.pos = _save1
_tmp = apply(:_null)
break if _tmp
self.pos = _save1
_tmp = apply(:_undefined)
break if _tmp
self.pos = _save1
break
end # end choice
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_boolean unless _tmp
return _tmp
end
# true = "true" {true_value}
def _true
_save = self.pos
while true # sequence
_tmp = match_string("true")
unless _tmp
self.pos = _save
break
end
@result = begin; true_value; end
_tmp = true
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_true unless _tmp
return _tmp
end
# false = "false" {false_value}
def _false
_save = self.pos
while true # sequence
_tmp = match_string("false")
unless _tmp
self.pos = _save
break
end
@result = begin; false_value; end
_tmp = true
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_false unless _tmp
return _tmp
end
# null = "null" {null_value}
def _null
_save = self.pos
while true # sequence
_tmp = match_string("null")
unless _tmp
self.pos = _save
break
end
@result = begin; null_value; end
_tmp = true
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_null unless _tmp
return _tmp
end
# undefined = "undefined" {undefined_value}
def _undefined
_save = self.pos
while true # sequence
_tmp = match_string("undefined")
unless _tmp
self.pos = _save
break
end
@result = begin; undefined_value; end
_tmp = true
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_undefined unless _tmp
return _tmp
end
# number = position (real | hex | int)
def _number
_save = self.pos
while true # sequence
_tmp = apply(:_position)
unless _tmp
self.pos = _save
break
end
_save1 = self.pos
while true # choice
_tmp = apply(:_real)
break if _tmp
self.pos = _save1
_tmp = apply(:_hex)
break if _tmp
self.pos = _save1
_tmp = apply(:_int)
break if _tmp
self.pos = _save1
break
end # end choice
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_number unless _tmp
return _tmp
end
# hexdigits = /[0-9A-Fa-f]/
def _hexdigits
_tmp = scan(/\A(?-mix:[0-9A-Fa-f])/)
set_failed_rule :_hexdigits unless _tmp
return _tmp
end
# hex = "0x" < hexdigits+ > {hexadecimal(text)}
def _hex
_save = self.pos
while true # sequence
_tmp = match_string("0x")
unless _tmp
self.pos = _save
break
end
_text_start = self.pos
_save1 = self.pos
_tmp = apply(:_hexdigits)
if _tmp
while true
_tmp = apply(:_hexdigits)
break unless _tmp
end
_tmp = true
else
self.pos = _save1
end
if _tmp
text = get_text(_text_start)
end
unless _tmp
self.pos = _save
break
end
@result = begin; hexadecimal(text); end
_tmp = true
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_hex unless _tmp
return _tmp
end
# digits = ("0" | /[1-9]/ /[0-9]/*)
def _digits
_save = self.pos
while true # choice
_tmp = match_string("0")
break if _tmp
self.pos = _save
_save1 = self.pos
while true # sequence
_tmp = scan(/\A(?-mix:[1-9])/)
unless _tmp
self.pos = _save1
break
end
while true
_tmp = scan(/\A(?-mix:[0-9])/)
break unless _tmp
end
_tmp = true
unless _tmp
self.pos = _save1
end
break
end # end sequence
break if _tmp
self.pos = _save
break
end # end choice
set_failed_rule :_digits unless _tmp
return _tmp
end
# int = < digits > {number(text)}
def _int
_save = self.pos
while true # sequence
_text_start = self.pos
_tmp = apply(:_digits)
if _tmp
text = get_text(_text_start)
end
unless _tmp
self.pos = _save
break
end
@result = begin; number(text); end
_tmp = true
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_int unless _tmp
return _tmp
end
# real = < digits "." digits ("e" /[-+]/? /[0-9]/+)? > {number(text)}
def _real
_save = self.pos
while true # sequence
_text_start = self.pos
_save1 = self.pos
while true # sequence
_tmp = apply(:_digits)
unless _tmp
self.pos = _save1
break
end
_tmp = match_string(".")
unless _tmp
self.pos = _save1
break
end
_tmp = apply(:_digits)
unless _tmp
self.pos = _save1
break
end
_save2 = self.pos
_save3 = self.pos
while true # sequence
_tmp = match_string("e")
unless _tmp
self.pos = _save3
break
end
_save4 = self.pos
_tmp = scan(/\A(?-mix:[-+])/)
unless _tmp
_tmp = true
self.pos = _save4
end
unless _tmp
self.pos = _save3
break
end
_save5 = self.pos
_tmp = scan(/\A(?-mix:[0-9])/)
if _tmp
while true
_tmp = scan(/\A(?-mix:[0-9])/)
break unless _tmp
end
_tmp = true
else
self.pos = _save5
end
unless _tmp
self.pos = _save3
end
break
end # end sequence
unless _tmp
_tmp = true
self.pos = _save2
end
unless _tmp
self.pos = _save1
end
break
end # end sequence
if _tmp
text = get_text(_text_start)
end
unless _tmp
self.pos = _save
break
end
@result = begin; number(text); end
_tmp = true
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_real unless _tmp
return _tmp
end
# string = position "\"" < /[^\\"]*/ > "\"" {string_value(text)}
def _string
_save = self.pos
while true # sequence
_tmp = apply(:_position)
unless _tmp
self.pos = _save
break
end
_tmp = match_string("\"")
unless _tmp
self.pos = _save
break
end
_text_start = self.pos
_tmp = scan(/\A(?-mix:[^\\"]*)/)
if _tmp
text = get_text(_text_start)
end
unless _tmp
self.pos = _save
break
end
_tmp = match_string("\"")
unless _tmp
self.pos = _save
break
end
@result = begin; string_value(text); end
_tmp = true
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_string unless _tmp
return _tmp
end
# line = { current_line }
def _line
@result = begin; current_line ; end
_tmp = true
set_failed_rule :_line unless _tmp
return _tmp
end
# column = { current_column }
def _column
@result = begin; current_column ; end
_tmp = true
set_failed_rule :_column unless _tmp
return _tmp
end
# position = line:l column:c { position(l, c) }
def _position
_save = self.pos
while true # sequence
_tmp = apply(:_line)
l = @result
unless _tmp
self.pos = _save
break
end
_tmp = apply(:_column)
c = @result
unless _tmp
self.pos = _save
break
end
@result = begin; position(l, c) ; end
_tmp = true
unless _tmp
self.pos = _save
end
break
end # end sequence
set_failed_rule :_position unless _tmp
return _tmp
end
Rules = {}
Rules[:_root] = rule_info("root", "- value? - end")
Rules[:_end] = rule_info("end", "!.")
Rules[:__hyphen_] = rule_info("-", "(\" \" | \"\\t\" | \"\\n\")*")
Rules[:_value] = rule_info("value", "(string | number | boolean)")
Rules[:_boolean] = rule_info("boolean", "position (true | false | null | undefined)")
Rules[:_true] = rule_info("true", "\"true\" {true_value}")
Rules[:_false] = rule_info("false", "\"false\" {false_value}")
Rules[:_null] = rule_info("null", "\"null\" {null_value}")
Rules[:_undefined] = rule_info("undefined", "\"undefined\" {undefined_value}")
Rules[:_number] = rule_info("number", "position (real | hex | int)")
Rules[:_hexdigits] = rule_info("hexdigits", "/[0-9A-Fa-f]/")
Rules[:_hex] = rule_info("hex", "\"0x\" < hexdigits+ > {hexadecimal(text)}")
Rules[:_digits] = rule_info("digits", "(\"0\" | /[1-9]/ /[0-9]/*)")
Rules[:_int] = rule_info("int", "< digits > {number(text)}")
Rules[:_real] = rule_info("real", "< digits \".\" digits (\"e\" /[-+]/? /[0-9]/+)? > {number(text)}")
Rules[:_string] = rule_info("string", "position \"\\\"\" < /[^\\\\\"]*/ > \"\\\"\" {string_value(text)}")
Rules[:_line] = rule_info("line", "{ current_line }")
Rules[:_column] = rule_info("column", "{ current_column }")
Rules[:_position] = rule_info("position", "line:l column:c { position(l, c) }")
end
================================================
FILE: lib/poetics/parser/poetics.kpeg
================================================
%% name = Poetics::Parser
root = - value? - end
end = !.
- = (" " | "\t" | "\n")*
value = string
| number
| boolean
boolean = position (
true
| false
| null
| undefined)
true = "true" ~true_value
false = "false" ~false_value
null = "null" ~null_value
undefined = "undefined" ~undefined_value
number = position (
real
| hex
| int )
hexdigits = /[0-9A-Fa-f]/
hex = '0x' < hexdigits+ > ~hexadecimal(text)
digits = '0' | /[1-9]/ /[0-9]/*
int = < digits > ~number(text)
real = < digits '.' digits ('e' /[-+]/? /[0-9]/+)? > ~number(text)
string = position '"' < /[^\\"]*/ > '"' ~string_value(text)
# keep track of column and line
line = { current_line }
column = { current_column }
position = line:l column:c { position(l, c) }
================================================
FILE: lib/poetics/parser.rb
================================================
require 'poetics/parser/parser'
class Poetics::Parser
include Poetics::Syntax
def self.parse_to_sexp(string)
parser = new string
unless parser.parse
parser.raise_error
end
parser.result.to_sexp
end
attr_reader :line, :column
def position(line, column)
@line = line
@column = column
end
end
================================================
FILE: lib/poetics/syntax/ast.rb
================================================
module Poetics
module Syntax
def number(value)
Number.new line, column, value
end
def hexadecimal(value)
Number.new line, column, value.to_i(16)
end
def true_value
True.new line, column
end
def false_value
False.new line, column
end
def null_value
Null.new line, column
end
def undefined_value
Undefined.new line, column
end
def string_value(value)
String.new line, column, value
end
end
end
================================================
FILE: lib/poetics/syntax/literal.rb
================================================
module Poetics
module Syntax
class Value < Node
def to_sexp
[sexp_name, value, line, column]
end
end
class Number < Value
attr_accessor :value
def initialize(line, column, value)
super
@value = value.to_f
end
def sexp_name
:number
end
end
class Boolean < Node
def to_sexp
[sexp_name, line, column]
end
end
class True < Boolean
def sexp_name
:true
end
end
class False < Boolean
def sexp_name
:false
end
end
class Null < Boolean
def sexp_name
:null
end
end
class Undefined < Boolean
def sexp_name
:undefined
end
end
class String < Value
attr_accessor :value
def initialize(line, column, text)
super
@value = text
end
def sexp_name
:string
end
end
end
end
================================================
FILE: lib/poetics/syntax/node.rb
================================================
module Poetics
module Syntax
class Node
attr_accessor :line, :column
def initialize(line, column, *)
@line = line
@column = column
end
def to_sexp
end
end
end
end
================================================
FILE: lib/poetics/syntax.rb
================================================
require 'poetics/syntax/node'
require 'poetics/syntax/literal'
require 'poetics/syntax/ast'
================================================
FILE: lib/poetics/version.rb
================================================
module Poetics
VERSION = "0.0.1"
RELEASE_DATE = "yyyy-mm-dd"
COMMAND_VERSION = "poetics #{VERSION} (1.1.0 #{RELEASE_DATE})"
end
================================================
FILE: lib/poetics.rb
================================================
require 'poetics/version'
require 'poetics/syntax'
require 'poetics/parser'
require 'poetics/library'
================================================
FILE: poetics.gemspec
================================================
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "poetics/version"
Gem::Specification.new do |s|
s.name = "poetics"
s.version = Poetics::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Brian Ford"]
s.email = ["brixen@gmail.com"]
s.homepage = "https://github.com/brixen/poetics"
s.summary = %q{A native implementation of CoffeeScript on the Rubinius VM}
s.description =<<-EOD
Poetics implements CoffeeScript (http://jashkenas.github.com/coffee-script/)
directly on the Rubinius VM (http://rubini.us). It includes a REPL for
exploratory programming, as well as executing CoffeeScript scripts directly.
EOD
s.files = `git ls-files`.split("\n")
s.test_files = Dir["spec/**/*.rb"]
s.executables = ["poetics"]
s.require_paths = ["lib"]
end
================================================
FILE: spec/custom/matchers/parse_as.rb
================================================
class ParseAsMatcher
def initialize(expected)
@expected = expected
end
def matches?(actual)
@actual = Poetics::Parser.parse_to_sexp actual
@actual == @expected
end
def failure_message
["Expected:\n#{@actual.inspect}\n",
"to equal:\n#{@expected.inspect}"]
end
end
class Object
def parse_as(sexp)
ParseAsMatcher.new sexp
end
end
================================================
FILE: spec/custom/runner/relates.rb
================================================
# NOTE: Copied from Rubinius
#
# SpecDataRelation enables concise specs that involve several different forms
# of the same data. This is specifically useful for the parser and compiler
# specs where the output of each stage is essentially related to the input
# Ruby source. Together with the #relates spec method, it enables specs like:
#
# describe "An If node" do
# relates "a if b" do
# parse do
# # return the expected sexp
# end
#
# compile do |g|
# # return the expected bytecode
# end
#
# jit do |as|
# # return the expected asm/machine code
# end
# end
#
# relates "if a; b; end" do
# # ...
# end
# end
class SpecDataRelation
# Provides a simple configurability so that any one or more of the possible
# processes can be run. See the custom options in custom/utils/options.rb.
def self.enable(process)
@processors ||= []
@processors << process
end
# Returns true if no process is specifically set or if +process+ is in the
# list of enabled processes. In other words, all processes are enabled by
# default, or any combination of them may be enabled.
def self.enabled?(process)
@processors.nil? or @processors.include?(process)
end
def initialize(ruby)
@ruby = ruby
end
# Formats the Ruby source code for reabable output in the -fs formatter
# option. If the source contains no newline characters, wraps the source in
# single quotes to set if off from the rest of the description string. If
# the source does contain newline characters, sets the indent level to four
# characters.
def format(ruby)
if /\n/ =~ ruby
lines = ruby.rstrip.to_a
if /( *)/ =~ lines.first
if $1.size > 4
dedent = $1.size - 4
ruby = lines.map { |l| l[dedent..-1] }.join
else
indent = " " * (4 - $1.size)
ruby = lines.map { |l| "#{indent}#{l}" }.join
end
end
"\n#{ruby}"
else
"'#{ruby}'"
end
end
# Creates spec example blocks if the compile process is enabled.
def compile(*plugins, &block)
return unless self.class.enabled? :compiler
ruby = @ruby
it "is compiled from #{format ruby}" do
generator = Rubinius::TestGenerator.new
generator.instance_eval(&block)
ruby.should compile_as(generator, *plugins)
end
end
def parse(&block)
return unless self.class.enabled? :parser
ruby = @ruby
it "is parsed from #{format ruby}" do
ruby.should parse_as(block.call)
end
end
end
class Object
def relates(str, &block)
SpecDataRelation.new(str).instance_eval(&block)
end
end
================================================
FILE: spec/custom/utils/options.rb
================================================
# Custom MSpec options
#
class MSpecOptions
def compiler
# The require is inside the method because this file has to be able to be
# loaded in MRI and there are parts of the custom ensemble that are
# Rubinius specific (primarily iseq, which could potentially be fixed by
# better structuring the compiler).
require 'spec/custom/runner/relates'
on("--compiler", "Run only the compile part of the compiler specs") do
SpecDataRelation.enable :compiler
end
end
def parser
on("--parser", "Run only the parse part of the compiler specs") do
SpecDataRelation.enable :parser
end
end
end
================================================
FILE: spec/custom/utils/script.rb
================================================
# Custom options for mspec-run
#
class MSpecRun
def custom_options(options)
options.compiler
options.parser
end
end
# Custom options for mspec-ci
#
class MSpecCI
def custom_options(options)
options.compiler
options.parser
end
end
# Custom options for mspec-tag
#
class MSpecTag
def custom_options(options)
options.compiler
options.parser
end
end
================================================
FILE: spec/custom.rb
================================================
require 'spec/custom/runner/relates'
require 'spec/custom/matchers/parse_as'
require 'spec/custom/utils/options'
require 'spec/custom/utils/script'
================================================
FILE: spec/default.mspec
================================================
# vim: filetype=ruby
require 'spec/custom'
class MSpecScript
set :target, 'rbx'
end
================================================
FILE: spec/spec_helper.rb
================================================
$: << File.expand_path('../../lib', __FILE__)
require 'poetics'
================================================
FILE: spec/syntax/literal_spec.rb
================================================
require 'spec/spec_helper'
describe "The Number node" do
relates "42" do
parse { [:number, 42.0, 1, 1] }
end
relates " 42" do
parse { [:number, 42.0, 1, 2] }
end
relates "42 " do
parse { [:number, 42.0, 1, 1] }
end
relates "1.23" do
parse { [:number, 1.23, 1, 1] }
end
relates "0x2a" do
parse { [:number, 42.0, 1, 1] }
end
end
describe "The True node" do
relates "true" do
parse { [:true, 1, 1] }
end
end
describe "The False node" do
relates "false" do
parse { [:false, 1, 1] }
end
end
describe "The Null node" do
relates "null" do
parse { [:null, 1, 1] }
end
end
describe "The Undefined node" do
relates "undefined" do
parse { [:undefined, 1, 1] }
end
end
describe "The String node" do
relates '"hello, world"' do
parse { [:string, "hello, world", 1, 1] }
end
relates <<-ruby do
"hello"
ruby
parse { [:string, "hello", 1, 7] }
end
end
gitextract_l4a1w08_/
├── .gitignore
├── Gemfile
├── LICENSE
├── README
├── Rakefile
├── bin/
│ └── poetics
├── lib/
│ ├── poetics/
│ │ ├── library/
│ │ │ └── code_loader.rb
│ │ ├── library.rb
│ │ ├── parser/
│ │ │ ├── parser.rb
│ │ │ └── poetics.kpeg
│ │ ├── parser.rb
│ │ ├── syntax/
│ │ │ ├── ast.rb
│ │ │ ├── literal.rb
│ │ │ └── node.rb
│ │ ├── syntax.rb
│ │ └── version.rb
│ └── poetics.rb
├── poetics.gemspec
└── spec/
├── custom/
│ ├── matchers/
│ │ └── parse_as.rb
│ ├── runner/
│ │ └── relates.rb
│ └── utils/
│ ├── options.rb
│ └── script.rb
├── custom.rb
├── default.mspec
├── spec_helper.rb
└── syntax/
└── literal_spec.rb
SYMBOL INDEX (120 symbols across 11 files)
FILE: lib/poetics/library/code_loader.rb
type Poetics (line 1) | module Poetics
class CodeLoader (line 2) | class CodeLoader
method evaluate (line 3) | def self.evaluate(string)
method execute_file (line 8) | def self.execute_file(name)
FILE: lib/poetics/parser.rb
class Poetics::Parser (line 3) | class Poetics::Parser
method parse_to_sexp (line 6) | def self.parse_to_sexp(string)
method position (line 17) | def position(line, column)
FILE: lib/poetics/parser/parser.rb
class Poetics::Parser (line 1) | class Poetics::Parser
method setup_parser (line 3) | def setup_parser(str, debug=false)
method initialize (line 18) | def initialize(str, debug=false)
method current_column (line 27) | def current_column(target=pos)
method current_line (line 35) | def current_line(target=pos)
method lines (line 48) | def lines
method get_text (line 56) | def get_text(start)
method show_pos (line 60) | def show_pos
method failure_info (line 69) | def failure_info
method failure_caret (line 81) | def failure_caret
method failure_character (line 89) | def failure_character
method failure_oneline (line 95) | def failure_oneline
class ParseError (line 109) | class ParseError < RuntimeError
method raise_error (line 112) | def raise_error
method show_error (line 116) | def show_error(io=STDOUT)
method set_failed_rule (line 137) | def set_failed_rule(name)
method match_string (line 146) | def match_string(str)
method scan (line 156) | def scan(reg)
method get_byte (line 167) | def get_byte
method get_byte (line 177) | def get_byte
method parse (line 188) | def parse(rule=nil)
class LeftRecursive (line 198) | class LeftRecursive
method initialize (line 199) | def initialize(detected=false)
class MemoEntry (line 206) | class MemoEntry
method initialize (line 207) | def initialize(ans, pos)
method inc! (line 216) | def inc!
method move! (line 220) | def move!(ans, pos, result)
method external_invoke (line 227) | def external_invoke(other, rule, *args)
method apply_with_args (line 248) | def apply_with_args(rule, *args)
method apply (line 285) | def apply(rule)
method grow_lr (line 321) | def grow_lr(rule, args, start_pos, m)
class RuleInfo (line 343) | class RuleInfo
method initialize (line 344) | def initialize(name, rendered)
method rule_info (line 352) | def self.rule_info(name, rendered)
method setup_foreign_grammar (line 357) | def setup_foreign_grammar; end
method _root (line 360) | def _root
method _end (line 396) | def _end
method __hyphen_ (line 406) | def __hyphen_
method _value (line 431) | def _value
method _boolean (line 452) | def _boolean
method _true (line 490) | def _true
method _false (line 512) | def _false
method _null (line 534) | def _null
method _undefined (line 556) | def _undefined
method _number (line 578) | def _number
method _hexdigits (line 613) | def _hexdigits
method _hex (line 620) | def _hex
method _digits (line 661) | def _digits
method _int (line 697) | def _int
method _real (line 723) | def _real
method _string (line 812) | def _string
method _line (line 853) | def _line
method _column (line 861) | def _column
method _position (line 869) | def _position
FILE: lib/poetics/syntax/ast.rb
type Poetics (line 1) | module Poetics
type Syntax (line 2) | module Syntax
function number (line 3) | def number(value)
function hexadecimal (line 7) | def hexadecimal(value)
function true_value (line 11) | def true_value
function false_value (line 15) | def false_value
function null_value (line 19) | def null_value
function undefined_value (line 23) | def undefined_value
function string_value (line 27) | def string_value(value)
FILE: lib/poetics/syntax/literal.rb
type Poetics (line 1) | module Poetics
type Syntax (line 2) | module Syntax
class Value (line 3) | class Value < Node
method to_sexp (line 4) | def to_sexp
class Number (line 9) | class Number < Value
method initialize (line 12) | def initialize(line, column, value)
method sexp_name (line 17) | def sexp_name
class Boolean (line 22) | class Boolean < Node
method to_sexp (line 23) | def to_sexp
class True (line 28) | class True < Boolean
method sexp_name (line 29) | def sexp_name
class False (line 34) | class False < Boolean
method sexp_name (line 35) | def sexp_name
class Null (line 40) | class Null < Boolean
method sexp_name (line 41) | def sexp_name
class Undefined (line 46) | class Undefined < Boolean
method sexp_name (line 47) | def sexp_name
class String (line 52) | class String < Value
method initialize (line 55) | def initialize(line, column, text)
method sexp_name (line 60) | def sexp_name
FILE: lib/poetics/syntax/node.rb
type Poetics (line 1) | module Poetics
type Syntax (line 2) | module Syntax
class Node (line 3) | class Node
method initialize (line 6) | def initialize(line, column, *)
method to_sexp (line 11) | def to_sexp
FILE: lib/poetics/version.rb
type Poetics (line 1) | module Poetics
FILE: spec/custom/matchers/parse_as.rb
class ParseAsMatcher (line 1) | class ParseAsMatcher
method initialize (line 2) | def initialize(expected)
method matches? (line 6) | def matches?(actual)
method failure_message (line 11) | def failure_message
class Object (line 17) | class Object
method parse_as (line 18) | def parse_as(sexp)
FILE: spec/custom/runner/relates.rb
class SpecDataRelation (line 28) | class SpecDataRelation
method enable (line 31) | def self.enable(process)
method enabled? (line 39) | def self.enabled?(process)
method initialize (line 43) | def initialize(ruby)
method format (line 52) | def format(ruby)
method compile (line 71) | def compile(*plugins, &block)
method parse (line 83) | def parse(&block)
class Object (line 93) | class Object
method relates (line 94) | def relates(str, &block)
FILE: spec/custom/utils/options.rb
class MSpecOptions (line 3) | class MSpecOptions
method compiler (line 4) | def compiler
method parser (line 16) | def parser
FILE: spec/custom/utils/script.rb
class MSpecRun (line 3) | class MSpecRun
method custom_options (line 4) | def custom_options(options)
class MSpecCI (line 12) | class MSpecCI
method custom_options (line 13) | def custom_options(options)
class MSpecTag (line 21) | class MSpecTag
method custom_options (line 22) | def custom_options(options)
Condensed preview — 26 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (39K chars).
[
{
"path": ".gitignore",
"chars": 39,
"preview": "*.rbc\n*.gem\n.bundle\nGemfile.lock\npkg/*\n"
},
{
"path": "Gemfile",
"chars": 115,
"preview": "source \"http://rubygems.org\"\n\ngemspec\n\ngem \"rake\", \">= 0.8.7\"\ngem \"kpeg\", \"~> 0.8.2\"\ngem \"mspec\", \"~> 1.5.17\"\n"
},
{
"path": "LICENSE",
"chars": 1076,
"preview": "Copyright (c) 2011 Brian Ford. All rights reserved.\n\nPermission is hereby granted, free of charge, to any person\nobtaini"
},
{
"path": "README",
"chars": 2225,
"preview": "1. What is Poetics?\n\nA native implementation of CoffeeScript [1] that runs on the Rubinius VM [2].\n\n Q. Whence comes th"
},
{
"path": "Rakefile",
"chars": 450,
"preview": "require 'bundler'\nBundler::GemHelper.install_tasks\n\ntask :default => :spec\n\nbase = File.expand_path '../lib/poetics/pars"
},
{
"path": "bin/poetics",
"chars": 2444,
"preview": "#!/usr/bin/env rbx\n#\n# vim: filetype=ruby\n\n$:.unshift File.expand_path('../../lib', __FILE__)\n\nrequire 'readline'\nrequir"
},
{
"path": "lib/poetics/library/code_loader.rb",
"chars": 273,
"preview": "module Poetics\n class CodeLoader\n def self.evaluate(string)\n # We're just parsing for now\n Poetics::Parser"
},
{
"path": "lib/poetics/library.rb",
"chars": 38,
"preview": "require 'poetics/library/code_loader'\n"
},
{
"path": "lib/poetics/parser/parser.rb",
"chars": 19573,
"preview": "class Poetics::Parser\n# STANDALONE START\n def setup_parser(str, debug=false)\n @string = str\n @pos = 0\n "
},
{
"path": "lib/poetics/parser/poetics.kpeg",
"chars": 902,
"preview": "%% name = Poetics::Parser\n\nroot = - value? - end\n\nend = !.\n- = (\" \" | \"\\t\" | \"\\n\")*\n\nvalue = stri"
},
{
"path": "lib/poetics/parser.rb",
"chars": 337,
"preview": "require 'poetics/parser/parser'\n\nclass Poetics::Parser\n include Poetics::Syntax\n\n def self.parse_to_sexp(string)\n p"
},
{
"path": "lib/poetics/syntax/ast.rb",
"chars": 500,
"preview": "module Poetics\n module Syntax\n def number(value)\n Number.new line, column, value\n end\n\n def hexadecimal(v"
},
{
"path": "lib/poetics/syntax/literal.rb",
"chars": 960,
"preview": "module Poetics\n module Syntax\n class Value < Node\n def to_sexp\n [sexp_name, value, line, column]\n e"
},
{
"path": "lib/poetics/syntax/node.rb",
"chars": 223,
"preview": "module Poetics\n module Syntax\n class Node\n attr_accessor :line, :column\n\n def initialize(line, column, *)\n"
},
{
"path": "lib/poetics/syntax.rb",
"chars": 92,
"preview": "require 'poetics/syntax/node'\nrequire 'poetics/syntax/literal'\nrequire 'poetics/syntax/ast'\n"
},
{
"path": "lib/poetics/version.rb",
"chars": 134,
"preview": "module Poetics\n VERSION = \"0.0.1\"\n RELEASE_DATE = \"yyyy-mm-dd\"\n COMMAND_VERSION = \"poetics #{VERSION} (1.1.0 #{RELEAS"
},
{
"path": "lib/poetics.rb",
"chars": 102,
"preview": "require 'poetics/version'\nrequire 'poetics/syntax'\nrequire 'poetics/parser'\nrequire 'poetics/library'\n"
},
{
"path": "poetics.gemspec",
"chars": 843,
"preview": "# -*- encoding: utf-8 -*-\n$:.push File.expand_path(\"../lib\", __FILE__)\nrequire \"poetics/version\"\n\nGem::Specification.new"
},
{
"path": "spec/custom/matchers/parse_as.rb",
"chars": 371,
"preview": "class ParseAsMatcher\n def initialize(expected)\n @expected = expected\n end\n\n def matches?(actual)\n @actual = Poe"
},
{
"path": "spec/custom/runner/relates.rb",
"chars": 2673,
"preview": "# NOTE: Copied from Rubinius\n#\n# SpecDataRelation enables concise specs that involve several different forms\n# of the sa"
},
{
"path": "spec/custom/utils/options.rb",
"chars": 638,
"preview": "# Custom MSpec options\n#\nclass MSpecOptions\n def compiler\n # The require is inside the method because this file has "
},
{
"path": "spec/custom/utils/script.rb",
"chars": 384,
"preview": "# Custom options for mspec-run\n#\nclass MSpecRun\n def custom_options(options)\n options.compiler\n options.parser\n "
},
{
"path": "spec/custom.rb",
"chars": 148,
"preview": "require 'spec/custom/runner/relates'\nrequire 'spec/custom/matchers/parse_as'\nrequire 'spec/custom/utils/options'\nrequire"
},
{
"path": "spec/default.mspec",
"chars": 87,
"preview": "# vim: filetype=ruby\nrequire 'spec/custom'\n\nclass MSpecScript\n set :target, 'rbx'\nend\n"
},
{
"path": "spec/spec_helper.rb",
"chars": 65,
"preview": "$: << File.expand_path('../../lib', __FILE__)\n\nrequire 'poetics'\n"
},
{
"path": "spec/syntax/literal_spec.rb",
"chars": 946,
"preview": "require 'spec/spec_helper'\n\ndescribe \"The Number node\" do\n relates \"42\" do\n parse { [:number, 42.0, 1, 1] }\n end\n\n "
}
]
About this extraction
This page contains the full source code of the brixen/poetics GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 26 files (34.8 KB), approximately 10.7k tokens, and a symbol index with 120 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.