Source code for noisemaker.dsl.parser

PRESET_KEYS = {"layers", "settings", "generator", "octaves", "post", "final", "unique"}


def unexpected(token):
    if token:
        val = token["value"]
        line = token.get("line")
        column = token.get("column")
        raise ValueError(f"Unexpected token: {val} (line {line}, column {column})")
    raise ValueError("Unexpected token: EOF")


[docs] def parse(tokens, enforce_preset_keys=True): i = 0 def peek(offset=0): idx = i + offset return tokens[idx] if idx < len(tokens) else None def peek_is(type_, offset=0): t = peek(offset) return t and t["type"] == type_ def consume(type_=None): nonlocal i t = peek() if not t or (type_ and t["type"] != type_): unexpected(t) i += 1 return t def match(type_): nonlocal i if peek_is(type_): i += 1 return True return False def parseProgram(): t = peek() if t and t["type"] == "{": body = parseObjectExpr(enforce_preset_keys) if i != len(tokens): unexpected(peek()) return {"type": "Program", "body": body} expr = parseExpression() if i != len(tokens): unexpected(peek()) return {"type": "Program", "body": expr} def parseObjectExpr(enforceKeys=False): consume("{") properties = [] seen = set() while not peek_is("}"): keyTok = peek() if not keyTok or keyTok["type"] not in ("identifier", "string"): unexpected(keyTok) key = consume(keyTok["type"])["value"] if key in seen: raise ValueError(f"Duplicate key: {key}") if enforceKeys and key not in PRESET_KEYS: raise ValueError(f"Unknown key: {key}") seen.add(key) consume(":") value = parseExpression() properties.append({"key": key, "value": value}) if not match(","): break consume("}") return {"type": "ObjectExpr", "properties": properties} def parseArrayExpr(): consume("[") elements = [] while not peek_is("]"): elements.append(parseExpression()) if not match(","): break consume("]") return {"type": "ArrayExpr", "elements": elements} def parseExpression(): t = peek() if not t: unexpected(t) if t["type"] == "string": consume("string") return {"type": "StringLiteral", "value": t["value"]} if t["type"] == "color": consume("color") return {"type": "StringLiteral", "value": t["value"]} return parseNumberExpr() def parseCallChain(): node = parseSingleCall() while match("."): next_node = parseSingleCall() next_node["input"] = node node = next_node return node def parseSingleCall(): callee = parseIdentifier() consume("(") args = parseArgList(")") return {"type": "CallExpr", "callee": callee, "args": args} def parseArgList(endToken): positional = [] named = {} usingNamed = None while not peek_is(endToken): arg = parseArg() if arg.get("named"): if usingNamed is False: raise ValueError("Cannot mix positional and named arguments") usingNamed = True if arg["name"] in named: raise ValueError(f"Duplicate argument: {arg['name']}") named[arg["name"]] = arg["value"] else: if usingNamed is True: raise ValueError("Cannot mix positional and named arguments") usingNamed = False positional.append(arg["value"]) if not match(","): break consume(endToken) return {"named": named} if usingNamed else {"positional": positional} def parseArg(): t = peek() next_tok = peek(1) if t["type"] == "identifier" and (next_tok and next_tok["type"] in (":", "=")): name = consume("identifier")["value"] consume(next_tok["type"]) value = parseExpression() return {"named": True, "name": name, "value": value} return {"value": parseExpression()} def parseIdentifier(): token = consume("identifier") return {"type": "Identifier", "name": token["value"]} def parseNumberExpr(): node = parseComparison() t = peek() if t and t["type"] == "identifier" and t["value"] == "if": consume("identifier") test_expr = parseNumberExpr() else_tok = consume("identifier") if else_tok["value"] != "else": unexpected(else_tok) false_expr = parseNumberExpr() node = {"type": "TernaryExpr", "test": test_expr, "consequent": node, "alternate": false_expr} elif match("?"): true_expr = parseNumberExpr() consume(":") false_expr = parseNumberExpr() node = {"type": "TernaryExpr", "test": node, "consequent": true_expr, "alternate": false_expr} return node def parseComparison(): node = parseAdd() while True: t = peek() if t and t["type"] in ("<", ">"): consume(t["type"]) right = parseAdd() node = {"type": "BinaryExpr", "operator": t["type"], "left": node, "right": right} else: break return node def parseAdd(): node = parseMul() while True: t = peek() if t and t["type"] in ("+", "-"): consume(t["type"]) right = parseMul() node = {"type": "BinaryExpr", "operator": t["type"], "left": node, "right": right} else: break return node def parseMul(): node = parseUnary() while True: t = peek() if t and t["type"] in ("*", "/"): consume(t["type"]) right = parseUnary() node = {"type": "BinaryExpr", "operator": t["type"], "left": node, "right": right} else: break return node def parseUnary(): t = peek() if t and t["type"] in ("+", "-"): consume(t["type"]) argument = parseUnary() return {"type": "UnaryExpr", "operator": t["type"], "argument": argument} return parseNumberPrimary() def parseNumberPrimary(): t = peek() if t["type"] == "number": consume("number") return {"type": "NumberLiteral", "value": t["value"]} if t["type"] == "(": consume("(") expr = parseNumberExpr() consume(")") return expr if t["type"] == "[": return parseArrayExpr() if t["type"] == "{": return parseObjectExpr() if ( t["type"] == "identifier" and t["value"] == "Math" and (peek(1) and peek(1)["type"] == ".") and (peek(2) and peek(2)["type"] == "identifier") and peek(2)["value"] == "PI" ): consume("identifier") consume(".") consume("identifier") import math return {"type": "NumberLiteral", "value": math.pi} if t["type"] == "identifier": if peek(1) and peek(1)["type"] == "(": return parseCallChain() id_ = parseIdentifier() if match("."): member = parseIdentifier() node = {"type": "MemberExpr", "object": id_, "property": member} while match("."): member = parseIdentifier() node = {"type": "MemberExpr", "object": node, "property": member} if match("("): args = parseArgList(")") node = {"type": "CallExpr", "callee": node, "args": args} while match("."): next_node = parseSingleCall() next_node["input"] = node node = next_node return node return node return id_ if t["type"] == "boolean": consume("boolean") return {"type": "BooleanLiteral", "value": t["value"]} if t["type"] == "null": consume("null") return {"type": "NullLiteral", "value": None} unexpected(t) return parseProgram()