Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion tools/hrw4u/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ SRC_FILES_HRW4U=src/visitor.py \
src/suggestions.py \
src/procedures.py \
src/sandbox.py \
src/kg_visitor.py
src/kg_visitor.py \
src/ast_nodes.py \
src/ast_visitor.py

ALL_HRW4U_FILES=$(SHARED_FILES) $(UTILS_FILES) $(SRC_FILES_HRW4U)

Expand Down
167 changes: 167 additions & 0 deletions tools/hrw4u/src/ast_nodes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
#
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import Union


class ValueKind(Enum):
STRING = "string"
IDENT = "ident"
PARAM_REF = "param_ref"
IP = "ip"
REGEX = "regex"


@dataclass(frozen=True, kw_only=True)
class Value:
raw: str
kind: ValueKind


@dataclass(frozen=True, kw_only=True)
class Node:
line: int


@dataclass(frozen=True)
class Target:
namespace: str | None
field: str

@staticmethod
def from_dotted(name: str) -> Target:
# TODO: the grammar lexes dotted paths as a single IDENT token;
# ideally the grammar would split namespace/field so this
# heuristic isn't needed.
dot = name.rfind(".")
if dot == -1:
return Target(namespace=None, field=name)
return Target(namespace=name[:dot], field=name[dot + 1:])


@dataclass(frozen=True, kw_only=True)
class Assignment(Node):
target: Target
operator: str # "=" or "+="
value: Value | int | bool | tuple


@dataclass(frozen=True, kw_only=True)
class FunctionCall(Node):
name: str
args: tuple[Value | int | bool, ...]

Comment on lines +60 to +71
Copy link
Copy Markdown
Author

@juanthropic juanthropic Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in d7f20b6. Added Value dataclass with ValueKind enum to preserve semantic distinction between string literals, identifiers, param refs, IPs, and regexes.


@dataclass(frozen=True, kw_only=True)
class Break(Node):
pass


@dataclass(frozen=True, kw_only=True)
class Comparison(Node):
left: Value | FunctionCall
operator: str # "==", "!=", ">", "<", "~", "!~", "in", "!in"
right: Value | int | bool | tuple
modifiers: tuple[str, ...]


@dataclass(frozen=True, kw_only=True)
class LogicalOp(Node):
operator: str # "&&" or "||"
left: ConditionExpr
right: ConditionExpr


@dataclass(frozen=True, kw_only=True)
class NotOp(Node):
operand: ConditionExpr


@dataclass(frozen=True, kw_only=True)
class BoolLiteral(Node):
value: bool


@dataclass(frozen=True, kw_only=True)
class IdentCondition(Node):
name: str


@dataclass(frozen=True, kw_only=True)
class ElifBranch(Node):
condition: ConditionExpr
body: tuple[BodyNode, ...]


@dataclass(frozen=True, kw_only=True)
class IfBlock(Node):
condition: ConditionExpr
body: tuple[BodyNode, ...]
elif_branches: tuple[ElifBranch, ...]
else_body: tuple[BodyNode, ...]


@dataclass(frozen=True, kw_only=True)
class Section(Node):
type: str
body: tuple[BodyNode, ...]


@dataclass(frozen=True, kw_only=True)
class ProcParam(Node):
name: str
default: Value | int | bool | None


@dataclass(frozen=True, kw_only=True)
class VarDecl(Node):
name: str
type_name: str
slot: int | None


@dataclass(frozen=True, kw_only=True)
class VarSection(Node):
scope: str
declarations: tuple[VarDecl, ...]


@dataclass(frozen=True, kw_only=True)
class UseDirective(Node):
spec: str


@dataclass(frozen=True, kw_only=True)
class ProcedureDecl(Node):
name: str
params: tuple[ProcParam, ...]
body: tuple[BodyNode, ...]


@dataclass(frozen=True, kw_only=True)
class HRW4UAST:
body: tuple[TopLevelNode, ...]


# Type aliases: must follow all class definitions (evaluated at runtime).
ConditionExpr = Union[Comparison, LogicalOp, NotOp, BoolLiteral, IdentCondition, FunctionCall]
BodyNode = Union[Assignment, FunctionCall, IfBlock, Break]
TopLevelNode = Union[UseDirective, VarSection, ProcedureDecl, Section]
Loading