-
Notifications
You must be signed in to change notification settings - Fork 0
/
mustard.grammar
95 lines (83 loc) · 3.83 KB
/
mustard.grammar
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/* Having trouble debugging this grammar? It's almost compatible with
GNU Bison - just add this line before everything else:
%%
Now you can use "bison modules/mustard.grammar -Wcounterexamples" to
see what's going on with any shift/reduce conflicts. It'll also create
a file called "mustard.tab.grammar" which is mostly C but contains the
references to Pike functions, which will thus not compile; but it's at
least *syntactically* valid. */
mustard: ;
mustard: flags group {flagmessage};
flags: {makeflags};
flags: flags "#" "name" value {addflag};
flags: flags "#" "name" "=" value {addflag2};
value: "name";
value: "string";
value: "number";
/* There's some complexity here to allow conditions to have optional 'else', and
to have optional braces. It layers in a bit. Lemme walk you through it. */
/* A group has one or more messages, which might be conditional. */
group: condmessage {gather};
group: condmessage group {gather};
/* In order to disambiguate, we will either be in "open" or "closed" form.
An "open" message can have optional else clauses; a "closed" one cannot. */
condmessage: opencond;
condmessage: closedcond;
/* When we're in "open" mode, it's legal to have if-else, and it's legal to
have if without else. However, inside the "if" branch of one with an else,
you cannot have an elseless condition - it MUST have its own else. This is
the key to making the 'else' bind to the nearest 'if'. However, in the 'if'
of something that doesn't have an 'else', it's fine to have either if-else
or if-without-else. */
opencond: if closedcond "else" opencond {conditional};
opencond: if condmessage {conditional};
/* "Closed" mode is the inside of an if block that has an else. You can't
omit the 'else' here, neither in the if nor the else. */
closedcond: message;
closedcond: if closedcond "else" closedcond {conditional};
/* Note that, all the above concepts of "if-else" apply equivalently to all
three forms of condition, since they all have the same "else" behaviour. */
/* Incidentally, the keywords are entirely handled by the grammar here, and
can be discarded for subsequent processing. The condition itself is all that
matters. Strange how that happens. */
if: "if" condition {taketwo};
if: "test" calc_cond {taketwo};
if: "cooldown" cooldown {taketwo};
/* Of course, if you need to override any of the above rules, all you need
to do is encase something in braces, which counts as a 'message' (the lowest
form, valid in all nodes). */
message: "string";
message: "comment" {makecomment};
message: "{" flags group "}" {flagmessage2};
message: "[" flags group "]" {flagmessage2};
message: "{" "}" {emptymessage};
message: "[" "]" {emptymessage};
message: "varname" varoper value {setvar};
message: "name" "(" params ")" closedcond {builtin};
message: "try" condmessage "catch" closedcond {trycatch}; /* Possible future expansion: "catch" "(" filter ")" condmessage */
/* For any grammar rule that ends with chaining to another message -
that is, anything where you stick a prefix onto a message - there have
to be two variants, one in "message" that chains to closedcond, and one
in "opencond" that chains to another "opencond". This maintains the
state of whether 'else' clauses are optional or mandatory. */
opencond: "name" "(" params ")" opencond {builtin};
opencond: "try" condmessage "catch" opencond {trycatch};
params: {makeparams};
params: param_list;
params: param_list ",";
param_list: "string" {makeparams};
param_list: param_list "," "string" {addparam};
condition: "(" condition ")" {taketwo};
condition: flags value oper value flags {cond};
condition: "test" calc_cond {taketwo}; /* if (test "expr") if-true if-false */
oper: "==";
oper: "=~";
oper: "-=";
oper: "in";
calc_cond: "string" {cond_calc};
calc_cond: "(" calc_cond ")" {taketwo};
cooldown: "number" {cd_naked};
cooldown: "(" flags "number" flags ")" {cd_flags};
varoper: "=";
varoper: "+=";
varoper: "-=";