1 module pushdown.test;
2 
3 unittest {
4   import pushdown;
5   // define some custom nodes
6   class Token(string type) : Node {
7     char value;
8     
9     this(char value) {
10       this.value = value;
11     }
12 
13     override string toString() {
14       import std.conv : to;
15       return value.to!string;
16     }
17   }
18 
19   class Expression : Node {
20     Node lhs;
21     Node op;
22     Node rhs;
23 
24     this(Node lhs, Node op, Node rhs) {
25       this.lhs = lhs;
26       this.op = op;
27       this.rhs = rhs;
28     }
29 
30     override string toString() {
31       return "expr("~lhs.toString~" "~op.toString~" "~rhs.toString~")";
32     }
33   }
34 
35   class Assignment : Node {
36     Token!"identifier" lvalue;
37     Node rvalue;
38 
39     this(Node lvalue, Node rvalue) {
40       this.lvalue = cast(Token!"identifier")lvalue; // note: dangerous. Only do this if you're 100% sure it will be that type
41       this.rvalue = rvalue;
42     }
43 
44     override string toString() {
45       return "assign("~lvalue.toString~" "~rvalue.toString()~")";
46     }
47   }
48 
49   class Block : Node {
50     Node[] children;
51 
52     this(Node[] children) {
53       this.children = children;
54     }
55 
56     override string toString() {
57       string s = "block(";
58       foreach(size_t i, child; children) {
59         if(i > 0)
60           s ~= " ";
61         s ~= child.toString();
62       }
63       return s~")";
64     }
65   }
66 
67   // define a term
68   alias Term = Either!(Token!"number", Token!"identifier", Expression);
69   // test some things with term
70   assert(Term.valid(new Token!"number"('3')));
71   assert(Term.valid(new Token!"identifier"('x')));
72   assert(Term.valid(new Expression(new Token!"number"('3'), new Token!"operator"('+'), new Token!"number"('4'))));
73   assert(!Term.valid(new Token!"operator"('+')));
74   // a very simplistic lexer - splits the string into chars and assigns each char a type
75   Node[] lex(string s) {
76     Node[] ret;
77     foreach(char c; s) {
78       void add(string type)() {
79         ret ~= new Token!type(c);
80       }
81       switch(c) {
82         case '~':
83         case '+':
84         case '-':
85         case '*':
86         case '/':
87         case '%':
88           add!"operator";
89           break;
90         case '0': .. case '9':
91           add!"number";
92           break;
93         case '=':
94           add!"assign";
95           break;
96         case ';':
97           add!"semicolon";
98           break;
99         case '{':
100           add!"lbrace";
101           break;
102         case '}':
103           add!"rbrace";
104           break;
105         default:
106           add!"identifier";
107       }
108     }
109     return ret;
110   }
111   // lex something our lexer
112   Node[] nodes = lex("{x=3+2*4;{xyz}y=6/2/x+9;}z=z+1;");
113   // function to find the precedence of operators
114   int precedence(Node n) {
115     if(auto op = cast(Token!"operator")n) {
116       final switch(op.value) {
117         case '~':
118           return 1;
119         case '+':
120         case '-':
121           return 2;
122         case '*':
123         case '/':
124         case '%':
125           return 3;
126       }
127     }
128     return 0;
129   }
130   // create our parser
131   Parser parser = cast(Parser)[
132     new SequenceRule!(
133       // action: what to do with the nodes if parsing succeeds
134       (Node[] nodes) => new Expression(nodes[0], nodes[1], nodes[2]),
135       // extra:  extra things to check for a successful parse. In this case, checking operator precedence
136       (Node[] nodes, Node[] next) => 
137         next.length > 0 // make sure we can do lookahead
138           ? precedence(nodes[1]) >= precedence(next[0]) // if so, make sure precedence of this is not lower than next
139           : true, // otherwise just sucessfully parse
140       // list of nodes to check for
141       Term, Token!"operator", Term
142     ),
143     new SequenceRule!(
144       (Node[] nodes) => new Assignment(nodes[0], nodes[2]),
145       (Node[] nodes, Node[] next) => true, // always successfully parse
146       Token!"identifier", Token!"assign", Term, Token!"semicolon"
147     ),
148     new BalancedEdgeRule!(
149       (Node[] nodes) => new Block(nodes[1..$-1]),
150       (Node[] nodes, Node[] next) => true,
151       Token!"lbrace", Token!"rbrace"
152     )
153   ];
154   // parse
155   Node[] res = parser.parse(nodes);
156   import std.conv;
157   // easy way to check an entire tree
158   assert(res.to!string == "[block(assign(x expr(3 + expr(2 * 4))) block(x y z) assign(y expr(expr(expr(6 / 2) / x) + 9))), assign(z expr(z + 1))]");
159 }