Advertisement
jewalky

Untitled

Feb 27th, 2016
154
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 13.17 KB | None | 0 0
  1. # this should output object tree from own config format
  2. import re
  3.  
  4.  
  5. class ConfigTokenizerError(Exception):
  6.     pass
  7.    
  8. class ConfigParserError(Exception):
  9.     pass
  10.    
  11. class ConfigError(Exception):
  12.     pass
  13.    
  14. class ConfigTokenizer:
  15.     def __init__(self, buf):
  16.         self.buffer = buf
  17.         self.line = 0
  18.         self.position = 0
  19.        
  20.     # token is either identifier, integer, float, string or other.
  21.     # comments are ignored.
  22.     def require_identifier(self):
  23.         v = self.get_identifier()
  24.         if v is None:
  25.             raise ConfigTokenizerError('Expected identifier at line %d' % self.line)
  26.         return v
  27.        
  28.     def get_identifier(self):
  29.         # identifier is something that contains only A-Za-z0-9_
  30.         try:
  31.             v = re.search(r'^([A-Za-z0-9_]+).*', self.buffer[self.position:]).group(1)
  32.             self.position += len(v)
  33.         except:
  34.             return None
  35.         return v
  36.        
  37.     def require_whitespace(self):
  38.         v = self.get_whitespace()
  39.         if v is None:
  40.             raise ConfigTokenizerError('Expected whitespace at line %d' % self.line)
  41.         return v
  42.        
  43.     def get_only_whitespace(self):
  44.         try:
  45.             v = re.search(r'^([\s\t\r\n]+).*', self.buffer[self.position:]).group(1)
  46.             if v is None or not v:
  47.                 return 0
  48.             self.position += len(v)
  49.             # check for newlines, advance line for all newlines
  50.             self.line += v.count('\n')
  51.             return len(v)
  52.         except:
  53.             return 0
  54.        
  55.     def get_whitespace(self):
  56.         gwl = 0
  57.         while True:
  58.             wl = 0
  59.             wl += self.get_only_whitespace()
  60.             # try to find nearest one-line comment
  61.             try:
  62.                 v = re.search(r'^(\/\/[^\n]*)', self.buffer[self.position:]).group(1)
  63.                 # imagine v is found.
  64.                 wl += len(v)
  65.                 self.position += len(v)
  66.                 # usually adds one newline.
  67.                 self.line += v.count('\n')
  68.             except:
  69.                 pass
  70.             wl += self.get_only_whitespace()
  71.             # try to find nearest multiline comment
  72.             if self.buffer[self.position:self.position+2] == '/*':
  73.                 npos = self.buffer.find('*/', self.position+2)
  74.                 if npos < 0:
  75.                     raise ConfigTokenizerError('No multiline comment end found at line %d' % self.line)
  76.                 v = self.buffer[self.position:npos+2]
  77.                 # v should now have all comment block
  78.                 wl += len(v)
  79.                 self.position += len(v)
  80.                 # add lines
  81.                 self.line += v.count('\n')
  82.             wl += self.get_only_whitespace()
  83.             gwl += wl
  84.             if wl <= 0:
  85.                 break
  86.         return gwl
  87.        
  88.     # "other" are characters (), {}, [], -, +, /, *, =, !, ?, :, ;.
  89.     # when you require this, you can also specify which character you need.
  90.     def require_other(self, which='(){}[]-+/*=!?:;.'):
  91.         v = self.get_other()
  92.         if v is None or v not in which:
  93.             raise ConfigTokenizerError('Expected one of "%s" at line %d' % (which, self.line))
  94.         return v
  95.        
  96.     def get_other(self, which='(){}[]-+/*=!?:;.'):
  97.         try:
  98.             v = re.search(r'^([\(\)\{\}\[\]\-\+/\*\=\!\?\:\;\.]).*', self.buffer[self.position:]).group(1)
  99.             if v not in which:
  100.                 return None
  101.             self.position += len(v)
  102.         except:
  103.             return None
  104.         return v
  105.        
  106.     def require_string(self):
  107.         v = self.get_string()
  108.         if v is None:
  109.             raise ConfigTokenizerError('Expected string literal at line %d' % self.line)
  110.         return v
  111.        
  112.     def get_escape(self, sequence):
  113.         seq_dict = {'n': '\n', 'r': '\r', 't': '\t', '\\': '\\', '"': '"'}
  114.         try:
  115.             if sequence[0] == 'x' or sequence[0] == 'u': # hexadecimal character
  116.                 v = re.search(r'^([0-9A-Fa-f]{1,4}).*', sequence[1:]).group(1)
  117.                 return len(v)+1, unichr(int(v, 16))
  118.             elif sequence[0] == '0': # octal character
  119.                 v = re.search(r'^([0-8]{1,6}).*', sequence[1:]).group(1)
  120.                 return len(v)+1, unichr(int(v, 8))
  121.             elif sequence[0] in seq_dict: # simple character
  122.                 return 1, seq_dict[sequence[0]]
  123.         except:
  124.             pass
  125.         return None, None # unknown/invalid escape causes discarding of whole string
  126.        
  127.     def get_string(self):
  128.         try:
  129.             v = re.search(r'^(\"([^\\\"]|\\.)*\").*', self.buffer[self.position:]).group(1)
  130.             self.position += len(v)
  131.             # now that we got the string, unescape it
  132.             v = v[1:-1]
  133.             ov = ''
  134.             i = -1
  135.             while i+1 < len(v):
  136.                 i += 1
  137.                 if v[i] == '\\': # escaped character. this is either \0####, \x####, \u####, \r, \n, \t or \\ or \"
  138.                     charlen, char = self.get_escape(v[i+1:])
  139.                     if char is None:
  140.                         return None
  141.                     ov += char
  142.                     i += charlen
  143.                 else:
  144.                     ov += v[i]
  145.             return ov
  146.         except:
  147.             pass
  148.         return None
  149.        
  150.     def require_integer(self):
  151.         v = self.get_integer()
  152.         if v is None:
  153.             raise ConfigTokenizerError('Expected integer at line %d' % self.line)
  154.         return v
  155.        
  156.     def get_integer(self):
  157.         try:
  158.             # formats allowed:
  159.             # 0x... (hexadecimal)
  160.             # 0... (octal)
  161.             # decimal
  162.             # allow for negative.
  163.             mul = 1
  164.             if self.buffer[self.position] == '-':
  165.                 mul = -1
  166.                 self.position += 1
  167.             v = re.search(r'^(0x[A-Fa-f0-9]+|0[0-8]+|[0-9]+).*', self.buffer[self.position:]).group(1)
  168.             self.position += len(v)
  169.             if v[0:2] == '0x':
  170.                 return int(v[2:], 16) * mul
  171.             elif v[0] == '0':
  172.                 return int(v[1:], 8) * mul
  173.             return int(v, 10) * mul
  174.         except:
  175.             return None
  176.            
  177.     def require_float(self):
  178.         v = self.get_float()
  179.         if v is None:
  180.             raise ConfigTokenizerError('Expected float at line %d' % self.line)
  181.         return v
  182.  
  183.     def get_float(self):
  184.         try:
  185.             # any count of decimal digits
  186.             # then dot (required)
  187.             # then any count of decimal digits and e or - (this is a hack to allow 1e-2 notation)
  188.             v = re.search(r'^([0-9]*([0-9\-e]+|\.[0-9\-e]*)?).*', self.buffer[self.position:]).group(1)
  189.             self.position += len(v)
  190.             return float(v)
  191.         except:
  192.             return None
  193.            
  194.     def is_eof(self):
  195.         return self.position >= len(self.buffer)
  196.  
  197.  
  198. class ConfigParser:
  199.     def is_directory_type(self, type):
  200.         return (type in ['directory', 'test'])
  201.        
  202.     def __init__(self, filename):
  203.         self.data = {'type': 'directory', 'value': {}} # data = list of directories
  204.    
  205.         with open(filename, 'r') as f:
  206.             tr = ConfigTokenizer(f.read())
  207.        
  208.         dir_stack = [self.data]
  209.         while not tr.is_eof():
  210.             c_type = tr.get_identifier()
  211.             if c_type is not None:
  212.                 tr.get_whitespace()
  213.                 c_name = tr.require_identifier()
  214.                 tr.get_whitespace()
  215.                 # then should come either = or {, depending on type.
  216.                 # we also might get :, if this inherits from another field (that field should exist already)
  217.                 c_parent = None
  218.                 if self.is_directory_type(c_type):
  219.                     c_inheritance = tr.get_other(':')
  220.                     if c_inheritance is not None:
  221.                         tr.get_whitespace()
  222.                         c_parent = tr.require_identifier()
  223.                         while True:
  224.                             if tr.get_other('.') is not None:
  225.                                 c_parent_part = tr.require_identifier()
  226.                                 c_parent += '.'+c_parent_part
  227.                                 continue
  228.                             break
  229.                         tr.get_whitespace()
  230.                 c_operator = tr.require_other('{=')
  231.                 if c_operator == '=':
  232.                     tr.get_whitespace()
  233.                     if c_type == 'string':
  234.                         c_value = tr.require_string()
  235.                     elif c_type == 'int':
  236.                         c_value = tr.require_integer()
  237.                     elif c_type == 'float':
  238.                         c_value = tr.require_float()
  239.                     elif c_type == 'bool':
  240.                         c_value = tr.require_identifier()
  241.                         if c_value == 'true':
  242.                             c_value = True
  243.                         elif c_value == 'false':
  244.                             c_value = False
  245.                         else:
  246.                             raise ConfigParserError('"true" or "false" expected at line %d' % tr.line)
  247.                     else:
  248.                         raise ConfigParserError('Type %s is not a field type at line %d' % (c_type, tr.line))
  249.                     if c_name in dir_stack[-1]['value']: # already exists, duplicate
  250.                         raise ConfigParserError('Duplicate value at line %d' % tr.line)
  251.                     dir_stack[-1]['value'][c_name] = {'type': c_type, 'value': c_value}
  252.                 else: # directory
  253.                     if not self.is_directory_type(c_type):
  254.                         raise ConfigParserError('Type %s is not a directory type at line %d' % (c_type, tr.line))
  255.                     # otherwise make dir
  256.                     if c_name in dir_stack[-1]['value']: # already exists, duplicate
  257.                         raise ConfigParserError('Duplicate value at line %d' % tr.line)
  258.                     # process inheritance
  259.                     dir = {'type': c_type, 'value': {}}
  260.                     if c_parent is not None:
  261.                         if c_parent not in dir_stack[-1]['value']:
  262.                             parent = self.get_node(c_parent)
  263.                             if parent is None:
  264.                                 raise ConfigParserError('Unknown inherited directory at line %d' % tr.line)
  265.                         else:
  266.                             parent = dir_stack[-1]['value'][c_parent]
  267.                         if not self.is_directory_type(parent['type']):
  268.                             raise ConfigParserError('Inherited field is not a directory type (%s) at line %d' % (parent['type'], tr.line))
  269.                         for k in parent['value']:
  270.                             dir['value'][k] = parent['value'][k]
  271.                     dir_stack[-1]['value'][c_name] = dir
  272.                     dir_stack.append(dir)
  273.                 tr.get_whitespace()
  274.             # check for closing bracket if we're in a directory
  275.             closing_brace = tr.get_other('}')
  276.             if closing_brace is not None:
  277.                 if len(dir_stack) == 1:
  278.                     raise ConfigParserError('Bracket mismatch at line %d' % tr.line)
  279.                 dir_stack = dir_stack[:-1] # go one level back
  280.                 tr.get_whitespace()
  281.  
  282.     # get value by path (dot-separated)
  283.     def get_node(self, path):
  284.         path = path.split('.')
  285.         cnode = self.data
  286.         for i in range(len(path)):
  287.             if not path[i]:
  288.                 continue
  289.             if not self.is_directory_type(cnode['type']):
  290.                 return None
  291.             if path[i] not in cnode['value']:
  292.                 return None
  293.             cnode = cnode['value'][path[i]]
  294.         return cnode
  295.        
  296.     def get_json_from_node(self, node):
  297.         if self.is_directory_type(node['type']):
  298.             # return map of objects
  299.             out = {}
  300.             for k in node['value']:
  301.                 out[k] = self.get_json_from_node(node['value'][k])
  302.             return out
  303.         else:
  304.             return node['value']
  305.        
  306.     def get_json(self, path):
  307.         node = self.get_node(path)
  308.         return self.get_json_from_node(node)
  309.        
  310.     def get_int(self, path):
  311.         node = self.get_node(path)
  312.         if node is None or not isinstance(node['value'], int):
  313.             raise ConfigError("Path %s not found or not an integer"%path)
  314.         return node['value']
  315.        
  316.     def get_float(self, path):
  317.         node = self.get_node(path)
  318.         if node is None or not isinstance(node['value'], float):
  319.             raise ConfigError("Path %s not found or not a float"%path)
  320.         return node['value']
  321.        
  322.     def get_bool(self, path):
  323.         node = self.get_node(path)
  324.         if node is None or not isinstance(node['value'], bool):
  325.             raise ConfigError("Path %s not found or not a bool"%path)
  326.         return node['value']
  327.        
  328.     def get_string(self, path):
  329.         node = self.get_node(path)
  330.         if node is None or (not isinstance(node['value'], unicode) and not isinstance(node['value'], str)):
  331.             raise ConfigError("Path %s not found or not a string"%path)
  332.         return node['value']
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement