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 }