1: <?php
2: // Parco
3: // Copyright (c) 2015 Niels Sonnich Poulsen (http://nielssp.dk)
4: // Licensed under the MIT license.
5: // See the LICENSE file or http://opensource.org/licenses/MIT for more information.
6: namespace Parco;
7:
8: /**
9: * A parser.
10: */
11: abstract class Parser
12: {
13:
14: /**
15: * Apply parser to input sequence.
16: *
17: * @param mixed $input
18: * Input sequence.
19: * @param int[] $pos
20: * Current position as a 2-element array consisting of a line
21: * number and a column number. See {@see Positional}.
22: * @return Result Parser result.
23: */
24: abstract public function parse($input, array $pos);
25:
26: /**
27: * Alternative composition of two parsers.
28: *
29: * `$p->alt($q)` is a parser that uses `$p` on the input and if `$p` fails
30: * uses `$q` on the same input. The parser fails if both $p or $q fail. The
31: * result is the result of the first parser that succeeded.
32: *
33: * @param Parser $other
34: * Other parser.
35: * @return Parser An alternative composition of the input parsers.
36: */
37: public function alt(Parser $other)
38: {
39: return new FuncParser(function ($input, array $pos) use ($other) {
40: $result = $this->parse($input, $pos);
41: if ($result->successful) {
42: return $result;
43: }
44: return $other->parse($input, $pos);
45: });
46: }
47:
48: /**
49: * Parser function applcication.
50: *
51: * `$p->map($f)` is a parser that succeeds and returns `$f($x)` if `$p`
52: * succeeds and returns `$x`. It fails if `$p` fails.
53: *
54: * @param callable $f Function to apply to parser output.
55: * @return Parser A parser that applies a function to the output of this
56: * parser.
57: */
58: public function map(callable $f)
59: {
60: return new FuncParser(function ($input, array $pos) use ($f) {
61: $result = $this->parse($input, $pos);
62: if (! $result->successful) {
63: return $result;
64: }
65: return new Success($f($result->result), $pos, $result->nextInput, $result->nextPos);
66: });
67: }
68:
69: /**
70: * Sequential composition of two parsers.
71: *
72: * `$p->seq($q)` is a parser that uses `$p` on the input followed by `$q`
73: * on the remaining input. The parser fails if either $p or $q fails. The
74: * result is an array containing both results.
75: *
76: * @param Parser $other
77: * Other parser.
78: * @return Parser A sequential composition of the input parsers.
79: */
80: public function seq(Parser $other)
81: {
82: return new FuncParser(function ($input, array $pos) use ($other) {
83: $a = $this->parse($input, $pos);
84: if (! $a->successful) {
85: return $a;
86: }
87: $b = $other->parse($a->nextInput, $a->nextPos);
88: if (! $b->successful) {
89: return $b;
90: }
91: return new Success(array(
92: $a->result,
93: $b->result
94: ), $pos, $b->nextInput, $b->nextPos);
95: });
96: }
97:
98: /**
99: * Sequential composition of two parsers returning only the left result.
100: *
101: * `$p->seq($q)` is a parser that uses `$p` on the input followed by `$q`
102: * on the remaining input. The parser fails if either $p or $q fails. The
103: * result is the result of `$p`.
104: *
105: * @param Parser $other
106: * Other parser.
107: * @return Parser A sequential composition of the input parsers.
108: */
109: public function seqL(Parser $other)
110: {
111: return new FuncParser(function ($input, array $pos) use ($other) {
112: $a = $this->parse($input, $pos);
113: if (! $a->successful) {
114: return $a;
115: }
116: $b = $other->parse($a->nextInput, $a->nextPos);
117: if (! $b->successful) {
118: return $b;
119: }
120: return new Success($a->result, $pos, $b->nextInput, $b->nextPos);
121: });
122: }
123:
124: /**
125: * Sequential composition of two parsers returning only the right result.
126: *
127: * `$p->seq($q)` is a parser that uses `$p` on the input followed by `$q`
128: * on the remaining input. The parser fails if either $p or $q fails. The
129: * result is the result of `$q`.
130: *
131: * @param Parser $other
132: * Other parser.
133: * @return Parser A sequential composition of the input parsers.
134: */
135: public function seqR(Parser $other)
136: {
137: return new FuncParser(function ($input, array $pos) use ($other) {
138: $a = $this->parse($input, $pos);
139: if (! $a->successful) {
140: return $a;
141: }
142: $b = $other->parse($a->nextInput, $a->nextPos);
143: if (! $b->successful) {
144: return $b;
145: }
146: return new Success($b->result, $pos, $b->nextInput, $b->nextPos);
147: });
148: }
149:
150: /**
151: * Change the failure message produced by this parser.
152: *
153: * `$p->withFailure($m)` fails if `$p` fails, then replaces `$p`'s failure
154: * message with `$m` instead.
155: *
156: * @param string $message
157: * The new failure message.
158: * @return Parser A parser with the new failure message.
159: */
160: public function withFailure($message)
161: {
162: return new FuncParser(function ($input, array $pos) use ($message) {
163: $r = $this->parse($input, $pos);
164: if ($r->successful) {
165: return $r;
166: }
167: return new Failure($message, $r->getPosition(), $r->nextInput, $r->nextPos);
168: });
169: }
170:
171: /**
172: * Change the value returned by this parser.
173: *
174: * `$p->withResult($x)` succeeds if `$p` succeds, then discards `$p`'s
175: * result and returns `$x` instead.
176: *
177: * @param mixed $result
178: * The new result.
179: * @return Parser A parser with the new result.
180: */
181: public function withResult($result)
182: {
183: return new FuncParser(function ($input, array $pos) use ($result) {
184: $r = $this->parse($input, $pos);
185: if (! $r->successful) {
186: return $r;
187: }
188: return new Success($result, $r->getPosition(), $r->nextInput, $r->nextPos);
189: });
190: }
191:
192: /**
193: * Add position to result of parser.
194: *
195: * `$p->positioned()` adds the position of the first input to the result of
196: * `$p`. The result must implement {@see Positional}.
197: *
198: * @return Parser A positioned parser.
199: */
200: public function positioned()
201: {
202: return new FuncParser(function ($input, array $pos) {
203: $r = $this->parse($input, $pos);
204: if ($r->successful) {
205: $r->get()->setPosition($pos);
206: }
207: return $r;
208: });
209: }
210: }
211: