lib/spoon/util/indent_parser.rb
require "parslet"
module Spoon
module Util
class IndentParser < Parslet::Parser
def initialize
super
@stack = [0]
@prev_stack = [0]
@current = 0
@last = 0
@matcher = /[ \t]/
end
# Check indentation level at current position and adjust stack
def check_indentation(source)
@current = 0
while source.matches?(@matcher)
source.consume(1)
@current += 1
end
@last = (@stack[@stack.length - 1] == nil ? 0 : @stack[@stack.length - 1])
if @current > @last
@prev_stack.push @last
@stack.push @current
elsif @current < @last
while @current != @stack[@stack.length - 1] && !@stack.empty?
@stack.pop
end
end
AlwaysMatch.new
end
# We need to do this, so next samedent won't be skipped
def fix_position(source)
source.bytepos = source.bytepos - @current
if @current <= @prev_stack[@prev_stack.length - 1]
while @last != @current && !@prev_stack.empty?
@last = @prev_stack.pop
end
return AlwaysMatch.new if @last == @current
end
NeverMatch.new "Mismatched indentation level"
end
rule(:checkdent) {
dynamic { |source|
check_indentation(source)
}
}
rule(:indent) {
dynamic {
@current > @last ? AlwaysMatch.new : NeverMatch.new("Not an indent")
}
}
rule(:dedent) {
dynamic { |source|
@current < @last ? fix_position(source) : NeverMatch.new("Not a dedent")
}
}
rule(:samedent) {
dynamic {
@current == @last ? AlwaysMatch.new : NeverMatch.new("Not a samedent")
}
}
end
class AlwaysMatch < Parslet::Atoms::Base
def try(_, _, _)
succ("")
end
end
class NeverMatch < Parslet::Atoms::Base
attr_accessor :msg
def initialize(msg = "ignore")
self.msg = msg
end
def try(source, context, _)
context.err(self, source, msg)
end
end
end
end