poetics 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/Gemfile +7 -0
- data/LICENSE +22 -0
- data/README +87 -0
- data/Rakefile +21 -0
- data/bin/poetics +140 -0
- data/lib/poetics.rb +4 -0
- data/lib/poetics/library.rb +1 -0
- data/lib/poetics/library/code_loader.rb +13 -0
- data/lib/poetics/parser.rb +21 -0
- data/lib/poetics/parser/parser.rb +875 -0
- data/lib/poetics/parser/poetics.kpeg +42 -0
- data/lib/poetics/syntax.rb +3 -0
- data/lib/poetics/syntax/ast.rb +31 -0
- data/lib/poetics/syntax/literal.rb +65 -0
- data/lib/poetics/syntax/node.rb +15 -0
- data/lib/poetics/version.rb +5 -0
- data/poetics.gemspec +23 -0
- data/spec/custom.rb +4 -0
- data/spec/custom/matchers/parse_as.rb +21 -0
- data/spec/custom/runner/relates.rb +97 -0
- data/spec/custom/utils/options.rb +21 -0
- data/spec/custom/utils/script.rb +26 -0
- data/spec/default.mspec +6 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/syntax/literal_spec.rb +60 -0
- metadata +98 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
%% name = Poetics::Parser
|
2
|
+
|
3
|
+
root = - value? - end
|
4
|
+
|
5
|
+
end = !.
|
6
|
+
- = (" " | "\t" | "\n")*
|
7
|
+
|
8
|
+
value = string
|
9
|
+
| number
|
10
|
+
| boolean
|
11
|
+
|
12
|
+
|
13
|
+
boolean = position (
|
14
|
+
true
|
15
|
+
| false
|
16
|
+
| null
|
17
|
+
| undefined)
|
18
|
+
|
19
|
+
true = "true" ~true_value
|
20
|
+
false = "false" ~false_value
|
21
|
+
null = "null" ~null_value
|
22
|
+
undefined = "undefined" ~undefined_value
|
23
|
+
|
24
|
+
|
25
|
+
number = position (
|
26
|
+
real
|
27
|
+
| hex
|
28
|
+
| int )
|
29
|
+
|
30
|
+
hexdigits = /[0-9A-Fa-f]/
|
31
|
+
hex = '0x' < hexdigits+ > ~hexadecimal(text)
|
32
|
+
digits = '0' | /[1-9]/ /[0-9]/*
|
33
|
+
int = < digits > ~number(text)
|
34
|
+
real = < digits '.' digits ('e' /[-+]/? /[0-9]/+)? > ~number(text)
|
35
|
+
|
36
|
+
|
37
|
+
string = position '"' < /[^\\"]*/ > '"' ~string_value(text)
|
38
|
+
|
39
|
+
# keep track of column and line
|
40
|
+
line = { current_line }
|
41
|
+
column = { current_column }
|
42
|
+
position = line:l column:c { position(l, c) }
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Poetics
|
2
|
+
module Syntax
|
3
|
+
def number(value)
|
4
|
+
Number.new line, column, value
|
5
|
+
end
|
6
|
+
|
7
|
+
def hexadecimal(value)
|
8
|
+
Number.new line, column, value.to_i(16)
|
9
|
+
end
|
10
|
+
|
11
|
+
def true_value
|
12
|
+
True.new line, column
|
13
|
+
end
|
14
|
+
|
15
|
+
def false_value
|
16
|
+
False.new line, column
|
17
|
+
end
|
18
|
+
|
19
|
+
def null_value
|
20
|
+
Null.new line, column
|
21
|
+
end
|
22
|
+
|
23
|
+
def undefined_value
|
24
|
+
Undefined.new line, column
|
25
|
+
end
|
26
|
+
|
27
|
+
def string_value(value)
|
28
|
+
String.new line, column, value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Poetics
|
2
|
+
module Syntax
|
3
|
+
class Value < Node
|
4
|
+
def to_sexp
|
5
|
+
[sexp_name, value, line, column]
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Number < Value
|
10
|
+
attr_accessor :value
|
11
|
+
|
12
|
+
def initialize(line, column, value)
|
13
|
+
super
|
14
|
+
@value = value.to_f
|
15
|
+
end
|
16
|
+
|
17
|
+
def sexp_name
|
18
|
+
:number
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class Boolean < Node
|
23
|
+
def to_sexp
|
24
|
+
[sexp_name, line, column]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class True < Boolean
|
29
|
+
def sexp_name
|
30
|
+
:true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class False < Boolean
|
35
|
+
def sexp_name
|
36
|
+
:false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Null < Boolean
|
41
|
+
def sexp_name
|
42
|
+
:null
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Undefined < Boolean
|
47
|
+
def sexp_name
|
48
|
+
:undefined
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class String < Value
|
53
|
+
attr_accessor :value
|
54
|
+
|
55
|
+
def initialize(line, column, text)
|
56
|
+
super
|
57
|
+
@value = text
|
58
|
+
end
|
59
|
+
|
60
|
+
def sexp_name
|
61
|
+
:string
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/poetics.gemspec
ADDED
@@ -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
|
data/spec/custom.rb
ADDED
@@ -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
|
@@ -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
|
@@ -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
|
@@ -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
|
data/spec/default.mspec
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
describe "The Number node" do
|
4
|
+
relates "42" do
|
5
|
+
parse { [:number, 42.0, 1, 1] }
|
6
|
+
end
|
7
|
+
|
8
|
+
relates " 42" do
|
9
|
+
parse { [:number, 42.0, 1, 2] }
|
10
|
+
end
|
11
|
+
|
12
|
+
relates "42 " do
|
13
|
+
parse { [:number, 42.0, 1, 1] }
|
14
|
+
end
|
15
|
+
|
16
|
+
relates "1.23" do
|
17
|
+
parse { [:number, 1.23, 1, 1] }
|
18
|
+
end
|
19
|
+
|
20
|
+
relates "0x2a" do
|
21
|
+
parse { [:number, 42.0, 1, 1] }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "The True node" do
|
26
|
+
relates "true" do
|
27
|
+
parse { [:true, 1, 1] }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "The False node" do
|
32
|
+
relates "false" do
|
33
|
+
parse { [:false, 1, 1] }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "The Null node" do
|
38
|
+
relates "null" do
|
39
|
+
parse { [:null, 1, 1] }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "The Undefined node" do
|
44
|
+
relates "undefined" do
|
45
|
+
parse { [:undefined, 1, 1] }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
describe "The String node" do
|
50
|
+
relates '"hello, world"' do
|
51
|
+
parse { [:string, "hello, world", 1, 1] }
|
52
|
+
end
|
53
|
+
|
54
|
+
relates <<-ruby do
|
55
|
+
"hello"
|
56
|
+
ruby
|
57
|
+
|
58
|
+
parse { [:string, "hello", 1, 7] }
|
59
|
+
end
|
60
|
+
end
|