Source code for sirepo.template.line_parser
# -*- coding: utf-8 -*-
"""Simple line parser.
Parses a line of input one character at a time.
:copyright: Copyright (c) 2016 RadiaSoft LLC. All Rights Reserved.
:license: http://www.apache.org/licenses/LICENSE-2.0.html
"""
from __future__ import absolute_import, division, print_function
import re
[docs]
class LineParser(object):
"""Parses a line of input one character at a time.
Call set_line() prior to other metho calls.
"""
def __init__(self, next_id=0):
self._id = next_id
self.index = 0
self.line = ""
self.line_number = 0
[docs]
def assert_char(self, char):
"""Raises an Error unless the current character matches"""
if self.next_char() != char:
self.raise_error("expected {}".format(char))
self.ignore_whitespace()
[docs]
def assert_end_of_line(self, comment_char="!"):
"""Raises an error unless the remaining line is empty"""
self.ignore_whitespace()
if self.has_char() and self.peek_char() != comment_char:
self.raise_error("left-over input")
[docs]
def has_char(self):
return self.index < len(self.line)
[docs]
def ignore_whitespace(self):
"""Skip whitespace"""
while self.has_char() and re.search(r"\s", self.peek_char()):
self.next_char()
[docs]
def increment_line_number(self):
self.line_number += 1
[docs]
def get_index(self):
return self.index
[docs]
def next_char(self):
"""Advance to the next character. Returns None if end of line"""
if self.has_char():
current = self.line[self.index]
self.index += 1
return current
return None
[docs]
def next_id(self):
self._id += 1
return self._id
[docs]
def parse_quoted_value(self):
self.assert_char('"')
value = self.read_until('"')
if self.line[self.index - 1] == "\\":
value += '"' + self.parse_quoted_value()
else:
self.assert_char('"')
return value
[docs]
def parse_value(self, end_regex=r"[\s,=\!)*]"):
"""Parses a value, possibly quoted."""
if self.peek_char() == '"':
return self.parse_quoted_value()
return self.read_until(end_regex)
[docs]
def peek_char(self):
if self.has_char():
return self.line[self.index]
return None
[docs]
def raise_error(self, message):
raise IOError(
"line {}, {}: {}".format(self.line_number, message, self.line[self.index :])
)
[docs]
def read_until(self, regex):
"""Reads until the end-of-line or the character regex is matched"""
value = ""
while self.has_char() and not re.search(regex, self.peek_char()):
value += self.next_char()
self.ignore_whitespace()
return value
[docs]
def reset_index(self, index):
"""Reset the character index"""
self.index = index
[docs]
def set_line(self, line):
"""Prepares a line for parsing."""
self.line = line
self.index = 0