WIP
This commit is contained in:
parent
307ea895c2
commit
f5173e8f9e
54 changed files with 1038 additions and 8 deletions
29
.rubocop.yml
Normal file
29
.rubocop.yml
Normal file
|
@ -0,0 +1,29 @@
|
|||
# The behavior of RuboCop can be controlled via the .rubocop.yml
|
||||
# configuration file. It makes it possible to enable/disable
|
||||
# certain cops (checks) and to alter their behavior if they accept
|
||||
# any parameters. The file can be placed either in your home
|
||||
# directory or in some project directory.
|
||||
#
|
||||
# RuboCop will start looking for the configuration file in the directory
|
||||
# where the inspected file is and continue its way up to the root directory.
|
||||
#
|
||||
# See https://github.com/rubocop-hq/rubocop/blob/master/manual/configuration.md
|
||||
|
||||
Layout/LineLength:
|
||||
Max: 120
|
||||
|
||||
Style/HashEachMethods:
|
||||
Enabled: true
|
||||
|
||||
Style/HashTransformKeys:
|
||||
Enabled: true
|
||||
|
||||
Style/HashTransformValues:
|
||||
Enabled: true
|
||||
|
||||
Metrics/BlockLength:
|
||||
Exclude:
|
||||
- spec/**/*_spec.rb
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
1
Gemfile
1
Gemfile
|
@ -5,6 +5,7 @@ source 'https://rubygems.org'
|
|||
# Specify your gem's dependencies in wag.gemspec
|
||||
gemspec
|
||||
|
||||
gem 'pry', '~> 0.13.0'
|
||||
gem 'rake', '~> 12.0'
|
||||
gem 'rspec', '~> 3.0'
|
||||
gem 'rubocop', '~> 0.80.1'
|
||||
|
|
|
@ -2,16 +2,23 @@ PATH
|
|||
remote: .
|
||||
specs:
|
||||
wag (0.1.0)
|
||||
dry-inflector (~> 0.2.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
ast (2.4.0)
|
||||
coderay (1.1.2)
|
||||
diff-lcs (1.3)
|
||||
dry-inflector (0.2.0)
|
||||
jaro_winkler (1.5.4)
|
||||
method_source (1.0.0)
|
||||
parallel (1.19.1)
|
||||
parser (2.7.0.5)
|
||||
ast (~> 2.4.0)
|
||||
pry (0.13.0)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
rainbow (3.0.0)
|
||||
rake (12.3.3)
|
||||
rexml (3.2.4)
|
||||
|
@ -43,6 +50,7 @@ PLATFORMS
|
|||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
pry (~> 0.13.0)
|
||||
rake (~> 12.0)
|
||||
rspec (~> 3.0)
|
||||
rubocop (~> 0.80.1)
|
||||
|
|
22
README.md
22
README.md
|
@ -1,8 +1,24 @@
|
|||
# Wag
|
||||
# WAG - The WebAssembly Code Generator
|
||||
|
||||
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/wag`. To experiment with that code, run `bin/console` for an interactive prompt.
|
||||
This Ruby gem allows you to generate WebAssembly programs programatically.
|
||||
|
||||
TODO: Delete this and the text above, and describe your gem
|
||||
## Example
|
||||
|
||||
```ruby
|
||||
WAG::Module.new.build do
|
||||
memory(1)
|
||||
export('memory', :memory)
|
||||
export('hello', :$hello)
|
||||
func(:$hello) do
|
||||
result(:i32)
|
||||
i32.const(16)
|
||||
end
|
||||
data do
|
||||
i32.const(16)
|
||||
<< "Hello World\00"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
20
lib/wag.rb
20
lib/wag.rb
|
@ -2,7 +2,25 @@
|
|||
|
||||
require 'wag/version'
|
||||
|
||||
module Wag
|
||||
module WAG
|
||||
class Error < StandardError; end
|
||||
# Your code goes here...
|
||||
|
||||
require 'wag/wat'
|
||||
|
||||
require 'wag/data'
|
||||
require 'wag/function'
|
||||
require 'wag/function_type'
|
||||
require 'wag/global'
|
||||
require 'wag/element'
|
||||
require 'wag/export'
|
||||
require 'wag/import'
|
||||
require 'wag/indices'
|
||||
require 'wag/inflector'
|
||||
require 'wag/instruction'
|
||||
require 'wag/label'
|
||||
require 'wag/memory'
|
||||
require 'wag/module'
|
||||
require 'wag/table'
|
||||
require 'wag/type'
|
||||
end
|
||||
|
|
18
lib/wag/data.rb
Normal file
18
lib/wag/data.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class Data
|
||||
include WAG::WAT
|
||||
|
||||
attr_reader :offset, :value
|
||||
|
||||
def initialize(offset, value)
|
||||
@offset = offset.to_i
|
||||
@value = value
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
[:data, [:"i32.const", offset], value]
|
||||
end
|
||||
end
|
||||
end
|
18
lib/wag/element.rb
Normal file
18
lib/wag/element.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class Element
|
||||
include WAG::WAT
|
||||
|
||||
attr_reader :table_id, :labels
|
||||
|
||||
def initialize(table_id, *labels)
|
||||
@table_id = table_id.to_i
|
||||
@labels = labels.map { |l| WAG::Label.from(l) }
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
[:elem, [:"i32.const", table_id]].concat(labels.map(&:to_sexpr))
|
||||
end
|
||||
end
|
||||
end
|
39
lib/wag/export.rb
Normal file
39
lib/wag/export.rb
Normal file
|
@ -0,0 +1,39 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class Export
|
||||
include WAG::WAT
|
||||
|
||||
attr_reader :name, :desc
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
end
|
||||
|
||||
def func(label, &block)
|
||||
@desc = Function.new(label)
|
||||
@desc.instance_exec(&block) if block
|
||||
@desc
|
||||
end
|
||||
|
||||
def global(label, type)
|
||||
@desc = Global.new(label, type)
|
||||
end
|
||||
|
||||
def memory(number, min: nil, max: nil, &block)
|
||||
@desc = Memory.new(number, min: min, max: max)
|
||||
@desc.instance_exec(&block) if block
|
||||
@desc
|
||||
end
|
||||
|
||||
def table(number, type = :funcref, min: nil, max: nil, &block)
|
||||
@desc = Table.new(number, type, min: min, max: max)
|
||||
@desc.instance_exec(&block) if block
|
||||
@desc
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
[:export, name, desc.to_sexpr]
|
||||
end
|
||||
end
|
||||
end
|
30
lib/wag/function.rb
Normal file
30
lib/wag/function.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class Function
|
||||
include WAG::WAT
|
||||
|
||||
attr_reader :label, :params, :results
|
||||
|
||||
def initialize(label)
|
||||
@label = WAG::Label.from(label)
|
||||
@params = []
|
||||
@results = []
|
||||
end
|
||||
|
||||
def param(*types)
|
||||
@params.concat(types.map { |t| WAG::Type.from(t) })
|
||||
end
|
||||
|
||||
def result(*types)
|
||||
@results.concat(types.map { |t| WAG::Type.from(t) })
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
[:func, @label.to_sexpr].tap do |func|
|
||||
func.push([:param].concat(@params.map(&:to_sexpr))) if @params
|
||||
func.push([:result].concat(@results.map(&:to_sexpr))) if @results
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
31
lib/wag/function_type.rb
Normal file
31
lib/wag/function_type.rb
Normal file
|
@ -0,0 +1,31 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class FunctionType
|
||||
include WAG::WAT
|
||||
|
||||
attr_reader :label, :params, :results
|
||||
|
||||
def initialize(label)
|
||||
@label = WAG::Label.from(label)
|
||||
@params = []
|
||||
@results = []
|
||||
end
|
||||
|
||||
def param(*types)
|
||||
@params.concat(types.map { |t| WAG::Type.from(t) })
|
||||
end
|
||||
|
||||
def result(*types)
|
||||
@results.concat(types.map { |t| WAG::Type.from(t) })
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
[:type, @label.to_sexpr,
|
||||
[:func].tap do |func|
|
||||
func.push([:param].concat(@params.map(&:to_sexpr))) if @params
|
||||
func.push([:result].concat(@results.map(&:to_sexpr))) if @results
|
||||
end]
|
||||
end
|
||||
end
|
||||
end
|
18
lib/wag/global.rb
Normal file
18
lib/wag/global.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class Global
|
||||
include WAG::WAT
|
||||
|
||||
attr_reader :label, :type
|
||||
|
||||
def initialize(label, type)
|
||||
@label = WAG::Label.from(label)
|
||||
@type = WAG::Type.from(type)
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
[:global, label.to_sexpr, type.to_sexpr]
|
||||
end
|
||||
end
|
||||
end
|
42
lib/wag/import.rb
Normal file
42
lib/wag/import.rb
Normal file
|
@ -0,0 +1,42 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class Import
|
||||
include WAG::WAT
|
||||
|
||||
attr_reader :module_name, :name, :desc
|
||||
|
||||
def initialize(module_name, name)
|
||||
@module_name = module_name
|
||||
@name = name
|
||||
end
|
||||
|
||||
def func(label, &block)
|
||||
@desc = Function.new(label)
|
||||
@desc.instance_exec(&block) if block
|
||||
@desc
|
||||
end
|
||||
|
||||
def global(label, type, &block)
|
||||
@desc = Global.new(label, type)
|
||||
@desc.instance_exec(&block) if block
|
||||
@desc
|
||||
end
|
||||
|
||||
def memory(number, min: nil, max: nil, &block)
|
||||
@desc = Memory.new(number, min: min, max: max)
|
||||
@desc.instance_exec(&block) if block
|
||||
@desc
|
||||
end
|
||||
|
||||
def table(number, type = :funcref, min: nil, max: nil, &block)
|
||||
@desc = Table.new(number, type, min: min, max: max)
|
||||
@desc.instance_exec(&block) if block
|
||||
@desc
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
[:import, module_name, name, desc.to_sexpr]
|
||||
end
|
||||
end
|
||||
end
|
14
lib/wag/indices.rb
Normal file
14
lib/wag/indices.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Indices
|
||||
require 'wag/indices/base'
|
||||
require 'wag/indices/func'
|
||||
require 'wag/indices/global'
|
||||
require 'wag/indices/label'
|
||||
require 'wag/indices/local'
|
||||
require 'wag/indices/mem'
|
||||
require 'wag/indices/table'
|
||||
require 'wag/indices/type'
|
||||
end
|
||||
end
|
16
lib/wag/indices/base.rb
Normal file
16
lib/wag/indices/base.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Indices
|
||||
class Base
|
||||
RANGE = [0..0xFFFFFFFF].freeze
|
||||
attr_reader :value
|
||||
|
||||
def initialize(value)
|
||||
raise "Index 0x#{value.to_s(16)} does not fit in an i32" unless RANGE.cover?(value)
|
||||
|
||||
@value = value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/indices/func.rb
Normal file
8
lib/wag/indices/func.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Indices
|
||||
class Func < Base
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/indices/global.rb
Normal file
8
lib/wag/indices/global.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Indices
|
||||
class Global < Base
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/indices/label.rb
Normal file
8
lib/wag/indices/label.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Indices
|
||||
class Label < Base
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/indices/local.rb
Normal file
8
lib/wag/indices/local.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Indices
|
||||
class Local < Base
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/indices/mem.rb
Normal file
8
lib/wag/indices/mem.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Indices
|
||||
class Mem < Base
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/indices/table.rb
Normal file
8
lib/wag/indices/table.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Indices
|
||||
class Table < Base
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/indices/type.rb
Normal file
8
lib/wag/indices/type.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Indices
|
||||
class Type < Base
|
||||
end
|
||||
end
|
||||
end
|
13
lib/wag/inflector.rb
Normal file
13
lib/wag/inflector.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'dry-inflector'
|
||||
|
||||
module WAG
|
||||
module Inflector
|
||||
module_function
|
||||
|
||||
def inflector
|
||||
@inflector ||= Dry::Inflector.new
|
||||
end
|
||||
end
|
||||
end
|
19
lib/wag/instruction.rb
Normal file
19
lib/wag/instruction.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
require 'wag/instructions/base'
|
||||
|
||||
require 'wag/instructions/block'
|
||||
require 'wag/instructions/br_if'
|
||||
require 'wag/instructions/br_table'
|
||||
require 'wag/instructions/br'
|
||||
require 'wag/instructions/else'
|
||||
require 'wag/instructions/end'
|
||||
require 'wag/instructions/if'
|
||||
require 'wag/instructions/loop'
|
||||
require 'wag/instructions/nop'
|
||||
require 'wag/instructions/return'
|
||||
require 'wag/instructions/unreachable'
|
||||
end
|
||||
end
|
24
lib/wag/instructions/base.rb
Normal file
24
lib/wag/instructions/base.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class Base
|
||||
include WAG::WAT
|
||||
|
||||
def self.instruction(op_code)
|
||||
klass = Class.new(WAG::Instruction::Base)
|
||||
klass.define_method(:op_code) { op_code }
|
||||
klass
|
||||
end
|
||||
|
||||
def name
|
||||
name = WAG::Inflector.inflector.demodulize(self.class.name.to_s)
|
||||
WAG::Inflector.inflector.underscore(name).to_sym
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
name.to_sym
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/instructions/block.rb
Normal file
8
lib/wag/instructions/block.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class Block < Base.instruction(0x02)
|
||||
end
|
||||
end
|
||||
end
|
17
lib/wag/instructions/br.rb
Normal file
17
lib/wag/instructions/br.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class Br < Base.instruction(0x0c)
|
||||
attr_reader :label
|
||||
|
||||
def initialize(label)
|
||||
@label = WAG::Label.from(label)
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
[name, label.to_sexpr]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
lib/wag/instructions/br_if.rb
Normal file
17
lib/wag/instructions/br_if.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class BrIf < Base.instruction(0x0d)
|
||||
attr_reader :label
|
||||
|
||||
def initialize(label)
|
||||
@label = WAG::Label.from(label)
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
[name, label.to_sexpr]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
lib/wag/instructions/br_table.rb
Normal file
17
lib/wag/instructions/br_table.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class BrTable < Base.instruction(0x0e)
|
||||
attr_reader :label
|
||||
|
||||
def initialize(*labels)
|
||||
@labels = labels.map { |label| WAG::Label.from(label) }
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
[name].concat(@labels.map(&:to_sexpr))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/instructions/else.rb
Normal file
8
lib/wag/instructions/else.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class Else < Base.instruction(0x05)
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/instructions/end.rb
Normal file
8
lib/wag/instructions/end.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class End < Base.instruction(0x0b)
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/instructions/if.rb
Normal file
8
lib/wag/instructions/if.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class If < Base.instruction(0x04)
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/instructions/loop.rb
Normal file
8
lib/wag/instructions/loop.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class Loop < Base.instruction(0x03)
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/instructions/nop.rb
Normal file
8
lib/wag/instructions/nop.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class Nop < Base.instruction(0x01)
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/instructions/return.rb
Normal file
8
lib/wag/instructions/return.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class Return < Base.instruction(0x0f)
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/instructions/unreachable.rb
Normal file
8
lib/wag/instructions/unreachable.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Instruction
|
||||
class Unreachable < Base.instruction(0x00)
|
||||
end
|
||||
end
|
||||
end
|
24
lib/wag/label.rb
Normal file
24
lib/wag/label.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class Label
|
||||
include WAG::WAT
|
||||
|
||||
attr_reader :value
|
||||
|
||||
def initialize(value)
|
||||
@value = value.to_s
|
||||
@value = @value[1..-1] if @value.start_with?('$')
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
"$#{value}".to_sym
|
||||
end
|
||||
|
||||
def self.from(value)
|
||||
return value if value.is_a?(WAG::Label)
|
||||
|
||||
WAG::Label.new(value)
|
||||
end
|
||||
end
|
||||
end
|
24
lib/wag/memory.rb
Normal file
24
lib/wag/memory.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class Memory
|
||||
include WAG::WAT
|
||||
|
||||
attr_reader :number, :min, :max
|
||||
|
||||
def initialize(number, min: nil, max: nil)
|
||||
@number = number
|
||||
@min = min
|
||||
@max = max
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
wat = [:memory, number]
|
||||
|
||||
wat.concat([:min, min]) if min
|
||||
wat.concat([:max, max]) if max
|
||||
|
||||
wat
|
||||
end
|
||||
end
|
||||
end
|
87
lib/wag/module.rb
Normal file
87
lib/wag/module.rb
Normal file
|
@ -0,0 +1,87 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class Module
|
||||
include WAG::WAT
|
||||
|
||||
def initialize
|
||||
@data = []
|
||||
@elements = []
|
||||
@exports = []
|
||||
@functions = []
|
||||
@globals = []
|
||||
@imports = []
|
||||
@memories = []
|
||||
@types = []
|
||||
end
|
||||
|
||||
def import(module_name, name, &block)
|
||||
import = Import.new(module_name, name)
|
||||
@imports << import
|
||||
import.instance_exec(&block) if block
|
||||
import
|
||||
end
|
||||
|
||||
def export(name, &block)
|
||||
export = Export.new(name)
|
||||
@exports << export
|
||||
export.instance_exec(&block) if block
|
||||
export
|
||||
end
|
||||
|
||||
def memory(number, min: nil, max: nil, &block)
|
||||
mem = Memory.new(number, min: min, max: max)
|
||||
@memories << mem
|
||||
mem.instance_exec(&block) if block
|
||||
mem
|
||||
end
|
||||
|
||||
def global(label, type, &block)
|
||||
global = Global.new(label, type)
|
||||
@globals << global
|
||||
global.instance_exec(&block) if block
|
||||
global
|
||||
end
|
||||
|
||||
def type(label, &block)
|
||||
type = FunctionType.new(label)
|
||||
@types << type
|
||||
type.instance_exec(&block) if block
|
||||
type
|
||||
end
|
||||
|
||||
def elem(table_id, *labels, &block)
|
||||
elem = Element.new(table_id, *labels)
|
||||
@elements << elem
|
||||
elem.instance_exec(&block) if block
|
||||
elem
|
||||
end
|
||||
|
||||
def data(offset, value, &block)
|
||||
data = Data.new(offset, value)
|
||||
@data << data
|
||||
data.instance_exec(&block) if block
|
||||
data
|
||||
end
|
||||
|
||||
def func(label, &block)
|
||||
func = Function.new(label)
|
||||
@functions << func
|
||||
func.instance_exec(&block) if block
|
||||
func
|
||||
end
|
||||
|
||||
def to_sexpr # rubocop:disable Metrics/AbcSize
|
||||
mod = [:module]
|
||||
mod.concat(@imports.map(&:to_sexpr))
|
||||
mod.concat(@exports.map(&:to_sexpr))
|
||||
mod.concat(@memories.map(&:to_sexpr))
|
||||
mod.concat(@globals.map(&:to_sexpr))
|
||||
mod.concat(@types.map(&:to_sexpr))
|
||||
mod.concat(@elements.map(&:to_sexpr))
|
||||
mod.concat(@data.map(&:to_sexpr))
|
||||
mod.concat(@functions.map(&:to_sexpr))
|
||||
mod
|
||||
end
|
||||
end
|
||||
end
|
29
lib/wag/table.rb
Normal file
29
lib/wag/table.rb
Normal file
|
@ -0,0 +1,29 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
class Table
|
||||
include WAG::WAT
|
||||
|
||||
RANGE = (0..0xFFFFFFFF).freeze
|
||||
|
||||
attr_reader :number, :type, :min, :max
|
||||
|
||||
def initialize(number, type = :funcref, min: nil, max: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
||||
raise '`min` must fit in u32 range' if min && !RANGE.cover?(min)
|
||||
raise '`max` must fit in u32 range' if max && !RANGE.cover?(max)
|
||||
raise '`max` must be greater than `min`' if min && max && min > max
|
||||
|
||||
@number = number
|
||||
@type = type
|
||||
@min = min
|
||||
@max = max
|
||||
end
|
||||
|
||||
def to_sexpr
|
||||
expr = [:table, number, type]
|
||||
expr.concat([:min, min]) if min
|
||||
expr.concat([:max, max]) if max
|
||||
expr
|
||||
end
|
||||
end
|
||||
end
|
19
lib/wag/type.rb
Normal file
19
lib/wag/type.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Type
|
||||
require 'wag/types/base'
|
||||
require 'wag/types/i32'
|
||||
require 'wag/types/i64'
|
||||
require 'wag/types/f32'
|
||||
require 'wag/types/f64'
|
||||
|
||||
module_function
|
||||
|
||||
def from(type)
|
||||
return type if type.is_a?(WAG::Type::Base)
|
||||
|
||||
const_get(WAG::Inflector.inflector.camelize(type.to_s)).new
|
||||
end
|
||||
end
|
||||
end
|
16
lib/wag/types/base.rb
Normal file
16
lib/wag/types/base.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Type
|
||||
class Base
|
||||
def to_sexpr
|
||||
name = WAG::Inflector.inflector.demodulize(self.class.name.to_s)
|
||||
WAG::Inflector.inflector.underscore(name).to_sym
|
||||
end
|
||||
|
||||
def value?
|
||||
true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/types/f32.rb
Normal file
8
lib/wag/types/f32.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Type
|
||||
class F32 < Base
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/types/f64.rb
Normal file
8
lib/wag/types/f64.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Type
|
||||
class F64 < Base
|
||||
end
|
||||
end
|
||||
end
|
28
lib/wag/types/function.rb
Normal file
28
lib/wag/types/function.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Type
|
||||
# A WebAssembly function type.
|
||||
class Function < Base
|
||||
attr_reader :parameter_types, :return_types
|
||||
|
||||
def initialize(parameter_types, return_types)
|
||||
parameter_types.map! { |t| Huia::Type.from(t) }
|
||||
return_types.map! { |t| Huia::Type.from(t) }
|
||||
|
||||
raise 'There must only be one return type (at the moment)' unless return_types.one?
|
||||
|
||||
unless parameter_types.all?(&:value?) && return_types.all?(&:value?)
|
||||
raise 'Function type parameters can only be value types'
|
||||
end
|
||||
|
||||
@parameter_types = parameter_types
|
||||
@return_types = return_types
|
||||
end
|
||||
|
||||
def value?
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/types/i32.rb
Normal file
8
lib/wag/types/i32.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Type
|
||||
class I32 < Base
|
||||
end
|
||||
end
|
||||
end
|
8
lib/wag/types/i64.rb
Normal file
8
lib/wag/types/i64.rb
Normal file
|
@ -0,0 +1,8 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
module Type
|
||||
class I64 < Base
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Wag
|
||||
module WAG
|
||||
VERSION = '0.1.0'
|
||||
end
|
||||
|
|
19
lib/wag/wat.rb
Normal file
19
lib/wag/wat.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WAG
|
||||
# A simple mixin which formats s-expressions as WAT.
|
||||
module WAT
|
||||
def to_wat
|
||||
wat_encode(to_sexpr)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def wat_encode(data)
|
||||
return "(#{data.map { |e| wat_encode(e) }.join(' ')})" if data.is_a?(Enumerable)
|
||||
return data.inspect if data.is_a?(String)
|
||||
|
||||
data.to_s
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require 'bundler/setup'
|
||||
require 'wag'
|
||||
require 'pry'
|
||||
|
||||
RSpec.configure do |config|
|
||||
# Enable flags like --only-failures and --next-failure
|
||||
|
|
55
spec/wag/export_spec.rb
Normal file
55
spec/wag/export_spec.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WAG::Export do
|
||||
let(:export) { described_class.new('marty') }
|
||||
subject { export }
|
||||
|
||||
describe 'When exporting memory' do
|
||||
before do
|
||||
export.memory(1, min: 1, max: 64)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:export, 'marty', [:memory, 1, :min, 1, :max, 64]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When exporting a table' do
|
||||
before do
|
||||
export.table(1, :funcref, min: 1, max: 64)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:export, 'marty', [:table, 1, :funcref, :min, 1, :max, 64]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When exporting a function' do
|
||||
before do
|
||||
export.func(:foo) do
|
||||
param(:f32, :f64)
|
||||
result(:i32)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:export, 'marty', [:func, :$foo, %i[param f32 f64], %i[result i32]]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When exporting a global' do
|
||||
before do
|
||||
export.global(:foo, :i32)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:export, 'marty', %i[global $foo i32]] }
|
||||
end
|
||||
end
|
||||
end
|
55
spec/wag/import_spec.rb
Normal file
55
spec/wag/import_spec.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WAG::Import do
|
||||
let(:import) { described_class.new('marty', 'mcfly') }
|
||||
subject { import }
|
||||
|
||||
describe 'When importing memory' do
|
||||
before do
|
||||
import.memory(1, min: 1, max: 64)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:import, 'marty', 'mcfly', [:memory, 1, :min, 1, :max, 64]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When importing a table' do
|
||||
before do
|
||||
import.table(1, :funcref, min: 1, max: 64)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:import, 'marty', 'mcfly', [:table, 1, :funcref, :min, 1, :max, 64]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When importing a function' do
|
||||
before do
|
||||
import.func(:foo) do
|
||||
param(:f32, :f64)
|
||||
result(:i32)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:import, 'marty', 'mcfly', [:func, :$foo, %i[param f32 f64], %i[result i32]]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When importing a global' do
|
||||
before do
|
||||
import.global(:foo, :i32)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:import, 'marty', 'mcfly', %i[global $foo i32]] }
|
||||
end
|
||||
end
|
||||
end
|
114
spec/wag/module_spec.rb
Normal file
114
spec/wag/module_spec.rb
Normal file
|
@ -0,0 +1,114 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe WAG::Module do
|
||||
let(:mod) { described_class.new }
|
||||
subject { mod }
|
||||
|
||||
describe 'When the module is empty' do
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:module] }
|
||||
end
|
||||
|
||||
describe '#to_wat' do
|
||||
subject { super().to_wat }
|
||||
it { is_expected.to eq '(module)' }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When the module imports a memory' do
|
||||
before do
|
||||
mod.import('js', 'memory').memory(1)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:module, [:import, 'js', 'memory', [:memory, 1]]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When the module exports a memory' do
|
||||
before do
|
||||
mod.export('memory').memory(1)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:module, [:export, 'memory', [:memory, 1]]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When the module declares a memory' do
|
||||
before do
|
||||
mod.memory(2)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:module, [:memory, 2]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When the module declares a global' do
|
||||
before do
|
||||
mod.global(:foo, :i32)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:module, %i[global $foo i32]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When the module declares a type' do
|
||||
before do
|
||||
mod.type(:foo) do
|
||||
param(:i32)
|
||||
result(:i32)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:module, [:type, :$foo, [:func, %i[param i32], %i[result i32]]]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When the module declares a table element' do
|
||||
before do
|
||||
mod.elem(1, :foo1, :foo2)
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:module, [:elem, [:"i32.const", 1], :$foo1, :$foo2]] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When the module declares data' do
|
||||
before do
|
||||
mod.data(16, 'Hello World')
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:module, [:data, [:"i32.const", 16], 'Hello World']] }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'When the module declares a function' do
|
||||
before do
|
||||
mod.func(:add) do
|
||||
param(:i32, :i32)
|
||||
result(:i32)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#to_sexpr' do
|
||||
subject { super().to_sexpr }
|
||||
it { is_expected.to eq [:module, [:func, :$add, %i[param i32 i32], %i[result i32]]] }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe Wag do
|
||||
RSpec.describe WAG do
|
||||
it 'has a version number' do
|
||||
expect(Wag::VERSION).not_to be nil
|
||||
expect(WAG::VERSION).not_to be nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ require_relative 'lib/wag/version'
|
|||
|
||||
Gem::Specification.new do |spec|
|
||||
spec.name = 'wag'
|
||||
spec.version = Wag::VERSION
|
||||
spec.version = WAG::VERSION
|
||||
spec.authors = ['James Harton']
|
||||
spec.email = ['james@automat.nz']
|
||||
|
||||
|
@ -30,4 +30,6 @@ Gem::Specification.new do |spec|
|
|||
spec.bindir = 'exe'
|
||||
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
||||
spec.require_paths = ['lib']
|
||||
|
||||
spec.add_runtime_dependency('dry-inflector', '~> 0.2.0')
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue