Class: Ppr::Preprocessor
- Inherits:
-
Object
- Object
- Ppr::Preprocessor
- Defined in:
- lib/ppr/ppr_core.rb
Overview
Describes the ruby preprocessor.
Usage:
ppr = Ppr::Preprocessor.new(<some options>)
ppr.preprocess(<some input stream>, <some output stream>)
Instance Method Summary collapse
-
#apply_macros(line) ⇒ Object
Applies recursively each element of
macros
toline
. -
#each_argument_range(line, start) ⇒ Object
Iterates over the range each argument of a
line
from offsetstart
. -
#get_argument_range(line, start) ⇒ Object
Gets the range of an argument starting at offset
start
inline
. -
#get_macro_def(line) ⇒ Object
Extract a macro definition from a
line
if there is one. -
#initialize(params = {}, apply: ".do", applyR: ".doR", define: ".def", defineR: ".defR", assign: ".assign", loadm: ".load", requirem: ".require", ifm: ".if", elsem: ".else", endifm: ".endif", endm: ".end", expand: ":<", separator: /^|[^\w]|$/, glue: "##", escape: "\\", includes: Dir.pwd) ⇒ Preprocessor
constructor
Creates a new preprocessor, where
apply
,applyR
,define
,defineR
,assign
,loadm
,requirem
,ifm
,elsem
andendm
are the keywords defining the beginings and end of a macro definitions, and whereseparator
is the regular expression used for separating macro references to the remaining of the code,expand
is the string representing the expansion operator of the macro,glue
is string used for glueing a macro expension to the text,escape
is the escape character. -
#is_elsem?(line) ⇒ Boolean
Tells if a line corresponds to an else keyword.
-
#is_endifm?(line) ⇒ Boolean
Tells if a line corresponds to an endif keyword.
-
#is_endm?(line) ⇒ Boolean
Tells if a line corresponds to an end keyword.
-
#parameter_get(param) ⇒ Object
Gets the value of parameter +param.
-
#parameter_set(param, value) ⇒ Object
Sets parameter
param
tovalue
. -
#preprocess(input, output) ⇒ Object
Preprocess an
input
stream and write the result to anoutput
stream. -
#run(&proc) ⇒ Object
Executes a macro in a safe context.
-
#unglue_back(string) ⇒ Object
Restores a
string
whose ending may have been glued. -
#unglue_front(string) ⇒ Object
Restores a
string
whose begining may have been glued.
Constructor Details
#initialize(params = {}, apply: ".do", applyR: ".doR", define: ".def", defineR: ".defR", assign: ".assign", loadm: ".load", requirem: ".require", ifm: ".if", elsem: ".else", endifm: ".endif", endm: ".end", expand: ":<", separator: /^|[^\w]|$/, glue: "##", escape: "\\", includes: Dir.pwd) ⇒ Preprocessor
Creates a new preprocessor, where apply
, applyR
, define
, defineR
, assign
, loadm
, requirem
, ifm
, elsem
and endm
are the keywords defining the beginings and end of a macro definitions, and where separator
is the regular expression used for separating macro references to the remaining of the code, expand
is the string representing the expansion operator of the macro, glue
is string used for glueing a macro expension to the text, escape
is the escape character.
Assigned parameters can be added through param
to be used within the macros of the preprocessed text.
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 |
# File 'lib/ppr/ppr_core.rb', line 402 def initialize(params = {}, apply: ".do", applyR: ".doR", define: ".def", defineR: ".defR", assign: ".assign", loadm: ".load", requirem: ".require", ifm: ".if", elsem: ".else", endifm: ".endif", endm: ".end", expand: ":<", separator: /^|[^\w]|$/, glue: "##", escape: "\\", includes: Dir.pwd) # Check and Initialize the keywords # NOTE: since there are a lot of checks, use a generic but # harder to read code. keys = [ "apply", "applyR", "define", "defineR", "assign", "loadm", "requirem", "ifm", "elsem", "endifm", "endm"] # Sort the keywords by string content to quickly find erroneously # identical ones. keys.sort_by! {|key| eval(key) } # Check for identical keywords. keys.each_with_index do |key,i| value = eval(key) if i+1 < keys.size then # Check if the next keyword has the same string. nvalue = eval(keys[i+1]) if value == nvalue then # Two keywords with same string. raise "'#{key}:#{value}' and '#{keys[i+1]}:#{nvalue}' keywords must be different." end end end # Seperate the begin of macro keywords from the others (they # are used differently). other_keys = ["elsem", "endifm", "endm"] begin_keys = keys - other_keys # Assign the begin of macro keywords to the corresponding attributes. begin_keys.each do |key| eval("@#{key} = #{key}.to_s") end # Generates the structures used for detecting the keywords. # For the begining of macros. @macro_keys = (begin_keys - other_keys).map do |key| self.instance_variable_get("@#{key}") end.sort!.reverse # For the other keywords. other_keys.each do |key| eval('@'+key+' = Regexp.new("^\s*#{Regexp.escape('+key+')}\s*$")') end # Sets the expand command. @expand = .to_s # Check and set the separator, the glue and the escape. @separator = Regexp.new("(?:#{separator}|#{glue})") @glue = glue.to_s # Initialize the current line number to 0. @number = LineNumber.new(0) # Initialize the macros. @macros = KeywordSearcher.new(@separator) # Initialize the stack for handling the if macros. @if_mode = [] # Create the execution context for the macros. @generator = SaferGenerator.new @context = Object.new # Process the preprocessing parameters. params.each do |k,v| parameter_set(k,v) end #include folder locations to search for load and require @includes = [] (@includes << includes).flatten! end |
Instance Method Details
#apply_macros(line) ⇒ Object
Applies recursively each element of macros
to line
.
NOTE: a same macro is apply only once in a portion of the line.
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 |
# File 'lib/ppr/ppr_core.rb', line 652 def apply_macros(line) # print "apply_macros on line=#{line}\n" # Initialize the expanded line. = "" # Look for a first macro. macro,range = @macros.find(line) while macro do # print "macro.name=#{macro.name}, range=#{range}\n" # If the are some arguments, extract them and cut the macro # of the line. if range.first > 0 then sline = [ line[0..(range.first-1)] ] else sline = [ "" ] end if line[range.last+1] == "(" then # print "Before line=#{line}\n" last = range.last+1 # Last character position of the arguments begin sline += each_argument_range(line,range.last+2).map do |arg_range| last = arg_range.last apply_macros(line[arg_range]) end rescue Exception => e # A problem occurs while extracting the arguments. # Re-raise it after processing its message. raise e, macro.(" " + e.,@number) end range = range.first..(last+1) end if range.last + 1 < line.size then sline << line[(range.last+1)..-1] else sline << "" end # print "After sline=#{sline}\n" result = macro.apply(@number,*(sline[1..-2])) # print "Macro result=#{result}, sline[0]=#{sline[0]} sline[-1]=#{sline[-1]}\n" # Recurse on the modified portion of the line if the macro # requires it result = apply_macros(result) unless macro.final? # print "Final expansion result=#{result}\n" # Join the macro expansion result to the begining of the line # removing the possible glue string. += unglue_back(sline[0]) + result # print "expanded = #{expanded}\n" # The remaining is to treat again after removing the possible # glue string line = unglue_front(sline[-1]) # Look for the next macro macro,range = @macros.find(line) end # Add the remaining of the line to the expansion result. += line # print "## expanded=#{expanded}\n" return end |
#each_argument_range(line, start) ⇒ Object
Iterates over the range each argument of a line
from offset start
.
NOTE: keywords included into a longer one are ignored.
537 538 539 540 541 542 543 544 545 546 547 548 549 550 |
# File 'lib/ppr/ppr_core.rb', line 537 def each_argument_range(line,start) return to_enum(:each_argument_range,line,start) unless block_given? begin # Get the next range range = get_argument_range(line,start) if range.last >= line.size then raise "invalid line for arguments: #{line}" end # Apply the block on the range. yield(range) # Prepares the next range. start = range.last + 2 end while start > 0 and line[start-1] != ")" end |
#get_argument_range(line, start) ⇒ Object
Gets the range of an argument starting at offset start
in line
.
526 527 528 529 530 531 532 |
# File 'lib/ppr/ppr_core.rb', line 526 def get_argument_range(line, start) if start >= line.size then raise "incomplete arguments in macro call." end range = line[start..-1].match(/(\\\)|\\,|[^\),])*/).offset(0) return (range[0]+start)..(range[1]+start-1) end |
#get_macro_def(line) ⇒ Object
Extract a macro definition from a line
if there is one.
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 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 |
# File 'lib/ppr/ppr_core.rb', line 569 def get_macro_def(line) line = line.strip # Locate and identify the macro keyword. macro_type = @macro_keys.find { |mdef| line.start_with?(mdef) } return nil unless macro_type line = line[(macro_type.size)..-1] if /^\w/.match(line) then # Actually the line was starting with a word including @define, # this is not a real macro definition. return nil end # Sets the flags according to the type. final = (macro_type == @define or macro_type == @apply) ? true : false named = (macro_type == @define or macro_type == @defineR or macro_type == @assign) ? true : false # Process the macro. line = line.strip if named then # Handle the case of named macros. # Extract the macro name. name = /[a-zA-Z_]\w*/.match(line).to_s if name.empty? then # Macro with no name, error. raise Macro.(""," macro definition without name.", @number) end line = line[name.size..-1] line = line.strip # Extract the arguments if any # print "line=#{line}\n" par = /^\(\s*[a-zA-Z_]\w*\s*(,\s*[a-zA-Z_]\w*\s*)*\)/.match(line) if par then if macro_type == @assign then # Assignment macro: there should not be any argument. raise Macro.(""," assignement with argument.", @number) end # There are arguments, process them. par = par.to_s # Extract them arguments = par.scan(/[a-zA-Z_]\w*/) line = line[par.size..-1].strip else # Check if there are some invalid arguments if line[0] == "(" then # Invalid arguments. raise Macro.(name, " invalid arguments for macro definition.", @number) end # No argument. arguments = [] end else # Handle the case of unnamed macros. name = "" end case macro_type when @assign then macro = Assign.new(name,@number,self,expand: @expand) when @loadm then macro = Load.new(@number,self,expand: @expand) macro.set_locations(@includes) when @requirem then macro = Require.new(@number,self,expand: @expand) macro.set_locations(@includes) when @ifm then macro = If.new(@number,self,expand: @expand) else macro = Macro.new(name,@number,self, *arguments,final: final,expand: @expand) end # Is it a one-line macro? unless line.empty? then # Yes, adds the content to the macro. macro << line end return macro end |
#is_elsem?(line) ⇒ Boolean
Tells if a line corresponds to an else keyword.
559 560 561 |
# File 'lib/ppr/ppr_core.rb', line 559 def is_elsem?(line) @elsem.match(line.strip) end |
#is_endifm?(line) ⇒ Boolean
Tells if a line corresponds to an endif keyword.
564 565 566 |
# File 'lib/ppr/ppr_core.rb', line 564 def is_endifm?(line) @endifm.match(line.strip) end |
#is_endm?(line) ⇒ Boolean
Tells if a line corresponds to an end keyword.
554 555 556 |
# File 'lib/ppr/ppr_core.rb', line 554 def is_endm?(line) @endm.match(line.strip) end |
#parameter_get(param) ⇒ Object
Gets the value of parameter +param.
495 496 497 |
# File 'lib/ppr/ppr_core.rb', line 495 def parameter_get(param) return @context.instance_variable_get(Ppr.to_attribute(param)) end |
#parameter_set(param, value) ⇒ Object
Sets parameter param
to value
.
489 490 491 492 |
# File 'lib/ppr/ppr_core.rb', line 489 def parameter_set(param,value) # print "Setting #{Ppr.to_attribute(param)} with #{value.to_s}\n" @context.instance_variable_set(Ppr.to_attribute(param),value.to_s) end |
#preprocess(input, output) ⇒ Object
Preprocess an input
stream and write the result to an output
stream.
755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 |
# File 'lib/ppr/ppr_core.rb', line 755 def preprocess(input, output) # # The current list of macros. # @macros = KeywordSearcher.new(@separator) # The macro currently being input. cur_macro = nil # Process the input line by line input.each_line.with_index do |line,i| @number.set(i+1) # check if the line is to skip. if @if_mode[-1] == :skip_to_else then # Skipping until next else... if is_elsem?(line) then # And there is an else, enter in keep to endif mode. @if_mode[-1] = :keep_to_endif end next # Skip. elsif @if_mode[-1] == :keep_to_else then # Keeping until an else is met. if is_elsem?(line) then # And there is an else, enter in skip to endif mode. @if_mode[-1] = :skip_to_endif # And skip current line since it is a keyword. next elsif is_endifm?(line) then # This is the end of the if macro. @if_mode.pop # And skip current line since it is a keyword. next end elsif @if_mode[-1] == :skip_to_endif then # Skipping until next endif. if is_endifm?(line) then # And there is an endif, end the if macro. @if_mode.pop end next # Skip elsif @if_mode[-1] == :keep_to_endif then if is_endifm?(line) @if_mode.pop # And there is an endif, end the if macro. @if_mode.pop # And skip current line since it is a keyword. next end end # No skip. # Check if there are invalid elsem or endifm if is_elsem?(line) then raise Macro.( "invalid #{@elsem} keyword.",@number) elsif is_endifm?(line) then raise Macro.( "invalid #{@endifm} keyword.",@number) end # Is a macro being input? if cur_macro then # Yes. if get_macro_def(line) then # Yet, there is a begining of a macro definition: error raise cur_macro.( "cannot define a new macro within another macro.",@number) end # Is the current macro being closed? if is_endm?(line) then # Yes, close the macro. output << close_macro(cur_macro) # The macro ends here. cur_macro = nil else # No add the line to the current macro. cur_macro << line end else # There in no macro being input. # Check if a new macro definition is present. cur_macro = get_macro_def(line) if cur_macro and !cur_macro.empty? then # This is a one-line macro close it straight await. output << close_macro(cur_macro) # The macro ends here. cur_macro = nil next # The macro definition is not to be kept in the result end next if cur_macro # A new multi-line macro definition is found, # it is not to be kept in the result. # Check if an end of multi-line macro defintion is present. if is_endm?(line) then # Not in a macro, so error. raise Macro.("", "#{@endm} outside a macro definition.",@number) end # Recursively apply the macro calls of the line. line = apply_macros(line) # Write the line to the output. # print ">#{line}" output << line end end end |
#run(&proc) ⇒ Object
Executes a macro in a safe context.
482 483 484 485 486 |
# File 'lib/ppr/ppr_core.rb', line 482 def run(&proc) @generator.run do |__stream__| @context.instance_exec(__stream__,&proc) end end |
#unglue_back(string) ⇒ Object
Restores a string
whose ending may have been glued.
514 515 516 517 518 519 520 521 522 523 |
# File 'lib/ppr/ppr_core.rb', line 514 def unglue_back(string) if string.end_with?(@glue) then # There is a glue, so remove it. string = string[0..(-@glue.size-1)] elsif string.end_with?("\\") then # There is an escape, so remove it. string = string[0..-2] end return string end |
#unglue_front(string) ⇒ Object
Restores a string
whose begining may have been glued.
502 503 504 505 506 507 508 509 510 511 |
# File 'lib/ppr/ppr_core.rb', line 502 def unglue_front(string) if string.start_with?(@glue) then # There is a glue, so remove it. string = string[@glue.size..-1] elsif string.start_with?("\\") then # There is an escape, so remove it. string = string[1..-1] end return string end |