← Files Lukka Digital Assets IntelARCHIVED FILE

skills/large-data-processing/references/lukka_notation.py

5.46 KB · Nov 14, 2026 · 01:23 UTC

↓ Download file

"""Lukka compact notation parser.

Hosted Lukka MCP servers return responses in a compact object notation -
unquoted keys, bare-string values (addresses, ISO timestamps, enum
values), ``_`` for null. This is NOT RFC 8259 JSON, so ``json.loads``
rejects it.

Usage:
    from lukka_notation import load

    data = load('/tmp/response.raw')   # handles both JSON and Lukka form

Table-shaped containers (``{cols: [...], rows: [[...], ...]}``) are
frequent in Lukka responses. Use ``rows_to_dicts`` to flatten them into
list-of-dicts shapes.
"""

import json
from pathlib import Path


class LukkaParser:
    """Recursive-descent parser for Lukka compact notation."""

    def __init__(self, s):
        self.s = s
        self.i = 0
        self.n = len(s)

    def _skip_ws(self):
        while self.i < self.n and self.s[self.i].isspace():
            self.i += 1

    def _peek(self):
        self._skip_ws()
        return self.s[self.i] if self.i < self.n else ''

    def _consume(self, c):
        self._skip_ws()
        if self.i < self.n and self.s[self.i] == c:
            self.i += 1
            return True
        return False

    def parse(self):
        val = self._parse_value()
        self._skip_ws()
        if self.i != self.n:
            raise ValueError(f"Trailing data at pos {self.i}")
        return val

    def _parse_value(self):
        self._skip_ws()
        if self.i >= self.n:
            raise ValueError("Unexpected end of input")
        c = self.s[self.i]
        if c == '{':
            return self._parse_object()
        if c == '[':
            return self._parse_array()
        if c == '"':
            return self._parse_string()
        return self._parse_bare_value()

    def _parse_object(self):
        self.i += 1
        obj = {}
        self._skip_ws()
        if self._peek() == '}':
            self.i += 1
            return obj
        while True:
            self._skip_ws()
            key = self._parse_key()
            if not self._consume(':'):
                raise ValueError(f"Expected ':' after key {key!r} at pos {self.i}")
            obj[key] = self._parse_value()
            self._skip_ws()
            if self._consume(','):
                continue
            if self._consume('}'):
                break
            raise ValueError(f"Expected ',' or '}}' at pos {self.i}")
        return obj

    def _parse_array(self):
        self.i += 1
        arr = []
        self._skip_ws()
        if self._peek() == ']':
            self.i += 1
            return arr
        while True:
            arr.append(self._parse_value())
            self._skip_ws()
            if self._consume(','):
                continue
            if self._consume(']'):
                break
            raise ValueError(f"Expected ',' or ']' at pos {self.i}")
        return arr

    def _parse_string(self):
        self.i += 1
        out = []
        while self.i < self.n:
            c = self.s[self.i]
            if c == '\\' and self.i + 1 < self.n:
                esc = self.s[self.i + 1]
                if esc == 'n':
                    out.append('\n')
                elif esc == 't':
                    out.append('\t')
                elif esc == 'r':
                    out.append('\r')
                elif esc == '"':
                    out.append('"')
                elif esc == '\\':
                    out.append('\\')
                else:
                    out.append(esc)
                self.i += 2
                continue
            if c == '"':
                self.i += 1
                return ''.join(out)
            out.append(c)
            self.i += 1
        raise ValueError("Unterminated string")

    def _parse_key(self):
        self._skip_ws()
        if self.i < self.n and self.s[self.i] == '"':
            return self._parse_string()
        j = self.i
        while j < self.n and self.s[j] not in ':,}{[]" \t\n\r':
            j += 1
        key = self.s[self.i:j]
        self.i = j
        return key

    def _parse_bare_value(self):
        j = self.i
        while j < self.n and self.s[j] not in ',}]':
            j += 1
        tok = self.s[self.i:j].rstrip()
        self.i = j
        if tok == '_' or tok == 'null':
            return None
        if tok == 'true':
            return True
        if tok == 'false':
            return False
        try:
            if '.' in tok or 'e' in tok or 'E' in tok:
                return float(tok)
            return int(tok)
        except ValueError:
            return tok


def loads(text):
    """Parse a string. Accepts standard JSON or Lukka compact notation."""
    text = text.strip()
    if not text:
        raise ValueError("Empty input")
    try:
        return json.loads(text)
    except json.JSONDecodeError:
        pass
    return LukkaParser(text).parse()


def load(path):
    """Load a file. Accepts standard JSON or Lukka compact notation."""
    return loads(Path(path).read_text())


def rows_to_dicts(container):
    """Convert ``{cols: [...], rows: [[...], ...]}`` into list-of-dicts.

    Returns ``None`` if the container is not in the expected column-oriented
    shape. Useful because many Lukka responses embed tables (transactions,
    candles, market caps, risk factors) as cols/rows pairs.
    """
    if not isinstance(container, dict):
        return None
    cols = container.get("cols")
    rows = container.get("rows")
    if not isinstance(cols, list) or not isinstance(rows, list):
        return None
    return [dict(zip(cols, row)) for row in rows if isinstance(row, list)]

SHA-256: 7d2354f3d5ff4c0eb2645b8e375693c424946afab2cbb4fe387004c34f9ccbe7