smacro 0.1.0
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.
- checksums.yaml +7 -0
- data/README.md +41 -0
- data/exe/smacro +4 -0
- data/lib/smacro/cli.rb +24 -0
- data/lib/smacro/version.rb +3 -0
- data/lib/smacro.rb +62 -0
- metadata +63 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2f5c8cb7f5f7c4c6deec9cf8f0d023d4bcdc152e8cf7378c2b624fd7a5c473eb
|
|
4
|
+
data.tar.gz: 1120a714fab2e01ee6e682e972e6e3d86e1b62c8c461311f6805adc91af627d3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 4737d41ed76a54a289fd41a8490b2caea1fe502664b29e7d7a0ac24dbe32b960437dd3a412e92dfca477f4b4cce11c96ea2ed202e0717780c39f44fef8788157
|
|
7
|
+
data.tar.gz: d07b412b1968f1fc6b2b7a68458b1494b1bab273964551b0fd5da4878019a3d530a3143c13132604fc04daeabac7a20337882b44968a625209c9c42dc5a03858
|
data/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# smacro
|
|
2
|
+
|
|
3
|
+
`smacro` expands Ruby macros before handing the resulting source to the Spinel AOT compiler. Macro definitions run under CRuby at build time; the generated program does not need the gem at runtime.
|
|
4
|
+
|
|
5
|
+
## Install locally
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
gem build smacro.gemspec
|
|
9
|
+
gem install ./smacro-0.1.0.gem
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The gem requires Ruby 3.0 or later and Prism. Spinel is needed to compile the expanded result.
|
|
13
|
+
|
|
14
|
+
## Use
|
|
15
|
+
|
|
16
|
+
Put macro definitions in `app.smacro.rb` next to `app.rb`:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
Smacro.define(:fields) do |*names|
|
|
20
|
+
"attr_accessor " + names.map { |name| ":#{name}" }.join(", ")
|
|
21
|
+
end
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Call the macro in `app.rb`:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
class User
|
|
28
|
+
fields :name, :email
|
|
29
|
+
end
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Expand and compile:
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
smacro app.rb -o app.expanded.rb
|
|
36
|
+
spinel app.expanded.rb
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Pass a macro file as the second argument when its name differs from `app.smacro.rb`. The existing `ruby smacro.rb app.rb` command still works. For Ruby code, use `require "smacro"` to access `Smacro.define` and `Smacro.expand`.
|
|
40
|
+
|
|
41
|
+
The current prototype accepts literal macro arguments (symbols, strings, integers, booleans, nil, and arrays). It does not yet support macro definitions inside the source file, nested macro calls, or Crystal's AST template syntax. `sh run.sh` compares the sample's CRuby and Spinel results.
|
data/exe/smacro
ADDED
data/lib/smacro/cli.rb
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
require "optparse"
|
|
2
|
+
require_relative "../smacro"
|
|
3
|
+
|
|
4
|
+
module Smacro
|
|
5
|
+
module CLI
|
|
6
|
+
def self.run(argv)
|
|
7
|
+
options = {}
|
|
8
|
+
parser = OptionParser.new do |opts|
|
|
9
|
+
opts.banner = "Usage: smacro [options] app.rb [app.smacro.rb]"
|
|
10
|
+
opts.on("-o", "--output FILE", "Write expanded Ruby to FILE") { |path| options[:output] = path }
|
|
11
|
+
opts.on("-v", "--version", "Print version") { puts Smacro::VERSION; return }
|
|
12
|
+
opts.on("-h", "--help", "Show help") { puts opts; return }
|
|
13
|
+
end
|
|
14
|
+
parser.parse!(argv)
|
|
15
|
+
abort parser.to_s unless (1..2).cover?(argv.length)
|
|
16
|
+
|
|
17
|
+
app, macro_file = argv
|
|
18
|
+
macro_file ||= app.sub(/\.rb\z/, ".smacro.rb")
|
|
19
|
+
load File.expand_path(macro_file) if File.exist?(macro_file)
|
|
20
|
+
expanded = Smacro.expand(File.read(app))
|
|
21
|
+
options[:output] ? File.write(options[:output], expanded) : print(expanded)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
data/lib/smacro.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# smacro: build-time macro expander for Spinel (PoC)
|
|
3
|
+
# ruby smacro.rb app.rb [app.smacro.rb] > app.expanded.rb
|
|
4
|
+
# spinel app.expanded.rb
|
|
5
|
+
require "prism"
|
|
6
|
+
require_relative "smacro/version"
|
|
7
|
+
|
|
8
|
+
module Smacro
|
|
9
|
+
MACROS = {}
|
|
10
|
+
|
|
11
|
+
def self.define(name, &blk)
|
|
12
|
+
MACROS[name] = blk
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# AST literal -> Ruby value. Anything not known at build time is an error.
|
|
16
|
+
def self.const_value(node)
|
|
17
|
+
case node
|
|
18
|
+
when Prism::SymbolNode then node.unescaped.to_sym
|
|
19
|
+
when Prism::StringNode then node.unescaped
|
|
20
|
+
when Prism::IntegerNode then node.value
|
|
21
|
+
when Prism::TrueNode then true
|
|
22
|
+
when Prism::FalseNode then false
|
|
23
|
+
when Prism::NilNode then nil
|
|
24
|
+
when Prism::ArrayNode then node.elements.map { |e| const_value(e) }
|
|
25
|
+
else
|
|
26
|
+
raise ArgumentError,
|
|
27
|
+
"macro arg is not a compile-time literal (#{node.class}) at line #{node.location.start_line}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Collector < Prism::Visitor
|
|
32
|
+
attr_reader :hits
|
|
33
|
+
|
|
34
|
+
def initialize
|
|
35
|
+
@hits = []
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def visit_call_node(node)
|
|
39
|
+
if node.receiver.nil? && node.block.nil? && MACROS.key?(node.name)
|
|
40
|
+
@hits << node # nested macros are not handled in the PoC
|
|
41
|
+
else
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.expand(src)
|
|
48
|
+
collector = Collector.new
|
|
49
|
+
Prism.parse(src).value.accept(collector)
|
|
50
|
+
|
|
51
|
+
bytes = src.b # Prism offsets are byte offsets
|
|
52
|
+
collector.hits.sort_by { |n| -n.location.start_offset }.each do |n|
|
|
53
|
+
args = (n.arguments ? n.arguments.arguments : []).map { |a| const_value(a) }
|
|
54
|
+
code = MACROS[n.name].call(*args).to_s
|
|
55
|
+
# Collapse to one line with ";" so every line number stays identical
|
|
56
|
+
# to the original file (free source map for error messages).
|
|
57
|
+
oneline = code.lines.map(&:strip).reject(&:empty?).join("; ")
|
|
58
|
+
bytes[n.location.start_offset, n.location.length] = oneline.b
|
|
59
|
+
end
|
|
60
|
+
bytes.force_encoding(Encoding::UTF_8)
|
|
61
|
+
end
|
|
62
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: smacro
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- femto
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-09-28 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: prism
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '1.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '2.0'
|
|
32
|
+
description: Expand Ruby source macros before compiling the result with Spinel.
|
|
33
|
+
executables:
|
|
34
|
+
- smacro
|
|
35
|
+
extensions: []
|
|
36
|
+
extra_rdoc_files: []
|
|
37
|
+
files:
|
|
38
|
+
- README.md
|
|
39
|
+
- exe/smacro
|
|
40
|
+
- lib/smacro.rb
|
|
41
|
+
- lib/smacro/cli.rb
|
|
42
|
+
- lib/smacro/version.rb
|
|
43
|
+
homepage: https://github.com/femto/smacro
|
|
44
|
+
licenses: []
|
|
45
|
+
metadata: {}
|
|
46
|
+
rdoc_options: []
|
|
47
|
+
require_paths:
|
|
48
|
+
- lib
|
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '3.0'
|
|
54
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0'
|
|
59
|
+
requirements: []
|
|
60
|
+
rubygems_version: 3.6.2
|
|
61
|
+
specification_version: 4
|
|
62
|
+
summary: Build-time Ruby macros for Spinel
|
|
63
|
+
test_files: []
|