Skip to content

Commit 322864d

Browse files
on-keydayclaude
andcommitted
ast: compare nodes by structure and by meaning
Three levels now, from cheapest to deepest: x.id() == y.id() the same node identical(a, x, y) the same tree, loc and every field included equivalent(a, x, y) the same meaning equivalent skips fields declared cosmetic in nodes.json, alongside weak the second per-field marker the schema carries. Type::is_explicit is the only one set: whether u8 was written out or inferred does not change the type. lexer::Loc fields are skipped without a declaration, since a source position is never part of meaning. Comparison needs to walk two nodes in step and for_each_field only hands out one value, so NodeData gains an overload taking the other node that passes (name, mine, theirs, weak, cosmetic). The existing one-sided signature is untouched, so printer, from_json and traverse are unaffected. weak fields are compared by id and not descended into, in both modes - following them runs into the same cycles traverse avoids. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 25c7c94 commit 322864d

5 files changed

Lines changed: 265 additions & 43 deletions

File tree

src/core/nast/compare.h

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
/*license*/
2+
#pragma once
3+
#include "nodes.h"
4+
#include "traverse.h"
5+
6+
// ノードを比べる。3 段ある。
7+
//
8+
// x.id() == y.id() 同じノードか。id を見るだけ (nodes.h)
9+
// identical(a, x, y) 木として同じか。全フィールドと loc まで見る
10+
// equivalent(a, x, y) 意味として同じか。位置と cosmetic なフィールドを飛ばす
11+
//
12+
// cosmetic は nodes.json 側の宣言で、weak と同じ扱い。今は Type::is_explicit だけが
13+
// 立っている。u8 と明示的に書いたか推論されたかは、型としての意味を変えないため。
14+
// lexer::Loc 型のフィールドは宣言なしで飛ばす。位置が意味に効くことはない。
15+
//
16+
// weak は所有辺ではないので、両方の比較とも id の一致だけを見て降りない。
17+
// 降りると belong や base で循環する。
18+
19+
namespace brgen::nast {
20+
21+
enum class CompareMode {
22+
identical,
23+
equivalent,
24+
};
25+
26+
namespace compare_detail {
27+
28+
template <class T>
29+
struct is_loc : std::false_type {};
30+
31+
template <>
32+
struct is_loc<lexer::Loc> : std::true_type {};
33+
34+
template <class T>
35+
constexpr bool compare(Arena& a, Node<T> l, Node<T> r, CompareMode mode);
36+
37+
template <class M>
38+
constexpr bool compare_field(Arena& a, const M& lv, const M& rv, bool weak, CompareMode mode) {
39+
if constexpr (node_of<M>::is_node) {
40+
if (weak) {
41+
return lv.id() == rv.id();
42+
}
43+
return compare(a, lv, rv, mode);
44+
}
45+
else if constexpr (vector_of<M>::is_vector) {
46+
if (lv.size() != rv.size()) {
47+
return false;
48+
}
49+
for (std::size_t i = 0; i < lv.size(); i++) {
50+
if (weak) {
51+
if (lv[i].id() != rv[i].id()) {
52+
return false;
53+
}
54+
}
55+
else if (!compare(a, lv[i], rv[i], mode)) {
56+
return false;
57+
}
58+
}
59+
return true;
60+
}
61+
else {
62+
return lv == rv;
63+
}
64+
}
65+
66+
template <class T>
67+
constexpr bool compare(Arena& a, Node<T> l, Node<T> r, CompareMode mode) {
68+
if (l.id() == r.id()) {
69+
return true; // 同じノードなら中身を見るまでもない
70+
}
71+
if (l.is_null() || r.is_null()) {
72+
return false;
73+
}
74+
auto* lh = a.header_at(l.id());
75+
auto* rh = a.header_at(r.id());
76+
if (!lh || !rh || lh->type != rh->type) {
77+
return false;
78+
}
79+
if (mode == CompareMode::identical && !(lh->loc == rh->loc)) {
80+
return false;
81+
}
82+
bool eq = true;
83+
auto li = lh->data_index;
84+
auto ri = rh->data_index;
85+
visit_node_type(lh->type, [&](auto tag) {
86+
using U = typename decltype(tag)::type;
87+
auto* ld = a.template data_at<U>(li);
88+
auto* rd = a.template data_at<U>(ri);
89+
if (!ld || !rd) {
90+
eq = (ld == rd);
91+
return;
92+
}
93+
ld->for_each_field(*rd, [&](const char*, const auto& lv, const auto& rv,
94+
bool weak, bool cosmetic) {
95+
if (!eq) {
96+
return;
97+
}
98+
using M = std::decay_t<decltype(lv)>;
99+
if (mode == CompareMode::equivalent && (cosmetic || is_loc<M>::value)) {
100+
return;
101+
}
102+
eq = compare_field(a, lv, rv, weak, mode);
103+
});
104+
});
105+
return eq;
106+
}
107+
108+
} // namespace compare_detail
109+
110+
// 木として同じか。位置も含めて全部見る。
111+
template <class T>
112+
constexpr bool identical(Arena& a, Node<T> l, Node<T> r) {
113+
return compare_detail::compare(a, l, r, CompareMode::identical);
114+
}
115+
116+
// 意味として同じか。位置と cosmetic なフィールドは見ない。
117+
template <class T>
118+
constexpr bool equivalent(Arena& a, Node<T> l, Node<T> r) {
119+
return compare_detail::compare(a, l, r, CompareMode::equivalent);
120+
}
121+
122+
} // namespace brgen::nast

src/core/nast/gen/nodes.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .writer import Writer
77

88

9-
def emit_fields(w: Writer, schema: Schema, node: dict) -> None:
9+
def emit_fields(w: Writer, schema: Schema, node: dict, self_type: str = "") -> None:
1010
"""フィールド宣言 + as_json + for_each_field。
1111
1212
NodeData<T> だけでなく NodeHeader や素の値型でも同じものを出す。
@@ -46,10 +46,33 @@ def emit_fields(w: Writer, schema: Schema, node: dict) -> None:
4646
)
4747
w.write(" }\n")
4848

49+
# 2 つを同じ順で並べて回す版。比較 (compare.h) が要る。
50+
# cosmetic は「論理的等価性に効かない」印で、weak と同じく schema 側の宣言。
51+
if not self_type:
52+
return
53+
w.write(" constexpr void for_each_field(const ", self_type, "& o_, auto&& f_) const {\n")
54+
for field in schema.all_fields(node):
55+
weak = "true" if field.get("weak") else "false"
56+
cosmetic = "true" if field.get("cosmetic") else "false"
57+
w.write(
58+
" f_(",
59+
json.dumps(field["name"]),
60+
",",
61+
field["name"],
62+
",o_.",
63+
field["name"],
64+
",",
65+
weak,
66+
",",
67+
cosmetic,
68+
");\n",
69+
)
70+
w.write(" }\n")
71+
4972

5073
def emit_node_header(w: Writer, schema: Schema) -> None:
5174
w.write("struct NodeHeader {\n")
52-
emit_fields(w, schema, schema.header)
75+
emit_fields(w, schema, schema.header, "NodeHeader")
5376
w.write("};\n")
5477

5578

@@ -225,7 +248,7 @@ def emit_node_data(w: Writer, schema: Schema) -> None:
225248
"struct NodeData<", node["name"], ">",
226249
(": NodeData<" + node["derive"] + ">" if "derive" in node else ""), " {\n",
227250
)
228-
emit_fields(w, schema, node)
251+
emit_fields(w, schema, node, "NodeData<" + node["name"] + ">")
229252
w.write("};\n")
230253

231254

src/core/nast/gen/tables.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def emit_structs(w: Writer, schema: Schema) -> None:
1414
"""
1515
for struct in schema.structs:
1616
w.write("struct ", struct["name"], " {\n")
17-
emit_fields(w, schema, struct)
17+
emit_fields(w, schema, struct, struct["name"])
1818
w.write("};\n")
1919

2020

src/core/nast/nodes.json

Lines changed: 76 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,58 @@
4545
}
4646
]
4747
},
48+
{
49+
"name": "InnerStruct",
50+
"over": "BodyStatement",
51+
"storage": "sparse",
52+
"fields": [
53+
{
54+
"name": "fields",
55+
"type": "vector<Node<Field>>"
56+
},
57+
{
58+
"name": "asserts",
59+
"type": "vector<Node<Assert>>"
60+
}
61+
]
62+
},
63+
{
64+
"name": "FormatState",
65+
"over": "Format",
66+
"storage": "sparse",
67+
"fields": [
68+
{
69+
"name": "encode_kind",
70+
"type": "FormatKind"
71+
},
72+
{
73+
"name": "decode_kind",
74+
"type": "FormatKind"
75+
},
76+
{
77+
"name": "encode_custom",
78+
"type": "Node<Function>"
79+
},
80+
{
81+
"name": "decode_custom",
82+
"type": "Node<Function>"
83+
},
84+
{
85+
"name": "fields",
86+
"type": "vector<Node<Field>>"
87+
},
88+
{
89+
"name": "functions",
90+
"type": "vector<Node<Function>>",
91+
"description": "Functions defined inside the format (without custom encode/decode functions)"
92+
},
93+
{
94+
"name": "asserts",
95+
"type": "vector<Node<Assert>>",
96+
"description": "Asserts defined inside the format"
97+
}
98+
]
99+
},
48100
{
49101
"name": "DocComment",
50102
"over": "Statement",
@@ -487,6 +539,17 @@
487539
"name": "native"
488540
}
489541
]
542+
},
543+
{
544+
"name": "FormatKind",
545+
"enums": [
546+
{
547+
"name": "as_is"
548+
},
549+
{
550+
"name": "custom"
551+
}
552+
]
490553
}
491554
],
492555
"nodes": [
@@ -844,12 +907,8 @@
844907
"name": "Body",
845908
"fields": [
846909
{
847-
"name": "elements",
910+
"name": "statements",
848911
"type": "vector<Node<Statement>>"
849-
},
850-
{
851-
"name": "struct_type",
852-
"type": "Node<StructType>"
853912
}
854913
]
855914
},
@@ -899,7 +958,8 @@
899958
"fields": [
900959
{
901960
"name": "is_explicit",
902-
"type": "bool"
961+
"type": "bool",
962+
"cosmetic": true
903963
}
904964
],
905965
"abstract": true
@@ -1195,10 +1255,6 @@
11951255
{
11961256
"name": "element_type",
11971257
"type": "Node<Type>"
1198-
},
1199-
{
1200-
"name": "is_bytes",
1201-
"type": "bool"
12021258
}
12031259
]
12041260
},
@@ -1220,18 +1276,20 @@
12201276
"name": "StructType",
12211277
"derive": "Type",
12221278
"fields": [
1223-
{
1224-
"name": "fields",
1225-
"type": "vector<Node<Statement>>"
1226-
},
12271279
{
12281280
"name": "base",
12291281
"type": "Node<Statement>",
12301282
"weak": true
1231-
},
1283+
}
1284+
]
1285+
},
1286+
{
1287+
"name": "InlineStructType",
1288+
"derive": "Type",
1289+
"fields": [
12321290
{
1233-
"name": "recursive",
1234-
"type": "bool"
1291+
"name": "inlined_format",
1292+
"type": "Node<Format>"
12351293
}
12361294
]
12371295
},
@@ -1241,29 +1299,8 @@
12411299
"fields": [
12421300
{
12431301
"name": "base",
1244-
"type": "Node<Expr>",
1302+
"type": "Node<ConditionalExpr>",
12451303
"weak": true
1246-
},
1247-
{
1248-
"name": "cond",
1249-
"type": "Node<Expr>"
1250-
},
1251-
{
1252-
"name": "conds",
1253-
"type": "vector<Node<Expr>>"
1254-
},
1255-
{
1256-
"name": "structs",
1257-
"type": "vector<Node<StructType>>"
1258-
},
1259-
{
1260-
"name": "union_fields",
1261-
"type": "vector<Node<Field>>",
1262-
"weak": true
1263-
},
1264-
{
1265-
"name": "exhaustive",
1266-
"type": "bool"
12671304
}
12681305
]
12691306
},

0 commit comments

Comments
 (0)