poetics 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|