1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
|
# coding: utf-8
module Rsec #:nodoc:
# error class for rescue
class SyntaxError < StandardError
attr_reader :msg, :line_text, :line, :col
# constructor
def initialize msg, line_text, line, col
@msg, @line_text, @line, @col = msg, line_text, line, col
end
# info with source position
def to_s
%Q<#@msg\n#@line_text\n#{' ' * @col}^>
end
end
# parse context inherits from StringScanner<br/>
# <br/>
# attributes:<br/>
# <pre>
# [R] string: string to parse
# [RW] pos: current position
# [R] source: source file name
# [R] current_line_text: current line text
# [R] cache: for memoization
# </pre>
class ParseContext < StringScanner
attr_reader :source, :cache, :last_fail_pos
attr_accessor :attr_names
def initialize str, source
super(str)
@source = source
@cache = {}
@last_fail_pos = 0
@last_fail_mask = 0
end
# clear packrat parser cache
def clear_cache
@cache.clear
end
# add fail message
def on_fail mask
if pos > @last_fail_pos
@last_fail_pos = pos
@last_fail_mask = mask
elsif pos == @last_fail_pos
@last_fail_mask |= mask
end
end
# generate parse error
def generate_error source
if self.pos <= @last_fail_pos
line = line @last_fail_pos
col = col @last_fail_pos
line_text = line_text @last_fail_pos
expect_tokens = Fail.get_tokens @last_fail_mask
expects = ", expect token [ #{expect_tokens.join ' | '} ]"
else
line = line pos
col = col pos
line_text = line_text pos
expects = nil
end
msg = "\nin #{source}:#{line} at #{col}#{expects}"
SyntaxError.new msg, line_text, line, col
end
# get line number
def line pos
string[0...pos].count("\n") + 1
end
# get column number: position in line
def col pos
return 1 if pos == 0
newline_pos = string.rindex "\n", pos - 1
if newline_pos
pos - newline_pos
else
pos + 1
end
end
# get line text containing pos
# the text is 80 at most
def line_text pos
from = string.rindex "\n", pos
(from = string.rindex "\n", pos - 1) if from == pos
from = from ? from + 1 : 0
from = pos - 40 if (from < pos - 40)
to = string.index("\n", pos)
to = to ? to - 1 : string.size
to = pos + 40 if (to > pos + 40)
string[from..to]
end
end
# the invalid token
INVALID = Object.new
class << INVALID
def to_str
'INVALID_TOKEN'
end
alias :[] :==
alias inspect to_str
end
end
|