evoasm 0.0.2.pre7
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/.gemrelease +2 -0
- data/.gitignore +16 -0
- data/Gemfile +4 -0
- data/Gemfile.rake +8 -0
- data/Gemfile.rake.lock +51 -0
- data/LICENSE.txt +373 -0
- data/Makefile +6 -0
- data/README.md +43 -0
- data/Rakefile +128 -0
- data/bin/gdb +2 -0
- data/data/tables/README.md +19 -0
- data/data/tables/x64.csv +1684 -0
- data/data/templates/evoasm-x64.c.erb +319 -0
- data/data/templates/evoasm-x64.h.erb +126 -0
- data/evoasm.gemspec +30 -0
- data/examples/abs.yml +20 -0
- data/examples/popcnt.yml +17 -0
- data/examples/sym_reg.yml +26 -0
- data/exe/evoasm-search +13 -0
- data/ext/evoasm_ext/evoasm-alloc.c +145 -0
- data/ext/evoasm_ext/evoasm-alloc.h +59 -0
- data/ext/evoasm_ext/evoasm-arch.c +44 -0
- data/ext/evoasm_ext/evoasm-arch.h +161 -0
- data/ext/evoasm_ext/evoasm-bitmap.h +114 -0
- data/ext/evoasm_ext/evoasm-buf.c +130 -0
- data/ext/evoasm_ext/evoasm-buf.h +47 -0
- data/ext/evoasm_ext/evoasm-error.c +31 -0
- data/ext/evoasm_ext/evoasm-error.h +75 -0
- data/ext/evoasm_ext/evoasm-free-list.c.tmpl +121 -0
- data/ext/evoasm_ext/evoasm-free-list.h.tmpl +86 -0
- data/ext/evoasm_ext/evoasm-log.c +108 -0
- data/ext/evoasm_ext/evoasm-log.h +69 -0
- data/ext/evoasm_ext/evoasm-misc.c +23 -0
- data/ext/evoasm_ext/evoasm-misc.h +282 -0
- data/ext/evoasm_ext/evoasm-param.h +37 -0
- data/ext/evoasm_ext/evoasm-search.c +2145 -0
- data/ext/evoasm_ext/evoasm-search.h +214 -0
- data/ext/evoasm_ext/evoasm-util.h +40 -0
- data/ext/evoasm_ext/evoasm-x64.c +275624 -0
- data/ext/evoasm_ext/evoasm-x64.h +5436 -0
- data/ext/evoasm_ext/evoasm.c +7 -0
- data/ext/evoasm_ext/evoasm.h +23 -0
- data/ext/evoasm_ext/evoasm_ext.c +1757 -0
- data/ext/evoasm_ext/extconf.rb +31 -0
- data/lib/evoasm/cli/search.rb +127 -0
- data/lib/evoasm/cli.rb +6 -0
- data/lib/evoasm/core_ext/array.rb +9 -0
- data/lib/evoasm/core_ext/integer.rb +10 -0
- data/lib/evoasm/core_ext/kwstruct.rb +13 -0
- data/lib/evoasm/core_ext/range.rb +5 -0
- data/lib/evoasm/core_ext.rb +1 -0
- data/lib/evoasm/error.rb +20 -0
- data/lib/evoasm/examples.rb +27 -0
- data/lib/evoasm/gen/enum.rb +169 -0
- data/lib/evoasm/gen/name_util.rb +80 -0
- data/lib/evoasm/gen/state.rb +176 -0
- data/lib/evoasm/gen/state_dsl.rb +152 -0
- data/lib/evoasm/gen/strio.rb +27 -0
- data/lib/evoasm/gen/translator.rb +1102 -0
- data/lib/evoasm/gen/version.rb +5 -0
- data/lib/evoasm/gen/x64/funcs.rb +495 -0
- data/lib/evoasm/gen/x64/inst.rb +781 -0
- data/lib/evoasm/gen/x64.rb +237 -0
- data/lib/evoasm/gen.rb +8 -0
- data/lib/evoasm/program.rb +23 -0
- data/lib/evoasm/search.rb +40 -0
- data/lib/evoasm/tasks/gen_task.rb +86 -0
- data/lib/evoasm/tasks/template_task.rb +52 -0
- data/lib/evoasm/version.rb +3 -0
- data/lib/evoasm.rb +22 -0
- data/test/test_helper.rb +1 -0
- data/test/x64/test_helper.rb +19 -0
- data/test/x64/x64_test.rb +87 -0
- metadata +221 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
RbConfig::MAKEFILE_CONFIG['CC'] = ENV['CC'] if ENV['CC']
|
4
|
+
|
5
|
+
if have_header('capstone/capstone.h')
|
6
|
+
$LDFLAGS << ' -lcapstone'
|
7
|
+
end
|
8
|
+
|
9
|
+
$warnflags.gsub! '-Wdeclaration-after-statement', ''
|
10
|
+
|
11
|
+
$CFLAGS << ' -std=c11 -pedantic -fstrict-aliasing'
|
12
|
+
$warnflags << ' -Wextra -Wall -Wno-unused-label -Wuninitialized'\
|
13
|
+
' -Wswitch-default -Wstrict-aliasing=3 -Wunreachable-code'\
|
14
|
+
' -Wundef -Wpointer-arith -Wwrite-strings -Wconversion -Winit-self -Wno-unused-parameter'
|
15
|
+
|
16
|
+
$LDFLAGS << ''
|
17
|
+
|
18
|
+
if RbConfig::MAKEFILE_CONFIG['CC'] =~ /clang/
|
19
|
+
$warnflags << ' -Wno-unknown-warning-option -Wno-parentheses-equality -Wno-error=ignored-attributes'\
|
20
|
+
' -Wno-missing-field-initializers -Wno-missing-braces'
|
21
|
+
end
|
22
|
+
|
23
|
+
if enable_config('debug')
|
24
|
+
$warnflags << ' -Werror -Wno-error=unused-function -Wno-error=pedantic'\
|
25
|
+
' -Wno-error=implicit-function-declaration'
|
26
|
+
$defs.push('-DEVOASM_MIN_LOG_LEVEL=EVOASM_LOG_LEVEL_DEBUG')
|
27
|
+
$CFLAGS.gsub!(/-O\d/, '')
|
28
|
+
$CFLAGS << ' -O0 -g3 -fno-omit-frame-pointer'
|
29
|
+
end
|
30
|
+
|
31
|
+
create_makefile('evoasm_ext')
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pastel'
|
3
|
+
require 'pry'
|
4
|
+
|
5
|
+
module Evoasm
|
6
|
+
module Cli
|
7
|
+
class Search
|
8
|
+
attr_reader :filename
|
9
|
+
|
10
|
+
def initialize(filename, options)
|
11
|
+
@filename = filename
|
12
|
+
raise ArgumentError, 'filename is nil' if filename.nil?
|
13
|
+
|
14
|
+
if options.any?{|o| o =~ /\--log-level=(\d)/}
|
15
|
+
Evoasm.log_level = $1.to_i
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def start!
|
20
|
+
x64 = X64.new
|
21
|
+
params = YAML.load(File.read filename)
|
22
|
+
insts = filter_insts x64.instructions, params['instructions']
|
23
|
+
|
24
|
+
p insts.map(&:name)
|
25
|
+
pastel = Pastel.new
|
26
|
+
|
27
|
+
program_size = parse_range params['program_size']
|
28
|
+
kernel_size = parse_range params['kernel_size']
|
29
|
+
program_counter = 0
|
30
|
+
max_programs = params['max_programs']
|
31
|
+
recur_limit = params['recur_limit'] || 0
|
32
|
+
|
33
|
+
domains = convert_domains_hash params['domains']
|
34
|
+
parameters = (params['parameters'] || %i(reg0 reg1 reg2 imm0 imm1)).map(&:to_sym)
|
35
|
+
|
36
|
+
start_ts = Time.now
|
37
|
+
|
38
|
+
search = Evoasm::Search.new x64,
|
39
|
+
examples: params['examples'],
|
40
|
+
instructions: insts,
|
41
|
+
kernel_size: kernel_size,
|
42
|
+
program_size: program_size,
|
43
|
+
population_size: params['population_size'],
|
44
|
+
parameters: parameters,
|
45
|
+
domains: domains,
|
46
|
+
recur_limit: recur_limit
|
47
|
+
|
48
|
+
search.start!(params['max_loss'] || 0.0) do |program, loss|
|
49
|
+
ts = Time.now
|
50
|
+
puts pastel.bold "Program #{program_counter}, #{ts.strftime '%H:%M:%S'} (found after #{(ts - start_ts).to_i} seconds)"
|
51
|
+
|
52
|
+
if program.buffer.respond_to? :disassemble
|
53
|
+
puts program.buffer.disassemble.join "\n"
|
54
|
+
else
|
55
|
+
puts program.instructions.map(&:name)
|
56
|
+
end
|
57
|
+
|
58
|
+
puts
|
59
|
+
|
60
|
+
if params['console'] != false
|
61
|
+
binding.pry
|
62
|
+
end
|
63
|
+
|
64
|
+
program_counter += 1
|
65
|
+
|
66
|
+
if program_counter == max_programs
|
67
|
+
# stops search
|
68
|
+
return false
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def filter_insts(insts, params)
|
75
|
+
op_types = %i(rm reg imm)
|
76
|
+
reg_types = %i(rflags)
|
77
|
+
bad_regs = %i(SP IP)
|
78
|
+
|
79
|
+
grep_regexp = params && Regexp.new(params['grep']) rescue nil
|
80
|
+
grep_v_regexp = params && Regexp.new(params['grep_v']) rescue nil
|
81
|
+
|
82
|
+
reg_types.concat params['reg_types'].map(&:to_sym)
|
83
|
+
|
84
|
+
insts.select do |inst|
|
85
|
+
next false if grep_regexp && inst.name !~ grep_regexp
|
86
|
+
next false if grep_v_regexp && inst.name =~ grep_v_regexp
|
87
|
+
next false if inst.operands.size == 0
|
88
|
+
|
89
|
+
inst.operands.all? do |op|
|
90
|
+
next false unless op_types.include? op.type
|
91
|
+
if op.register
|
92
|
+
next false unless reg_types.include?(op.register.type)
|
93
|
+
next false if bad_regs.include? op.register.name
|
94
|
+
end
|
95
|
+
|
96
|
+
true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def convert_domains_hash(hash)
|
102
|
+
Hash(hash).map do |k, v|
|
103
|
+
new_k = k.to_sym
|
104
|
+
new_v =
|
105
|
+
case v
|
106
|
+
when Array
|
107
|
+
v.map {|e| e.is_a?(String) ? e.to_sym : e }
|
108
|
+
else
|
109
|
+
v
|
110
|
+
end
|
111
|
+
[new_k, new_v]
|
112
|
+
end.to_h
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_range(str)
|
116
|
+
case str
|
117
|
+
when Integer
|
118
|
+
str
|
119
|
+
when /^\(?(\d+)\.\.(\.?)(\d+)\)?$/
|
120
|
+
Range.new($1.to_i, $3.to_i, !$2.empty?)
|
121
|
+
else
|
122
|
+
raise ArgumentError, "invalid range '#{str}'"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
data/lib/evoasm/cli.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# The MIT License (MIT)
|
2
|
+
# Copyright (c) 2015 Maxim Chernyak
|
3
|
+
class KwStruct < Struct
|
4
|
+
def self.new(*members, &block)
|
5
|
+
super.tap do |struct_class|
|
6
|
+
struct_class.class_eval <<-RUBY
|
7
|
+
def initialize(#{members.map { |m| "#{m}: nil" }.join(', ')})
|
8
|
+
super(#{members.join(', ')})
|
9
|
+
end
|
10
|
+
RUBY
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'evoasm/core_ext/range'
|
data/lib/evoasm/error.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Evoasm
|
2
|
+
class Error
|
3
|
+
def message
|
4
|
+
msg = __message
|
5
|
+
|
6
|
+
case code
|
7
|
+
when :not_encodable
|
8
|
+
"#{msg} #{parameter}"
|
9
|
+
when :missing_param
|
10
|
+
"#{msg} (#{parameter})"
|
11
|
+
when :missing_feature
|
12
|
+
"missing features #{features.join ', '}"
|
13
|
+
when :invalid_access
|
14
|
+
"#{msg} (#{instruction}/#{register})"
|
15
|
+
else
|
16
|
+
msg || code
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Evoasm
|
2
|
+
module Examples
|
3
|
+
|
4
|
+
class << self
|
5
|
+
def convert(examples)
|
6
|
+
examples.inject(nil) do |(in_arity, out_arity), example|
|
7
|
+
inputs, outputs = example
|
8
|
+
example_in_arity = inputs.size
|
9
|
+
example_out_arity = outputs.size
|
10
|
+
|
11
|
+
validate_example inputs, example_in_arity, in_arity
|
12
|
+
validate_example outputs, example_out_arity, out_arity
|
13
|
+
|
14
|
+
[example_in_arity, example_out_arity]
|
15
|
+
end.unshift(examples.flatten)
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def validate_example(example, example_arity, arity)
|
20
|
+
if arity && arity != example_arity
|
21
|
+
raise ArgumentError, "invalid arity for example '#{example}'"\
|
22
|
+
" (#{example_arity} for #{arity})"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require 'evoasm/gen/strio'
|
2
|
+
require 'evoasm/gen/name_util'
|
3
|
+
|
4
|
+
module Evoasm
|
5
|
+
module Gen
|
6
|
+
class Enum
|
7
|
+
include NameUtil
|
8
|
+
|
9
|
+
attr_reader :name, :flags
|
10
|
+
alias_method :flags?, :flags
|
11
|
+
|
12
|
+
def initialize(name = nil, elems = [], prefix: nil, flags: false)
|
13
|
+
@name = name
|
14
|
+
@prefix = prefix
|
15
|
+
@map = {}
|
16
|
+
@counter = 0
|
17
|
+
@flags = flags
|
18
|
+
add_all elems
|
19
|
+
end
|
20
|
+
|
21
|
+
def n
|
22
|
+
@counter
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_c(io = StrIO.new, typedef: true)
|
26
|
+
raise 'name missing' if !name
|
27
|
+
|
28
|
+
type_name = c_type_name
|
29
|
+
|
30
|
+
io.puts "#{typedef ? 'typedef ' : ''}enum #{type_name} {"
|
31
|
+
io.indent do
|
32
|
+
each do |elem, value|
|
33
|
+
elem_name = elem_name_to_c elem
|
34
|
+
c_value =
|
35
|
+
if valid_elem?(value)
|
36
|
+
elem_name_to_c value
|
37
|
+
else
|
38
|
+
if flags?
|
39
|
+
"1 << #{value}"
|
40
|
+
else
|
41
|
+
"#{value}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
io.puts "#{elem_name} = #{c_value},"
|
45
|
+
end
|
46
|
+
if !flags?
|
47
|
+
io.puts n_elem_to_c
|
48
|
+
end
|
49
|
+
end
|
50
|
+
io.write '}'
|
51
|
+
io.write " #{type_name}" if typedef
|
52
|
+
io.puts ';'
|
53
|
+
io.puts "#define #{bitsize_to_c} #{bitsize}"
|
54
|
+
unless flags?
|
55
|
+
io.puts "#define #{bitsize_to_c true} #{bitsize true}"
|
56
|
+
else
|
57
|
+
io.puts "#define #{all_to_c} #{all_value}"
|
58
|
+
end
|
59
|
+
|
60
|
+
io.string
|
61
|
+
end
|
62
|
+
|
63
|
+
def bitsize(with_n = false)
|
64
|
+
if flags?
|
65
|
+
@map.size
|
66
|
+
else
|
67
|
+
Math.log2(max + 1 + (with_n ? 1 : 0)).ceil.to_i
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def max
|
72
|
+
@map.each_with_index.inject(0) do |acc, (index, (k, v))|
|
73
|
+
if v
|
74
|
+
[v + 1, acc + 1].max
|
75
|
+
else
|
76
|
+
acc + 1
|
77
|
+
end
|
78
|
+
end - 1
|
79
|
+
end
|
80
|
+
|
81
|
+
def c_type(typedef = false)
|
82
|
+
"#{typedef ? '' : 'enum '}#{c_type_name}"
|
83
|
+
end
|
84
|
+
|
85
|
+
def c_type_name
|
86
|
+
name_to_c name, @prefix
|
87
|
+
end
|
88
|
+
|
89
|
+
def all_to_c
|
90
|
+
name_to_c "#{prefix_name}_all", @prefix, const: true
|
91
|
+
end
|
92
|
+
|
93
|
+
def all_value
|
94
|
+
(2**@map.size) - 1
|
95
|
+
end
|
96
|
+
|
97
|
+
def bitsize_to_c(with_n = false)
|
98
|
+
name_to_c "#{prefix_name}_bitsize#{with_n ? '_WITH_N' : ''}", @prefix, const: true
|
99
|
+
end
|
100
|
+
|
101
|
+
def n_elem_to_c
|
102
|
+
name_to_c "n_#{prefix_name}s", @prefix, const: true
|
103
|
+
end
|
104
|
+
|
105
|
+
def keys
|
106
|
+
@map.keys
|
107
|
+
end
|
108
|
+
|
109
|
+
def add(elem, alias_elem = nil)
|
110
|
+
fail ArgumentError, 'can only add symbols or strings' \
|
111
|
+
unless valid_elem?(elem) && (!alias_elem || valid_elem?(alias_elem))
|
112
|
+
|
113
|
+
return if @map.key? elem
|
114
|
+
|
115
|
+
value = alias_elem || @counter
|
116
|
+
@counter += 1 if alias_elem.nil?
|
117
|
+
|
118
|
+
@map[elem] = value
|
119
|
+
end
|
120
|
+
|
121
|
+
def add_all(elems)
|
122
|
+
elems.each do |elem|
|
123
|
+
add elem
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def each(&block)
|
128
|
+
return to_enum(:each) if block.nil?
|
129
|
+
@map.each_key do |k|
|
130
|
+
block[k, self[k]]
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def alias(key)
|
135
|
+
key = @map[key]
|
136
|
+
case key
|
137
|
+
when Symbol, String
|
138
|
+
key
|
139
|
+
else
|
140
|
+
nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def [](elem)
|
145
|
+
value = @map[elem]
|
146
|
+
|
147
|
+
if @map.key? value
|
148
|
+
@map.fetch value
|
149
|
+
else
|
150
|
+
value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
private
|
155
|
+
def prefix_name
|
156
|
+
name.to_s.sub(/_id$/, '')
|
157
|
+
end
|
158
|
+
|
159
|
+
def elem_name_to_c(elem_name)
|
160
|
+
# convention: _id does not appear in element's name
|
161
|
+
name_to_c elem_name, Array(@prefix) + [prefix_name], const: true
|
162
|
+
end
|
163
|
+
|
164
|
+
def valid_elem?(elem)
|
165
|
+
elem.is_a?(Symbol) || elem.is_a?(String)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Evoasm
|
2
|
+
module Gen
|
3
|
+
module NameUtil
|
4
|
+
def namespace
|
5
|
+
'evoasm'
|
6
|
+
end
|
7
|
+
|
8
|
+
def const_name_to_c(name, prefix)
|
9
|
+
name_to_c name, prefix, const: true
|
10
|
+
end
|
11
|
+
|
12
|
+
def name_to_c(name, prefix = nil, const: false)
|
13
|
+
c_name = [namespace, *prefix, name.to_s.sub(/\?$/, '')].compact.join '_'
|
14
|
+
if const
|
15
|
+
c_name.upcase
|
16
|
+
else
|
17
|
+
c_name
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def indep_arch_prefix(name = nil)
|
22
|
+
['arch', name]
|
23
|
+
end
|
24
|
+
|
25
|
+
def arch_prefix(name = nil)
|
26
|
+
[arch, name]
|
27
|
+
end
|
28
|
+
|
29
|
+
def error_code_to_c(name)
|
30
|
+
prefix = name == :ok ? :error_code : indep_arch_prefix(:error_code)
|
31
|
+
const_name_to_c name, prefix
|
32
|
+
end
|
33
|
+
|
34
|
+
def reg_name_to_c(name)
|
35
|
+
const_name_to_c name, arch_prefix(:reg)
|
36
|
+
end
|
37
|
+
|
38
|
+
def exception_to_c(name)
|
39
|
+
const_name_to_c name, arch_prefix(:exception)
|
40
|
+
end
|
41
|
+
|
42
|
+
def reg_type_to_c(name)
|
43
|
+
const_name_to_c name, arch_prefix(:reg_type)
|
44
|
+
end
|
45
|
+
|
46
|
+
def operand_type_to_c(name)
|
47
|
+
const_name_to_c name, arch_prefix(:operand_type)
|
48
|
+
end
|
49
|
+
|
50
|
+
def inst_name_to_c(inst)
|
51
|
+
const_name_to_c inst.name, arch_prefix(:inst)
|
52
|
+
end
|
53
|
+
|
54
|
+
def operand_size_to_c(size)
|
55
|
+
const_name_to_c size, :operand_size
|
56
|
+
end
|
57
|
+
|
58
|
+
def bit_mask_to_c(mask)
|
59
|
+
name =
|
60
|
+
case mask
|
61
|
+
when Range then"#{mask.min}_#{mask.max}"
|
62
|
+
else mask.to_s
|
63
|
+
end
|
64
|
+
const_name_to_c name, arch_prefix(:bit_mask)
|
65
|
+
end
|
66
|
+
|
67
|
+
def feature_name_to_c(name)
|
68
|
+
const_name_to_c name, arch_prefix(:feature)
|
69
|
+
end
|
70
|
+
|
71
|
+
def inst_flag_to_c(flag)
|
72
|
+
const_name_to_c flag, arch_prefix(:inst_flag)
|
73
|
+
end
|
74
|
+
|
75
|
+
def param_name_to_c(name)
|
76
|
+
const_name_to_c name, arch_prefix(:param)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,176 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Evoasm::Gen
|
4
|
+
State = Struct.new(:children, :actions, :ret, :_local_params) do
|
5
|
+
attr_accessor :id, :comment, :parents
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
self.children = []
|
9
|
+
self.parents = []
|
10
|
+
self.actions = []
|
11
|
+
self._local_params = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def local_params
|
15
|
+
child_local_params = children.map do |child, _, _|
|
16
|
+
child.local_params
|
17
|
+
end
|
18
|
+
all_local_params = (_local_params + child_local_params)
|
19
|
+
all_local_params.flatten!
|
20
|
+
all_local_params.uniq!
|
21
|
+
|
22
|
+
all_local_params
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_local_param(param)
|
26
|
+
if param.to_s[0] != '_'
|
27
|
+
fail ArgumentError, 'params must start with underscore'
|
28
|
+
end
|
29
|
+
|
30
|
+
_local_params << param unless _local_params.include? param
|
31
|
+
end
|
32
|
+
|
33
|
+
protected def add_parent(parent)
|
34
|
+
parents << parent unless parents.include? parent
|
35
|
+
end
|
36
|
+
|
37
|
+
def add_child(child, cond = nil, priority)
|
38
|
+
child.add_parent self
|
39
|
+
children << [child, cond, priority]
|
40
|
+
end
|
41
|
+
|
42
|
+
%i(sets asserts calls writes debugs).each do |name|
|
43
|
+
action_name = name.to_s[0..-2].to_sym
|
44
|
+
define_method name do
|
45
|
+
actions.select { |action, _| action == action_name }
|
46
|
+
.map { |_, args| args }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private def roots
|
51
|
+
return [self] if parents.empty?
|
52
|
+
parents.flat_map(&:roots)
|
53
|
+
end
|
54
|
+
|
55
|
+
def root
|
56
|
+
roots = roots()
|
57
|
+
fail 'multiple roots' if roots.size > 1
|
58
|
+
roots.first
|
59
|
+
end
|
60
|
+
|
61
|
+
def empty?
|
62
|
+
actions.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
def terminal?
|
66
|
+
children.empty?
|
67
|
+
end
|
68
|
+
|
69
|
+
def ret?
|
70
|
+
ret != nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_gv
|
74
|
+
require 'gv'
|
75
|
+
|
76
|
+
graph = GV::Graph.open 'ast'
|
77
|
+
graph[:ranksep] = 1.5
|
78
|
+
graph[:statesep] = 0.8
|
79
|
+
__to_gv__ graph
|
80
|
+
graph
|
81
|
+
end
|
82
|
+
|
83
|
+
def __to_gv__(graph, gv_parent = nil, cond = nil, attrs = {}, index = nil, seen = {})
|
84
|
+
if seen.key?(self)
|
85
|
+
# return
|
86
|
+
else
|
87
|
+
seen[self] = true
|
88
|
+
end
|
89
|
+
|
90
|
+
edge_label = ''
|
91
|
+
state_label = ''
|
92
|
+
|
93
|
+
if cond
|
94
|
+
if cond.first == :else
|
95
|
+
edge_label << "<b> else</b><br></br>\n"
|
96
|
+
else
|
97
|
+
edge_label << "<b> if</b> #{expr_to_s cond}<br></br>\n"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if attrs
|
102
|
+
attrs.each do |name, value|
|
103
|
+
edge_label << "<b> #{name}</b>: #{value}<br></br>\n"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
actions.each do |name, args|
|
108
|
+
state_label << send(:"label_#{name}", *args)
|
109
|
+
end
|
110
|
+
|
111
|
+
state_label << "<i>#{comment}</i>\n" if comment
|
112
|
+
|
113
|
+
gv_state = graph.node object_id.to_s,
|
114
|
+
shape: (self.ret? ? :house : (state_label.empty? ? :point : :box)),
|
115
|
+
label: graph.html(state_label)
|
116
|
+
|
117
|
+
children.each_with_index do |(child, cond, attrs), index|
|
118
|
+
child.__to_gv__(graph, gv_state, cond, attrs, index, seen)
|
119
|
+
end
|
120
|
+
|
121
|
+
if gv_parent
|
122
|
+
graph.edge gv_parent.name + '.' + gv_state.name + index.to_s,
|
123
|
+
gv_parent, gv_state,
|
124
|
+
label: graph.html(edge_label)
|
125
|
+
end
|
126
|
+
|
127
|
+
graph
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
def label_set(name, value, _options = {})
|
133
|
+
"<b>set</b> #{name} := #{expr_to_s value}<br></br>"
|
134
|
+
end
|
135
|
+
|
136
|
+
def label_assert(cond)
|
137
|
+
"<b>assert</b> #{expr_to_s cond}<br></br>"
|
138
|
+
end
|
139
|
+
|
140
|
+
def label_call(name)
|
141
|
+
"<b>call</b> #{name}<br></br>"
|
142
|
+
end
|
143
|
+
|
144
|
+
def label_debug(_format, *_args)
|
145
|
+
''
|
146
|
+
end
|
147
|
+
|
148
|
+
def label_write(value, size)
|
149
|
+
label =
|
150
|
+
if value.is_a?(Integer) && size.is_a?(Integer)
|
151
|
+
if size == 8
|
152
|
+
'x%x' % value
|
153
|
+
else
|
154
|
+
"b%0#{size}b" % value
|
155
|
+
end
|
156
|
+
elsif size.is_a? Array
|
157
|
+
Array(value).zip(Array(size)).map do |v, s|
|
158
|
+
"#{expr_to_s v} [#{expr_to_s s}]"
|
159
|
+
end.join ', '
|
160
|
+
else
|
161
|
+
"#{expr_to_s value} [#{expr_to_s size}]"
|
162
|
+
end
|
163
|
+
"<b>output</b> #{label}<br></br>"
|
164
|
+
end
|
165
|
+
|
166
|
+
def expr_to_s(pred)
|
167
|
+
case pred
|
168
|
+
when Array
|
169
|
+
pred, *args = *pred
|
170
|
+
"#{pred}(#{args.map { |a| expr_to_s(a) }.join(', ')})"
|
171
|
+
else
|
172
|
+
pred
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|