-
Notifications
You must be signed in to change notification settings - Fork 874
hrw4u: add an AST round-trip test over the whole corpus #13699
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
masaori335
wants to merge
4
commits into
apache:master
Choose a base branch
from
masaori335:asf-master-hrw4u-round-trip-test
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
a3291fe
hrw4u: add an AST round-trip test over the whole corpus
masaori335 8eb2e73
hrw4u: keep bool spelling in every value context
masaori335 bea9efe
hrw4u: keep number spelling in every value context
masaori335 b0c4180
hrw4u: make the per-test sandbox fixture compile
masaori335 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,144 @@ | ||
| # | ||
| # 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. | ||
| """Render an AST back to hrw4u source. Test-only; see test_ast_roundtrip.py for why. | ||
|
|
||
| Whitespace is not reproduced: the emitter derives its own indentation and never reads the | ||
| source's. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from hrw4u.ast_nodes import * | ||
|
|
||
| INDENT = " " | ||
|
|
||
|
|
||
| def unparse(ast: HRW4UAST) -> str: | ||
| return "\n".join(_top_level(item) for item in ast.body) + "\n" | ||
|
|
||
|
|
||
| def _top_level(node: TopLevelNode) -> str: | ||
| match node: | ||
| case Comment(): | ||
| return node.text | ||
| case UseDirective(): | ||
| return f"use {node.spec}" | ||
| case ProcedureDecl(): | ||
| params = ", ".join(_proc_param(p) for p in node.params) | ||
| return _braced(f"procedure {node.name}({params})", [_body_item(b, 1) for b in node.body]) | ||
| case VarSection(): | ||
| keyword = "SESSION_VARS" if node.scope == "session" else "VARS" | ||
| return _braced(keyword, [_var_item(d) for d in node.declarations]) | ||
| case Section(): | ||
| return _braced(node.type, [_body_item(b, 1) for b in node.body]) | ||
| raise ValueError(f"unparse: unhandled top-level node {type(node).__name__}") | ||
|
|
||
|
|
||
| def _braced(header: str, lines: list[str]) -> str: | ||
| return "\n".join([f"{header} {{", *(f"{INDENT}{line}" for line in lines), "}"]) | ||
|
|
||
|
|
||
| def _proc_param(p: ProcParam) -> str: | ||
| return f"${p.name}" if p.default is None else f"${p.name}={_value(p.default)}" | ||
|
|
||
|
|
||
| def _var_item(node: VarDecl | Comment) -> str: | ||
| if isinstance(node, Comment): | ||
| return node.text | ||
| slot = "" if node.slot is None else f" @{node.slot}" | ||
| return f"{node.name}: {node.type_name}{slot};" | ||
|
|
||
|
|
||
| def _body_item(node: BodyNode, depth: int) -> str: | ||
| match node: | ||
| case Comment(): | ||
| return node.text | ||
| case Break(): | ||
| return "break;" | ||
| case FunctionCall(): | ||
| return f"{_call(node)};" | ||
| case Assignment(): | ||
| return f"{_target(node.target)} {node.operator} {_value(node.value)};" | ||
| case IfBlock(): | ||
| return _if_block(node, depth) | ||
| raise ValueError(f"unparse: unhandled body node {type(node).__name__}") | ||
|
|
||
|
|
||
| def _if_block(node: IfBlock, depth: int) -> str: | ||
| pad = INDENT * depth | ||
| lines = [f"if {_condition(node.condition)} {{"] | ||
| lines += [f"{INDENT}{line}" for line in _nested(node.body, depth)] | ||
| for arm in node.elif_branches: | ||
| lines.append(f"}} elif {_condition(arm.condition)} {{") | ||
| lines += [f"{INDENT}{line}" for line in _nested(arm.body, depth)] | ||
| if node.has_else: | ||
| lines.append("} else {") | ||
| lines += [f"{INDENT}{line}" for line in _nested(node.else_body, depth)] | ||
| lines.append("}") | ||
| return f"\n{pad}".join(lines) | ||
|
|
||
|
|
||
| def _nested(body: tuple[BodyNode, ...], depth: int) -> list[str]: | ||
| return [line for item in body for line in _body_item(item, depth + 1).splitlines()] | ||
|
|
||
|
|
||
| def _target(t: Target) -> str: | ||
| return t.field if t.namespace is None else f"{t.namespace}.{t.field}" | ||
|
|
||
|
|
||
| def _call(node: FunctionCall) -> str: | ||
| return f"{node.name}({', '.join(_value(a) for a in node.args)})" | ||
|
|
||
|
|
||
| def _condition(node: ConditionExpr) -> str: | ||
| match node: | ||
| case Group(): | ||
| return f"({_condition(node.inner)})" | ||
| case LogicalOp(): | ||
| return f"{_condition(node.left)} {node.operator} {_condition(node.right)}" | ||
| case NotOp(): | ||
| return f"!{_condition(node.operand)}" | ||
| case BoolLiteral(): | ||
| return "true" if node.value else "false" | ||
| case IdentCondition(): | ||
| return node.name | ||
| case FunctionCall(): | ||
| return _call(node) | ||
| case Comparison(): | ||
| return _comparison(node) | ||
| raise ValueError(f"unparse: unhandled condition node {type(node).__name__}") | ||
|
|
||
|
|
||
| def _comparison(node: Comparison) -> str: | ||
| left = node.left.raw if isinstance(node.left, IdentValue) else _call(node.left) | ||
| mods = f" with {', '.join(node.modifiers)}" if node.modifiers else "" | ||
| return f"{left} {node.operator} {_value(node.right)}{mods}" | ||
|
|
||
|
|
||
| def _value(v: ValueExpr | RegexValue | SetValue) -> str: | ||
| match v: | ||
| case LiteralStringValue(): | ||
| return f'"{v.raw}"' | ||
| case NumberValue() | BoolValue() | IdentValue() | IPValue() | IpRangeValue(): | ||
| return v.raw | ||
| case ParamRef(): | ||
| return f"${v.raw}" | ||
| case RegexValue(): | ||
| return f"/{v.raw}/" | ||
| case SetValue(): | ||
| return f"[{v.raw}]" | ||
| raise ValueError(f"unparse: unhandled value {type(v).__name__}") | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # The emitter echoes a bool's spelling, so the AST cannot normalize one anywhere: | ||
| # an assignment RHS is not the only value context that reaches header_rewrite. | ||
| REMAP { | ||
| if inbound.req.X-Debug == TRUE { | ||
| set-config("proxy.config.http.cache.http", FALSE); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # The emitter echoes a bool's spelling, so the AST cannot normalize one anywhere: | ||
| # an assignment RHS is not the only value context that reaches header_rewrite. | ||
| cond %{REMAP_PSEUDO_HOOK} [AND] | ||
| cond %{CLIENT-HEADER:X-Debug} =TRUE | ||
| set-config "proxy.config.http.cache.http" FALSE |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # The emitter echoes a number's digits, so the AST cannot normalize one anywhere: | ||
| # 007 and 7 are different bytes once a number reaches a header value. | ||
| REMAP { | ||
| if random(0100) > 007 { | ||
| inbound.req.X-Count = 007; | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no-op;/skip-remap;parse but never compile, so nothing that producesoutput loses anything here.
Out of scope for this PR: the conflation is unchanged from the branch point
(
src/ast_visitor.py:118-119, already pinned bytest_ast_visitor.py), and anode/flag only means something once we decide whether to drop the grammar
alternative or make the bare form work — a language change, not a test change.
Filing that separately.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed as #13701