dbc 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/dbcparse.rb +281 -0
- data/lib/dbc/ctokenizer.rb +379 -0
- data/lib/dbc/ctype.rb +2397 -0
- data/lib/dbc/dbc.rb +237 -0
- data/lib/dbc/define.rb +302 -0
- data/lib/dbc/expand_function.rb +132 -0
- data/lib/dbc/ocl.rb +2166 -0
- data/lib/dbc/parameters.rb +400 -0
- data/lib/dbc/parseerrorhandler.rb +19 -0
- data/lib/dbc/preprocessor.rb +1790 -0
- data/lib/dbc/searchpath.rb +40 -0
- metadata +49 -0
data/bin/dbcparse.rb
ADDED
@@ -0,0 +1,281 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Copyright (c) 2004 Charles M Mills
|
4
|
+
# This document is licenced under The MIT Licence.
|
5
|
+
# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
6
|
+
# See included LICENCE file.
|
7
|
+
|
8
|
+
if RUBY_VERSION =~ /\A1\.6/
|
9
|
+
warn("attempting to run with Ruby 1.6.* (should be using Ruby 1.8+)")
|
10
|
+
$stdin = STDIN
|
11
|
+
$stdout = STDOUT
|
12
|
+
$stderr = STDERR
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'getoptlong'
|
16
|
+
|
17
|
+
command_dir = File.dirname($0)
|
18
|
+
command = File.basename($0)
|
19
|
+
#if $0 != command
|
20
|
+
# $:.unshift(command_dir)
|
21
|
+
#end
|
22
|
+
require 'dbc/searchpath'
|
23
|
+
require 'dbc/preprocessor'
|
24
|
+
require 'dbc/expand_function'
|
25
|
+
require 'dbc/ctype'
|
26
|
+
require 'dbc/dbc'
|
27
|
+
|
28
|
+
class GetoptLong
|
29
|
+
def error(msg)
|
30
|
+
raise GetoptLong::Error, msg
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
opts = GetoptLong.new(
|
35
|
+
[ "--docs", "-d", GetoptLong::NO_ARGUMENT ],
|
36
|
+
[ "--help", "-h", GetoptLong::NO_ARGUMENT ],
|
37
|
+
[ "--no_line", "-n", GetoptLong::NO_ARGUMENT ],
|
38
|
+
[ "--preprocess-only","-p", GetoptLong::NO_ARGUMENT ],
|
39
|
+
[ "--quiet", "-q", GetoptLong::NO_ARGUMENT ],
|
40
|
+
# [ "--trace", "-t", GetoptLong::NO_ARGUMENT ],
|
41
|
+
[ "--check_level", "-c", GetoptLong::REQUIRED_ARGUMENT ],
|
42
|
+
[ "--output", "-o", GetoptLong::REQUIRED_ARGUMENT ],
|
43
|
+
# cc compatibility options
|
44
|
+
[ "--define", "-D", GetoptLong::REQUIRED_ARGUMENT ],
|
45
|
+
[ "--include", "-I", GetoptLong::REQUIRED_ARGUMENT ]
|
46
|
+
)
|
47
|
+
|
48
|
+
# initialize with defaults
|
49
|
+
line_info = true
|
50
|
+
quiet = false
|
51
|
+
dest_file = nil
|
52
|
+
src_file = nil
|
53
|
+
# could make overwrite an option in the future
|
54
|
+
overwrite = true
|
55
|
+
docs = nil
|
56
|
+
preprocess_only = nil
|
57
|
+
check_level = nil
|
58
|
+
search_path = SearchPath.new
|
59
|
+
defines = []
|
60
|
+
|
61
|
+
begin
|
62
|
+
i = 1
|
63
|
+
opts.each do |opt, arg|
|
64
|
+
case opt
|
65
|
+
when "--check_level"
|
66
|
+
check_level = arg.to_i
|
67
|
+
unless DBC.valid_check_level?(check_level)
|
68
|
+
opts.error("invalid check level: #{arg}")
|
69
|
+
end
|
70
|
+
when "--include"
|
71
|
+
# should support including file in the future
|
72
|
+
begin
|
73
|
+
search_path << arg
|
74
|
+
rescue
|
75
|
+
opts.error($!)
|
76
|
+
end
|
77
|
+
when "--define"
|
78
|
+
unless arg =~ /\A([A-Za-z_]\w*)(?:\(([\w,\s]*)\))?(?:=(.*))?\Z/
|
79
|
+
opts.error("macro names must be identifiers: #{arg}")
|
80
|
+
end
|
81
|
+
macro = $1
|
82
|
+
opts.error("cannot use 'defined' as a macro name") if macro == 'defined'
|
83
|
+
value = $3 || '1'
|
84
|
+
params = $2.split(/\s*,\s*/) if $2
|
85
|
+
defines << [macro,params,value]
|
86
|
+
when "--output"
|
87
|
+
if dest_file
|
88
|
+
opts.error("multiple output files give")
|
89
|
+
end
|
90
|
+
dest_file = arg
|
91
|
+
when "--docs" then docs = true
|
92
|
+
when "--preprocess-only" then preprocess_only = true
|
93
|
+
when "--quiet" then quiet = true
|
94
|
+
when "--no_line" then line_info = false
|
95
|
+
when "--help"
|
96
|
+
puts "Converts OCL design by contract tags to C code."
|
97
|
+
puts "Usage:"
|
98
|
+
puts "\t#{command} [options] [commands] input_file"
|
99
|
+
puts "Options:"
|
100
|
+
puts "\t-d, --docs : generate Doxygen documentation"
|
101
|
+
puts "\t-p, --preprocess-only : preprocess input files only"
|
102
|
+
puts "\t-h, --help : print this message"
|
103
|
+
puts "\t-n, --no_line : do not output '\#line' directives"
|
104
|
+
puts "\t-q, --quiet : supress error messages"
|
105
|
+
puts "Commands:"
|
106
|
+
puts "\t-o, --output <file>"
|
107
|
+
puts "\t-c, --check_level <0,1,2>"
|
108
|
+
puts "\t-D, --define <macro(=value)>"
|
109
|
+
puts "\t-I, --include <directory>"
|
110
|
+
puts ""
|
111
|
+
puts "Check levels: 0 = none, 1 = preconditions only, 2 = all conditions."
|
112
|
+
puts "Reads from STDIN if input file is '-' or none is given."
|
113
|
+
puts "Outputs to STDOUT if no output file is given."
|
114
|
+
exit(0)
|
115
|
+
else raise GetoptLong::InvalidOption, "unrecognized option '#{opt}'"
|
116
|
+
end # case
|
117
|
+
i += 1
|
118
|
+
end # each
|
119
|
+
rescue GetoptLong::InvalidOption, GetoptLong::Error
|
120
|
+
msg = $!.message
|
121
|
+
warn "<command line>:#{i}: #{msg} (-h will show vaild options)"
|
122
|
+
exit(2)
|
123
|
+
end
|
124
|
+
|
125
|
+
if [check_level, preprocess_only, docs].nitems > 1
|
126
|
+
warn %q{conflicting options: only one of the following may be selected:
|
127
|
+
--check_level
|
128
|
+
--preprocess-only
|
129
|
+
--docs}
|
130
|
+
exit(2)
|
131
|
+
end
|
132
|
+
|
133
|
+
# default condition is all
|
134
|
+
check_level = DBC::ALL unless check_level
|
135
|
+
|
136
|
+
if ARGV.length > 1
|
137
|
+
warn "expecting one input file, got #{ARGV.length}"
|
138
|
+
exit(2)
|
139
|
+
end
|
140
|
+
|
141
|
+
unless ARGV.empty? || ARGV[0] == '-'
|
142
|
+
src_file = ARGV[0]
|
143
|
+
unless File.file?(src_file)
|
144
|
+
warn "file does not exist: #{src_file}"
|
145
|
+
exit(2)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
if dest_file
|
150
|
+
if File.exists?(dest_file) and not overwrite
|
151
|
+
warn "destination file exists: #{dest_file}"
|
152
|
+
exit(2)
|
153
|
+
end
|
154
|
+
# this is always bad - so don't allow it even if overwrite is true
|
155
|
+
if src_file and File.expand_path(dest_file) == File.expand_path(src_file)
|
156
|
+
warn "destination file and source file are the same"
|
157
|
+
exit(2)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
if src_file
|
162
|
+
text = File.open(src_file, 'r') do |in_file|
|
163
|
+
in_file.read
|
164
|
+
end
|
165
|
+
else
|
166
|
+
text = STDIN.read
|
167
|
+
end
|
168
|
+
|
169
|
+
### Compatibility with various compilers ###
|
170
|
+
search_path.unshift('/usr/include') if File.exists?('/usr/include')
|
171
|
+
search_path.unshift(File.dirname(src_file)) if src_file
|
172
|
+
# search for /usr/lib/gcc*/<system>/<version>/include
|
173
|
+
Dir['/usr/lib/gcc*/*/*/include'].each do |gcc_inc|
|
174
|
+
search_path << gcc_inc if File.directory?(gcc_inc)
|
175
|
+
end
|
176
|
+
case RUBY_PLATFORM
|
177
|
+
when /i[0-9]86|cygwin|mingw/
|
178
|
+
defines << ['__i386__', nil, nil]
|
179
|
+
# I have found that these setting work best with GNU C libs
|
180
|
+
# experiance may vary :)
|
181
|
+
defines << ['__GLIBC_HAVE_LONG_LONG', nil, '1']
|
182
|
+
defines << ['__extension__', nil, ' ']
|
183
|
+
when /powerpc|darwin/
|
184
|
+
defines << ['__ppc__', nil, nil]
|
185
|
+
end
|
186
|
+
# for gcc 3.4+ compatibility
|
187
|
+
defines << ['__builtin_va_list', nil, 'int *']
|
188
|
+
defines << ['SHLIB_COMPAT', ['arg'], '(0)']
|
189
|
+
#############################################
|
190
|
+
|
191
|
+
# output file text
|
192
|
+
outstr = ''
|
193
|
+
# included files
|
194
|
+
includes = {}
|
195
|
+
|
196
|
+
# Cache Tokens => Preprocessor => Parse OCL => Parse C Types
|
197
|
+
# Cached tokens are output.
|
198
|
+
if not docs and check_level == DBC::NONE
|
199
|
+
outstr = text
|
200
|
+
else
|
201
|
+
begin
|
202
|
+
if docs
|
203
|
+
outstr = DBC.parse_docs(CTokenizer::Lexer.new(text, src_file))
|
204
|
+
elsif preprocess_only
|
205
|
+
preproc = Preprocessor::Parser.new(text, src_file) do |f|
|
206
|
+
if inc_text = includes[f]
|
207
|
+
inc_text
|
208
|
+
else
|
209
|
+
begin
|
210
|
+
File.open(search_path.find(f)) { |in_f| includes[f] = in_f.read }
|
211
|
+
rescue ArgumentError
|
212
|
+
preproc.error($!)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
defines.each { |d,p,v| preproc.define(d, p, v) }
|
217
|
+
preproc.each do |t|
|
218
|
+
outstr << t[1]
|
219
|
+
end
|
220
|
+
else
|
221
|
+
# cache statements
|
222
|
+
cache = DBC::Cache.new(text, src_file)
|
223
|
+
# preprocesses all tokens
|
224
|
+
preproc = Preprocessor::Parser.new(cache) do |f|
|
225
|
+
# open included files
|
226
|
+
if inc_text = includes[f]
|
227
|
+
inc_text
|
228
|
+
else
|
229
|
+
begin
|
230
|
+
File.open(search_path.find(f)) { |in_f| includes[f] = in_f.read }
|
231
|
+
rescue ArgumentError
|
232
|
+
preproc.error($!)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
# define tokens passed
|
237
|
+
defines.each { |d,p,v| preproc.define(d, p, v) }
|
238
|
+
|
239
|
+
# extracts DBC condtions
|
240
|
+
source = DBC::OCLParser.new(preproc)
|
241
|
+
|
242
|
+
# extract all C Statements
|
243
|
+
DBC.parse(source) do |context, f_body|
|
244
|
+
stmt = cache.reset
|
245
|
+
line = stmt.line
|
246
|
+
stmt = stmt.cache
|
247
|
+
# remove end of file token if it exists
|
248
|
+
stmt.pop unless stmt.empty? or stmt.last[0]
|
249
|
+
|
250
|
+
# define C types
|
251
|
+
context.each do |ctxt|
|
252
|
+
CType[ctxt.identifier] = ctxt if ctxt.typedef?
|
253
|
+
end
|
254
|
+
|
255
|
+
if f_body and preproc.base?
|
256
|
+
unless context.first
|
257
|
+
raise CTokenizer.error(nil, line, "unmatched braket")
|
258
|
+
end
|
259
|
+
outstr << expand_function(source.conditions, context.first, \
|
260
|
+
stmt, line, line_info)
|
261
|
+
else
|
262
|
+
stmt.each do |t|
|
263
|
+
outstr << t[1]
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
rescue CTokenizer::Error, CType::EvaluationError
|
269
|
+
warn $!
|
270
|
+
exit(-1)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
if dest_file
|
275
|
+
File.open(dest_file, 'w') do |out_file|
|
276
|
+
out_file.write(outstr)
|
277
|
+
end
|
278
|
+
else
|
279
|
+
STDOUT.write(outstr)
|
280
|
+
end
|
281
|
+
|
@@ -0,0 +1,379 @@
|
|
1
|
+
# Copyright (c) 2004 Charles M Mills
|
2
|
+
# This document is licenced under The MIT Licence.
|
3
|
+
# THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
|
4
|
+
# See included LICENCE file.
|
5
|
+
|
6
|
+
module CTokenizer
|
7
|
+
class Error < StandardError
|
8
|
+
def initialize(file, line)
|
9
|
+
@file = file
|
10
|
+
@line = line
|
11
|
+
end
|
12
|
+
def to_s
|
13
|
+
"#{@file + ':' if @file}#{@line}: #{super}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def CTokenizer.error(file, line, msg)
|
18
|
+
raise CTokenizer::Error.new(file, line), msg
|
19
|
+
end
|
20
|
+
|
21
|
+
def CTokenizer.check_string(str)
|
22
|
+
raise "expecting a String: #{str.class}" unless str.class <= String
|
23
|
+
end
|
24
|
+
def CTokenizer.check_token(t)
|
25
|
+
raise "expecting a Array[2]: #{t.inspect}" \
|
26
|
+
unless t.class <= Array and t.length == 2
|
27
|
+
end
|
28
|
+
|
29
|
+
def CTokenizer.create_newlines(start, finish)
|
30
|
+
newlines = ''
|
31
|
+
(finish - start).times { newlines << "\n" }
|
32
|
+
[:NEWLINE, newlines.freeze].freeze
|
33
|
+
end
|
34
|
+
|
35
|
+
def CTokenizer.line_count(str)
|
36
|
+
count = 0
|
37
|
+
str.scan(/\r\n|\n\r|\n|\r/) { count += 1 } if str.class == String
|
38
|
+
count
|
39
|
+
end
|
40
|
+
|
41
|
+
# tokens are immutable
|
42
|
+
def CTokenizer.split_token(str)
|
43
|
+
check_string(str)
|
44
|
+
# would be easier if '\n' was the only kind of newline....
|
45
|
+
token = case str
|
46
|
+
when /\A[\t ]+/o
|
47
|
+
[:SPACE, $&]
|
48
|
+
when /\A(?:\r\n|\n\r|\r|\n)/o
|
49
|
+
[:NEWLINE, $&]
|
50
|
+
when /\A\\[\t ]*(?:\r\n|\n\r|\r|\n)/o
|
51
|
+
[:SPACE, $&]
|
52
|
+
when /\A\/\*.*?\*\//m
|
53
|
+
[:COMMENT, $&]
|
54
|
+
when /\A\/\/(?:\\[ \t]*(?:\r\n|\n\r|\r|\n)|[^\r\n])+/o
|
55
|
+
# scarry comment - bad style - beward of line \ at end of line...
|
56
|
+
[:COMMENT, $&]
|
57
|
+
when /\A(?:\+=|\-=|\*=|\/=|%=|\&=|\^=|\|=|<<=|>>=|##|\.\.\.)/
|
58
|
+
[:SYMBOL, $&]
|
59
|
+
when /\A(?:==|!=|<=|>=|->|\&\&|\|\||<<|>>|\+\+|\-\-)/o
|
60
|
+
[:SYMBOL, $&]
|
61
|
+
when /\A(?:<:|:>|<%|%>)/o
|
62
|
+
[:SYMBOL, $&]
|
63
|
+
when /\A[\(\)\[\]\{\}\|\&\+\-\/\*%<>\.,=!:;\?\^~#]/o
|
64
|
+
[:SYMBOL, $&]
|
65
|
+
when /\AL?'(?:[^']|\\.)*'/o
|
66
|
+
[:CHARACTER, $&]
|
67
|
+
when /\AL?"(?:[^"]|\\.)*"/o
|
68
|
+
[:STRING, $&]
|
69
|
+
when /\A[a-zA-Z_]\w*/o
|
70
|
+
[:IDENTIFIER, $&]
|
71
|
+
# FLOAT should come before INTEGER
|
72
|
+
when /\A(?:[0-9]*\.[0-9]+)|(?:[0-9]+\.)[eE][-+]?[0-9]+?[fFlL]?/o
|
73
|
+
[:FLOAT, $&]
|
74
|
+
when /\A[0-9]+[eE][-+]?[0-9]+[fFlL]?/o
|
75
|
+
[:FLOAT, $&]
|
76
|
+
when /\A0[xX][0-9a-fA-F]+(?:(?:[uU][lL]?)|(?:[lL][uU]?)?)/o
|
77
|
+
[:INTEGER, $&]
|
78
|
+
when /\A0[0-7]+(?:(?:[uU][lL]?)|(?:[lL][uU]?)?)/o
|
79
|
+
[:INTEGER, $&]
|
80
|
+
when /\A\d+(?:(?:[uU][lL]?)|(?:[lL][uU]?)?)/o
|
81
|
+
[:INTEGER, $&]
|
82
|
+
when /\A\Z/o
|
83
|
+
[false, false] # end of file
|
84
|
+
when /\A./m
|
85
|
+
[:UNKNOWN, $&]
|
86
|
+
else
|
87
|
+
raise "shouldn't get here!"
|
88
|
+
end # case
|
89
|
+
token[1].freeze
|
90
|
+
[token.freeze, $']
|
91
|
+
end
|
92
|
+
|
93
|
+
def CTokenizer.split(str)
|
94
|
+
tokens = []
|
95
|
+
until str.empty?
|
96
|
+
t, str = CTokenizer.split_token(str)
|
97
|
+
tokens << t
|
98
|
+
end # until
|
99
|
+
tokens
|
100
|
+
end
|
101
|
+
|
102
|
+
def CTokenizer.join(tokens)
|
103
|
+
str = ''
|
104
|
+
tokens.each do |t|
|
105
|
+
str << t[1]
|
106
|
+
end
|
107
|
+
str
|
108
|
+
end
|
109
|
+
|
110
|
+
def error(msg)
|
111
|
+
CTokenizer.error(file, line, msg)
|
112
|
+
end
|
113
|
+
|
114
|
+
def token_error(token)
|
115
|
+
self.error("unrecognized token: #{token}")
|
116
|
+
end
|
117
|
+
|
118
|
+
def parse_error(token)
|
119
|
+
self.error("parse error on token: #{token}")
|
120
|
+
end
|
121
|
+
|
122
|
+
def warning(msg)
|
123
|
+
warn "#{file + ':' if file}#{line}: #{msg}"
|
124
|
+
end
|
125
|
+
|
126
|
+
def to_a
|
127
|
+
ary = []
|
128
|
+
until self.empty?
|
129
|
+
ary << self.shift
|
130
|
+
end
|
131
|
+
ary
|
132
|
+
end
|
133
|
+
|
134
|
+
def each
|
135
|
+
until self.empty?
|
136
|
+
yield(self.shift)
|
137
|
+
end
|
138
|
+
self
|
139
|
+
end
|
140
|
+
|
141
|
+
def collect
|
142
|
+
ary = []
|
143
|
+
until self.empty?
|
144
|
+
ary << yield(self.shift)
|
145
|
+
end
|
146
|
+
ary
|
147
|
+
end
|
148
|
+
|
149
|
+
class Lexer
|
150
|
+
# C Lexer which keeps ALL tokens
|
151
|
+
include CTokenizer
|
152
|
+
|
153
|
+
def initialize(str, file=nil, line=1)
|
154
|
+
CTokenizer.check_string(str)
|
155
|
+
@rest = str
|
156
|
+
@file = file
|
157
|
+
@line = line
|
158
|
+
end
|
159
|
+
|
160
|
+
attr_reader :file, :line
|
161
|
+
|
162
|
+
def empty?
|
163
|
+
@rest.empty?
|
164
|
+
end
|
165
|
+
|
166
|
+
def shift
|
167
|
+
t, @rest = CTokenizer.split_token(@rest)
|
168
|
+
@line += CTokenizer.line_count(t[1])
|
169
|
+
|
170
|
+
t
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
# provides a way of keeping track of the current scope
|
176
|
+
module Scoped
|
177
|
+
attr_reader :scope
|
178
|
+
|
179
|
+
def start_of_line?
|
180
|
+
@start_line
|
181
|
+
end
|
182
|
+
|
183
|
+
def macro?
|
184
|
+
@macro
|
185
|
+
end
|
186
|
+
|
187
|
+
def process_scope(t)
|
188
|
+
case t[0]
|
189
|
+
when :SPACE, :COMMENT
|
190
|
+
# do nothing
|
191
|
+
when :NEWLINE
|
192
|
+
@start_line = true
|
193
|
+
@macro = false
|
194
|
+
else
|
195
|
+
unless @macro
|
196
|
+
# these tokens are ignored if inside of a macro
|
197
|
+
case t[1]
|
198
|
+
when '#'
|
199
|
+
@macro = @start_line
|
200
|
+
when '{'
|
201
|
+
@scope += 1
|
202
|
+
when '}'
|
203
|
+
@scope -= 1
|
204
|
+
end
|
205
|
+
end
|
206
|
+
@start_line = false
|
207
|
+
end
|
208
|
+
t
|
209
|
+
end
|
210
|
+
end # Scoped
|
211
|
+
|
212
|
+
# wraps a lexer and uses that to produce new tokens
|
213
|
+
class LexerBase
|
214
|
+
include CTokenizer
|
215
|
+
|
216
|
+
def initialize(str, file=nil, line=1)
|
217
|
+
if (str.class <= String)
|
218
|
+
@source = Lexer.new(str, file, line)
|
219
|
+
else
|
220
|
+
@source = str
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
def file
|
225
|
+
@source.file
|
226
|
+
end
|
227
|
+
def line
|
228
|
+
@source.line
|
229
|
+
end
|
230
|
+
|
231
|
+
def shift
|
232
|
+
t = @source.shift
|
233
|
+
CTokenizer.check_token(t)
|
234
|
+
t
|
235
|
+
end
|
236
|
+
|
237
|
+
def empty?
|
238
|
+
@source.empty?
|
239
|
+
end
|
240
|
+
|
241
|
+
end # LexerBase
|
242
|
+
|
243
|
+
class Cache
|
244
|
+
include CTokenizer
|
245
|
+
def initialize(file=nil, line=1)
|
246
|
+
@cache = []
|
247
|
+
@file = file
|
248
|
+
@line = line
|
249
|
+
end
|
250
|
+
attr_reader :cache, :file, :line
|
251
|
+
def reset(file, line)
|
252
|
+
@file = file
|
253
|
+
@line = line
|
254
|
+
end
|
255
|
+
def <<(t)
|
256
|
+
@cache << t
|
257
|
+
end
|
258
|
+
def shift
|
259
|
+
t = @cache.shift
|
260
|
+
CTokenizer.check_token(t)
|
261
|
+
@line += CTokenizer.line_count(t[1])
|
262
|
+
t
|
263
|
+
end
|
264
|
+
def empty?
|
265
|
+
@cache.empty?
|
266
|
+
end
|
267
|
+
end # Cache
|
268
|
+
|
269
|
+
# Skips macro tokens - NOT lossy - converts macro into a single newline token
|
270
|
+
class SkipMacros < LexerBase
|
271
|
+
include Scoped
|
272
|
+
def initialize(str, file=nil, line=1)
|
273
|
+
super(str, file, line)
|
274
|
+
@scope = 0
|
275
|
+
@macro = false
|
276
|
+
@start_line = true
|
277
|
+
end
|
278
|
+
def shift
|
279
|
+
t = process_scope(super)
|
280
|
+
if macro?
|
281
|
+
start = self.line
|
282
|
+
token = t[1].dup
|
283
|
+
while macro? and not empty?
|
284
|
+
t = process_scope(super)
|
285
|
+
token << t[1]
|
286
|
+
end
|
287
|
+
self.error("expecting newline") unless empty? or t[0] == :NEWLINE
|
288
|
+
t = [:NEWLINE, token.freeze].freeze
|
289
|
+
end
|
290
|
+
t
|
291
|
+
end
|
292
|
+
end # SkipMacros
|
293
|
+
|
294
|
+
# writes tokens to some output source
|
295
|
+
# may not be useful...
|
296
|
+
class Splitter < LexerBase
|
297
|
+
def initialize(out, source, file=nil, line=1)
|
298
|
+
super(source, file, line)
|
299
|
+
@out = out
|
300
|
+
end
|
301
|
+
|
302
|
+
def shift
|
303
|
+
t = super
|
304
|
+
@out << t
|
305
|
+
t
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
# C Lexer
|
310
|
+
class CLexer < LexerBase
|
311
|
+
|
312
|
+
def CLexer.reserved_word?(str)
|
313
|
+
str =~ /\A(?:auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|union|unsigned|void|volatile|while)\Z/o
|
314
|
+
end
|
315
|
+
|
316
|
+
def CLexer.convert_token(t)
|
317
|
+
case t[0]
|
318
|
+
when :SYMBOL
|
319
|
+
t = case t[1]
|
320
|
+
when '<:'
|
321
|
+
'['
|
322
|
+
when ':>'
|
323
|
+
']'
|
324
|
+
when '<%'
|
325
|
+
'{'
|
326
|
+
when '%>'
|
327
|
+
'}'
|
328
|
+
else
|
329
|
+
t[1]
|
330
|
+
end # case
|
331
|
+
t = [t, t].freeze
|
332
|
+
when :IDENTIFIER
|
333
|
+
if CLexer.reserved_word?(t[1])
|
334
|
+
t = [t[1], t[1]].freeze
|
335
|
+
end
|
336
|
+
end #case
|
337
|
+
t
|
338
|
+
end
|
339
|
+
|
340
|
+
def shift
|
341
|
+
CLexer.convert_token(super)
|
342
|
+
end
|
343
|
+
|
344
|
+
end # CLexer
|
345
|
+
|
346
|
+
# C Preprocessor Lexer
|
347
|
+
class CPLexer < LexerBase
|
348
|
+
|
349
|
+
def CPLexer.reserved_word?(str)
|
350
|
+
str =~ /\A(?:if|ifdef|ifndef|else|elif|endif|include|include_next|define|defined|undef|line|error|pragma)\Z/
|
351
|
+
end
|
352
|
+
|
353
|
+
def CPLexer.convert_token(t)
|
354
|
+
case t[0]
|
355
|
+
when :SYMBOL
|
356
|
+
case t[1]
|
357
|
+
when '!', '*', '/', '%', '+', '-', \
|
358
|
+
'<<', '>>', '(', ')', \
|
359
|
+
'<', '<=', '>', '>=', '==', '!=', \
|
360
|
+
'&', '^', '|', '&&', '||', \
|
361
|
+
'?', ':', '#', '##', '...', ','
|
362
|
+
t = [t[1], t[1]].freeze
|
363
|
+
end # case
|
364
|
+
when :IDENTIFIER
|
365
|
+
if CPLexer.reserved_word?(t[1])
|
366
|
+
t = [t[1], t[1]].freeze
|
367
|
+
end
|
368
|
+
end # case
|
369
|
+
t
|
370
|
+
end
|
371
|
+
|
372
|
+
def shift
|
373
|
+
CPLexer.convert_token(super)
|
374
|
+
end
|
375
|
+
|
376
|
+
end # CPLexer
|
377
|
+
|
378
|
+
end # Tokenizer
|
379
|
+
|