csquare-cast 0.2.2
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.
- data/.gemtest +0 -0
- data/History.txt +130 -0
- data/Manifest.txt +22 -0
- data/README.rdoc +2200 -0
- data/Rakefile +55 -0
- data/cast.gemspec +26 -0
- data/ext/cast/cast.c +8 -0
- data/ext/cast/cast.h +144 -0
- data/ext/cast/extconf.rb +3 -0
- data/ext/cast/parser.c +287 -0
- data/ext/cast/yylex.c +6610 -0
- data/ext/cast/yylex.re +324 -0
- data/lib/cast.rb +14 -0
- data/lib/cast/c.tab.rb +3414 -0
- data/lib/cast/c.y +901 -0
- data/lib/cast/c_nodes.rb +1096 -0
- data/lib/cast/inspect.rb +57 -0
- data/lib/cast/node.rb +740 -0
- data/lib/cast/node_list.rb +841 -0
- data/lib/cast/parse.rb +255 -0
- data/lib/cast/preprocessor.rb +78 -0
- data/lib/cast/tempfile.rb +186 -0
- data/lib/cast/to_s.rb +555 -0
- data/test/test_helper.rb +192 -0
- metadata +118 -0
data/lib/cast/parse.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
######################################################################
|
2
|
+
#
|
3
|
+
# C.default_parser and the parse_* methods.
|
4
|
+
#
|
5
|
+
# Yeah, this could be so much faster.
|
6
|
+
#
|
7
|
+
######################################################################
|
8
|
+
|
9
|
+
module C
|
10
|
+
@@default_parser = Parser.new
|
11
|
+
def self.default_parser
|
12
|
+
@@default_parser
|
13
|
+
end
|
14
|
+
def self.default_parser=(val)
|
15
|
+
@@default_parser = val
|
16
|
+
end
|
17
|
+
|
18
|
+
class Node
|
19
|
+
#
|
20
|
+
# Return true if `str' is parsed to something `==' to this Node.
|
21
|
+
# str is first converted to a String using #to_s, then given to
|
22
|
+
# self.class.parse (along with the optional `parser').
|
23
|
+
#
|
24
|
+
def match?(str, parser=nil)
|
25
|
+
node = self.class.parse(str.to_s, parser) rescue (return false)
|
26
|
+
self == node
|
27
|
+
end
|
28
|
+
#
|
29
|
+
# Same as #match?.
|
30
|
+
#
|
31
|
+
def =~(*args)
|
32
|
+
match?(*args)
|
33
|
+
end
|
34
|
+
private
|
35
|
+
end
|
36
|
+
class NodeList
|
37
|
+
#
|
38
|
+
# As defined in Node.
|
39
|
+
#
|
40
|
+
def match?(arr, parser=nil)
|
41
|
+
arr = arr.to_a
|
42
|
+
return false if arr.length != self.length
|
43
|
+
each_with_index do |node, i|
|
44
|
+
node.match?(arr[i], parser) or return false
|
45
|
+
end
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.parse(s, parser=nil)
|
51
|
+
TranslationUnit.parse(s, parser)
|
52
|
+
end
|
53
|
+
|
54
|
+
class TranslationUnit
|
55
|
+
def self.parse(s, parser=nil)
|
56
|
+
parser ||= C.default_parser
|
57
|
+
parser.parse(s)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Declaration
|
62
|
+
def self.parse(s, parser=nil)
|
63
|
+
parser ||= C.default_parser
|
64
|
+
ents = parser.parse(s).entities
|
65
|
+
if ents.length == 1 && # int i; int j;
|
66
|
+
ents[0].is_a?(Declaration) # void f() {}
|
67
|
+
return ents[0].detach
|
68
|
+
else
|
69
|
+
raise ParseError, "invalid Declaration"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Parameter
|
75
|
+
def self.parse(s, parser=nil)
|
76
|
+
parser ||= C.default_parser
|
77
|
+
ents = parser.parse("void f(#{s}) {}").entities
|
78
|
+
if ents.length == 1 && # ) {} void (
|
79
|
+
ents[0].is_a?(FunctionDef) && # ); void(
|
80
|
+
ents[0].type.params && #
|
81
|
+
ents[0].type.params.length <= 1 # x,y
|
82
|
+
param = ents[0].type.params[0]
|
83
|
+
if param.nil?
|
84
|
+
return Parameter.new(Void.new)
|
85
|
+
else
|
86
|
+
return param.detach
|
87
|
+
end
|
88
|
+
else
|
89
|
+
raise ParseError, "invalid Parameter"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Declarator
|
95
|
+
def self.parse(s, parser=nil)
|
96
|
+
parser ||= C.default_parser
|
97
|
+
# if there's a ':', declare in a struct so we can populate num_bits
|
98
|
+
if s =~ /:/
|
99
|
+
ents = parser.parse("struct {int #{s};};").entities
|
100
|
+
if ents.length == 1 && # i:1;}; struct {int i
|
101
|
+
ents[0].type.members.length == 1 && # i:1; int j
|
102
|
+
ents[0].type.members[0].declarators.length == 1 # i:1,j
|
103
|
+
return ents[0].type.members[0].declarators[0].detach
|
104
|
+
end
|
105
|
+
else
|
106
|
+
ents = parser.parse("int #{s};").entities
|
107
|
+
if ents.length == 1 && # f; int f;
|
108
|
+
ents[0].declarators.length == 1 # i,j
|
109
|
+
return ents[0].declarators[0].detach
|
110
|
+
end
|
111
|
+
end
|
112
|
+
raise ParseError, "invalid Declarator"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class FunctionDef
|
117
|
+
def self.parse(s, parser=nil)
|
118
|
+
parser ||= C.default_parser
|
119
|
+
ents = parser.parse(s).entities
|
120
|
+
if ents.length == 1 && # void f(); void g();
|
121
|
+
ents[0].is_a?(FunctionDef) # int i;
|
122
|
+
return ents[0].detach
|
123
|
+
else
|
124
|
+
raise ParseError, "invalid FunctionDef"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
class Enumerator
|
130
|
+
def self.parse(s, parser=nil)
|
131
|
+
parser ||= C.default_parser
|
132
|
+
ents = parser.parse("enum {#{s}};").entities
|
133
|
+
if ents.length == 1 && # } enum {
|
134
|
+
ents[0].is_a?(Declaration) && # } f() {
|
135
|
+
ents[0].type.members.length == 1 # X, Y
|
136
|
+
return ents[0].type.members[0].detach
|
137
|
+
else
|
138
|
+
raise ParseError, "invalid Enumerator"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
class MemberInit
|
144
|
+
def self.parse(s, parser=nil)
|
145
|
+
parser ||= C.default_parser
|
146
|
+
ents = parser.parse("int f() {struct s x = {#{s}};}").entities
|
147
|
+
if ents.length == 1 && # } int f() {
|
148
|
+
ents[0].def.stmts.length == 1 && # }} f() {{
|
149
|
+
ents[0].def.stmts[0].declarators.length == 1 && # 1}, y
|
150
|
+
ents[0].def.stmts[0].declarators[0].init.member_inits.length == 1 # 1, 2
|
151
|
+
return ents[0].def.stmts[0].declarators[0].init.member_inits[0].detach
|
152
|
+
else
|
153
|
+
raise ParseError, "invalid MemberInit"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class Member
|
159
|
+
def self.parse(s, parser=nil)
|
160
|
+
parser ||= C.default_parser
|
161
|
+
ents = parser.parse("int f() {struct s x = {.#{s} = 1};}").entities
|
162
|
+
if ents.length == 1 && # a = 1};} int f() {struct s x = {a
|
163
|
+
ents[0].def.stmts.length == 1 && # a = 1}; struct s y = {.a
|
164
|
+
#ents[0].def.stmts[0].length == 1 && # a = 1}, x = {.a
|
165
|
+
ents[0].def.stmts[0].declarators.length == 1 && # a = 1}, x = {.a
|
166
|
+
ents[0].def.stmts[0].declarators[0].init.member_inits.length == 1 && # x = 1, y
|
167
|
+
ents[0].def.stmts[0].declarators[0].init.member_inits[0].member && # 1
|
168
|
+
ents[0].def.stmts[0].declarators[0].init.member_inits[0].member.length == 1 # a .b
|
169
|
+
return ents[0].def.stmts[0].declarators[0].init.member_inits[0].member[0].detach
|
170
|
+
else
|
171
|
+
raise ParseError, "invalid Member"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class Statement
|
177
|
+
def self.parse(s, parser=nil)
|
178
|
+
parser ||= C.default_parser
|
179
|
+
ents = parser.parse("void f() {#{s}}").entities
|
180
|
+
if ents.length == 1 && # } void f() {
|
181
|
+
ents[0].def.stmts.length == 1 && # ;;
|
182
|
+
ents[0].def.stmts[0].is_a?(self) # int i;
|
183
|
+
return ents[0].def.stmts[0].detach
|
184
|
+
else
|
185
|
+
raise ParseError, "invalid #{self}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
class Label
|
191
|
+
def self.parse(s, parser=nil)
|
192
|
+
parser ||= C.default_parser
|
193
|
+
ents = parser.parse("void f() {switch (0) #{s};}").entities
|
194
|
+
if ents.length == 1 && # } void f() {
|
195
|
+
ents[0].def.stmts.length == 1 && # ;
|
196
|
+
ents[0].def.stmts[0].stmt && #
|
197
|
+
ents[0].def.stmts[0].stmt.labels.length == 1 && # x
|
198
|
+
ents[0].def.stmts[0].stmt.labels[0].is_a?(self)
|
199
|
+
return ents[0].def.stmts[0].stmt.labels[0].detach
|
200
|
+
else
|
201
|
+
raise ParseError, "invalid #{self}"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
class Expression
|
207
|
+
def self.parse(s, parser=nil)
|
208
|
+
parser ||= C.default_parser
|
209
|
+
if s =~ /\A(\s*(\/\*.*?\*\/)?)*\{/
|
210
|
+
# starts with a brace -- must be a CompoundLiteral. in this
|
211
|
+
# case, use a Declarator since only those can handle typeless
|
212
|
+
# CompoundLiterals.
|
213
|
+
ents = parser.parse("int i = #{s};").entities
|
214
|
+
if ents.length == 1 && # 1; int i = 1
|
215
|
+
ents[0].declarators.length == 1 && # 1, j = 2
|
216
|
+
ents[0].declarators[0].init.is_a?(self)
|
217
|
+
return ents[0].declarators[0].init.detach
|
218
|
+
else
|
219
|
+
raise ParseError, "invalid #{self}"
|
220
|
+
end
|
221
|
+
else
|
222
|
+
ents = parser.parse("void f() {#{s};}").entities
|
223
|
+
if ents.length == 1 && # } void f() {
|
224
|
+
ents[0].def.stmts.length == 1 && # ;
|
225
|
+
ents[0].def.stmts[0].is_a?(ExpressionStatement) && # int i
|
226
|
+
ents[0].def.stmts[0].expr.is_a?(self)
|
227
|
+
return ents[0].def.stmts[0].expr.detach
|
228
|
+
else
|
229
|
+
raise ParseError, "invalid #{self}"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
class Type
|
236
|
+
def self.parse(s, parser=nil)
|
237
|
+
parser ||= C.default_parser
|
238
|
+
ents = parser.parse("void f() {(#{s})x;}").entities
|
239
|
+
if ents.length == 1 && # 1);} void f() {(int
|
240
|
+
ents[0].def.stmts.length == 1 && # 1); (int
|
241
|
+
ents[0].def.stmts[0].expr.type.is_a?(self)
|
242
|
+
return ents[0].def.stmts[0].expr.type.detach
|
243
|
+
else
|
244
|
+
raise ParseError, "invalid #{self}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
# Make sure we didn't miss any
|
250
|
+
CORE_C_NODE_CLASSES.each do |c|
|
251
|
+
c.methods.include? 'parse' or
|
252
|
+
c.methods.include? :parse or
|
253
|
+
raise "#{c}#parse not defined"
|
254
|
+
end
|
255
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'open3'
|
2
|
+
require 'rbconfig'
|
3
|
+
|
4
|
+
##############################################################################
|
5
|
+
#
|
6
|
+
# A wrapper around the C preprocessor: RbConfig::CONFIG["CPP"]
|
7
|
+
#
|
8
|
+
##############################################################################
|
9
|
+
|
10
|
+
module C
|
11
|
+
class Preprocessor
|
12
|
+
INCLUDE_REGEX = /(?:[^ ]|\\ )+\.h/i
|
13
|
+
COMPILER = RbConfig::CONFIG["CC"]
|
14
|
+
|
15
|
+
class Error < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :command
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_accessor :pwd, :include_path, :macros
|
23
|
+
|
24
|
+
def initialize
|
25
|
+
@include_path = []
|
26
|
+
@macros = {}
|
27
|
+
end
|
28
|
+
def preprocess(text, line_markers = false)
|
29
|
+
args = %w{-E}
|
30
|
+
args << "-P" unless line_markers
|
31
|
+
run args, text
|
32
|
+
=begin
|
33
|
+
if clean_gnu_artifacts
|
34
|
+
output.gsub!(/\b__asm\((?:"[^"]*"|[^)"]*)*\)/, '')
|
35
|
+
output.gsub!(/\b__attribute__\(\((?:[^()]|\([^()]+\))+\)\)/, '')
|
36
|
+
output.gsub!(/ __inline /, ' ')
|
37
|
+
end
|
38
|
+
=end
|
39
|
+
end
|
40
|
+
def get_all_includes(text)
|
41
|
+
args = %w{-E -M}
|
42
|
+
split_includes run(args, text)
|
43
|
+
end
|
44
|
+
def get_system_includes(text)
|
45
|
+
get_all_includes(text) - get_project_includes(text)
|
46
|
+
end
|
47
|
+
def get_project_includes(text)
|
48
|
+
args = %w{-E -MM}
|
49
|
+
split_includes run(args, text)
|
50
|
+
end
|
51
|
+
def preprocess_file(file_name)
|
52
|
+
preprocess(File.read(file_name))
|
53
|
+
end
|
54
|
+
|
55
|
+
private # -------------------------------------------------------
|
56
|
+
|
57
|
+
def run(args, input)
|
58
|
+
options = {:stdin_data => input}
|
59
|
+
options[:chdir] = pwd if pwd
|
60
|
+
output, error, result = Open3.capture3(COMPILER, *args, *include_args, *macro_args, '-', options)
|
61
|
+
raise Error, error unless result.success?
|
62
|
+
output
|
63
|
+
end
|
64
|
+
def include_args
|
65
|
+
include_path.map do |path|
|
66
|
+
"-I#{path}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
def macro_args
|
70
|
+
macros.map do |key, val|
|
71
|
+
"-D#{key}#{val.nil? ? '' : "=#{val}"}"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
def split_includes(text)
|
75
|
+
text.scan(INCLUDE_REGEX)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,186 @@
|
|
1
|
+
######################################################################
|
2
|
+
#
|
3
|
+
# Alternative to the standard ruby tempfile library, which lets you
|
4
|
+
# specify a filename suffix.
|
5
|
+
#
|
6
|
+
######################################################################
|
7
|
+
|
8
|
+
require 'delegate'
|
9
|
+
require 'tmpdir'
|
10
|
+
require 'thread'
|
11
|
+
|
12
|
+
# A class for managing temporary files. This library is written to be
|
13
|
+
# thread safe.
|
14
|
+
module C
|
15
|
+
class Tempfile < DelegateClass(File)
|
16
|
+
MAX_TRY = 10
|
17
|
+
@@cleanlist = []
|
18
|
+
|
19
|
+
# Creates a temporary file of mode 0600 in the temporary directory
|
20
|
+
# whose name is basename.pid.n.suffix and opens with mode "w+". A
|
21
|
+
# Tempfile object works just like a File object.
|
22
|
+
#
|
23
|
+
# If tmpdir is omitted, the temporary directory is determined by
|
24
|
+
# Dir::tmpdir provided by 'tmpdir.rb'.
|
25
|
+
# When $SAFE > 0 and the given tmpdir is tainted, it uses
|
26
|
+
# /tmp. (Note that ENV values are tainted by default)
|
27
|
+
def initialize(basename, tmpdir=Dir::tmpdir, suffix=nil)
|
28
|
+
if $SAFE > 0 and tmpdir.tainted?
|
29
|
+
tmpdir = '/tmp'
|
30
|
+
end
|
31
|
+
|
32
|
+
lock = nil
|
33
|
+
tmpname = nil
|
34
|
+
n = failure = 0
|
35
|
+
|
36
|
+
Thread.exclusive do
|
37
|
+
begin
|
38
|
+
begin
|
39
|
+
tmpname = File.join(tmpdir, make_tmpname(basename, n, suffix))
|
40
|
+
lock = tmpname + '.lock'
|
41
|
+
n += 1
|
42
|
+
end while @@cleanlist.include?(tmpname) or
|
43
|
+
File.exist?(lock) or File.exist?(tmpname)
|
44
|
+
|
45
|
+
Dir.mkdir(lock)
|
46
|
+
rescue
|
47
|
+
failure += 1
|
48
|
+
retry if failure < MAX_TRY
|
49
|
+
raise "cannot generate tempfile `%s'" % tmpname
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@data = [tmpname]
|
54
|
+
@clean_proc = Tempfile.callback(@data)
|
55
|
+
ObjectSpace.define_finalizer(self, @clean_proc)
|
56
|
+
|
57
|
+
@tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600)
|
58
|
+
@tmpname = tmpname
|
59
|
+
@@cleanlist << @tmpname
|
60
|
+
@data[1] = @tmpfile
|
61
|
+
@data[2] = @@cleanlist
|
62
|
+
|
63
|
+
super(@tmpfile)
|
64
|
+
|
65
|
+
# Now we have all the File/IO methods defined, you must not
|
66
|
+
# carelessly put bare puts(), etc. after this.
|
67
|
+
|
68
|
+
Dir.rmdir(lock)
|
69
|
+
end
|
70
|
+
|
71
|
+
def make_tmpname(basename, n, suffix)
|
72
|
+
sprintf('%s%d.%d%s', basename, $$, n, suffix)
|
73
|
+
end
|
74
|
+
private :make_tmpname
|
75
|
+
|
76
|
+
# Opens or reopens the file with mode "r+".
|
77
|
+
def open
|
78
|
+
@tmpfile.close if @tmpfile
|
79
|
+
@tmpfile = File.open(@tmpname, 'r+')
|
80
|
+
@data[1] = @tmpfile
|
81
|
+
__setobj__(@tmpfile)
|
82
|
+
end
|
83
|
+
|
84
|
+
def _close # :nodoc:
|
85
|
+
@tmpfile.close if @tmpfile
|
86
|
+
@data[1] = @tmpfile = nil
|
87
|
+
end
|
88
|
+
protected :_close
|
89
|
+
|
90
|
+
# Closes the file. If the optional flag is true, unlinks the file
|
91
|
+
# after closing.
|
92
|
+
#
|
93
|
+
# If you don't explicitly unlink the temporary file, the removal
|
94
|
+
# will be delayed until the object is finalized.
|
95
|
+
def close(unlink_now=false)
|
96
|
+
if unlink_now
|
97
|
+
close!
|
98
|
+
else
|
99
|
+
_close
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Closes and unlinks the file.
|
104
|
+
def close!
|
105
|
+
_close
|
106
|
+
@clean_proc.call
|
107
|
+
ObjectSpace.undefine_finalizer(self)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Unlinks the file. On UNIX-like systems, it is often a good idea
|
111
|
+
# to unlink a temporary file immediately after creating and opening
|
112
|
+
# it, because it leaves other programs zero chance to access the
|
113
|
+
# file.
|
114
|
+
def unlink
|
115
|
+
# keep this order for thread safeness
|
116
|
+
begin
|
117
|
+
File.unlink(@tmpname) if File.exist?(@tmpname)
|
118
|
+
@@cleanlist.delete(@tmpname)
|
119
|
+
@data = @tmpname = nil
|
120
|
+
ObjectSpace.undefine_finalizer(self)
|
121
|
+
rescue Errno::EACCES
|
122
|
+
# may not be able to unlink on Windows; just ignore
|
123
|
+
end
|
124
|
+
end
|
125
|
+
alias delete unlink
|
126
|
+
|
127
|
+
# Returns the full path name of the temporary file.
|
128
|
+
def path
|
129
|
+
@tmpname
|
130
|
+
end
|
131
|
+
|
132
|
+
# Returns the size of the temporary file. As a side effect, the IO
|
133
|
+
# buffer is flushed before determining the size.
|
134
|
+
def size
|
135
|
+
if @tmpfile
|
136
|
+
@tmpfile.flush
|
137
|
+
@tmpfile.stat.size
|
138
|
+
else
|
139
|
+
0
|
140
|
+
end
|
141
|
+
end
|
142
|
+
alias length size
|
143
|
+
|
144
|
+
class << self
|
145
|
+
def callback(data) # :nodoc:
|
146
|
+
pid = $$
|
147
|
+
lambda{
|
148
|
+
if pid == $$
|
149
|
+
path, tmpfile, cleanlist = *data
|
150
|
+
|
151
|
+
print "removing ", path, "..." if $DEBUG
|
152
|
+
|
153
|
+
tmpfile.close if tmpfile
|
154
|
+
|
155
|
+
# keep this order for thread safeness
|
156
|
+
File.unlink(path) if File.exist?(path)
|
157
|
+
cleanlist.delete(path) if cleanlist
|
158
|
+
|
159
|
+
print "done\n" if $DEBUG
|
160
|
+
end
|
161
|
+
}
|
162
|
+
end
|
163
|
+
|
164
|
+
# If no block is given, this is a synonym for new().
|
165
|
+
#
|
166
|
+
# If a block is given, it will be passed tempfile as an argument,
|
167
|
+
# and the tempfile will automatically be closed when the block
|
168
|
+
# terminates. In this case, open() returns nil.
|
169
|
+
def open(*args)
|
170
|
+
tempfile = new(*args)
|
171
|
+
|
172
|
+
if block_given?
|
173
|
+
begin
|
174
|
+
yield(tempfile)
|
175
|
+
ensure
|
176
|
+
tempfile.close
|
177
|
+
end
|
178
|
+
|
179
|
+
nil
|
180
|
+
else
|
181
|
+
tempfile
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|