openmsx_utils.py 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. # $Id$
  2. def parseTclValue(value):
  3. '''Parse a string as a Tcl expression and returns a list containing the
  4. words in the original string.
  5. No substitution is done, since this is not a Tcl interpreter, but otherwise
  6. Tcl parsing rules are followed.
  7. http://www.tcl.tk/man/tcl8.4/TclCmd/Tcl.htm
  8. '''
  9. chars = iter(list(value) + [ None ])
  10. ch = chars.next()
  11. words = []
  12. while True:
  13. while ch == ' ':
  14. ch = chars.next()
  15. if ch is None:
  16. return words
  17. quoteType, endChar, keepBackslash = {
  18. '"': ( 'quote', '"', False ),
  19. '{': ( 'brace', '}', True ),
  20. }.get(ch, ( None, ' ', False ) )
  21. if quoteType is not None:
  22. ch = chars.next()
  23. braceLevel = 0
  24. buf = []
  25. while True:
  26. if ch == endChar:
  27. if endChar == '}' and braceLevel > 0:
  28. braceLevel -= 1
  29. else:
  30. break
  31. elif ch == '{':
  32. braceLevel += 1
  33. elif ch == '\\':
  34. if keepBackslash:
  35. buf.append(ch)
  36. ch = chars.next()
  37. # Note: Tcl supports multi-line with backslash; we don't.
  38. if ch is None:
  39. raise ValueError('backslash at end of string')
  40. buf.append(ch)
  41. ch = chars.next()
  42. if ch is None:
  43. break
  44. words.append(u''.join(buf))
  45. if quoteType is not None:
  46. if ch is None:
  47. raise ValueError, 'unterminated open-%s' % quoteType
  48. ch = chars.next()
  49. if ch not in (' ', None):
  50. raise ValueError(
  51. 'extra characters after close-%s: %s'
  52. % (quoteType, ch)
  53. )
  54. if __name__ == '__main__':
  55. # TODO: Make this into a unit test.
  56. for inp, out in (
  57. ( '', [] ),
  58. ( '""', [ '' ] ),
  59. ( '{}', [ '' ] ),
  60. ( 'abc', [ 'abc' ] ),
  61. ( 'abc def', [ 'abc', 'def' ] ),
  62. ( '"abc def"', [ 'abc def' ] ),
  63. ( '{abc def}', [ 'abc def' ] ),
  64. ( '"{abc def}"', [ '{abc def}' ] ),
  65. ( '{"abc def"}', [ '"abc def"' ] ),
  66. ( '"abc}{def"', [ 'abc}{def' ] ),
  67. ( '{abc"def}', [ 'abc"def' ] ),
  68. ( '"a b" {c d} "e f"', [ 'a b', 'c d', 'e f' ] ),
  69. ( '"a b" {c d} "e f"', [ 'a b', 'c d', 'e f' ] ),
  70. ( 'ab { } cd " " ef', [ 'ab', ' ', 'cd', ' ', 'ef' ] ),
  71. ( 'abc"def', [ 'abc"def' ] ),
  72. ( 'abc{def', [ 'abc{def' ] ),
  73. ( r'\"abc\"', [ '"abc"' ] ),
  74. ( r'\{abc\}', [ '{abc}' ] ),
  75. ( r'"\{abc\"def\}"', [ '{abc"def}' ] ),
  76. ( r'{ \{ }', [ r' \{ ' ] ),
  77. ( r'\\', [ '\\' ] ),
  78. ( r'abc\ def', [ 'abc def' ] ),
  79. ( r'{abc\}def}', [ r'abc\}def' ] ),
  80. ( r'{abc\{def}', [ r'abc\{def' ] ),
  81. ( r'"abc\"def"', [ 'abc"def' ] ),
  82. ( 'xyz {{a b c} {d e {f}} g {h}}',
  83. [ 'xyz', '{a b c} {d e {f}} g {h}' ] ),
  84. ):
  85. try:
  86. result = parseTclValue(inp)
  87. except ValueError, message:
  88. print 'ERROR:', inp, ':', message
  89. else:
  90. if result != out:
  91. print 'ERROR:', inp, '->', result, '!=', out
  92. def tclEscape(string):
  93. '''Escape strings so that they don't trigger Tcl functionality.'''
  94. newString = string.replace('\\', '\\\\')
  95. newString = newString.replace('\n', '\\r').replace('$', '\$')
  96. newString = newString.replace('"', '\\"').replace('\'', '\\\'')
  97. newString = newString.replace('[', '\[').replace(']', '\]')
  98. return newString
  99. # this class is used to mark a string as being already escaped
  100. class EscapedStr(str):
  101. pass