Class: NCPP::CFileInterpreter
- Inherits:
-
Interpreter
- Object
- Interpreter
- NCPP::CFileInterpreter
- Defined in:
- lib/ncpp/interpreter.rb
Overview
Scans C/C++ source files for commands and expands them in place
Instance Attribute Summary collapse
-
#incomplete_files ⇒ Object
readonly
Returns the value of attribute incomplete_files.
-
#lines_parsed ⇒ Object
readonly
Returns the value of attribute lines_parsed.
Instance Method Summary collapse
-
#initialize(file_list, out_path, cmd_prefix = COMMAND_PREFIX, extra_cmds = {}, extra_vars = {}, template_args = [], safe: false, puritan: false, no_cache: false, cmd_cache: {}) ⇒ CFileInterpreter
constructor
A new instance of CFileInterpreter.
- #process_file(file_path, verbose: true, debug: false) ⇒ Object
- #run(verbose: true, debug: false) ⇒ Object
Methods inherited from Interpreter
#call, #commands, #def_command, #def_variable, #eval_expr, #eval_str, #get_binding, #get_cacheable_cache, #get_command, #get_new_commands, #get_new_variables, #get_variable, #node_impure?, #unknown_command_error, #unknown_variable_error, #variables
Constructor Details
#initialize(file_list, out_path, cmd_prefix = COMMAND_PREFIX, extra_cmds = {}, extra_vars = {}, template_args = [], safe: false, puritan: false, no_cache: false, cmd_cache: {}) ⇒ CFileInterpreter
Returns a new instance of CFileInterpreter.
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 |
# File 'lib/ncpp/interpreter.rb', line 481 def initialize(file_list, out_path, cmd_prefix = COMMAND_PREFIX, extra_cmds = {}, extra_vars = {}, template_args=[], safe: false, puritan: false, no_cache: false, cmd_cache: {}) @EXTRA_CMDS, @EXTRA_VARS = extra_cmds, extra_vars super(cmd_prefix, extra_cmds, extra_vars, safe: safe, puritan: puritan, no_cache: no_cache, cmd_cache: cmd_cache) @file_list = file_list.is_a?(Array) ? file_list : [file_list] @current_file = nil @out_path = out_path @incomplete_files = [] @lines_parsed = 0 @template_args = template_args @recorded = '' @consume_mode = false @lick_mode = false @commands.merge!({ embed: ->(filename, newline_steps=nil) { dir = File.dirname(@current_file || Dir.pwd) path = File.(filename, dir) raise "File not found: #{path}" unless File.exist? path File.binread(path).bytes.join(',') }.returns(String).impure .describe( 'Reads all bytes from the specified file and joins them into a comma-separated String representation.' ), embed_hex: ->(filename, newline_steps=nil) { dir = File.dirname(@current_file || Dir.pwd) path = File.(filename, dir) raise "File not found: #{path}" unless File.exist? path bytes = File.binread(path).bytes bytes.map! {|b| b.to_i.to_hex }.join(',') }.returns(String).impure .describe( 'Reads all bytes from the specified file and joins them as hex into a comma-separated String representation.' ), read: ->(filename) { dir = File.dirname(@current_file || Dir.pwd) path = File.(filename, dir) raise "File not found: #{path}" unless File.exist? path File.read(path) }.returns(String).impure .describe('Reads the file specified and returns its contents as a String.'), read_lines: ->(filename) { dir = File.dirname(@current_file || Dir.pwd) path = File.(filename, dir) raise "File not found: #{path}" unless File.exist? path File.readlines(path) }.returns(Array).impure .describe('Reads the file specified and returns an Array containing each line.'), read_bytes: ->(filename) { dir = File.dirname(@current_file || Dir.pwd) path = File.(filename, dir) raise "File not found: #{path}" unless File.exist? path File.binread(path).bytes }.returns(Array).impure .describe('Reads the file specified and returns an Array containing each byte.'), import: ->(template_file, *arg_vals) { t_interpreter = CFileInterpreter.new(nil,nil,@COMMAND_PREFIX,@EXTRA_CMDS,@EXTRA_VARS,[*arg_vals]) dir = File.dirname(@current_file || Dir.pwd) path = File.(template_file, dir) ret, _, t_args = t_interpreter.process_file(path) @lines_parsed += t_interpreter.lines_parsed if t_args.length > 0 puts "WARNING".underline_yellow + ': '.yellow + "#{t_args.length} template arg#{'s' if t_args.length != 1}"\ " not used.".yellow end ret }.returns(String).impure .describe( "Takes a template file name and a value for each arg exported by the template. The template file is " \ "processed by the interpreter, and the generated code is embedded into the current file." ), expect: ->(*arg_names) { argc, targc = @template_args.length, arg_names.length if targc != argc raise "#{argc} template arg#{'s' if argc != 1} given when #{targc} #{targc==1 ? 'is' : 'are'} required." end arg_names.each_with_index do |arg, i| Utils.valid_identifier_check(arg) @variables[arg.to_sym] = @template_args.first @template_args = @template_args.drop(1) end }.impure .describe( "Declares the variables that should be defined when importing the template. " \ "This command is specific to CFileInterpreter." ), start_consume: -> { @consume_mode = true }.impure .describe( "Starts consume mode; the following parsed lines will stored in a variable held by the interpreter, which "\ "can only be accessed by the 'spit' command. Consumed lines will not be put in the generated source file." ), end_consume: -> { @consume_mode = false }.impure .describe('Ends consume parse mode.'), start_lick: -> { @lick_mode = true }.impure .describe('Starts lick parse mode.'), end_lick: -> { @lick_mode = false }.impure .describe('Ends lick parse mode.'), spit: ->(retain = false) { ret = @recorded.clone @recorded.clear unless retain ret }.returns(String).impure .describe('Gets what was consumed or licked.'), clear_consumed: -> { @recorded.clear }.impure .describe('Clears the variable containing what was consumed or licked.'), clear_licked: -> { @recorded.clear }.impure .describe('Clears the variable containing what was licked or consumed.'), }) end |
Instance Attribute Details
#incomplete_files ⇒ Object (readonly)
Returns the value of attribute incomplete_files.
479 480 481 |
# File 'lib/ncpp/interpreter.rb', line 479 def incomplete_files @incomplete_files end |
#lines_parsed ⇒ Object (readonly)
Returns the value of attribute lines_parsed.
479 480 481 |
# File 'lib/ncpp/interpreter.rb', line 479 def lines_parsed @lines_parsed end |
Instance Method Details
#process_file(file_path, verbose: true, debug: false) ⇒ Object
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 |
# File 'lib/ncpp/interpreter.rb', line 626 def process_file(file_path, verbose: true, debug: false) raise "#{file_path} does not exist." unless File.exist?(file_path) @current_file = file_path success = true # cursor state in_comment = false in_string = false in_expr = false # TODO: multi-line expression parsing output = '' File.readlines(file_path).each_with_index do |line, lineno| cursor = 0 new_line = "" while cursor < line.length # stop parsing rest of line on single-line comment if !in_comment && !in_string && line[cursor, 2] == "//" new_line << line[cursor..-1] break # enter multi-line comment elsif !in_comment && !in_string && line[cursor, 2] == "/*" in_comment = true new_line << "/*" cursor += 2 next # leave comment elsif in_comment && line[cursor, 2] == "*/" in_comment = false new_line << "*/" cursor += 2 next # enter string elsif !in_comment && line[cursor] == '"' in_string = !in_string new_line << '"' cursor += 1 next end # enter command if !in_comment && !in_string && line[cursor, @COMMAND_PREFIX.length] == @COMMAND_PREFIX && (cursor == 0 || !/[0-9A-Za-z_]/.match?(line[cursor-1])) expr_src = line[(cursor + @COMMAND_PREFIX.length)..] begin tree = @parser.parse(expr_src) rtree_s = tree.to_s.reverse # finds the end of the expression (hacky) expr_end = /\d+/.match(rtree_s[..rtree_s.index('__last_char__: '.reverse)].reverse).to_s if expr_end.empty? raise 'Could not find an end to expression on line; multi-line expressions are not yet supported' end last_paren = Integer(expr_end) + 1 ast = @transformer.apply(tree) value = eval_expr(ast) @out_stack << value.to_s unless value.nil? new_line << @out_stack.join("\n") unless @out_stack.empty? @out_stack.clear cursor += @COMMAND_PREFIX.length + last_paren # move cursor past expression next rescue Parslet::ParseFailed => e puts "#{file_path}:#{lineno+1}: parse failed at expression".yellow puts 'ERROR'.underline_red + ": #{e.parse_failure_cause.ascii_tree}".red rescue Exception => e puts "#{file_path}:#{lineno+1}: parse failed at expression".yellow puts 'ERROR'.underline_red + ": #{debug ? e. : e.to_s}".red # fall through, copy raw text instead end success = false end new_line << line[cursor] cursor += 1 end new_line = (line != new_line && new_line.strip.empty?) ? '' : new_line if @consume_mode @recorded << new_line else @recorded << new_line if @lick_mode output << new_line end @lines_parsed += 1 end [output, success, @template_args] end |
#run(verbose: true, debug: false) ⇒ Object
610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 |
# File 'lib/ncpp/interpreter.rb', line 610 def run(verbose: true, debug: false) @file_list.each do |file| if verbose puts "Processing #{file}".cyan end out, success, _ = process_file(file, verbose: verbose, debug: debug) @incomplete_files << file unless success new_file_path = @out_path + '/' + file FileUtils.mkdir_p(File.dirname(new_file_path)) File.write(new_file_path, out) end end |