#!/usr/bin/env python # # Copyright 2026 David Vazgenovich Shakaryan import re import copy WS = set(' \t') WSNL = {*WS, '\n'} ESCAPES = { '\n': '', '\\': '\\', '"': '"', 'n': '\n', 't': '\t', } RE_IDENT = re.compile(r'[A-Za-z0-9_]+') RE_NUM = re.compile( r'[+-]?(?:' r'0x[0-9a-fA-F]+(?:_[0-9a-fA-F]+)*|' r'0o[0-7]+(?:_[0-7]+)*|' r'0b[01]+(?:_[01]+)*|' r'(?:0|[1-9][0-9]*(?:_[0-9]+)*)' r'(\.[0-9]+(?:_[0-9]+)*(?:[eE][+-]?[0-9]+(?:_[0-9]+)*)?|' r'[eE][+-]?[0-9]+(?:_[0-9]+)*)?|' r'(inf|nan)' r')') def _raise_unexpected_eof(ctx): raise SyntaxError(f'Unexpected end of input while parsing {ctx}') from None def _raise_expected_char(char, pos): if type(char) is tuple: s = ' or '.join(repr(c) for c in char) else: s = repr(char) raise SyntaxError(f'Expected {s} at position {pos}') from None def _raise_unexpected_char(char, pos, ctx=None): ctx = f' {ctx}' if ctx else '' raise SyntaxError( f'Unexpected {repr(char)}{ctx} at position {pos}') from None def _raise_invalid_escape(seq, pos): seq = ''.join( '\\' if (r := repr(c)[1:-1]) == '\\\\' else r if len(r) == 1 else f'<{r}>' for c in seq) raise SyntaxError(f'Invalid escape sequence {seq} at pos {pos}') from None def skip_chars(src, pos, chars, skip_comments=False): try: while True: char = src[pos] if skip_comments and char == '#': while src[pos] != '\n': pos += 1 continue if char == '\\': try: if src[pos+1] != '\n': _raise_expected_char('\n', pos + 1) except IndexError: _raise_unexpected_eof('line continuation') pos += 2 continue if char not in chars: break pos += 1 except IndexError: pass return pos def skip(src, pos, depth): if depth: return skip_chars(src, pos, WSNL, skip_comments=True) return skip_chars(src, pos, WS) def ensure_path(d, path): curr = d for n in path: if type(curr) is not dict: raise SyntaxError( f'Cannot resolve {repr(n)} in {type(curr).__name__}') curr = curr.setdefault(n, {}) return curr def dict_unpack(d, val): if type(val) is not dict: raise SyntaxError(f'Cannot dict-unpack {type(val).__name__}') for k, v in val.items(): d[k] = v def dict_assign(d, path, val): tgt = ensure_path(d, path[:-1]) if type(tgt) is not dict: raise SyntaxError( f"Cannot assign key '{path[-1]}' in {type(tgt).__name__}") tgt[path[-1]] = val def dict_delete(d, path): try: for n in path[:-1]: d = d[n] del d[path[-1]] except KeyError, TypeError: return def parse_dict_unpack(src, pos, depth, root, sect): try: if src[pos+1] == '*': pos += 2 else: _raise_expected_char('*', pos + 1) except IndexError: _raise_unexpected_eof('dict unpack') pos = skip(src, pos, depth) return parse_value(src, pos, depth, root, sect) def parse_sequence(src, pos, depth, delim, term, on_item): pos += 1 depth += 1 while True: pos = skip(src, pos, depth) try: char = src[pos] except IndexError: _raise_unexpected_eof('sequence') if char == term: return pos + 1 pos = on_item(src, pos, depth) pos = skip(src, pos, depth) try: char = src[pos] if char == delim: pos += 1 elif char != term: _raise_expected_char((delim, term), pos) except IndexError: _raise_unexpected_eof('sequence') def parse_inline_list(src, pos, depth, root, sect): res = [] def on_item(src, pos, depth): unpack = False if src[pos] == '*': unpack = True pos = skip(src, pos + 1, depth) pos, val = parse_value(src, pos, depth, root, sect) if unpack: if type(val) is not list: raise SyntaxError(f'Cannot list-unpack {type(val).__name__}') res.extend(val) else: res.append(val) return pos pos = parse_sequence(src, pos, depth, ',', ']', on_item) return pos, res def parse_inline_dict(src, pos, depth, root, sect): res = {} def on_item(src, pos, depth): char = src[pos] if char == '*': pos, val = parse_dict_unpack(src, pos, depth, root, sect) dict_unpack(res, val) else: pos, key, val = parse_assign(src, pos, depth, root, sect) dict_assign(res, key, val) return pos pos = parse_sequence(src, pos, depth, ',', '}', on_item) return pos, res def parse_str(src, pos, multiline=False): try: if multiline: pos += 3 if src[pos] == '\n': pos += 1 else: pos += 1 start = pos res = '' while True: curr = src[pos] if curr == '"' and (not multiline or src.startswith('"""', pos)): res += src[start:pos] break if curr == '\\': res += src[start:pos] nxt = src[pos+1] if nxt == 'u': if src[pos+2] != '{': _raise_expected_char('{', pos + 2) end = src.find('}', pos + 3) if end == -1: raise IndexError code = src[pos+3:end] try: code_int = int(code, 16) if not (0 <= code_int <= 0xd7ff or 0xe000 <= code_int <= 0x10ffff): _raise_invalid_escape(f'\\u{{{code}}}', pos) res += chr(code_int) except ValueError: _raise_invalid_escape(f'\\u{{{code}}}', pos) pos = end + 1 else: try: res += ESCAPES[nxt] except KeyError: _raise_invalid_escape(f'\\{nxt}', pos) pos += 2 start = pos continue code = ord(curr) if ((code < 32 and (not multiline or code != 10 and code != 9)) or code == 127): _raise_unexpected_char(curr, pos, ctx='in string') pos += 1 except IndexError: _raise_unexpected_eof('string') return pos + (3 if multiline else 1), res def parse_raw_str(src, pos, multiline=False): try: if multiline: pos += 3 if src[pos] == '\n': pos += 1 else: pos += 1 start = pos while (src[pos] != "'" or (multiline and not src.startswith("'''", pos))): code = ord(src[pos]) if ((code < 32 and (not multiline or code != 10 and code != 9)) or code == 127): _raise_unexpected_char(src[pos], pos, ctx='in raw string') pos += 1 except IndexError: _raise_unexpected_eof('raw string') return pos + (3 if multiline else 1), src[start:pos] def parse_literal(src, pos, depth, root, sect): char = src[pos] # guaranteed to exist by parse_value() if char == "'": return parse_raw_str(src, pos, multiline=src.startswith("'''", pos)) if char == '"': return parse_str(src, pos, multiline=src.startswith('"""', pos)) if char == 't': if src.startswith('true', pos): return pos + 4, True if char == 'f': if src.startswith('false', pos): return pos + 5, False if char == 'n': if src.startswith('null', pos): return pos + 4, None if char == '{': return parse_inline_dict(src, pos, depth, root, sect) if char == '[': return parse_inline_list(src, pos, depth, root, sect) if (m := RE_NUM.match(src, pos)): return m.end(), float(m[0]) if m.lastindex else int(m[0], 0) _raise_unexpected_char(char, pos) def parse_value(src, pos, depth, root, sect): try: char = src[pos] except IndexError: _raise_unexpected_eof('value') ref = root if char == '$' else sect if char == '@' else None if ref is None: pos, val = parse_literal(src, pos, depth, root, sect) if type(val) is str: while True: pos = skip(src, pos, depth) try: char = src[pos] except IndexError: break if char == "'": pos, v = parse_raw_str( src, pos, multiline=src.startswith("'''", pos)) val += v elif char == '"': pos, v = parse_str( src, pos, multiline=src.startswith('"""', pos)) val += v else: break else: pos = skip(src, pos + 1, depth) pos, key = parse_key_segment(src, pos) try: val = ref[key] except KeyError: if ref is root: raise SyntaxError( f'Invalid global reference {repr(key)}') from None else: raise SyntaxError( f'Invalid local reference {repr(key)}') from None pos, val = parse_selectors(src, pos, depth, root, sect, val, copy_=ref) return pos, val def parse_selectors(src, pos, depth, root, sect, val, copy_=False): while True: pos = skip(src, pos, depth) try: char = src[pos] except IndexError: break if char == '.': pos = skip(src, pos + 1, depth) pos, key = parse_key_segment(src, pos) elif char == '[': depth += 1 pos = skip(src, pos + 1, depth) try: nxt = src[pos] except IndexError: _raise_unexpected_eof('selector') continue_ = False if nxt == '^': pos = skip(src, pos + 1, depth) def on_item(src, pos, depth): nonlocal val, copy_ pos, key = parse_key(src, pos, depth) if copy_: val = copy.deepcopy(val) copy_ = False dict_delete(val, key) return pos if src[pos] == '{': pos = parse_sequence(src, pos, depth, ',', '}', on_item) else: pos = on_item(src, pos, depth) continue_ = True elif nxt == '&': pos = skip(src, pos + 1, depth) nval = {} def on_item(src, pos, depth): pos, key = parse_key(src, pos, depth) v = val for n in key: try: v = v[n] except KeyError, TypeError: return pos dict_assign(nval, key, v) return pos if src[pos] == '{': pos = parse_sequence(src, pos, depth, ',', '}', on_item) else: pos = on_item(src, pos, depth) val = nval continue_ = True else: if nxt == ':': key = None else: pos, key = parse_value(src, pos, depth, root, sect) pos = skip(src, pos, depth) try: nxt = src[pos] except IndexError: _raise_unexpected_eof('selector') if nxt == ':': pos = skip(src, pos + 1, depth) try: nxt = src[pos] except IndexError: _raise_unexpected_eof('selector') if nxt == ']': stop = None else: pos, stop = parse_value(src, pos, depth, root, sect) key = slice(key, stop) pos = skip(src, pos, depth) try: if src[pos] != ']': _raise_expected_char(']', pos) except IndexError: _raise_unexpected_eof('selector') pos += 1 depth -= 1 if continue_: continue else: break try: val = val[key] except KeyError, IndexError, TypeError: raise SyntaxError( f'Cannot resolve {repr(key)} in {type(val).__name__}' ) from None return pos, copy.deepcopy(val) if copy_ else val def parse_key_segment(src, pos): try: char = src[pos] except IndexError: _raise_unexpected_eof('key segment') if char == "'": return parse_raw_str(src, pos) if char == '"': return parse_str(src, pos) if (m := RE_IDENT.match(src, pos)): return m.end(), m[0] else: raise SyntaxError(f'Invalid key identifier at position {pos}') def parse_key(src, pos, depth): pos, seg = parse_key_segment(src, pos) key = (seg,) while True: pos = skip(src, pos, depth) try: char = src[pos] except IndexError: return pos, key if char != '.': return pos, key pos = skip(src, pos + 1, depth) pos, seg = parse_key_segment(src, pos) key += (seg,) def parse_assign(src, pos, depth, root, sect): pos, key = parse_key(src, pos, depth) pos = skip(src, pos, depth) try: char = src[pos] except IndexError: char = None if char != '=': _raise_expected_char('=', pos) pos += 1 pos = skip(src, pos, depth) pos, val = parse_value(src, pos, depth, root, sect) return pos, key, val def parse_dict_header(src, pos, depth, root): depth += 1 pos = skip(src, pos + 1, depth) try: if src[pos] == ']': return pos + 1, root except IndexError: _raise_unexpected_eof('header') pos, key = parse_key(src, pos, depth) try: if src[pos] != ']': _raise_expected_char(']', pos) except IndexError: _raise_unexpected_eof('header') sect = ensure_path(root, key) if type(sect) is not dict: raise SyntaxError(f'Cannot enter {type(sect).__name__} context: {key}') return pos + 1, sect def parse_list_header(src, pos, depth, root): depth += 1 pos = skip(src, pos + 2, depth) pos, key = parse_key(src, pos, depth) if not src.startswith(']]', pos): _raise_expected_char(']]', pos) if len(key) > 1: d = ensure_path(root, key[:-1]) if type(d) is not dict: raise SyntaxError( f'Cannot resolve {repr(key[-1])} in {type(d).__name__}') else: d = root l = d.setdefault(key[-1], []) if type(l) is not list: raise SyntaxError(f'Cannot append to {type(l).__name__}: {key}') sect = {} l.append(sect) return pos + 2, sect def loads(src): root = {} sect = root pos = 0 while True: pos = skip_chars(src, pos, WSNL, skip_comments=True) try: char = src[pos] except IndexError: break if char == '[': try: char = src[pos+1] except IndexError: _raise_unexpected_eof('header') if char == '[': pos, sect = parse_list_header(src, pos, 0, root) else: pos, sect = parse_dict_header(src, pos, 0, root) elif char == '*': pos, val = parse_dict_unpack(src, pos, 0, root, sect) dict_unpack(sect, val) else: pos, key, val = parse_assign(src, pos, 0, root, sect) dict_assign(sect, key, val) pos = skip_chars(src, pos, WS, skip_comments=True) try: if src[pos] != '\n': _raise_expected_char('\n', pos) except IndexError: pass return root def load(f): b = f.read() try: src = b.decode() except AttributeError: raise TypeError('File must be opened in binary mode') return loads(src)