Skip to content

Commit ac517ba

Browse files
committed
feat(parser): begin work on new pattern system
so long, regex!
1 parent faf8626 commit ac517ba

File tree

1 file changed

+383
-0
lines changed

1 file changed

+383
-0
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
/*
2+
* Copyright (c) 2021 ByteSkript org (Moderocky)
3+
* View the full licence information and permissions:
4+
* https://github.com/Moderocky/ByteSkript/blob/master/LICENSE
5+
*/
6+
7+
package org.byteskript.skript.compiler;
8+
9+
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.Nullable;
11+
12+
import java.util.*;
13+
import java.util.concurrent.atomic.AtomicReference;
14+
import java.util.function.Function;
15+
import java.util.function.Supplier;
16+
import java.util.stream.IntStream;
17+
import java.util.stream.Stream;
18+
19+
import static org.byteskript.skript.compiler.NewPattern.Node.*;
20+
21+
public class NewPattern {
22+
public static void main(final String[] args) {
23+
final Node elementPattern = recurse(self -> input("value", either(text(",", either(text(" ", self), self)), text(")", end()))));
24+
final Node listPattern = text("(", elementPattern);
25+
{
26+
final Resolved match = match("(1, 2, 3, 4)", listPattern);
27+
if (match != null) {
28+
System.out.println("List is:");
29+
for (final String input : match.group("value")) {
30+
System.out.println(" - " + input);
31+
}
32+
}
33+
}
34+
35+
{
36+
final Node twoListsPattern = labelledChild("one", listPattern, text(" and ", labelledChild("two", listPattern, end())));
37+
final Resolved twoMatch = match("(1,2,3) and (4, 5, 6)", twoListsPattern);
38+
if (twoMatch != null) {
39+
System.out.println("List 1 is:");
40+
for (final String input : twoMatch.group("one.value")) {
41+
System.out.println(" - " + input);
42+
}
43+
System.out.println("List 2 is:");
44+
for (final String input : twoMatch.group("two.value")) {
45+
System.out.println(" - " + input);
46+
}
47+
}
48+
}
49+
50+
final Node digit = oneOf(text("0"), text("1"), text("2"), text("3"), text("4"), text("5"), text("6"), text("7"), text("8"), text("9"));
51+
final Node naturalNumber = label("value", recurse(self -> oneOf(digit, child(digit, self))));
52+
final Resolved natural = match("69", naturalNumber);
53+
final Node integer = label("value", optional("-", labelledChild("natural", naturalNumber, end())));
54+
final Node decimal = label("value", oneOf(naturalNumber, child(naturalNumber, text(".", naturalNumber))));
55+
{
56+
57+
if (natural != null) System.out.println("Natural is " + natural.group("value").get(0));
58+
{
59+
final Resolved intMatch = match("-67", integer);
60+
if (intMatch != null) System.out.println("Integer is " + intMatch.group("value").get(0));
61+
}
62+
{
63+
final Resolved decMatch = match("67.80085", decimal);
64+
if (decMatch != null) System.out.println("Decimal is " + decMatch.group("value").get(0));
65+
}
66+
}
67+
68+
{
69+
final Node statement = maybeWhitespace(oneOf(
70+
labelledChild("statement", text("print", maybeWhitespace(label("value", integer))), maybeWhitespace(text(";"))),
71+
labelledChild("statement", text("hi"), maybeWhitespace(text(";"))),
72+
labelledChild("statement", text("hiya"), maybeWhitespace(text(";")))
73+
));
74+
final Node block = text("{", maybeWhitespace(child(recurse(self -> oneOf(statement, child(statement, self))), maybeWhitespace(text("}")))));
75+
final Node function = text("function ", identifier("name", maybeWhitespace(block)));
76+
final Resolved resolved = match("""
77+
function foo {
78+
hi;
79+
print -5;
80+
hiya;
81+
}""", function);
82+
83+
if (resolved != null) {
84+
System.out.println("Function is named " + resolved.group("name"));
85+
for (final String line : resolved.group("statement", true)) {
86+
System.out.println(" - " + line);
87+
}
88+
}
89+
}
90+
}
91+
92+
private static Resolved match(final String input, final Node pattern) {
93+
return matches(0, input, pattern).max(Comparator.naturalOrder()).orElse(null);
94+
}
95+
96+
interface Context {
97+
Node redirect();
98+
String name();
99+
}
100+
101+
record ChildContext(ChildNode node, Node redirect) implements Context {
102+
public Context parent() {
103+
return node.context();
104+
}
105+
106+
@Override
107+
public String name() {
108+
return (parent() != null ? parent().name() + "." : "") + (node.label() != null ? node.label() : "");
109+
}
110+
}
111+
112+
sealed interface Node permits Branch, Dynamic, Labelled, Literal, Terminal {
113+
Context context();
114+
115+
Set<Node> children();
116+
117+
static Literal text(final String text, final Node then) {
118+
return new Literal(text, then);
119+
}
120+
121+
static Node optional(final String value, final Node then) {
122+
return oneOf(text(value, then), then);
123+
}
124+
125+
static Node maybeWhitespace(final Node then) {
126+
final Node oneWhitespace = oneOf(text(" "), text("\n"));
127+
return recurse(self -> oneOf(then, child(oneWhitespace, self)));
128+
}
129+
130+
static Literal text(final String text) {
131+
return new Literal(text, end());
132+
}
133+
134+
static Branch either(final Node one, final Node other) {
135+
return new Branch(one, other);
136+
}
137+
138+
static Node oneOf(final Node first, final Node... others) {
139+
return Arrays.stream(others).reduce(first, Branch::new);
140+
}
141+
142+
static Dynamic recurse(final Function<Dynamic, Node> function) {
143+
final AtomicReference<Dynamic> reference = new AtomicReference<>();
144+
reference.set(new Dynamic(() -> function.apply(reference.get())));
145+
return reference.get();
146+
}
147+
148+
static Identifier identifier(final Node then) {
149+
return new Identifier(null, then);
150+
}
151+
152+
static Identifier identifier(final String name, final Node then) {
153+
return new Identifier(name, then);
154+
}
155+
156+
static Input input(final String name, final Node then) {
157+
return new Input(name, then);
158+
}
159+
160+
static ChildNode labelledChild(final String name, final Node subNode, final Node then) {
161+
return new ChildNode(name, subNode, then);
162+
}
163+
164+
static ChildNode label(final String label, final Node subNode) {
165+
return new ChildNode(label, subNode, end());
166+
}
167+
168+
static ChildNode child(final Node subNode, final Node then) {
169+
return new ChildNode(null, subNode, then);
170+
}
171+
172+
static Terminal end() {
173+
return new Terminal();
174+
}
175+
176+
Node withContext(final Context context);
177+
}
178+
179+
sealed interface Labelled extends Node permits Input, Identifier, ChildNode {
180+
String label();
181+
default String fullLabel() {
182+
return (context() != null ? context().name() + "." : "") + label();
183+
}
184+
}
185+
186+
record ChildNode(Context context, String label, Node rawNode, Node then) implements Labelled {
187+
ChildNode(final String label, final Node rawNode, final Node then) {
188+
this(null, label, rawNode, then);
189+
}
190+
191+
@Override
192+
public Set<Node> children() {
193+
return Set.of(node());
194+
}
195+
196+
public Node node() {
197+
return rawNode.withContext(new ChildContext(this, then));
198+
}
199+
200+
@Override
201+
public Node withContext(final Context context) {
202+
final Node newThen = then.withContext(context);
203+
return new ChildNode(context, label, rawNode.withContext(new ChildContext(this, newThen)), newThen);
204+
}
205+
}
206+
207+
record Literal(Context context, String text, Node next) implements Node {
208+
Literal(final String text, final Node next) {
209+
this(null, text, next);
210+
}
211+
212+
@Override
213+
public Set<Node> children() {
214+
return Set.of(next);
215+
}
216+
217+
@Override
218+
public Node withContext(final Context context) {
219+
return new Literal(context, text, next.withContext(context));
220+
}
221+
}
222+
223+
record Terminal(Context context) implements Node {
224+
Terminal() {
225+
this(null);
226+
}
227+
228+
@Override
229+
public Set<Node> children() {
230+
return Collections.emptySet();
231+
}
232+
233+
@Override
234+
public Node withContext(final Context context) {
235+
return new Terminal(context);
236+
}
237+
}
238+
239+
record Branch(Context context, Node primary, Node secondary) implements Node {
240+
Branch(final Node primary, final Node secondary) {
241+
this(null, primary, secondary);
242+
}
243+
244+
@Override
245+
public Set<Node> children() {
246+
return Set.of(primary, secondary);
247+
}
248+
249+
@Override
250+
public Node withContext(final Context context) {
251+
return new Branch(context, primary.withContext(context), secondary.withContext(context));
252+
}
253+
}
254+
255+
record Input(Context context, @Nullable String label, Node next) implements Labelled {
256+
Input(final String label, final Node next) {
257+
this(null, label, next);
258+
}
259+
260+
@Override
261+
public Set<Node> children() {
262+
return Set.of(next);
263+
}
264+
265+
@Override
266+
public Node withContext(final Context context) {
267+
return new Input(context, label, next.withContext(context));
268+
}
269+
}
270+
271+
record Identifier(Context context, @Nullable String label, Node next) implements Labelled {
272+
Identifier(final String label, final Node next) {
273+
this(null, label, next);
274+
}
275+
276+
@Override
277+
public Set<Node> children() {
278+
return Set.of(next);
279+
}
280+
281+
@Override
282+
public Node withContext(final Context context) {
283+
return new Identifier(context, label, next.withContext(context));
284+
}
285+
}
286+
287+
record Dynamic(Context context, Supplier<Node> supplier) implements Node {
288+
Dynamic(final Supplier<Node> supplier) {
289+
this(null, supplier);
290+
}
291+
292+
@Override
293+
public Set<Node> children() {
294+
return supplier.get().children();
295+
}
296+
297+
@Override
298+
public Node withContext(final Context context) {
299+
return new Dynamic(context, () -> supplier.get().withContext(context));
300+
}
301+
}
302+
303+
record MatchElement(Node node, int start, int end) {
304+
@Override
305+
public @NotNull String toString() {
306+
return (node.context() != null ? node.context().name() + "." : "") + node.getClass().getSimpleName() + "[" + start + "--" + end + "]";
307+
}
308+
}
309+
310+
record Resolved(List<MatchElement> match, String input) implements Comparable<Resolved> {
311+
public Resolved prepend(final Node element, final int start, final String extraInput) {
312+
final List<MatchElement> newList = new ArrayList<>(match.size() + 1);
313+
newList.add(new MatchElement(element, start, start + extraInput.length()));
314+
newList.addAll(match);
315+
return new Resolved(newList, extraInput + input);
316+
}
317+
318+
@Override
319+
public int compareTo(@NotNull final NewPattern.Resolved o) {
320+
if (this.match.size() != o.match.size()) return this.match.size() - o.match.size();
321+
else return this.input.compareTo(o.input);
322+
}
323+
324+
public List<String> group(final String name, final boolean ignoreContext) {
325+
return match.stream()
326+
.filter(element -> element.node() instanceof final Labelled labelled && name.equals(ignoreContext ? labelled.label() : labelled.fullLabel()))
327+
.map(elem -> input.substring(elem.start(), elem.end()))
328+
.toList();
329+
}
330+
331+
public List<String> group(final String name) {
332+
return group(name, false);
333+
}
334+
335+
public Resolved tag(final Node node, final int start, final int end) {
336+
final List<MatchElement> newList = new ArrayList<>(match.size() + 1);
337+
newList.add(new MatchElement(node, start, end));
338+
newList.addAll(match);
339+
return new Resolved(newList, this.input);
340+
}
341+
342+
public int startIndexOf(final Node node) {
343+
return match.stream().filter(e -> e.node.equals(node)).map(MatchElement::start).findFirst().orElse(-1);
344+
}
345+
346+
public int endIndexOf(final Node node) {
347+
return match.stream().filter(e -> e.node.equals(node)).map(MatchElement::end).findFirst().orElse(-1);
348+
}
349+
}
350+
351+
static Stream<Resolved> matches(final int start, final String text, final Node root) {
352+
if (root instanceof final Literal literal) {
353+
if (!text.startsWith(literal.text())) return Stream.empty();
354+
return literal.children().stream()
355+
.flatMap(child -> matches(start + literal.text().length(), text.substring(literal.text().length()), child))
356+
.map(v -> v.prepend(literal, start, literal.text()));
357+
} else if (root instanceof final Identifier identifier) {
358+
if (!Character.isJavaIdentifierStart(text.charAt(0))) return Stream.empty();
359+
int cursor = 1;
360+
for (; cursor < text.length() && Character.isJavaIdentifierPart(text.charAt(cursor)); cursor++);
361+
return IntStream.iterate(cursor, i -> i > 0, i -> i - 1).boxed()
362+
.flatMap(limit -> identifier.children().stream().flatMap(child -> matches(start + limit, text.substring(limit), child))
363+
.map(v -> v.prepend(identifier, start, text.substring(0, limit))));
364+
} else if (root instanceof final Branch branch) {
365+
return branch.children().stream().flatMap(child -> matches(start, text, child).map(v -> v.tag(branch, start, start + text.length())));
366+
} else if (root instanceof final Input input) {
367+
return IntStream.iterate(text.length(), i -> i > 0, i -> i - 1).boxed()
368+
.flatMap(limit -> input.children().stream().flatMap(child -> matches(start + limit, text.substring(limit), child))
369+
.map(v -> v.prepend(input, start, text.substring(0, limit))));
370+
} else if (root instanceof final Dynamic dynamic) {
371+
final Node invoke = dynamic.supplier().get();
372+
return matches(start, text, invoke).map(v -> v.tag(dynamic, start, v.endIndexOf(invoke)));
373+
} else if (root instanceof final ChildNode labelledNode) {
374+
return matches(start, text, labelledNode.node()).map(v -> v.tag(labelledNode, start, v.startIndexOf(labelledNode.then())));
375+
} else if (root instanceof final Terminal terminal) {
376+
if (terminal.context() != null && terminal.context().redirect() != null)
377+
return matches(start, text, terminal.context().redirect()).map(v -> v.tag(terminal, start, start));
378+
if (!text.isEmpty()) return Stream.empty();
379+
return Stream.of(new Resolved(List.of(), "").tag(terminal, start, start));
380+
}
381+
throw new AssertionError("unreachable");
382+
}
383+
}

0 commit comments

Comments
 (0)