├── .document ├── .gitignore ├── .rspec ├── .yardopts ├── ChangeLog.md ├── LICENSE.txt ├── README.md ├── Rakefile ├── cparser.gemspec ├── gemspec.yml ├── lib ├── cparser.rb └── cparser │ ├── parser.rb │ └── version.rb └── spec ├── cparser_spec.rb └── spec_helper.rb /.document: -------------------------------------------------------------------------------- 1 | - 2 | ChangeLog.* 3 | LICENSE.txt 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | doc/ 2 | pkg/ 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --colour --format documentation 2 | -------------------------------------------------------------------------------- /.yardopts: -------------------------------------------------------------------------------- 1 | --markup markdown --title "cparser Documentation" --protected 2 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | ### 0.1.0 / 2011-01-17 2 | 3 | * Initial release: 4 | 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Hal Brodigan 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cparser 2 | 3 | * [Homepage](http://github.com/postmodern/cparser) 4 | 5 | ## Description 6 | 7 | A pure Ruby ANSI C Parser. 8 | 9 | ## Features 10 | 11 | * Parses ANSI C. 12 | * Pure Ruby. 13 | 14 | ## Examples 15 | 16 | require 'cparser' 17 | 18 | ## Requirements 19 | 20 | * [parslet](http://kschiess.github.com/parslet/) ~> 1.0 21 | 22 | ## Install 23 | 24 | $ gem install cparser 25 | 26 | ## Copyright 27 | 28 | Copyright (c) 2011 Hal Brodigan 29 | 30 | See {file:LICENSE.txt} for details. 31 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | require 'rake' 3 | 4 | begin 5 | gem 'rubygems-tasks', '~> 0.1' 6 | require 'rubygems/tasks' 7 | 8 | Gem::Tasks.new 9 | rescue LoadError => e 10 | warn e.message 11 | warn "Run `gem install rubygems-tasks` to install 'rubygems/tasks'." 12 | end 13 | 14 | begin 15 | gem 'rspec', '~> 2.4' 16 | require 'rspec/core/rake_task' 17 | 18 | RSpec::Core::RakeTask.new 19 | rescue LoadError => e 20 | task :spec do 21 | abort "Please run `gem install rspec` to install RSpec." 22 | end 23 | end 24 | task :default => :spec 25 | 26 | begin 27 | gem 'yard', '~> 0.6.0' 28 | require 'yard' 29 | 30 | YARD::Rake::YardocTask.new 31 | rescue LoadError => e 32 | task :yard do 33 | abort "Please run `gem install yard` to install YARD." 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /cparser.gemspec: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'yaml' 4 | 5 | Gem::Specification.new do |gemspec| 6 | files = if File.directory?('.git') 7 | `git ls-files`.split($/) 8 | elsif File.directory?('.hg') 9 | `hg manifest`.split($/) 10 | elsif File.directory?('.svn') 11 | `svn ls -R`.split($/).select { |path| File.file?(path) } 12 | else 13 | Dir['{**/}{.*,*}'].select { |path| File.file?(path) } 14 | end 15 | 16 | filter_files = lambda { |paths| 17 | case paths 18 | when Array 19 | (files & paths) 20 | when String 21 | (files & Dir[paths]) 22 | end 23 | } 24 | 25 | version = { 26 | :file => 'lib/cparser/version.rb', 27 | :constant => 'CParser::VERSION' 28 | } 29 | 30 | defaults = { 31 | 'name' => File.basename(File.dirname(__FILE__)), 32 | 'files' => files, 33 | 'executables' => filter_files['bin/*'].map { |path| File.basename(path) }, 34 | 'test_files' => filter_files['{test/{**/}*_test.rb,spec/{**/}*_spec.rb}'], 35 | 'extra_doc_files' => filter_files['*.{txt,rdoc,md,markdown,tt,textile}'], 36 | } 37 | 38 | metadata = defaults.merge(YAML.load_file('gemspec.yml')) 39 | 40 | gemspec.name = metadata.fetch('name',defaults[:name]) 41 | gemspec.version = if metadata['version'] 42 | metadata['version'] 43 | elsif File.file?(version[:file]) 44 | require File.join('.',version[:file]) 45 | eval(version[:constant]) 46 | end 47 | 48 | gemspec.summary = metadata.fetch('summary',metadata['description']) 49 | gemspec.description = metadata.fetch('description',metadata['summary']) 50 | 51 | case metadata['license'] 52 | when Array 53 | gemspec.licenses = metadata['license'] 54 | when String 55 | gemspec.license = metadata['license'] 56 | end 57 | 58 | case metadata['authors'] 59 | when Array 60 | gemspec.authors = metadata['authors'] 61 | when String 62 | gemspec.author = metadata['authors'] 63 | end 64 | 65 | gemspec.email = metadata['email'] 66 | gemspec.homepage = metadata['homepage'] 67 | 68 | case metadata['require_paths'] 69 | when Array 70 | gemspec.require_paths = metadata['require_paths'] 71 | when String 72 | gemspec.require_path = metadata['require_paths'] 73 | end 74 | 75 | gemspec.files = filter_files[metadata['files']] 76 | 77 | gemspec.executables = metadata['executables'] 78 | gemspec.extensions = metadata['extensions'] 79 | 80 | if Gem::VERSION < '1.7.' 81 | gemspec.default_executable = gemspec.executables.first 82 | end 83 | 84 | gemspec.test_files = filter_files[metadata['test_files']] 85 | 86 | unless gemspec.files.include?('.document') 87 | gemspec.extra_rdoc_files = metadata['extra_doc_files'] 88 | end 89 | 90 | gemspec.post_install_message = metadata['post_install_message'] 91 | gemspec.requirements = metadata['requirements'] 92 | 93 | if gemspec.respond_to?(:required_ruby_version=) 94 | gemspec.required_ruby_version = metadata['required_ruby_version'] 95 | end 96 | 97 | if gemspec.respond_to?(:required_rubygems_version=) 98 | gemspec.required_rubygems_version = metadata['required_ruby_version'] 99 | end 100 | 101 | parse_versions = lambda { |versions| 102 | case versions 103 | when Array 104 | versions.map { |v| v.to_s } 105 | when String 106 | versions.split(/,\s*/) 107 | end 108 | } 109 | 110 | if metadata['dependencies'] 111 | metadata['dependencies'].each do |name,versions| 112 | gemspec.add_dependency(name,parse_versions[versions]) 113 | end 114 | end 115 | 116 | if metadata['runtime_dependencies'] 117 | metadata['runtime_dependencies'].each do |name,versions| 118 | gemspec.add_runtime_dependency(name,parse_versions[versions]) 119 | end 120 | end 121 | 122 | if metadata['development_dependencies'] 123 | metadata['development_dependencies'].each do |name,versions| 124 | gemspec.add_development_dependency(name,parse_versions[versions]) 125 | end 126 | end 127 | end 128 | -------------------------------------------------------------------------------- /gemspec.yml: -------------------------------------------------------------------------------- 1 | name: cparser 2 | summary: A pure Ruby ANSI C Parser. 3 | description: CParse is an ANSI C Parser written purely in Ruby. 4 | license: MIT 5 | authors: Postmodern 6 | email: postmodern.mod3@gmail.com 7 | homepage: http://github.com/postmodern/cparser 8 | has_yard: true 9 | 10 | dependencies: 11 | parslet: ~> 1.0 12 | 13 | development_dependencies: 14 | rubygems-tasks: ~> 0.1 15 | rspec: ~> 2.4 16 | yard: ~> 0.6.0 17 | -------------------------------------------------------------------------------- /lib/cparser.rb: -------------------------------------------------------------------------------- 1 | require 'cparser/parser' 2 | require 'cparser/version' 3 | -------------------------------------------------------------------------------- /lib/cparser/parser.rb: -------------------------------------------------------------------------------- 1 | require 'parslet' 2 | 3 | module CParser 4 | # 5 | # The ANSI C Parser using the 6 | # [Parslet](http://kschiess.github.com/parslet/) library. 7 | # 8 | # ANSI C Grammar: 9 | # 10 | # * http://www.lysator.liu.se/c/ANSI-C-grammar-l.html 11 | # * http://www.lysator.liu.se/c/ANSI-C-grammar-y.html 12 | # 13 | class Parser < Parslet::Parser 14 | 15 | rule(:new_line) { match('[\n\r]').repeat(1) } 16 | 17 | rule(:space) { match('[ \t\v\n\f]') } 18 | rule(:spaces) { space.repeat(1) } 19 | rule(:space?) { space.maybe } 20 | rule(:spaces?) { space.repeat } 21 | 22 | rule(:digit) { match('[0-9]') } 23 | rule(:digits) { digit.repeat(1) } 24 | rule(:digits?) { digit.repeat } 25 | 26 | rule(:alpha) { match('[a-zA-Z_]') } 27 | rule(:xdigit) { digit | match('[a-fA-F]') } 28 | 29 | rule(:e) { match('[eE]') >> match('[+-]').maybe >> digit.repeat(1) } 30 | rule(:float_size) { match('[fFlL]') } 31 | rule(:int_size) { match('[uUlL]').repeat } 32 | 33 | rule(:e?) { e.maybe } 34 | rule(:float_size?) { float_size.maybe } 35 | rule(:int_size?) { int_size.maybe } 36 | 37 | rule(:comment) { 38 | (str('/*') >> (str('*/').absnt? >> any).repeat >> str('*/')) | 39 | (str('//') >> (new_line.absnt? >> any).repeat >> new_line) 40 | } 41 | 42 | def self.keywords(*names) 43 | names.each do |name| 44 | rule("#{name}_keyword") { str(name.to_s).as(:keyword) >> spaces? } 45 | end 46 | end 47 | 48 | keywords :auto, :break, :case, :char, :const, :continue, :default, :do, 49 | :double, :else, :enum, :extern, :float, :for, :goto, :if, :int, 50 | :long, :register, :return, :short, :signed, :sizeof, :static, 51 | :struct, :switch, :typedef, :union, :unsigned, :void, :volatile, 52 | :while 53 | 54 | rule(:identifier) { 55 | (alpha >> (alpha | digit).repeat).as(:identifier) >> spaces? 56 | } 57 | 58 | rule(:hex_constant) { 59 | (match('0[xX]') >> xdigit.repeat(1) >> int_size?).as(:hex) >> spaces? 60 | } 61 | rule(:octal_constant) { 62 | (str('0') >> digits >> int_size?).as(:octal) >> spaces? 63 | } 64 | rule(:decimal_constant) { 65 | (digits >> int_size.maybe).as(:decimal) >> spaces? 66 | } 67 | rule(:string_constant) { 68 | ( 69 | str('L').maybe >> str("'") >> 70 | (match("\\.") | match("[^\\']")).repeat(1) >> 71 | str("'") 72 | ).as(:string) >> spaces? 73 | } 74 | 75 | rule(:float_constant) { 76 | ( 77 | (digits >> e >> float_size?) | 78 | (digits? >> str('.') >> digits >> e? >> float_size?) | 79 | (digits >> str('.') >> digits? >> e? >> float_size?) 80 | ).as(:float) >> spaces? 81 | } 82 | 83 | rule(:constant) { 84 | hex_constant | 85 | octal_constant | 86 | decimal_constant | 87 | float_constant | 88 | string_constant 89 | } 90 | 91 | rule(:string_literal) { 92 | ( 93 | str('L').maybe >> str('"') >> 94 | (match("\\.") | match('[^\\"]')).repeat >> 95 | str('"') 96 | ).as(:string) >> spaces? 97 | } 98 | 99 | def self.symbols(symbols) 100 | symbols.each do |name,symbol| 101 | rule(name) { str(symbol) >> spaces? } 102 | end 103 | end 104 | 105 | symbols :ellipsis => '...', 106 | :semicolon => ';', 107 | :comma => ',', 108 | :colon => ':', 109 | :left_paren => '(', 110 | :right_paren => ')', 111 | :member_access => '.', 112 | :question_mark => '?' 113 | 114 | rule(:left_brace) { (str('{') | str('<%')) >> spaces? } 115 | rule(:right_brace) { (str('}') | str('%>')) >> spaces? } 116 | 117 | rule(:left_bracket) { (str('[') | str('<:')) >> spaces? } 118 | rule(:right_bracket) { (str(']') | str(':>')) >> spaces? } 119 | 120 | def self.operators(operators={}) 121 | trailing_chars = Hash.new { |hash,symbol| hash[symbol] = [] } 122 | 123 | operators.each_value do |symbol| 124 | operators.each_value do |op| 125 | if op[0,symbol.length] == symbol 126 | char = op[symbol.length,1] 127 | 128 | unless (char.nil? || char.empty?) 129 | trailing_chars[symbol] << char 130 | end 131 | end 132 | end 133 | end 134 | 135 | operators.each do |name,symbol| 136 | trailing = trailing_chars[symbol] 137 | 138 | if trailing.empty? 139 | rule(name) { str(symbol).as(:operator) >> spaces? } 140 | else 141 | pattern = "[#{Regexp.escape(trailing.join)}]" 142 | 143 | rule(name) { 144 | (str(symbol) >> match(pattern).absnt?).as(:operator) >> spaces? 145 | } 146 | end 147 | end 148 | end 149 | 150 | operators :right_shift_assign => '>>=', 151 | :left_shift_assign => '<<=', 152 | :add_assign => '+=', 153 | :subtract_assign => '-=', 154 | :multiply_assign => '*=', 155 | :divide_assign => '/=', 156 | :modulus_assign => '%=', 157 | :binary_and_assign => '&=', 158 | :xor_assign => '^=', 159 | :binary_or_assign => '|=', 160 | :inc => '++', 161 | :dec => '--', 162 | :pointer_access => '->', 163 | :logical_and => '&&', 164 | :logical_or => '||', 165 | :less_equal => '<=', 166 | :greater_equal => '>=', 167 | :equal => '==', 168 | :not_equal => '!=', 169 | :assign => '=', 170 | :add => '+', 171 | :subtract => '-', 172 | :multiply => '*', 173 | :divide => '/', 174 | :modulus => '%', 175 | :less => '<', 176 | :greater => '>', 177 | :negate => '!', 178 | :binary_or => '|', 179 | :binary_and => '&', 180 | :xor => '^', 181 | :left_shift => '<<', 182 | :right_shift => '>>', 183 | :inverse => '~' 184 | 185 | rule(:primary_expression) { 186 | (identifier | constant | string_literal) | 187 | (left_paren >> expression >> right_paren) 188 | } 189 | 190 | rule(:postfix_expression) { 191 | primary_expression >> ( 192 | (left_bracket >> expression >> right_bracket) | 193 | (left_paren >> argument_expression_list.maybe >> right_paren) | 194 | ((member_access | pointer_access) >> identifier) | 195 | inc | dec 196 | ).repeat 197 | } 198 | 199 | rule(:argument_expression_list) { 200 | (assignment_expression >> comma >> argument_expression_list) | 201 | assignment_expression 202 | } 203 | 204 | rule(:sizeof_expression) { 205 | sizeof_keyword >> ( 206 | (unary_expression.as(:expr)) | 207 | (left_paren >> type_name.as(:type) >> right_paren) 208 | ) 209 | } 210 | 211 | rule(:unary_expression) { 212 | sizeof_expression.as(:sizeof) | 213 | postfix_expression | 214 | (inc >> unary_expression).as(:inc) | 215 | (dec >> unary_expression).as(:dec) | 216 | (unary_operator >> cast_expression).as(:unary) 217 | } 218 | 219 | rule(:unary_operator) { 220 | (binary_and | multiply | add | subtract | inverse | negate) 221 | } 222 | 223 | rule(:cast_expression) { 224 | ( 225 | left_paren >> type_name.as(:type) >> right_paren >> 226 | cast_expression 227 | ).as(:cast) | unary_expression 228 | } 229 | 230 | rule(:multiplicative_expression) { 231 | ( 232 | cast_expression.as(:left) >> 233 | (multiply | divide | modulus) >> 234 | multiplicative_expression.as(:right) 235 | ).as(:multiplicative) | cast_expression 236 | } 237 | 238 | rule(:additive_expression) { 239 | ( 240 | multiplicative_expression.as(:left) >> 241 | (add | subtract) >> 242 | additive_expression.as(:right) 243 | ).as(:additive) | multiplicative_expression 244 | } 245 | 246 | rule(:shift_expression) { 247 | ( 248 | additive_expression.as(:left) >> 249 | (left_shift | right_shift) >> 250 | shift_expression.as(:right) 251 | ).as(:shift) | additive_expression 252 | } 253 | 254 | rule(:relational_expression) { 255 | ( 256 | shift_expression.as(:left) >> 257 | (less | greater | less_equal | greater_equal) >> 258 | relational_expression.as(:right) 259 | ).as(:relational) | shift_expression 260 | } 261 | 262 | rule(:equality_expression) { 263 | ( 264 | relational_expression.as(:left) >> 265 | (equal | not_equal) >> 266 | equality_expression.as(:right) 267 | ).as(:equality) | relational_expression 268 | } 269 | 270 | rule(:and_expression) { 271 | ( 272 | equality_expression.as(:left) >> 273 | binary_and >> 274 | and_expression.as(:right) 275 | ).as(:binary_and) | equality_expression 276 | } 277 | 278 | rule(:exclusive_or_expression) { 279 | ( 280 | and_expression.as(:left) >> 281 | xor >> 282 | exclusive_or_expression.as(:right) 283 | ).as(:xor) | and_expression 284 | } 285 | 286 | rule(:inclusive_or_expression) { 287 | ( 288 | exclusive_or_expression.as(:left) >> 289 | binary_or >> 290 | inclusive_or_expression.as(:right) 291 | ).as(:binary_or) | exclusive_or_expression 292 | } 293 | 294 | rule(:logical_and_expression) { 295 | ( 296 | inclusive_or_expression.as(:left) >> 297 | logical_and >> 298 | logical_and_expression.as(:right) 299 | ).as(:logical_and) | inclusive_or_expression 300 | } 301 | 302 | rule(:logical_or_expression) { 303 | ( 304 | logical_and_expression.as(:left) >> 305 | logical_or >> 306 | logical_or_expression.as(:right) 307 | ).as(:logical_or) | logical_and_expression 308 | } 309 | 310 | rule(:conditional_expression) { 311 | ( 312 | logical_or_expression.as(:condition) >> question_mark >> 313 | expression.as(:true) >> colon >> 314 | conditional_expression.as(:false) 315 | ).as(:conditional) | logical_or_expression 316 | } 317 | 318 | rule(:assignment_expression) { 319 | ( 320 | unary_expression.as(:left) >> 321 | assignment_operator >> 322 | assignment_expression.as(:right) 323 | ).as(:assign) | conditional_expression 324 | } 325 | 326 | rule(:assignment_operator) { 327 | assign | 328 | multiply_assign | 329 | divide_assign | 330 | modulus_assign | 331 | add_assign | 332 | subtract_assign | 333 | left_shift_assign | 334 | right_shift_assign | 335 | binary_and_assign | 336 | xor_assign | 337 | binary_or_assign 338 | } 339 | 340 | rule(:expression) { 341 | assignment_expression >> (comma >> assignment_expression).repeat 342 | } 343 | rule(:expression?) { expression.maybe } 344 | 345 | rule(:constant_expression) { conditional_expression } 346 | rule(:constant_expression?) { constant_expression.maybe } 347 | 348 | rule(:declaration) { 349 | declaration_specifiers >> init_declarator_list.maybe >> semicolon 350 | } 351 | 352 | rule(:declaration_specifiers) { 353 | ( 354 | storage_class_specifier.as(:specifier) | 355 | type_specifier.as(:type) | 356 | type_qualifier.as(:qualifier) 357 | ).repeat(1) 358 | } 359 | 360 | rule(:init_declarator_list) { 361 | init_declarator >> (comma >> init_declarator).repeat 362 | } 363 | 364 | rule(:init_declarator) { 365 | declarator >> (assign >> initializer).maybe 366 | } 367 | 368 | rule(:storage_class_specifier) { 369 | typedef_keyword | 370 | extern_keyword | 371 | static_keyword | 372 | auto_keyword | 373 | register_keyword 374 | } 375 | 376 | rule(:type_specifier) { 377 | void_keyword | 378 | char_keyword | 379 | short_keyword | 380 | int_keyword | 381 | long_keyword | 382 | float_keyword | 383 | double_keyword | 384 | signed_keyword | 385 | unsigned_keyword | 386 | struct_or_union_specifier | 387 | enum_specifier 388 | } 389 | 390 | rule(:struct_or_union_specifier) { 391 | struct_or_union >> ( 392 | ( 393 | identifier.maybe >> 394 | (left_brace >> struct_declaration_list >> right_brace) 395 | ) | identifier 396 | ) 397 | } 398 | 399 | rule(:struct_or_union) { struct_keyword | union_keyword } 400 | 401 | rule(:struct_declaration_list) { struct_declaration.repeat(1) } 402 | 403 | rule(:struct_declaration) { 404 | specifier_qualifier_list >> struct_declarator_list >> semicolon 405 | } 406 | 407 | rule(:specifier_qualifier_list) { 408 | (type_specifier | type_qualifier).repeat(1) 409 | } 410 | 411 | rule(:struct_declarator_list) { 412 | struct_declarator >> (comma >> struct_declarator).repeat 413 | } 414 | 415 | rule(:struct_declarator) { 416 | (declarator.maybe >> (colon >> constant_expression)) | 417 | declarator 418 | } 419 | 420 | rule(:enum_specifier) { 421 | enum_keyword >> ( 422 | ( 423 | identifier.maybe >> (left_brace >> enumerator_list >> right_brace) 424 | ) | identifier 425 | ) 426 | } 427 | 428 | rule(:enumerator_list) { 429 | enumerator >> (comma >> enumerator).repeat 430 | } 431 | 432 | rule(:enumerator) { 433 | identifier >> (assign >> constant_expression).maybe 434 | } 435 | 436 | rule(:type_qualifier) { const_keyword | volatile_keyword } 437 | 438 | rule(:declarator) { pointer? >> direct_declarator } 439 | 440 | rule(:direct_declarator) { 441 | (identifier | (left_paren >> declarator >> right_paren)) >> 442 | ( 443 | ( 444 | left_bracket >> 445 | constant_expression.maybe.as(:size) >> 446 | right_bracket 447 | ).as(:array) | ( 448 | left_paren >> 449 | (parameter_type_list | identifier_list).maybe >> 450 | right_paren 451 | ) 452 | ).repeat 453 | } 454 | 455 | rule(:pointer) { 456 | multiply >> (multiply | type_qualifier_list).repeat 457 | } 458 | rule(:pointer?) { pointer.maybe } 459 | 460 | rule(:type_qualifier_list) { type_qualifier.repeat(1) } 461 | 462 | rule(:parameter_type_list) { 463 | parameter_list >> (comma >> ellipsis).maybe 464 | } 465 | rule(:parameter_type_list?) { parameter_type_list.maybe } 466 | 467 | rule(:parameter_list) { 468 | parameter_declaration >> (comma >> parameter_declaration).repeat 469 | } 470 | 471 | rule(:parameter_declaration) { 472 | declaration_specifiers >> (declarator | abstract_declarator).maybe 473 | } 474 | 475 | rule(:identifier_list) { 476 | identifier >> (comma >> identifier).repeat 477 | } 478 | 479 | rule(:type_name) { 480 | specifier_qualifier_list >> abstract_declarator.maybe 481 | } 482 | 483 | rule(:abstract_declarator) { 484 | (pointer? >> direct_abstract_declarator) | pointer 485 | } 486 | 487 | rule(:direct_abstract_declarator) { 488 | ( 489 | (left_paren >> abstract_declarator >> right_paren) | 490 | (left_bracket >> constant_expression? >> right_bracket) | 491 | (left_paren >> parameter_type_list? >> right_paren) 492 | ) >> ( 493 | (left_bracket >> constant_expression? >> right_bracket) | 494 | (left_paren >> parameter_type_list? >> right_paren) 495 | ).repeat 496 | } 497 | 498 | rule(:initializer) { 499 | assignment_expression | 500 | (left_brace >> initializer_list >> comma.maybe >> right_brace) 501 | } 502 | 503 | rule(:initializer_list) { 504 | initializer >> (comma >> initializer).repeat 505 | } 506 | 507 | rule(:statement) { 508 | labeled_statement | 509 | compound_statement | 510 | expression_statement | 511 | selection_statement | 512 | iteration_statement | 513 | jump_statement 514 | } 515 | 516 | rule(:label_statement) { 517 | (identifier | default_keyword).as(:name) >> colon >> 518 | statement.as(:body) 519 | } 520 | 521 | rule(:case_statement) { 522 | case_keyword >> constant_expression.as(:key) >> colon >> 523 | statement.as(:body) 524 | } 525 | 526 | rule(:labeled_statement) { 527 | label_statement.as(:label) | case_statement.as(:case) 528 | } 529 | 530 | rule(:compound_statement) { 531 | left_brace >> 532 | declaration_list.maybe.as(:declarations) >> statement_list.maybe >> 533 | right_brace 534 | } 535 | 536 | rule(:declaration_list) { declaration.repeat(1) } 537 | 538 | rule(:statement_list) { statement.repeat(1) } 539 | 540 | rule(:expression_statement) { expression? >> semicolon } 541 | 542 | rule(:if_statement) { 543 | if_keyword >> 544 | left_paren >> expression.as(:condition) >> right_paren >> 545 | statement.as(:body) >> 546 | (else_keyword >> statement.as(:else)).maybe 547 | } 548 | 549 | rule(:switch_statement) { 550 | switch_keyword >> 551 | left_paren >> expression.as(:expression) >> right_paren >> 552 | statement.as(:body) 553 | } 554 | 555 | rule(:selection_statement) { 556 | if_statement.as(:if) | switch_statement.as(:switch) 557 | } 558 | 559 | rule(:while_statement) { 560 | while_keyword >> 561 | left_paren >> expression.as(:condition) >> right_paren >> 562 | statement.as(:body) 563 | } 564 | 565 | rule(:do_while_statement) { 566 | do_keyword >> statement.as(:body) >> while_keyword >> 567 | left_paren >> expression.as(:condition) >> right_paren >> semicolon 568 | } 569 | 570 | rule(:for_statement) { 571 | for_keyword >> left_paren >> 572 | expression_statement.as(:initializer) >> 573 | expression_statement.as(:condition) >> 574 | expression.maybe.as(:update) >> 575 | right_paren >> 576 | statement.as(:body) 577 | } 578 | 579 | rule(:iteration_statement) { 580 | while_statement.as(:while) | 581 | do_while_statement.as(:do_while) | 582 | for_statement.as(:for) 583 | } 584 | 585 | rule(:jump_statement) { 586 | ( 587 | (goto_keyword >> identifier.as(:goto)) | 588 | continue_keyword.as(:continue) | 589 | break_keyword.as(:break) | 590 | (return_keyword >> expression.maybe.as(:value)).as(:return) 591 | ) >> semicolon 592 | } 593 | 594 | rule(:translation_unit) { external_declaration.repeat(1) } 595 | 596 | rule(:external_declaration) { 597 | function_definition.as(:function) | 598 | declaration 599 | } 600 | 601 | rule(:function_definition) { 602 | declaration_specifiers.maybe >> 603 | declarator >> 604 | declaration_list.maybe >> 605 | compound_statement.as(:body) 606 | } 607 | 608 | root :translation_unit 609 | 610 | end 611 | end 612 | -------------------------------------------------------------------------------- /lib/cparser/version.rb: -------------------------------------------------------------------------------- 1 | module CParser 2 | # cparser version 3 | VERSION = "0.1.0" 4 | end 5 | -------------------------------------------------------------------------------- /spec/cparser_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | require 'cparser' 3 | 4 | describe CParser do 5 | it "should have a VERSION constant" do 6 | subject.const_get('VERSION').should_not be_empty 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | gem 'rspec', '~> 2.4' 2 | require 'rspec' 3 | require 'cparser/version' 4 | 5 | include CParser 6 | --------------------------------------------------------------------------------