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: "\\") ⇒ 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: "\\") ⇒ 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.
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 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 |
# File 'lib/ppr/ppr_core.rb', line 386 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: "\\") # 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 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.
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 |
# File 'lib/ppr/ppr_core.rb', line 629 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.
516 517 518 519 520 521 522 523 524 525 526 527 528 529 |
# File 'lib/ppr/ppr_core.rb', line 516 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
.
505 506 507 508 509 510 511 |
# File 'lib/ppr/ppr_core.rb', line 505 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.
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 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 |
# File 'lib/ppr/ppr_core.rb', line 548 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) when @requirem then macro = Require.new(@number,self,expand: @expand) 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.
538 539 540 |
# File 'lib/ppr/ppr_core.rb', line 538 def is_elsem?(line) @elsem.match(line) end |
#is_endifm?(line) ⇒ Boolean
Tells if a line corresponds to an endif keyword.
543 544 545 |
# File 'lib/ppr/ppr_core.rb', line 543 def is_endifm?(line) @endifm.match(line) end |
#is_endm?(line) ⇒ Boolean
Tells if a line corresponds to an end keyword.
533 534 535 |
# File 'lib/ppr/ppr_core.rb', line 533 def is_endm?(line) @endm.match(line) end |
#parameter_get(param) ⇒ Object
Gets the value of parameter +param.
474 475 476 |
# File 'lib/ppr/ppr_core.rb', line 474 def parameter_get(param) return @context.instance_variable_get(Ppr.to_attribute(param)) end |
#parameter_set(param, value) ⇒ Object
Sets parameter param
to value
.
468 469 470 471 |
# File 'lib/ppr/ppr_core.rb', line 468 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.
732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 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 |
# File 'lib/ppr/ppr_core.rb', line 732 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.
461 462 463 464 465 |
# File 'lib/ppr/ppr_core.rb', line 461 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.
493 494 495 496 497 498 499 500 501 502 |
# File 'lib/ppr/ppr_core.rb', line 493 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.
481 482 483 484 485 486 487 488 489 490 |
# File 'lib/ppr/ppr_core.rb', line 481 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 |