/LanguageExt.Tests/ParsecTests.cs

https://github.com/louthy/language-ext · C# · 785 lines · 618 code · 148 blank · 19 comment · 64 complexity · 29c68a9226419747b690dee4cfa4f635 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6. using Xunit;
  7. using LanguageExt;
  8. using M = LanguageExt.Map;
  9. using LanguageExt.Parsec;
  10. using static LanguageExt.Prelude;
  11. using static LanguageExt.Parsec.Prim;
  12. using static LanguageExt.Parsec.Char;
  13. using static LanguageExt.Parsec.Expr;
  14. using static LanguageExt.Parsec.Token;
  15. using LanguageExt.UnitsOfMeasure;
  16. namespace LanguageExt.Tests
  17. {
  18. public class ParsecTests
  19. {
  20. [Fact]
  21. public void MultiLineNestedComments()
  22. {
  23. var jstp = makeTokenParser(Language.JavaStyle.With(NestedComments: true));
  24. var ws = jstp.WhiteSpace;
  25. var test3 = parse(ws, @"
  26. /*
  27. */
  28. ");
  29. }
  30. [Fact]
  31. public void MultiLineComments()
  32. {
  33. var jstp = makeTokenParser(Language.JavaStyle.With(NestedComments: false));
  34. var ws = jstp.WhiteSpace;
  35. var test3 = parse(ws, @"
  36. /*
  37. */
  38. ");
  39. }
  40. [Fact]
  41. public void ResultComb()
  42. {
  43. var p = result(1234);
  44. var r = parse(p, "Hello");
  45. Assert.False(r.IsFaulted);
  46. Assert.True(r.Reply.Result == 1234);
  47. }
  48. [Fact]
  49. public void ZeroComb()
  50. {
  51. var p = zero<Unit>();
  52. var r = parse(p, "Hello");
  53. Assert.True(r.IsFaulted);
  54. }
  55. [Fact]
  56. public void ItemComb()
  57. {
  58. var p = anyChar;
  59. var r = parse(p, "Hello");
  60. Assert.False(r.IsFaulted);
  61. Assert.True(r.Reply.Result == 'H');
  62. Assert.True(r.Reply.State.ToString() == "ello");
  63. }
  64. [Fact]
  65. public void ItemFailComb()
  66. {
  67. var p = anyChar;
  68. var r = parse(p, "");
  69. Assert.True(r.IsFaulted);
  70. }
  71. [Fact]
  72. public void Item2Comb()
  73. {
  74. var p = anyChar;
  75. var r1 = parse(p, "Hello");
  76. Assert.False(r1.IsFaulted);
  77. Assert.True(r1.Reply.Result == 'H');
  78. Assert.True(r1.Reply.State.ToString() == "ello");
  79. var r2 = parse(p, r1.Reply.State);
  80. Assert.False(r2.IsFaulted);
  81. Assert.True(r2.Reply.Result == 'e');
  82. Assert.True(r2.Reply.State.ToString() == "llo");
  83. }
  84. [Fact]
  85. public void Item1LinqComb()
  86. {
  87. var p = from x in anyChar
  88. select x;
  89. var r = parse(p, "Hello");
  90. Assert.False(r.IsFaulted);
  91. Assert.True(r.Reply.Result == 'H');
  92. Assert.True(r.Reply.State.ToString() == "ello");
  93. }
  94. [Fact]
  95. public void Item2LinqComb()
  96. {
  97. var p = from x in anyChar
  98. from y in anyChar
  99. select Tuple(x, y);
  100. var r = parse(p, "Hello");
  101. Assert.False(r.IsFaulted);
  102. Assert.True(r.Reply.Result.Item1 == 'H');
  103. Assert.True(r.Reply.Result.Item2 == 'e');
  104. Assert.True(r.Reply.State.ToString() == "llo");
  105. }
  106. [Fact]
  107. public void EitherFirstComb()
  108. {
  109. var p = either(ch('a'), ch('1'));
  110. var r = parse(p, "a");
  111. Assert.False(r.IsFaulted);
  112. Assert.True(r.Reply.Result == 'a');
  113. Assert.True(r.Reply.State.ToString() == "");
  114. }
  115. [Fact]
  116. public void EitherSecondComb()
  117. {
  118. var p = either(ch('a'), ch('1'));
  119. var r = parse(p, "1");
  120. Assert.False(r.IsFaulted);
  121. Assert.True(r.Reply.Result == '1');
  122. Assert.True(r.Reply.State.ToString() == "");
  123. }
  124. [Fact]
  125. public void EitherLINQComb()
  126. {
  127. var p = from x in either(ch('a'), ch('1'))
  128. from y in either(ch('a'), ch('1'))
  129. select Tuple(x, y);
  130. var r = parse(p, "a1");
  131. Assert.False(r.IsFaulted);
  132. Assert.True(r.Reply.Result.Item1 == 'a');
  133. Assert.True(r.Reply.Result.Item2 == '1');
  134. Assert.True(r.Reply.State.ToString() == "");
  135. }
  136. [Fact]
  137. public void UpperComb()
  138. {
  139. var p = upper;
  140. var r = parse(p, "Hello");
  141. Assert.False(r.IsFaulted);
  142. Assert.True(r.Reply.Result == 'H');
  143. Assert.True(r.Reply.State.ToString() == "ello");
  144. }
  145. [Fact]
  146. public void UpperFailComb()
  147. {
  148. var p = upper;
  149. var r = parse(p, "hello");
  150. Assert.True(r.IsFaulted);
  151. }
  152. [Fact]
  153. public void LowerComb()
  154. {
  155. var p = lower;
  156. var r = parse(p, "hello");
  157. Assert.False(r.IsFaulted);
  158. Assert.True(r.Reply.Result == 'h');
  159. Assert.True(r.Reply.State.ToString() == "ello");
  160. }
  161. [Fact]
  162. public void LowerFailComb()
  163. {
  164. var p = lower;
  165. var r = parse(p, "Hello");
  166. Assert.True(r.IsFaulted);
  167. }
  168. [Fact]
  169. public void DigitComb()
  170. {
  171. var p = digit;
  172. var r = parse(p, "1234");
  173. Assert.False(r.IsFaulted);
  174. Assert.True(r.Reply.Result == '1');
  175. Assert.True(r.Reply.State.ToString() == "234");
  176. }
  177. [Fact]
  178. public void DigitFailComb()
  179. {
  180. var p = digit;
  181. var r = parse(p, "Hello");
  182. Assert.True(r.IsFaulted);
  183. }
  184. [Fact]
  185. public void LetterComb()
  186. {
  187. var p = letter;
  188. var r = parse(p, "hello");
  189. Assert.False(r.IsFaulted);
  190. Assert.True(r.Reply.Result == 'h');
  191. Assert.True(r.Reply.State.ToString() == "ello");
  192. }
  193. [Fact]
  194. public void LetterFailComb()
  195. {
  196. var p = letter;
  197. var r = parse(p, "1ello");
  198. Assert.True(r.IsFaulted);
  199. }
  200. [Fact]
  201. public void WordComb()
  202. {
  203. var p = asString(many1(letter));
  204. var r = parse(p, "hello ");
  205. Assert.False(r.IsFaulted);
  206. Assert.True(r.Reply.Result == "hello");
  207. Assert.True(r.Reply.State.ToString() == " ");
  208. }
  209. [Fact]
  210. public void WordFailComb()
  211. {
  212. var p = asString(many1(letter));
  213. var r = parse(p, "1ello ");
  214. Assert.True(r.IsFaulted);
  215. }
  216. [Fact]
  217. public void StringMatchComb()
  218. {
  219. var p = str("hello");
  220. var r = parse(p, "hello world");
  221. Assert.False(r.IsFaulted);
  222. Assert.True(r.Reply.Result == "hello");
  223. Assert.True(r.Reply.State.ToString() == " world");
  224. }
  225. [Fact]
  226. public void StringMatchFailComb()
  227. {
  228. var p = str("hello");
  229. var r = parse(p, "no match");
  230. Assert.True(r.IsFaulted);
  231. }
  232. [Fact]
  233. public void NaturalNumberComb()
  234. {
  235. var tok = makeTokenParser(Language.HaskellStyle);
  236. var p = tok.Natural;
  237. var r = parse(p, "1234 ");
  238. Assert.False(r.IsFaulted);
  239. Assert.True(r.Reply.Result == 1234);
  240. Assert.True(r.Reply.State.ToString() == "");
  241. }
  242. [Fact]
  243. public void NaturalNumberFailComb()
  244. {
  245. var tok = makeTokenParser(Language.HaskellStyle);
  246. var p = tok.Natural;
  247. var r = parse(p, "no match");
  248. Assert.True(r.IsFaulted);
  249. }
  250. [Fact]
  251. public void IntegerNumberComb()
  252. {
  253. var tok = makeTokenParser(Language.HaskellStyle);
  254. var p = tok.Integer;
  255. var r = parse(p, "1234 ");
  256. Assert.False(r.IsFaulted);
  257. Assert.True(r.Reply.Result == 1234);
  258. Assert.True(r.Reply.State.ToString() == "");
  259. }
  260. [Fact]
  261. public void IntegerNegativeNumberComb()
  262. {
  263. var tok = makeTokenParser(Language.HaskellStyle);
  264. var p = tok.Integer;
  265. var r = parse(p, "-1234 ");
  266. Assert.False(r.IsFaulted);
  267. Assert.True(r.Reply.Result == -1234);
  268. Assert.True(r.Reply.State.ToString() == "");
  269. }
  270. [Fact]
  271. public void IntegerNumberFailComb()
  272. {
  273. var tok = makeTokenParser(Language.HaskellStyle);
  274. var p = tok.Integer;
  275. var r = parse(p, "no match");
  276. Assert.True(r.IsFaulted);
  277. }
  278. [Fact]
  279. public void BracketAndIntegerComb()
  280. {
  281. var tok = makeTokenParser(Language.HaskellStyle);
  282. var p = from x in tok.Brackets(tok.Integer)
  283. from _ in tok.WhiteSpace
  284. select x;
  285. var r = parse(p, "[1] ");
  286. Assert.False(r.IsFaulted);
  287. Assert.True(r.Reply.Result == 1);
  288. Assert.True(r.Reply.State.ToString() == "");
  289. }
  290. [Fact]
  291. public void BracketAndIntegerFailComb()
  292. {
  293. var tok = makeTokenParser(Language.HaskellStyle);
  294. var p = tok.Brackets(tok.Integer);
  295. var r = parse(p, "[x] ");
  296. Assert.True(r.IsFaulted);
  297. }
  298. [Fact]
  299. public void BracketAndIntegerListComb()
  300. {
  301. var tok = makeTokenParser(Language.HaskellStyle);
  302. var p = from x in tok.BracketsCommaSep(tok.Integer)
  303. from _ in tok.WhiteSpace
  304. select x;
  305. var r = parse(p, "[1,2,3,4] ");
  306. Assert.False(r.IsFaulted);
  307. var arr = r.Reply.Result.ToArray();
  308. Assert.True(arr[0] == 1);
  309. Assert.True(arr[1] == 2);
  310. Assert.True(arr[2] == 3);
  311. Assert.True(arr[3] == 4);
  312. Assert.True(r.Reply.State.ToString() == "");
  313. }
  314. [Fact]
  315. public void BracketAndSpacedIntegerListComb()
  316. {
  317. var tok = makeTokenParser(Language.HaskellStyle);
  318. var p = from x in tok.BracketsCommaSep(tok.Integer)
  319. from _ in tok.WhiteSpace
  320. select x;
  321. var r = parse(p, "[ 1, 2 ,3, 4] ");
  322. Assert.False(r.IsFaulted);
  323. var arr = r.Reply.Result.ToArray();
  324. Assert.True(arr[0] == 1);
  325. Assert.True(arr[1] == 2);
  326. Assert.True(arr[2] == 3);
  327. Assert.True(arr[3] == 4);
  328. Assert.True(r.Reply.State.ToString() == "");
  329. }
  330. [Fact]
  331. public void BracketAndIntegerListFailComb()
  332. {
  333. var tok = makeTokenParser(Language.HaskellStyle);
  334. var p = tok.BracketsCommaSep(tok.Integer);
  335. var r = parse(p, "[1,x,3,4] ");
  336. Assert.True(r.IsFaulted);
  337. }
  338. [Fact]
  339. public void JunkEmptyComb()
  340. {
  341. var tok = makeTokenParser(Language.HaskellStyle);
  342. var p = tok.WhiteSpace;
  343. var r = parse(p, "");
  344. Assert.False(r.IsFaulted);
  345. Assert.True(r.Reply.Result == unit);
  346. }
  347. [Fact]
  348. public void JunkNoMatchComb()
  349. {
  350. var tok = makeTokenParser(Language.HaskellStyle);
  351. var p = tok.WhiteSpace;
  352. var r = parse(p, ",");
  353. Assert.False(r.IsFaulted);
  354. Assert.True(r.Reply.Result == unit);
  355. }
  356. [Fact]
  357. public void JunkFourSpacesComb()
  358. {
  359. var tok = makeTokenParser(Language.HaskellStyle);
  360. var p = tok.WhiteSpace;
  361. var r = parse(p, " ,");
  362. Assert.False(r.IsFaulted);
  363. Assert.True(r.Reply.Result == unit);
  364. }
  365. [Fact]
  366. public void JunkFourSpacesThenCommentComb()
  367. {
  368. var tok = makeTokenParser(Language.JavaStyle);
  369. var p = tok.WhiteSpace;
  370. var r = parse(p, " // A comment\nabc");
  371. Assert.False(r.IsFaulted);
  372. Assert.True(r.Reply.Result == unit);
  373. Assert.True(r.Reply.State.ToString() == "abc");
  374. }
  375. [Fact]
  376. public void StringLiteralComb()
  377. {
  378. var tok = makeTokenParser(Language.HaskellStyle);
  379. var p = tok.StringLiteral;
  380. var r = parse(p, "\"/abc\"");
  381. Assert.False(r.IsFaulted);
  382. Assert.True(r.Reply.Result == "/abc");
  383. }
  384. [Theory]
  385. [InlineData("1234")]
  386. [InlineData("12345")]
  387. [InlineData("123456")]
  388. [InlineData("1234567")]
  389. [InlineData("12345678")]
  390. public void ParseNTimes(string input)
  391. {
  392. var p = asString(manyn(digit, 4));
  393. var r = parse(p, input).ToEither();
  394. Assert.True(r.IfLeft("") == "1234");
  395. }
  396. [Theory]
  397. [InlineData("")]
  398. [InlineData("1")]
  399. [InlineData("12")]
  400. [InlineData("123")]
  401. public void ParseNTimesFail(string input)
  402. {
  403. var p = asString(manyn(digit, 4));
  404. var r = parse(p, input).ToEither();
  405. Assert.True(r.IsLeft);
  406. }
  407. [Theory]
  408. [InlineData("1", "1")]
  409. [InlineData("12", "12")]
  410. [InlineData("123", "123")]
  411. [InlineData("1234", "1234")]
  412. [InlineData("12345", "1234")]
  413. public void ParseN1Times(string input, string expected)
  414. {
  415. var p = asString(manyn1(digit, 4));
  416. var r = parse(p, input).ToEither();
  417. Assert.True(r.IfLeft("") == expected);
  418. }
  419. [Fact]
  420. public void ParseN1TimesFail()
  421. {
  422. var p = asString(manyn1(digit, 4));
  423. var r = parse(p, "").ToEither();
  424. Assert.True(r.IsLeft);
  425. }
  426. [Theory]
  427. [InlineData("", "")]
  428. [InlineData("1", "1")]
  429. [InlineData("12", "12")]
  430. [InlineData("123", "123")]
  431. [InlineData("1234", "1234")]
  432. [InlineData("12345", "1234")]
  433. public void ParseN0Times(string input, string expected)
  434. {
  435. var p = asString(manyn0(digit, 4));
  436. var r = parse(p, input).ToEither();
  437. Assert.True(r.IfLeft("") == expected);
  438. }
  439. [Fact]
  440. public void ParseN0TimesZeroNegative()
  441. {
  442. Assert.True(parse(asString(manyn0(digit, 0)), "123").ToEither().IfLeft("x") == "");
  443. Assert.True(parse(asString(manyn0(digit, -1)), "123").ToEither().IfLeft("x") == "");
  444. }
  445. [Fact]
  446. public void SepByTest()
  447. {
  448. // greedy, but works because of nice input
  449. Assert.Equal(Seq("this", "is", "a", "path"), parse(sepBy1(asString(many1(alphaNum)), ch('/')), "this/is/a/path").ToEither());
  450. Assert.Equal(Seq("this", "is", "a", "path"), parse(sepBy1(asString(many1(alphaNum)), ch('/')), "this/is/a/path.").ToEither());
  451. // greedy + runs into dead end
  452. Assert.True(parse(sepBy1(asString(many1(alphaNum)), ch('/')), "this/is/a/path/").IsFaulted);
  453. // consume as many items as possible without failing
  454. Assert.Equal(Seq("this", "is", "a", "path"), parse(from x in asString(many1(alphaNum)) from xs in many(attempt(from sep in ch('/') from word in asString(many1(alphaNum)) select word)) select x.Cons(xs), "this/is/a/path/").ToEither());
  455. }
  456. [Fact]
  457. public void EndByTest()
  458. {
  459. // greedy, but works because of nice input
  460. Assert.Equal(Seq("this", "is", "a", "folder"), parse(endBy1(asString(many1(alphaNum)), ch('/')), "this/is/a/folder//").ToEither());
  461. Assert.Equal(Seq("this", "is", "a", "folder"), parse(endBy1(asString(many1(alphaNum)), ch('/')), "this/is/a/folder/").ToEither());
  462. // greedy + runs into dead end
  463. Assert.True(parse(endBy1(attempt(asString(many1(alphaNum))), attempt(ch('/'))), "this/is/a/folder/filename").IsFaulted);
  464. // consume as many items as possible without failing
  465. Assert.Equal(Seq("this", "is", "a", "folder"), parse(many1(attempt(from word in asString(many1(alphaNum)) from sep in ch('/') select word)), "this/is/a/folder/filename").ToEither());
  466. }
  467. [Fact]
  468. public void SepEndByTest()
  469. {
  470. Assert.Equal(Seq("this", "is", "a", "path"), parse(sepEndBy1(asString(many1(alphaNum)), ch('/')), "this/is/a/path//").ToEither());
  471. Assert.Equal(Seq("this", "is", "a", "path"), parse(sepEndBy1(asString(many1(alphaNum)), ch('/')), "this/is/a/path/").ToEither());
  472. Assert.Equal(Seq("this", "is", "a", "path"), parse(sepEndBy1(asString(many1(alphaNum)), ch('/')), "this/is/a/path").ToEither());
  473. Assert.True(parse(sepEndBy1(asString(many1(alphaNum)), ch('/')), ".").IsFaulted);
  474. Assert.Equal(Seq("this", "is", "a", "path"), parse(sepEndBy(asString(many1(alphaNum)), ch('/')), "this/is/a/path//").ToEither());
  475. Assert.Equal(Seq("this", "is", "a", "path"), parse(sepEndBy(asString(many1(alphaNum)), ch('/')), "this/is/a/path/").ToEither());
  476. Assert.Equal(Seq("this", "is", "a", "path"), parse(sepEndBy(asString(many1(alphaNum)), ch('/')), "this/is/a/path").ToEither());
  477. Assert.Equal(Seq<string>(), parse(sepEndBy(asString(many1(alphaNum)), ch('/')), ".").ToEither());
  478. }
  479. [Fact]
  480. public void ParallelCheck()
  481. {
  482. // works
  483. Parallel.ForEach(Enumerable.Repeat("", 4), str => parse(from _ in notFollowedBy(anyChar).label("end of input") select unit, str));
  484. // sometimes crashes (net461)
  485. Parallel.ForEach(Enumerable.Repeat("", 4), str => parse(from _ in eof select unit, str));
  486. }
  487. [Fact]
  488. public void FlattenCheck()
  489. {
  490. Parser<string> HexDigitParser() =>
  491. from hexDigitString in asString(flatten(sepBy1(many1(hexDigit), ch('-'))))
  492. from end in eof
  493. select hexDigitString;
  494. Assert.Equal("17CBA779DF1E4794A4992AAB59802C19", parse(HexDigitParser(), "17CBA779-DF1E-4794-A499-2AAB59802C19").ToOption());
  495. Assert.Equal("17CBA779DF1E4794A4992AAB59802C19", parse(HexDigitParser(), "17CBA779DF1E4794A4992AAB59802C19").ToOption());
  496. Assert.Equal(Option<string>.None, parse(HexDigitParser(), "-17CBA779-DF1E-4794-A499-2AAB59802C19").ToOption());
  497. Assert.Equal(Option<string>.None, parse(HexDigitParser(), "17CBA779-DF1E-4794-A499--2AAB59802C19").ToOption());
  498. }
  499. [Fact]
  500. public void ConsCheck()
  501. {
  502. Parser<string> HexDigitBlocksParser() =>
  503. from guidString in asString(flatten(cons(many1(hexDigit), many(cons(ch('-'), many1(hexDigit))))))
  504. from end in eof
  505. select guidString;
  506. Assert.Equal("17CBA779-DF1E-4794-A499-2AAB59802C19", parse(HexDigitBlocksParser(), "17CBA779-DF1E-4794-A499-2AAB59802C19").ToOption());
  507. Assert.Equal("17CBA779DF1E4794A4992AAB59802C19", parse(HexDigitBlocksParser(), "17CBA779DF1E4794A4992AAB59802C19").ToOption());
  508. Assert.Equal(Option<string>.None, parse(HexDigitBlocksParser(), "-17CBA779-DF1E-4794-A499-2AAB59802C19").ToOption());
  509. Assert.Equal(Option<string>.None, parse(HexDigitBlocksParser(), "17CBA779-DF1E-4794-A499--2AAB59802C19").ToOption());
  510. }
  511. [Fact]
  512. public void EMailParserCheck()
  513. {
  514. // Note: This e-mail parser is not correct! See https://tools.ietf.org/html/rfc5322#section-3.4.1 and related RFCs
  515. Parser<string> QuickAndDirtyEMailParser() =>
  516. from localpart in asString(flatten(cons(many1(alphaNum), many(cons(oneOf('-','.'), many1(alphaNum)))))) // simple name
  517. from at in ch('@')
  518. from domain in asString(flatten(cons(many1(alphaNum), many1(cons(ch('.'), many1(alphaNum)))))) // domain with at least one dot
  519. select $"{localpart}{at}{domain.ToLower()}";
  520. Assert.Equal("john@example.org", parse(QuickAndDirtyEMailParser(), "john@EXAMPLE.org").ToOption());
  521. Assert.EndsWith("expecting letter or digit or '@'", parse(QuickAndDirtyEMailParser(), "john @EXAMPLE.org").ToEither().IfRight(""));
  522. Assert.EndsWith("expecting letter or digit", parse(QuickAndDirtyEMailParser(), ".john @EXAMPLE.org").ToEither().IfRight(""));
  523. Assert.EndsWith("expecting letter or digit or '.'", parse(QuickAndDirtyEMailParser(), "john.doe@EXAMPLE").ToEither().IfRight(""));
  524. Assert.Equal("john-doe@example.org", parse(QuickAndDirtyEMailParser(), "john-doe@EXAMPLE.org").ToOption());
  525. Assert.Equal("john.doe@example.org", parse(QuickAndDirtyEMailParser(), "john.doe@EXAMPLE.org").ToOption());
  526. }
  527. [Fact]
  528. public void ExpressionResultShouldBeSixDueToMultiplicationOperationPriority()
  529. {
  530. // Arrange
  531. var tokenParser = makeTokenParser(Language.JavaStyle);
  532. var reservedOp = tokenParser.ReservedOp;
  533. // Natural parser
  534. var natural = from n in tokenParser.Natural
  535. select Expr.Natural(n);
  536. // Binary operator expression factory
  537. Func<Expr, Expr, Expr> binaryOp(string op) =>
  538. (Expr lhs, Expr rhs) =>
  539. op == "+" ? Expr.Add(lhs, rhs)
  540. : op == "-" ? Expr.Sub(lhs, rhs)
  541. : op == "/" ? Expr.Div(lhs, rhs)
  542. : op == "*" ? Expr.Mul(lhs, rhs)
  543. : throw new NotSupportedException();
  544. // Binary operator parser builder
  545. Operator<Expr> binary(string op, Assoc assoc) =>
  546. Operator.Infix(assoc,
  547. from x in reservedOp(op)
  548. select binaryOp(op));
  549. // Operator table
  550. Operator<Expr>[][] table =
  551. {
  552. new[] { binary("+", Assoc.Left), binary("-", Assoc.Left) },
  553. new[] { binary("*", Assoc.Left), binary("/", Assoc.Left) }
  554. };
  555. // Null because it will be not null later and can be used by the lazyp parser
  556. Parser<Expr> expr = null;
  557. // Build up the expression term
  558. var term = either(
  559. attempt(natural),
  560. tokenParser.Parens(lazyp(() => expr)));
  561. // Build the expression parser
  562. expr = buildExpressionParser(table, term).label("expression");
  563. var expression = "2 + 2 * 2";
  564. var exprectedResult = 6;
  565. // Act
  566. var actualResult = parse(expr, expression)
  567. .ToOption()
  568. .Map(ex => ex.Eval())
  569. .IfNone(0);
  570. // Assert
  571. Assert.Equal(exprectedResult, actualResult);
  572. }
  573. public abstract class Expr
  574. {
  575. public abstract int Eval();
  576. public static Expr Natural(int x) => new NaturalExpr(x);
  577. public static Expr Add(Expr left, Expr right) => new AddExpr(left, right);
  578. public static Expr Sub(Expr left, Expr right) => new SubExpr(left, right);
  579. public static Expr Mul(Expr left, Expr right) => new MulExpr(left, right);
  580. public static Expr Div(Expr left, Expr right) => new DivExpr(left, right);
  581. public class NaturalExpr : Expr
  582. {
  583. public int Value;
  584. public NaturalExpr(int value) => Value = value;
  585. public override int Eval() => Value;
  586. }
  587. public class AddExpr : Expr
  588. {
  589. public readonly Expr Left;
  590. public readonly Expr Right;
  591. public AddExpr(Expr left, Expr right)
  592. {
  593. Left = left;
  594. Right = right;
  595. }
  596. public override int Eval() =>
  597. Left.Eval() + Right.Eval();
  598. }
  599. public class SubExpr : Expr
  600. {
  601. public readonly Expr Left;
  602. public readonly Expr Right;
  603. public SubExpr(Expr left, Expr right)
  604. {
  605. Left = left;
  606. Right = right;
  607. }
  608. public override int Eval() =>
  609. Left.Eval() - Right.Eval();
  610. }
  611. public class MulExpr : Expr
  612. {
  613. public readonly Expr Left;
  614. public readonly Expr Right;
  615. public MulExpr(Expr left, Expr right)
  616. {
  617. Left = left;
  618. Right = right;
  619. }
  620. public override int Eval() =>
  621. Left.Eval() * Right.Eval();
  622. }
  623. public class DivExpr : Expr
  624. {
  625. public readonly Expr Left;
  626. public readonly Expr Right;
  627. public DivExpr(Expr left, Expr right)
  628. {
  629. Left = left;
  630. Right = right;
  631. }
  632. public override int Eval() =>
  633. Left.Eval() / Right.Eval();
  634. }
  635. }
  636. }
  637. }