import sys
class GNException(Exception):
pass
def ToGNString(value, allow_dicts = True):
if isinstance(value, str):
if value.find('\n') >= 0:
raise GNException("Trying to print a string with a newline in it.")
return '"' + \
value.replace('\\', '\\\\').replace('"', '\\"').replace('$', '\\$') + \
'"'
if sys.version_info.major < 3 and isinstance(value, unicode):
return ToGNString(value.encode('utf-8'))
if isinstance(value, bool):
if value:
return "true"
return "false"
if isinstance(value, list):
return '[ %s ]' % ', '.join(ToGNString(v) for v in value)
if isinstance(value, dict):
if not allow_dicts:
raise GNException("Attempting to recursively print a dictionary.")
result = ""
for key in sorted(value):
if not isinstance(key, str) and not isinstance(key, unicode):
raise GNException("Dictionary key is not a string.")
result += "%s = %s\n" % (key, ToGNString(value[key], False))
return result
if isinstance(value, int):
return str(value)
raise GNException("Unsupported type when printing to GN.")
def FromGNString(input_string):
parser = GNValueParser(input_string)
return parser.Parse()
def FromGNArgs(input_string):
parser = GNValueParser(input_string)
return parser.ParseArgs()
def UnescapeGNString(value):
result = ''
i = 0
while i < len(value):
if value[i] == '\\':
if i < len(value) - 1:
next_char = value[i + 1]
if next_char in ('$', '"', '\\'):
result += next_char
i += 1
else:
result += '\\'
else:
result += value[i]
i += 1
return result
def _IsDigitOrMinus(char):
return char in "-0123456789"
class GNValueParser(object):
def __init__(self, string):
self.input = string
self.cur = 0
def IsDone(self):
return self.cur == len(self.input)
def ConsumeWhitespace(self):
while not self.IsDone() and self.input[self.cur] in ' \t\n':
self.cur += 1
def ConsumeComment(self):
if self.IsDone() or self.input[self.cur] != '#':
return
while not self.IsDone() and self.input[self.cur] == '#':
while not self.IsDone() and self.input[self.cur] != '\n':
self.cur += 1
if not self.IsDone():
self.cur += 1
def Parse(self):
result = self._ParseAllowTrailing()
self.ConsumeWhitespace()
if not self.IsDone():
raise GNException("Trailing input after parsing:\n " +
self.input[self.cur:])
return result
def ParseArgs(self):
d = {}
self.ConsumeWhitespace()
self.ConsumeComment()
while not self.IsDone():
ident = self._ParseIdent()
self.ConsumeWhitespace()
if self.input[self.cur] != '=':
raise GNException("Unexpected token: " + self.input[self.cur:])
self.cur += 1
self.ConsumeWhitespace()
val = self._ParseAllowTrailing()
self.ConsumeWhitespace()
self.ConsumeComment()
d[ident] = val
return d
def _ParseAllowTrailing(self):
self.ConsumeWhitespace()
if self.IsDone():
raise GNException("Expected input to parse.")
next_char = self.input[self.cur]
if next_char == '[':
return self.ParseList()
elif _IsDigitOrMinus(next_char):
return self.ParseNumber()
elif next_char == '"':
return self.ParseString()
elif self._ConstantFollows('true'):
return True
elif self._ConstantFollows('false'):
return False
else:
raise GNException("Unexpected token: " + self.input[self.cur:])
def _ParseIdent(self):
ident = ''
next_char = self.input[self.cur]
if not next_char.isalpha() and not next_char=='_':
raise GNException("Expected an identifier: " + self.input[self.cur:])
ident += next_char
self.cur += 1
next_char = self.input[self.cur]
while next_char.isalpha() or next_char.isdigit() or next_char=='_':
ident += next_char
self.cur += 1
next_char = self.input[self.cur]
return ident
def ParseNumber(self):
self.ConsumeWhitespace()
if self.IsDone():
raise GNException('Expected number but got nothing.')
begin = self.cur
if not self.IsDone() and _IsDigitOrMinus(self.input[self.cur]):
self.cur += 1
while not self.IsDone() and self.input[self.cur].isdigit():
self.cur += 1
number_string = self.input[begin:self.cur]
if not len(number_string) or number_string == '-':
raise GNException("Not a valid number.")
return int(number_string)
def ParseString(self):
self.ConsumeWhitespace()
if self.IsDone():
raise GNException('Expected string but got nothing.')
if self.input[self.cur] != '"':
raise GNException('Expected string beginning in a " but got:\n ' +
self.input[self.cur:])
self.cur += 1
begin = self.cur
while not self.IsDone() and self.input[self.cur] != '"':
if self.input[self.cur] == '\\':
self.cur += 1 if self.IsDone():
raise GNException("String ends in a backslash in:\n " +
self.input)
self.cur += 1
if self.IsDone():
raise GNException('Unterminated string:\n ' + self.input[begin:])
end = self.cur
self.cur += 1
return UnescapeGNString(self.input[begin:end])
def ParseList(self):
self.ConsumeWhitespace()
if self.IsDone():
raise GNException('Expected list but got nothing.')
if self.input[self.cur] != '[':
raise GNException("Expected [ for list but got:\n " +
self.input[self.cur:])
self.cur += 1
self.ConsumeWhitespace()
if self.IsDone():
raise GNException("Unterminated list:\n " + self.input)
list_result = []
previous_had_trailing_comma = True
while not self.IsDone():
if self.input[self.cur] == ']':
self.cur += 1 return list_result
if not previous_had_trailing_comma:
raise GNException("List items not separated by comma.")
list_result += [ self._ParseAllowTrailing() ]
self.ConsumeWhitespace()
if self.IsDone():
break
previous_had_trailing_comma = self.input[self.cur] == ','
if previous_had_trailing_comma:
self.cur += 1
self.ConsumeWhitespace()
raise GNException("Unterminated list:\n " + self.input)
def _ConstantFollows(self, constant):
end = self.cur + len(constant)
if end > len(self.input):
return False if self.input[self.cur:end] == constant:
self.cur = end
return True
return False