antlr-gemerator 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0b211050cf47ab23a191f9264309bf28ba7393c1a25711debd67b74d254e2026
4
+ data.tar.gz: 5f888d09bd2cc518860b394501c491646c7eca3f9315e32d663d8dafb1682be7
5
+ SHA512:
6
+ metadata.gz: 6b78306e25a3051e1b70d749b1893c7a367bc883d822ec3f3411c1b76bd0d0c6f778fdfc6d99520cc9a11749d61c222c0d83625e582ca7de85990a67bf7e349c
7
+ data.tar.gz: 9baab78ca3ca863f4188450b0eac44a41cb0778dcb922f2d96fdff7a21f58f138d20f83f16db884994afb3299bcae5ed43e7b516d73254f6f542a1e1da4e97f3
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'pry-byebug'
7
+ gem 'antlr4-native'
8
+ end
9
+
10
+ group :development, :test do
11
+ gem 'rake'
12
+ end
data/README.md ADDED
@@ -0,0 +1,143 @@
1
+ # antlr-gemerator
2
+
3
+ Generate a complete Rubygem from any ANTLR grammar.
4
+
5
+ ## What is this thing?
6
+
7
+ This gem leverages the functionality in [antlr4-native](https://github.com/camertron/antlr4-native) to generate a complete rubygem from an ANTLR grammar. The resulting gem contains a working parser and visitor, and is ready to be published to rubygems.org.
8
+
9
+ ## Background
10
+
11
+ ANTLR is a wonderful tool for generating parsers, and is capable of parsing just about anything. Parsers (and corresponding lexers) are described via a grammar file, which declaratively defines the rules of the language or format. ANTLR generates parser code in the desired target programming language (Java by default), which can subsequently be used to parse documents that use the syntax defined in the grammar.
12
+
13
+ For example, let's say you want to parse some [Lua](https://www.lua.org/about.html) code. You'd obtain ANTLR (usually by downloading a .jar file from the ANTLR website), find a grammar for Lua, then run the ANTLR tool like this:
14
+
15
+ ```bash
16
+ java -jar /path/to/antlr.jar -o /path/to/output/dir ./Lua.g4
17
+ ```
18
+
19
+ ANTLR will emit a bunch of .java files you can include in your Java project to parse Lua source code.
20
+
21
+ ### ANTLR and Ruby
22
+
23
+ ANTLR can generate parsers in a number of target programming languages, but unfortunately Ruby isn't one of them. A couple of attempts have been made over the years to add a Ruby target to ANTLR, including [this excellent one](https://github.com/MODLanguage/antlr4-ruby-runtime) from [@twalmsley](https://github.com/twalmsley). Unfortunately none of these attempts have been merged into ANTLR proper yet.
24
+
25
+ The real problem with a Ruby target however is execution speed. The runtime linked above runs \~80% slower than the equivalent Java-based Python parser I'm working on.
26
+
27
+ ### Speeding things up with native extensions
28
+
29
+ One way to speed things up is to generate a parser in a more performant language and somehow build it as a Ruby native extension or FFI-compatible library. As it happens, ANTLR can target C++... and Ruby extensions can be written in C++! From there it was just a matter of writing some C++ glue code, and voila! ANTLR parsers wrapped in a loving Ruby embrace.
30
+
31
+ ## Usage
32
+
33
+ antlr-gemerator runs from the command-line. First, install the gem by running:
34
+
35
+ ```bash
36
+ gem install antlr-gemerator
37
+ ```
38
+
39
+ If you're using rbenv, don't forget to run `rbenv rehash` to make the `antlr-gemerator` executable available in your shell.
40
+
41
+ Next, change directory to where you'd like to generate your new gem and invoke `antlr-gemerator`:
42
+
43
+ ```bash
44
+ antlr-gemerator create \
45
+ --author 'Mickey Mouse' \
46
+ --desc 'A Lua parser for Ruby' \
47
+ --email 'mickey@disney.com' \
48
+ --homepage 'https://github.com/mickeymouse/lua-parser-rb' \
49
+ --grammar path/to/Lua.g4 \
50
+ --root chunk
51
+ ```
52
+
53
+ **NOTE**: You can specify the `--grammar` option more than once, i.e. for each .g4 file. It's common for the parser and lexer in ANTLR grammars to exist in individual files.
54
+
55
+ **NOTE**: The `--root` option tells antlr-gemerator which context represents the root of the parse tree. This context functions as the starting point for visitors. Look inside your .g4 file (the parser one if there is more than one) and find the first grammar element. For the Lua grammar, this root element is called `chunk`.
56
+
57
+ You should see a bunch of console output as antlr-gemerator emits all the files necessary for a Lua parser gem. It will also add the ANTLR runtime as a git submodule and build the native extension for you.
58
+
59
+ ### Using your gem
60
+
61
+ Now that your gem has been generated and built, try it out by parsing some Lua code. Save the following snippet as tester.rb and run it with `bundle exec ruby tester.rb`:
62
+
63
+ ```ruby
64
+ require 'lua-parser'
65
+
66
+ lua_code = <<~END
67
+ -- test for even number
68
+ if n % 2 == 0 then
69
+ print "The number is even"
70
+ end
71
+
72
+ -- test for odd number
73
+ if not (n % 2 == 0) then
74
+ print "The number is odd"
75
+ end
76
+ END
77
+
78
+ class MyFuncVisitor < LuaParser::Visitor
79
+ def visit_functioncall(ctx)
80
+ puts ctx.var_or_exp.text
81
+ visit_children(ctx)
82
+ end
83
+ end
84
+
85
+ parser = LuaParser::Parser.parse(lua_code)
86
+ parser.visit(MyFuncVisitor.new)
87
+ ```
88
+
89
+ You should see the following output:
90
+
91
+ ```
92
+ $> bundle exec ruby tester.rb
93
+ print
94
+ print
95
+ ```
96
+
97
+ The `MyFuncVisitor` instance passed to `Parser#visit` prints the name of each function call, then visits the child contexts in the parsed subtree.
98
+
99
+ ## Publishing your gem
100
+
101
+ To package your gem into a .gem file, run:
102
+
103
+ ```bash
104
+ bundle exec rake build
105
+ ```
106
+
107
+ The .gem file will be built into the pkg/ directory. Publish it by running:
108
+
109
+ ```bash
110
+ gem push pkg/lua-parser-1.0.0.gem
111
+ ```
112
+
113
+ You'll need to be signed into rubygems.org before publishing. Take a look at this [handy guide](https://guides.rubygems.org/publishing/) for instructions.
114
+
115
+ ## Maintaining your gem
116
+
117
+ Each gem created by antlr-gemerator comes with several rake tasks to help make maintenance easier.
118
+
119
+ 1. `build`: builds the gem into a .gem file suitable for publishing to rubygems.org.
120
+ 2. `compile`: builds the native extension (i.e. compiles all the generated C++ code and the ANTLR runtime).
121
+ 3. `generate`: regenerates the C++ code by invoking antlr4-native. It's like running `antlr-gemerator` all over again, but _after_ your gem has been created.
122
+
123
+ For example, to compile the native extension, run:
124
+
125
+ ```bash
126
+ bundle exec rake compile
127
+ ```
128
+
129
+ ## Caveats
130
+
131
+ See the caveats listed in [antlr4-native's README](https://github.com/camertron/antlr4-native-rb#caveats).
132
+
133
+ ## System Requirements
134
+
135
+ See the system requirements listed in [antlr4-native's README](https://github.com/camertron/antlr4-native-rb#system-requirements).
136
+
137
+ ## License
138
+
139
+ Licensed under the MIT license. See LICENSE.txt for details.
140
+
141
+ ## Authors
142
+
143
+ * Cameron C. Dutro: http://github.com/camertron
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'bundler'
2
+ require 'antlr4-native'
3
+
4
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,22 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+ require 'antlr-gemerator/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'antlr-gemerator'
6
+ s.version = ::AntlrGemerator::VERSION
7
+ s.authors = ['Cameron Dutro']
8
+ s.email = ['camertron@gmail.com']
9
+ s.homepage = 'http://github.com/camertron/antlr-gemerator'
10
+
11
+ s.description = s.summary = 'Generate a complete Rubygem from any ANTLR4 grammar.'
12
+
13
+ s.platform = Gem::Platform::RUBY
14
+
15
+ s.add_dependency 'antlr4-native', '~> 1.0'
16
+ s.add_dependency 'gli', '~> 2.0'
17
+
18
+ s.executables << 'antlr-gemerator'
19
+
20
+ s.require_path = 'lib'
21
+ s.files = Dir['{lib,spec}/**/*', 'Gemfile', 'README.md', 'Rakefile', 'antlr-gemerator.gemspec']
22
+ end
@@ -0,0 +1,42 @@
1
+ #! /usr/bin/env ruby
2
+
3
+ require 'gli'
4
+ require 'antlr-gemerator'
5
+ require 'antlr-gemerator/version'
6
+
7
+ include GLI::App
8
+
9
+ program_desc 'Generate a complete Rubygem from an ANTLR grammar.'
10
+
11
+ version AntlrGemerator::VERSION
12
+
13
+ subcommand_option_handling :normal
14
+ arguments :strict
15
+
16
+ desc 'Generate a complete Rubygem from an ANTLR grammar.'
17
+ command :create do |c|
18
+ c.desc 'Grammar file'
19
+ c.flag [:g, :grammar], type: String, multiple: true, required: true
20
+
21
+ c.desc 'Root ANTLR context'
22
+ c.flag [:r, :root], type: String, required: true
23
+
24
+ c.desc "Gem author's name"
25
+ c.flag [:a, :author], type: String, required: true
26
+
27
+ c.desc "Gem author's email address"
28
+ c.flag [:e, :email], type: String, required: true
29
+
30
+ c.desc "Gem's homepage"
31
+ c.flag [:h, :homepage], type: String, required: true
32
+
33
+ c.desc "Gem's description"
34
+ c.flag [:d, :desc], type: String, required: true
35
+
36
+ c.action do |global_options, options, args|
37
+ AntlrGemerator.create(options)
38
+ end
39
+ end
40
+
41
+ exit run(ARGV)
42
+
@@ -0,0 +1,109 @@
1
+ require 'fileutils'
2
+ require 'antlr4-native'
3
+ require 'etc'
4
+
5
+ module AntlrGemerator
6
+ autoload :Template, 'antlr-gemerator/template'
7
+
8
+ class << self
9
+ def create(options)
10
+ root_dir = '.'
11
+ lib_dir = File.join(root_dir, 'lib')
12
+ ext_dir = File.join(root_dir, 'ext')
13
+
14
+ # ANTLR does weird things if the grammar file isn't in the current
15
+ # working directory
16
+ grammars = options[:grammar].map do |g|
17
+ local_g = File.join(root_dir, File.basename(g))
18
+ FileUtils.cp(g, local_g)
19
+ local_g
20
+ end
21
+
22
+ generator = Antlr4Native::Generator.new(
23
+ grammar_files: grammars,
24
+ output_dir: ext_dir,
25
+ parser_root_method: options[:root]
26
+ )
27
+
28
+ bindings = {
29
+ gem_name: generator.gem_name,
30
+ ext_name: generator.ext_name,
31
+ gem_namespace: generator.parser_ns,
32
+ gem_author_name: options[:author],
33
+ gem_author_email: options[:email],
34
+ gem_homepage: options[:homepage] || '',
35
+ gem_description: options[:desc],
36
+ grammar_files_array: grammars,
37
+ root_method: options[:root]
38
+ }
39
+
40
+ # root level files
41
+ render 'gitignore.erb', File.join(root_dir, '.gitignore'), bindings
42
+ render 'Gemfile.erb', File.join(root_dir, 'Gemfile'), bindings
43
+ render 'gemspec.erb', File.join(root_dir, "#{bindings[:gem_name]}.gemspec"), bindings
44
+ render 'Rakefile.erb', File.join(root_dir, 'Rakefile'), bindings
45
+
46
+ # lib
47
+ mkdir File.join(lib_dir, bindings[:gem_name])
48
+
49
+ render 'entrypoint.rb.erb', File.join(lib_dir, "#{bindings[:gem_name]}.rb"), bindings
50
+ render 'version.rb.erb', File.join(lib_dir, bindings[:gem_name], 'version.rb'), bindings
51
+
52
+ # ext
53
+ antlr_version = Antlr4Native::Generator::ANTLR_VERSION
54
+ antlr_upstream_dir = File.join(ext_dir, bindings[:gem_name], 'antlr4-upstream')
55
+
56
+ mkdir File.join(ext_dir, bindings[:gem_name])
57
+
58
+ extension_dir = File.join(ext_dir, bindings[:gem_name])
59
+ extconf_path = File.join(extension_dir, 'extconf.rb')
60
+ render 'extconf.rb.erb', extconf_path, bindings
61
+
62
+ run 'git init', root_dir
63
+
64
+ # git is very stupid and won't let me pass a tag to git submodule add
65
+ run "git submodule add git://github.com/antlr/antlr4 #{antlr_upstream_dir}", root_dir
66
+ Dir.chdir(antlr_upstream_dir) { run "git checkout #{antlr_version}" }
67
+
68
+ generator.generate
69
+
70
+ bx 'bundle install', root_dir
71
+
72
+ # build
73
+ bx "ruby extconf.rb", extension_dir
74
+ run "make -j #{Etc.nprocessors}", extension_dir
75
+ end
76
+
77
+ private
78
+
79
+ def render(src, dest, bindings)
80
+ puts "RENDER #{dest}"
81
+ tmpl = Template.new(File.read(File.join(template_dir, src)), bindings)
82
+ File.write(dest, tmpl.render)
83
+ end
84
+
85
+ def mkdir(dir)
86
+ "MKDIR #{dir}"
87
+ FileUtils.mkdir_p(dir)
88
+ end
89
+
90
+ def run(cmd, in_dir = '.')
91
+ puts "RUN #{cmd}"
92
+ Dir.chdir(in_dir) { system(cmd) }
93
+ end
94
+
95
+ def bx(cmd, in_dir)
96
+ runner = -> { run(cmd, in_dir) }
97
+
98
+ if Kernel.const_defined?(:Bundler)
99
+ Bundler.with_clean_env(&runner)
100
+ else
101
+ runner.call
102
+ end
103
+ end
104
+
105
+ def template_dir
106
+ @template_dir ||= File.expand_path(File.join('antlr-gemerator', 'templates'), __dir__)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,27 @@
1
+ require 'erb'
2
+
3
+ module AntlrGemerator
4
+ class Template
5
+ def initialize(tmpl, bindings)
6
+ @tmpl = tmpl
7
+ @bindings = bindings
8
+ end
9
+
10
+ def render
11
+ ERB.new(@tmpl).result(binding)
12
+ end
13
+
14
+ def method_missing(mtd, *args, &block)
15
+ if @bindings.include?(mtd)
16
+ @bindings[mtd]
17
+ else
18
+ raise NoMethodError, "no method `#{mtd}' for #{self.class}"
19
+ end
20
+ end
21
+
22
+ def respond_to?(mtd)
23
+ return true if @bindings.include?(mtd)
24
+ super
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'pry-byebug'
7
+ gem 'antlr4-native', '~> 1.0'
8
+ end
9
+
10
+ group :development, :test do
11
+ gem 'rake'
12
+ end
@@ -0,0 +1,23 @@
1
+ require 'bundler'
2
+
3
+ require 'antlr4-native'
4
+ require 'etc'
5
+
6
+ Bundler::GemHelper.install_tasks
7
+
8
+ task :generate do
9
+ generator = Antlr4Native::Generator.new(
10
+ grammar_files: <%= grammar_files_array.inspect %>,
11
+ output_dir: 'ext/',
12
+ parser_root_method: '<%= root_method %>'
13
+ )
14
+
15
+ generator.generate
16
+ end
17
+
18
+ task :compile do
19
+ Dir.chdir(File.join(%w(ext <%= gem_name %>))) do
20
+ load 'extconf.rb'
21
+ exec "make -j #{Etc.nprocessors}"
22
+ end
23
+ end
@@ -0,0 +1,2 @@
1
+ # load the native extension
2
+ require File.expand_path(File.join('..', 'ext', '<%= gem_name %>', '<%= ext_name %>'), __dir__)
@@ -0,0 +1,34 @@
1
+ require 'mkmf-rice'
2
+
3
+ extension_name = '<%= ext_name %>'
4
+ dir_config(extension_name)
5
+
6
+ have_library('stdc++')
7
+
8
+ $CFLAGS << ' -std=c++14'
9
+
10
+ include_paths = [
11
+ '.',
12
+ 'antlrgen',
13
+ 'antlr4-upstream/runtime/Cpp/runtime/src',
14
+ 'antlr4-upstream/runtime/Cpp/runtime/src/atn',
15
+ 'antlr4-upstream/runtime/Cpp/runtime/src/dfa',
16
+ 'antlr4-upstream/runtime/Cpp/runtime/src/misc',
17
+ 'antlr4-upstream/runtime/Cpp/runtime/src/support',
18
+ 'antlr4-upstream/runtime/Cpp/runtime/src/tree',
19
+ 'antlr4-upstream/runtime/Cpp/runtime/src/tree/pattern',
20
+ 'antlr4-upstream/runtime/Cpp/runtime/src/tree/xpath'
21
+ ]
22
+
23
+ $srcs = []
24
+
25
+ include_paths.each do |include_path|
26
+ $INCFLAGS << " -I#{include_path}"
27
+ $VPATH << include_path
28
+
29
+ Dir.glob("#{include_path}/*.cpp").each do |path|
30
+ $srcs << path
31
+ end
32
+ end
33
+
34
+ create_makefile(extension_name)
@@ -0,0 +1,31 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
2
+ require '<%= gem_name %>/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = '<%= gem_name %>'
6
+ s.version = ::<%= gem_namespace %>::VERSION
7
+ s.authors = ['<%= gem_author_name %>']
8
+ s.email = ['<%= gem_author_email %>']
9
+ s.homepage = '<%= gem_homepage %>'
10
+
11
+ s.description = s.summary = '<%= gem_description %>'
12
+
13
+ s.platform = Gem::Platform::RUBY
14
+
15
+ s.add_dependency 'rice', '~> 2.0'
16
+
17
+ s.extensions = File.join(*%w(ext <%= gem_name %> extconf.rb))
18
+
19
+ s.require_path = 'lib'
20
+ s.files = Dir[
21
+ '{lib,spec}/**/*',
22
+ 'ext/<%= gem_name %>/*.{cpp,h}',
23
+ 'ext/<%= gem_name %>/extconf.rb',
24
+ 'ext/<%= gem_name %>/antlrgen/*',
25
+ 'ext/<%= gem_name %>/antlr4-upstream/runtime/Cpp/runtime/src/**/*.{cpp,h}',
26
+ 'Gemfile',
27
+ 'README.md',
28
+ 'Rakefile',
29
+ '<%= gem_name %>.gemspec'
30
+ ]
31
+ end
@@ -0,0 +1,7 @@
1
+ Gemfile.lock
2
+ pkg/
3
+ *.o
4
+ *.o.tmp
5
+ ext/<%= ext_name %>/Makefile
6
+ ext/<%= ext_name %>/mkmf.log
7
+ *.bundle
@@ -0,0 +1,3 @@
1
+ module <%= gem_namespace %>
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,3 @@
1
+ module AntlrGemerator
2
+ VERSION = '1.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: antlr-gemerator
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Cameron Dutro
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: antlr4-native
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: gli
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ description: Generate a complete Rubygem from any ANTLR4 grammar.
42
+ email:
43
+ - camertron@gmail.com
44
+ executables:
45
+ - antlr-gemerator
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - Gemfile
50
+ - README.md
51
+ - Rakefile
52
+ - antlr-gemerator.gemspec
53
+ - bin/antlr-gemerator
54
+ - lib/antlr-gemerator.rb
55
+ - lib/antlr-gemerator/template.rb
56
+ - lib/antlr-gemerator/templates/Gemfile.erb
57
+ - lib/antlr-gemerator/templates/Rakefile.erb
58
+ - lib/antlr-gemerator/templates/entrypoint.rb.erb
59
+ - lib/antlr-gemerator/templates/extconf.rb.erb
60
+ - lib/antlr-gemerator/templates/gemspec.erb
61
+ - lib/antlr-gemerator/templates/gitignore.erb
62
+ - lib/antlr-gemerator/templates/version.rb.erb
63
+ - lib/antlr-gemerator/version.rb
64
+ homepage: http://github.com/camertron/antlr-gemerator
65
+ licenses: []
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubygems_version: 3.0.6
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: Generate a complete Rubygem from any ANTLR4 grammar.
86
+ test_files: []