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