module Oppen
class PrintStack
class PrintStackEntry
attr_reader :offset
attr_reader :break_type
def initialize(offset, break_type)
@offset = offset
@break_type = break_type
end
end
attr_reader :buffer
attr_reader :config
attr_reader :genspace
attr_reader :items
attr_reader :new_line
attr_reader :width
attr_reader :space
def initialize(width, new_line, config, space, out)
@buffer = out
@config = config
@genspace =
if space.respond_to?(:call)
raise ArgumentError, 'space argument must be a Proc of arity 1' \
if space.to_proc.arity != 1
space
else
->(n) { space * n }
end
@indent = 0
@items = []
@new_line = new_line
@width = width
@space = width
end
def output
buffer.truncate(buffer.pos)
buffer.string
end
def print(token, token_width, trim_on_break: 0)
case token
in Token::Begin
handle_begin token, token_width
in Token::End
handle_end
in Token::Break
handle_break token, token_width, trim_on_break:
in Token::String
handle_string token, token_width
end
end
def handle_begin(token, token_width)
if token_width > space
type =
if token.break_type == Token::BreakType::CONSISTENT
Token::BreakType::CONSISTENT
else
Token::BreakType::INCONSISTENT
end
if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
indent = token.offset
if !items.empty?
indent += top.offset
end
else
indent = space - token.offset
end
push PrintStackEntry.new indent, type
else
push PrintStackEntry.new 0, Token::BreakType::FITS
end
end
def handle_end
pop
end
def handle_break(token, token_width, trim_on_break: 0)
block = top
case block.break_type
in Token::BreakType::FITS
@space -= token.width
write token
in Token::BreakType::CONSISTENT
@space = block.offset - token.offset
indent =
if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
token.offset
else
width - space
end
erase(trim_on_break)
write token.line_continuation
print_new_line indent
in Token::BreakType::INCONSISTENT
if token_width > space
@space = block.offset - token.offset
indent =
if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
token.offset
else
width - space
end
erase(trim_on_break)
write token.line_continuation
print_new_line indent
else
@space -= token.width
write token
end
end
end
def handle_string(token, token_width)
return if token.value.empty?
@space = [0, space - token_width].max
if @indent.positive?
indent @indent
@indent = 0
end
write token
end
def push(print_stack_entry)
items.append(print_stack_entry)
end
def pop
if items.empty?
raise 'Popping empty stack'
end
items.pop
end
def top
if items.empty?
raise 'Accessing empty stack'
end
items.last
end
def print_new_line(amount)
write new_line
if config&.indent_anchor == Config::IndentAnchor::ON_BEGIN
@space = width - top.offset - amount
@indent = width - space
else
@indent = amount
end
end
def write(obj)
buffer.write(obj.to_s)
end
def erase(count = 0)
raise ArgumentError, "count = #{count} must be non-negative" if count.negative?
buffer.seek(-count, IO::SEEK_CUR)
@space += count
end
def indent(amount)
raise ArgumentError 'Indenting using negative amount' if amount.negative?
write genspace.(amount) if amount.positive?
end
end
end