├── js └── demo │ ├── .gitignore │ ├── input.json │ ├── server.js │ ├── client.js │ └── Demo.scala ├── project ├── build.properties └── plugins.sbt ├── src ├── test │ ├── resources │ │ └── fixtures │ │ │ ├── json │ │ │ ├── comments │ │ │ │ ├── prototype │ │ │ │ │ ├── step0 │ │ │ │ │ │ ├── error.txt │ │ │ │ │ │ ├── token.txt │ │ │ │ │ │ ├── cache.txt │ │ │ │ │ │ ├── fragment.txt │ │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step1 │ │ │ │ │ │ ├── error.txt │ │ │ │ │ │ ├── token.txt │ │ │ │ │ │ ├── cache.txt │ │ │ │ │ │ ├── fragment.txt │ │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step2 │ │ │ │ │ │ ├── error.txt │ │ │ │ │ │ ├── token.txt │ │ │ │ │ │ ├── cache.txt │ │ │ │ │ │ ├── fragment.txt │ │ │ │ │ │ └── node.txt │ │ │ │ │ └── step3 │ │ │ │ │ │ ├── error.txt │ │ │ │ │ │ ├── token.txt │ │ │ │ │ │ ├── cache.txt │ │ │ │ │ │ ├── fragment.txt │ │ │ │ │ │ └── node.txt │ │ │ │ └── input │ │ │ │ │ ├── step0.txt │ │ │ │ │ ├── step1.txt │ │ │ │ │ ├── step2.txt │ │ │ │ │ └── step3.txt │ │ │ ├── error-recovery │ │ │ │ ├── input │ │ │ │ │ ├── step0.txt │ │ │ │ │ ├── step1.txt │ │ │ │ │ ├── step2.txt │ │ │ │ │ ├── step3.txt │ │ │ │ │ ├── step4.txt │ │ │ │ │ ├── step5.txt │ │ │ │ │ ├── step6.txt │ │ │ │ │ ├── step7.txt │ │ │ │ │ ├── step8.txt │ │ │ │ │ ├── step9.txt │ │ │ │ │ ├── step10.txt │ │ │ │ │ ├── step11.txt │ │ │ │ │ ├── step12.txt │ │ │ │ │ ├── step13.txt │ │ │ │ │ ├── step14.txt │ │ │ │ │ ├── step15.txt │ │ │ │ │ └── step16.txt │ │ │ │ └── prototype │ │ │ │ │ ├── step3 │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step8 │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step14 │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step16 │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step0 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── node.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ └── cache.txt │ │ │ │ │ ├── step1 │ │ │ │ │ ├── node.txt │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── fragment.txt │ │ │ │ │ ├── step2 │ │ │ │ │ ├── node.txt │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ └── cache.txt │ │ │ │ │ ├── step4 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step5 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step6 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ ├── node.txt │ │ │ │ │ └── fragment.txt │ │ │ │ │ ├── step9 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step10 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step7 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step11 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step12 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step13 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ └── step15 │ │ │ │ │ ├── token.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ ├── simple │ │ │ │ ├── prototype │ │ │ │ │ ├── step0 │ │ │ │ │ │ ├── error.txt │ │ │ │ │ │ ├── token.txt │ │ │ │ │ │ ├── cache.txt │ │ │ │ │ │ ├── fragment.txt │ │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step1 │ │ │ │ │ │ ├── error.txt │ │ │ │ │ │ ├── token.txt │ │ │ │ │ │ ├── fragment.txt │ │ │ │ │ │ ├── cache.txt │ │ │ │ │ │ └── node.txt │ │ │ │ │ ├── step2 │ │ │ │ │ │ ├── error.txt │ │ │ │ │ │ ├── token.txt │ │ │ │ │ │ ├── fragment.txt │ │ │ │ │ │ ├── cache.txt │ │ │ │ │ │ └── node.txt │ │ │ │ │ └── step3 │ │ │ │ │ │ ├── error.txt │ │ │ │ │ │ ├── token.txt │ │ │ │ │ │ ├── fragment.txt │ │ │ │ │ │ ├── cache.txt │ │ │ │ │ │ └── node.txt │ │ │ │ └── input │ │ │ │ │ ├── step0.txt │ │ │ │ │ ├── step2.txt │ │ │ │ │ ├── step1.txt │ │ │ │ │ └── step3.txt │ │ │ ├── dense-fragments │ │ │ │ ├── input │ │ │ │ │ ├── step0.txt │ │ │ │ │ ├── step1.txt │ │ │ │ │ ├── step2.txt │ │ │ │ │ ├── step3.txt │ │ │ │ │ └── step4.txt │ │ │ │ └── prototype │ │ │ │ │ ├── step1 │ │ │ │ │ └── fragment.txt │ │ │ │ │ ├── step3 │ │ │ │ │ └── fragment.txt │ │ │ │ │ ├── step4 │ │ │ │ │ └── fragment.txt │ │ │ │ │ ├── step2 │ │ │ │ │ └── fragment.txt │ │ │ │ │ └── step0 │ │ │ │ │ └── fragment.txt │ │ │ ├── large │ │ │ │ └── prototype │ │ │ │ │ ├── step1 │ │ │ │ │ ├── error.txt │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ └── node.txt │ │ │ │ │ └── step2 │ │ │ │ │ ├── fragment.txt │ │ │ │ │ ├── cache.txt │ │ │ │ │ ├── error.txt │ │ │ │ │ └── node.txt │ │ │ ├── fragment-recovery │ │ │ │ ├── input │ │ │ │ │ ├── step1.txt │ │ │ │ │ ├── step0.txt │ │ │ │ │ └── step2.txt │ │ │ │ └── prototype │ │ │ │ │ ├── step1 │ │ │ │ │ ├── token.txt │ │ │ │ │ └── fragment.txt │ │ │ │ │ ├── step0 │ │ │ │ │ ├── token.txt │ │ │ │ │ └── fragment.txt │ │ │ │ │ └── step2 │ │ │ │ │ ├── token.txt │ │ │ │ │ └── fragment.txt │ │ │ ├── fragment-recovery-advanced │ │ │ │ ├── input │ │ │ │ │ ├── step0.txt │ │ │ │ │ ├── step1.txt │ │ │ │ │ └── step2.txt │ │ │ │ └── prototype │ │ │ │ │ ├── step0 │ │ │ │ │ ├── token.txt │ │ │ │ │ └── fragment.txt │ │ │ │ │ ├── step1 │ │ │ │ │ ├── token.txt │ │ │ │ │ └── fragment.txt │ │ │ │ │ └── step2 │ │ │ │ │ ├── token.txt │ │ │ │ │ └── fragment.txt │ │ │ └── config.json │ │ │ └── calculator │ │ │ ├── recovery │ │ │ ├── input │ │ │ │ ├── step0.txt │ │ │ │ └── step1.txt │ │ │ └── prototype │ │ │ │ ├── step0 │ │ │ │ ├── token.txt │ │ │ │ ├── error.txt │ │ │ │ └── node.txt │ │ │ │ └── step1 │ │ │ │ ├── token.txt │ │ │ │ ├── error.txt │ │ │ │ └── node.txt │ │ │ ├── expressions │ │ │ ├── input │ │ │ │ ├── step0.txt │ │ │ │ ├── step2.txt │ │ │ │ └── step1.txt │ │ │ └── prototype │ │ │ │ ├── step0 │ │ │ │ ├── error.txt │ │ │ │ ├── token.txt │ │ │ │ └── node.txt │ │ │ │ ├── step1 │ │ │ │ ├── error.txt │ │ │ │ ├── token.txt │ │ │ │ └── node.txt │ │ │ │ └── step2 │ │ │ │ ├── error.txt │ │ │ │ ├── token.txt │ │ │ │ └── node.txt │ │ │ └── config.json │ └── scala │ │ └── name.lakhin.eliah.projects │ │ └── papacarlo │ │ └── test │ │ ├── utils │ │ ├── SyntaxMonitor.scala │ │ ├── EmptyMonitor.scala │ │ ├── ErrorMonitor.scala │ │ ├── TokenizerMonitor.scala │ │ ├── Monitor.scala │ │ ├── Test.scala │ │ ├── FragmentationMonitor.scala │ │ ├── CacheMonitor.scala │ │ ├── NodeMonitor.scala │ │ ├── Resources.scala │ │ └── DebugMonitor.scala │ │ ├── JsonParserSpec.scala │ │ └── CalculatorSpec.scala └── main │ └── scala │ └── name.lakhin.eliah.projects │ └── papacarlo │ ├── syntax │ ├── Result.scala │ ├── Issue.scala │ ├── Packrat.scala │ ├── Error.scala │ ├── State.scala │ ├── rules │ │ ├── RequiredRule.scala │ │ ├── NamedRule.scala │ │ ├── SequentialRule.scala │ │ ├── CapturingRule.scala │ │ ├── ChoiceRule.scala │ │ ├── TokenRule.scala │ │ ├── RecoveryRule.scala │ │ └── RepetitionRule.scala │ ├── Cache.scala │ ├── NodeAccessor.scala │ ├── Expressions.scala │ └── Rule.scala │ ├── lexis │ ├── SkipLevel.scala │ ├── SeamType.scala │ ├── TokenReference.scala │ ├── Context.scala │ ├── Token.scala │ ├── Fragment.scala │ ├── Tokenizer.scala │ └── Matcher.scala │ ├── utils │ ├── Signal.scala │ ├── Difference.scala │ ├── Registry.scala │ └── Bounds.scala │ ├── examples │ └── Calculator.scala │ └── Syntax.scala ├── .gitignore ├── NOTICE ├── doc ├── add-sbt-plugin-sbt-updates.sh ├── chores.md └── compile.md ├── .scalafix.conf └── CHANGES.md /js/demo/.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | input.json -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.4.4 2 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step0/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step1/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step2/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step3/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step0.txt: -------------------------------------------------------------------------------- 1 | { -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step0/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step1/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step2/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step3/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | target/ 4 | *.iml 5 | tags 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/recovery/input/step0.txt: -------------------------------------------------------------------------------- 1 | 1 * * 3 -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step3/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step8/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/input/step0.txt: -------------------------------------------------------------------------------- 1 | 1 + 2 + 3 -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/prototype/step0/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/prototype/step1/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/prototype/step2/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/recovery/input/step1.txt: -------------------------------------------------------------------------------- 1 | 1 + 2 * (3 - 4 -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/dense-fragments/input/step0.txt: -------------------------------------------------------------------------------- 1 | {{}{}{{}}}{} -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/dense-fragments/input/step1.txt: -------------------------------------------------------------------------------- 1 | {{{}{}{{}}}{} -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/dense-fragments/input/step2.txt: -------------------------------------------------------------------------------- 1 | {{{}{}{{}}}{}} -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step14/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step16/error.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/dense-fragments/input/step3.txt: -------------------------------------------------------------------------------- 1 | {{{}{[}]{{}}}{}} -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/dense-fragments/input/step4.txt: -------------------------------------------------------------------------------- 1 | {{{}[{[]}]{{}}}{}} -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step1.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 3 | } -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | "Papa Carlo" 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/input/step2.txt: -------------------------------------------------------------------------------- 1 | (1 + 2) * ((3) + 4) -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step2.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": 3 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step0/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/input/step1.txt: -------------------------------------------------------------------------------- 1 | 1 - -2 / 3 * +4 / 5 + 6 + 7% + 8 -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/recovery/prototype/step0/token.txt: -------------------------------------------------------------------------------- 1 | number~0~ * * number 2 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step3.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world" 3 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step4.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/prototype/step0/token.txt: -------------------------------------------------------------------------------- 1 | number~0~ + number + number 2 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step0/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step1/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step2/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step2/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: 3 | } 4 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/recovery/prototype/step0/error.txt: -------------------------------------------------------------------------------- 1 | > operand required: 2 | 1 * <<<>>>* 3 3 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/recovery/prototype/step1/token.txt: -------------------------------------------------------------------------------- 1 | number~0~ + number * (number - number 2 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step0/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | >>> 4 | 5 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step1/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | unknownalphanum 3 | } 4 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step3/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string 3 | } 4 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step4/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | } 4 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/large/prototype/step1/error.txt: -------------------------------------------------------------------------------- 1 | > array entries must be separated with , sign (581:9) -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/large/prototype/step2/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | 32 (230:15) - (238:7) 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/prototype/step2/token.txt: -------------------------------------------------------------------------------- 1 | (~0~number + number) * ((number) + number) 2 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step5.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | ["array value 1" 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step0/error.txt: -------------------------------------------------------------------------------- 1 | > object must end with } sign: 2 | {<<<>>> 3 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/input/step0.txt: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "world", // comment 1 3 | "second": "line" 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step1/error.txt: -------------------------------------------------------------------------------- 1 | > code mismatched: 2 | { 3 | <<<"key>>> 4 | } 5 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/dense-fragments/prototype/step1/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | {<<<{{}>>>{}{{}}}{} 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step2/error.txt: -------------------------------------------------------------------------------- 1 | > code mismatched: 2 | { 3 | <<<"key 1":>>> 4 | } 5 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step0/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | >>> 5 | 6 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step2/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": 4 | }>>> 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/large/prototype/step2/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 343. Fragment: 32 (230:15) - (238:7). 3 | 4 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/input/step1.txt: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "world", // comment 1 // subcomment 3 | "second": "line" 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step6.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | ["array value 1", "array value 2"] 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step5/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | [~1:2:0~string 4 | }~2:0~ 5 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/recovery/prototype/step1/error.txt: -------------------------------------------------------------------------------- 1 | > ) expected, but end of fragment found: 2 | 1 + 2 * (3 - 4<<<>>> 3 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/input/step2.txt: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "world", // comment 1 // subcomment 3 | 4 | "second": "line" 5 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step7.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | key2: ["array value 1", "array value 2"] 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step4/error.txt: -------------------------------------------------------------------------------- 1 | > code mismatched: 2 | { 3 | "key 1": "hello world"<<<,>>> 4 | } 5 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/input/step3.txt: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "world", /* 3 | "second": "line", */ 4 | "third": "line" 5 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step8.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | "key 2": ["array value 1", "array value 2"] 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step9.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | "key 2": ["array value 1", "array value 2"], 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step1/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 5 | } 6 | >>> 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step3/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world" 4 | }>>> 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step4/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | }>>> 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step6/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | [~1:2:0~string, string] 4 | ~2:0~} 5 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/prototype/step1/token.txt: -------------------------------------------------------------------------------- 1 | number~0~ - -number / number * +number / number + number + number% + number 2 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step2/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": 5 | } 6 | >>> 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step8/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: [~1:2:0~string, string] 4 | ~2:0~} 5 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step0/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, //~3:2:0~ comment 1 3 | ~2:0~string: string 4 | } 5 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step9/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: [~1:2:0~string, string],~2:0~ 4 | } 5 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step10/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: [~1:2:0~string, string, string],~2:0~ 4 | } 5 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step5/error.txt: -------------------------------------------------------------------------------- 1 | > code mismatched: 2 | { 3 | "key 1": "hello world"<<<, 4 | ["array value 1">>> 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step7/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | alphanumnumber: [~1:2:0~string, string] 4 | ~2:0~} 5 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/large/prototype/step1/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | 94 (580:11) - (580:51) 3 | 4 | > invalidate: 5 | 84 (575:18) - (589:7) 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/large/prototype/step2/error.txt: -------------------------------------------------------------------------------- 1 | > array entries must be separated with , sign (581:9) 2 | 3 | > code mismatched (232:16) - (233:14) -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step1/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, //~3:2:0~ comment 1 // subcomment 3 | ~2:0~string: string 4 | } 5 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step10.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | "key 2": ["array value 1", "array value 2", "array value 3"], 4 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step3/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world" 5 | } 6 | >>> 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step4/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | } 6 | >>> 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step2/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, //~3:2:0~ comment 1 // subcomment 3 | 4 | ~2:0~ string: string 5 | } 6 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step5/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | ["array value 1" 5 | }>>> 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step3/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, /*~4:2:0~ 3 | "second": "line", */ 4 | ~2:0~ string: string 5 | } 6 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step11/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: 4 | string: [~1:2:0~string, string, string],~2:0~ 5 | } 6 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step11.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | "key 1.1": 4 | "key 2": ["array value 1", "array value 2", "array value 3"], 5 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step6/error.txt: -------------------------------------------------------------------------------- 1 | > code mismatched: 2 | { 3 | "key 1": "hello world"<<<, 4 | ["array value 1", "array value 2"]>>> 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step12/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: string 4 | string: [~1:2:0~string, string, string],~2:0~ 5 | } 6 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step13/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: string, 4 | string: [~1:2:0~string, string, string],~2:0~ 5 | } 6 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step7/error.txt: -------------------------------------------------------------------------------- 1 | > code mismatched: 2 | { 3 | "key 1": "hello world"<<<, 4 | key2: ["array value 1", "array value 2"]>>> 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step12.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | "key 1.1": "value 1.1" 4 | "key 2": ["array value 1", "array value 2", "array value 3"], 5 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step5/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | ["array value 1" 6 | } 7 | >>> 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step9/error.txt: -------------------------------------------------------------------------------- 1 | > code mismatched: 2 | { 3 | "key 1": "hello world", 4 | "key 2": ["array value 1", "array value 2"]<<<,>>> 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step0/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "hello": "world", // comment 1 5 | "second": "line" 6 | } 7 | >>> 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step13.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | "key 1.1": "value 1.1", 4 | "key 2": ["array value 1", "array value 2", "array value 3"], 5 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step1/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | <<<{ 3 | "key 4 | }>>> 5 | 6 | 7 | > invalidate: 8 | <<<{ 9 | "key 10 | } 11 | >>> 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step7/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | key2: ["array value 1", "array value 2"] 5 | }>>> 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step8/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | "key 2": ["array value 1", "array value 2"] 5 | }>>> 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/large/prototype/step1/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 840. Fragment: 84 (575:18) - (589:7). 3 | 4 | > create: 5 | Node 919. Fragment: 94 (580:11) - (580:51). 6 | 7 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/input/step0.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "value 1", 3 | "key 2": "value 2", 4 | "key 3": ["array element", 2.6e-2, true, false], 5 | "key 4": {"subkey": null} 6 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step9/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | "key 2": ["array value 1", "array value 2"], 5 | }>>> 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step14/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: string, 4 | string: [~1:2:0~string, string, string],~2:0~ 5 | string: number 6 | } 7 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step15/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: string 4 | string: [~1:2:0~string, string, string],~2:0~ 5 | string: number 6 | } 7 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step16/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: string, 4 | string: [~1:2:0~string, string, string],~2:0~ 5 | string: number 6 | } 7 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step6/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | ["array value 1", "array value 2"] 6 | } 7 | >>> 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/input/step2.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "value 1", 3 | "key 2": "value 2", 4 | "key 3": ["array element", 100, true, "false", ["a", "b"]], 5 | "key 4": {"subkey": null} 6 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step1/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "hello": "world", // comment 1 // subcomment 5 | "second": "line" 6 | } 7 | >>> 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step14.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | "key 1.1": "value 1.1", 4 | "key 2": ["array value 1", "array value 2", "array value 3"], 5 | "key 3": 100 6 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step15.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | "key 1.1": "value 1.1" 4 | "key 2": ["array value 1", "array value 2", "array value 3"], 5 | "key 3": 100 6 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/input/step16.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "hello world", 3 | "key 1.1": "value 1.1", 4 | "key 2": ["array value 1", "array value 2", "array value 3"], 5 | "key 3": 100 6 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step10/error.txt: -------------------------------------------------------------------------------- 1 | > code mismatched: 2 | { 3 | "key 1": "hello world", 4 | "key 2": ["array value 1", "array value 2", "array value 3"]<<<,>>> 5 | } 6 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/input/step1.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "value 1", 3 | "key 2": "value 2", 4 | "key 3": ["array element", 2.6e-2, true, "false", ["a", "b"]], 5 | "key 4": {"subkey": null} 6 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step10/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | { 3 | "key 1": "hello world", 4 | "key 2": <<<["array value 1", "array value 2", "array value 3"]>>>, 5 | } 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step7/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | key2: ["array value 1", "array value 2"] 6 | } 7 | >>> 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step9/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | "key 2": ["array value 1", "array value 2"], 6 | } 7 | >>> 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step0/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: string, 4 | string: [~1:2:0~string, number, true, false],~2:0~ 5 | string: {~2:2:0~string: null} 6 | ~2:0~} 7 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step2/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "hello": "world", // comment 1 // subcomment 5 | 6 | "second": "line" 7 | } 8 | >>> 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step3/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "hello": "world", /* 5 | "second": "line", */ 6 | "third": "line" 7 | } 8 | >>> 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step11/error.txt: -------------------------------------------------------------------------------- 1 | > code mismatched: 2 | { 3 | "key 1": "hello world", 4 | "key 1.1": 5 | "key 2"<<<: ["array value 1", "array value 2", "array value 3"],>>> 6 | } 7 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step10/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 5. Fragment: 3 | { 4 | "key 1": "hello world", 5 | "key 2": <<<["array value 1", "array value 2", "array value 3"]>>>, 6 | } 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step11/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | "key 1.1": 5 | "key 2": ["array value 1", "array value 2", "array value 3"], 6 | }>>> 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/input/step3.txt: -------------------------------------------------------------------------------- 1 | { 2 | "key 1": "value 1", 3 | "key 2": "value 2", 4 | "key 3": ["array element", 100, true, "false", ["a", "b"]], 5 | "key 3.5": "value 3.5", 6 | "key 4": {"subkey": null} 7 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/dense-fragments/prototype/step3/fragment.txt: -------------------------------------------------------------------------------- 1 | > remove: 2 | {{{}<<<{}>>>{{}}}{}} 3 | 4 | 5 | > create: 6 | {{{}{<<<[}]>>>{{}}}{}} 7 | 8 | 9 | > invalidate: 10 | {<<<{{}{[}]{{}}}>>>{}} 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step13/error.txt: -------------------------------------------------------------------------------- 1 | > code mismatched: 2 | { 3 | "key 1": "hello world", 4 | "key 1.1": "value 1.1", 5 | "key 2": ["array value 1", "array value 2", "array value 3"]<<<,>>> 6 | } 7 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step12/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | "key 1.1": "value 1.1" 5 | "key 2": ["array value 1", "array value 2", "array value 3"], 6 | }>>> 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step13/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | "key 1.1": "value 1.1", 5 | "key 2": ["array value 1", "array value 2", "array value 3"], 6 | }>>> 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step4/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 { 3 | entry: entry 2 >> 1 { 4 | key: "key 1" 5 | value: string 3 >> 2 { 6 | value: "hello world" 7 | } 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step5/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 { 3 | entry: entry 2 >> 1 { 4 | key: "key 1" 5 | value: string 3 >> 2 { 6 | value: "hello world" 7 | } 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step6/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 { 3 | entry: entry 2 >> 1 { 4 | key: "key 1" 5 | value: string 3 >> 2 { 6 | value: "hello world" 7 | } 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step7/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 { 3 | entry: entry 2 >> 1 { 4 | key: "key 1" 5 | value: string 3 >> 2 { 6 | value: "hello world" 7 | } 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step1/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: string, 4 | string: [~1:2:0~string, number, true, string, [~1:1:2:0~string, string]]~1:2:0~,~2:0~ 5 | string: {~2:2:0~string: null} 6 | ~2:0~} 7 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step2/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: string, 4 | string: [~1:2:0~string, number, true, string, [~1:1:2:0~string, string]]~1:2:0~,~2:0~ 5 | string: {~2:2:0~string: null} 6 | ~2:0~} 7 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step2/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | { 3 | "key 1": "value 1", 4 | "key 2": "value 2", 5 | "key 3": <<<["array element", 100, true, "false", ["a", "b"]]>>>, 6 | "key 4": {"subkey": null} 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step13/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | "key 1.1": "value 1.1", 6 | "key 2": ["array value 1", "array value 2", "array value 3"], 7 | } 8 | >>> 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step14/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | "key 1.1": "value 1.1", 5 | "key 2": ["array value 1", "array value 2", "array value 3"], 6 | "key 3": 100 7 | }>>> 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step15/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | "key 1.1": "value 1.1" 5 | "key 2": ["array value 1", "array value 2", "array value 3"], 6 | "key 3": 100 7 | }>>> 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step16/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "hello world", 4 | "key 1.1": "value 1.1", 5 | "key 2": ["array value 1", "array value 2", "array value 3"], 6 | "key 3": 100 7 | }>>> 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step3/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~ 2 | string: string, 3 | string: string, 4 | string: [~1:2:0~string, number, true, string, [~1:1:2:0~string, string]]~1:2:0~,~2:0~ 5 | string: string, 6 | string: {~2:2:0~string: null} 7 | ~2:0~} 8 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step15/error.txt: -------------------------------------------------------------------------------- 1 | > object entries must be separated with , sign: 2 | { 3 | "key 1": "hello world", 4 | "key 1.1": "value 1.1" 5 | <<<>>>"key 2": ["array value 1", "array value 2", "array value 3"], 6 | "key 3": 100 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step2/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 7. Fragment: 3 | { 4 | "key 1": "value 1", 5 | "key 2": "value 2", 6 | "key 3": <<<["array element", 100, true, "false", ["a", "b"]]>>>, 7 | "key 4": {"subkey": null} 8 | } 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step0/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | <<<{ 3 | "hello": "world", // comment 1 4 | "second": "line" 5 | }>>> 6 | 7 | 8 | > invalidate: 9 | <<<{ 10 | "hello": "world", // comment 1 11 | "second": "line" 12 | } 13 | >>> 14 | 15 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step14/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | "key 1.1": "value 1.1", 6 | "key 2": ["array value 1", "array value 2", "array value 3"], 7 | "key 3": 100 8 | } 9 | >>> 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step15/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | "key 1.1": "value 1.1" 6 | "key 2": ["array value 1", "array value 2", "array value 3"], 7 | "key 3": 100 8 | } 9 | >>> 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step16/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | "key 1.1": "value 1.1", 6 | "key 2": ["array value 1", "array value 2", "array value 3"], 7 | "key 3": 100 8 | } 9 | >>> 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step3/fragment.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | <<<{ 3 | "key 1": "value 1", 4 | "key 2": "value 2", 5 | "key 3": ["array element", 100, true, "false", ["a", "b"]], 6 | "key 3.5": "value 3.5", 7 | "key 4": {"subkey": null} 8 | }>>> 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step6/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | { 3 | "key 1": "hello world", 4 | <<<["array value 1", "array value 2"]>>> 5 | } 6 | 7 | 8 | > invalidate: 9 | <<<{ 10 | "key 1": "hello world", 11 | ["array value 1", "array value 2"] 12 | }>>> 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step3/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "value 1", 5 | "key 2": "value 2", 6 | "key 3": ["array element", 100, true, "false", ["a", "b"]], 7 | "key 3.5": "value 3.5", 8 | "key 4": {"subkey": null} 9 | } 10 | >>> 11 | 12 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step1/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | <<<{ 3 | "hello": "world", // comment 1 // subcomment 4 | "second": "line" 5 | }>>> 6 | 7 | 8 | > invalidate: 9 | <<<{ 10 | "hello": "world", // comment 1 // subcomment 11 | "second": "line" 12 | } 13 | >>> 14 | 15 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery/input/step1.txt: -------------------------------------------------------------------------------- 1 | {node 0 2 | {node 0-0 3 | {node 0-0-0 4 | 5 | 6 | 7 | {node 0-0-1 8 | 9 | } 10 | } 11 | 12 | {node 0-1 13 | {node 0-1-0 14 | 15 | } 16 | 17 | {node 0-1-1 18 | 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step2/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | <<<{ 3 | "hello": "world", // comment 1 // subcomment 4 | 5 | "second": "line" 6 | }>>> 7 | 8 | 9 | > invalidate: 10 | <<<{ 11 | "hello": "world", // comment 1 // subcomment 12 | 13 | "second": "line" 14 | } 15 | >>> 16 | 17 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery-advanced/input/step0.txt: -------------------------------------------------------------------------------- 1 | {node 0 2 | {node 00 3 | {node 000 4 | 5 | } 6 | 7 | {node 001 8 | 9 | } 10 | } 11 | 12 | {node 01 13 | {node 010 14 | 15 | } 16 | 17 | {node 011 18 | 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery-advanced/input/step1.txt: -------------------------------------------------------------------------------- 1 | {node 0 2 | {node 00 3 | {node 000 4 | 5 | } 6 | 7 | {node 001 8 | } 9 | } 10 | } 11 | 12 | {node 01 13 | {node 010 14 | 15 | } 16 | 17 | {node 011 18 | 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery-advanced/input/step2.txt: -------------------------------------------------------------------------------- 1 | {node 0 2 | {node 00 3 | {node 000 4 | 5 | 6 | 7 | {node 001 8 | 9 | } 10 | } 11 | } 12 | 13 | {node 01 14 | {node 010 15 | 16 | } 17 | 18 | {node 011 19 | 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery/input/step0.txt: -------------------------------------------------------------------------------- 1 | {node 0 2 | {node 0-0 3 | {node 0-0-0 4 | 5 | } 6 | 7 | {node 0-0-1 8 | 9 | } 10 | } 11 | 12 | {node 0-1 13 | {node 0-1-0 14 | 15 | } 16 | 17 | {node 0-1-1 18 | 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step3/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | <<<{ 3 | "hello": "world", /* 4 | "second": "line", */ 5 | "third": "line" 6 | }>>> 7 | 8 | 9 | > invalidate: 10 | <<<{ 11 | "hello": "world", /* 12 | "second": "line", */ 13 | "third": "line" 14 | } 15 | >>> 16 | 17 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery/input/step2.txt: -------------------------------------------------------------------------------- 1 | {node 0 2 | {node 0-0 3 | {node 0-0-0 4 | 5 | 6 | 7 | {node 0-0-1 8 | 9 | } 10 | } 11 | } 12 | 13 | {node 0-1 14 | {node 0-1-0 15 | 16 | } 17 | 18 | {node 0-1-1 19 | 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "expressions": { 3 | "steps": 3, 4 | "independentSteps": true, 5 | "monitors": ["node", "error", "token"] 6 | }, 7 | "recovery": { 8 | "steps": 2, 9 | "independentSteps": true, 10 | "monitors": ["node", "error", "token"] 11 | } 12 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/dense-fragments/prototype/step4/fragment.txt: -------------------------------------------------------------------------------- 1 | > remove: 2 | {{{}{<<<[}]>>>{{}}}{}} 3 | 4 | 5 | > create: 6 | {{{}[{<<<[]>>>}]{{}}}{}} 7 | 8 | 9 | > create: 10 | {{{}[<<<{[]}>>>]{{}}}{}} 11 | 12 | 13 | > create: 14 | {{{}<<<[{[]}]>>>{{}}}{}} 15 | 16 | 17 | > invalidate: 18 | {<<<{{}[{[]}]{{}}}>>>{}} 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step8/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | "key 2": ["array value 1", "array value 2"] 6 | } 7 | >>> 8 | 9 | > create: 10 | Node 5. Fragment: 11 | { 12 | "key 1": "hello world", 13 | "key 2": <<<["array value 1", "array value 2"]>>> 14 | } 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/dense-fragments/prototype/step2/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | {{<<<{}>>>{}{{}}}{}} 3 | 4 | 5 | > remove: 6 | {<<<{{}>>>{}{{}}}{}} 7 | 8 | 9 | > create: 10 | {<<<{{}{}{{}}}>>>{}} 11 | 12 | 13 | > remove: 14 | <<<{{{}{}{{}}}>>>{}} 15 | 16 | 17 | > create: 18 | <<<{{{}{}{{}}}{}}>>> 19 | 20 | 21 | > invalidate: 22 | <<<{{{}{}{{}}}{}} 23 | >>> 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step3/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | entry:2 3 | { 4 | <<<"key 1": "hello world">>> 5 | } 6 | 7 | 8 | > create: 9 | string:3 10 | { 11 | "key 1": <<<"hello world">>> 12 | } 13 | 14 | 15 | > merge: 16 | object 1 { 17 | entry: entry 2 >> 1 { 18 | key: "key 1" 19 | value: string 3 >> 2 { 20 | value: "hello world" 21 | } 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/dense-fragments/prototype/step0/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | {<<<{}>>>{}{{}}}{} 3 | 4 | 5 | > create: 6 | {{}<<<{}>>>{{}}}{} 7 | 8 | 9 | > create: 10 | {{}{}{<<<{}>>>}}{} 11 | 12 | 13 | > create: 14 | {{}{}<<<{{}}>>>}{} 15 | 16 | 17 | > create: 18 | <<<{{}{}{{}}}>>>{} 19 | 20 | 21 | > create: 22 | {{}{}{{}}}<<<{}>>> 23 | 24 | 25 | > invalidate: 26 | <<<{{}{}{{}}}{} 27 | >>> 28 | 29 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step11/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | "key 1.1": 6 | "key 2": ["array value 1", "array value 2", "array value 3"], 7 | } 8 | >>> 9 | 10 | > remove: 11 | Node 5. Fragment: 12 | { 13 | "key 1": "hello world", 14 | "key 1.1": 15 | "key 2": <<<["array value 1", "array value 2", "array value 3"]>>>, 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step12/error.txt: -------------------------------------------------------------------------------- 1 | > object entries must be separated with , sign: 2 | { 3 | "key 1": "hello world", 4 | "key 1.1": "value 1.1" 5 | <<<>>>"key 2": ["array value 1", "array value 2", "array value 3"], 6 | } 7 | 8 | 9 | > code mismatched: 10 | { 11 | "key 1": "hello world", 12 | "key 1.1": "value 1.1" 13 | "key 2": ["array value 1", "array value 2", "array value 3"]<<<,>>> 14 | } 15 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step12/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "hello world", 5 | "key 1.1": "value 1.1" 6 | "key 2": ["array value 1", "array value 2", "array value 3"], 7 | } 8 | >>> 9 | 10 | > create: 11 | Node 14. Fragment: 12 | { 13 | "key 1": "hello world", 14 | "key 1.1": "value 1.1" 15 | "key 2": <<<["array value 1", "array value 2", "array value 3"]>>>, 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step1/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | { 3 | "key 1": "value 1", 4 | "key 2": "value 2", 5 | "key 3": ["array element", 2.6e-2, true, "false", <<<["a", "b"]>>>], 6 | "key 4": {"subkey": null} 7 | } 8 | 9 | 10 | > invalidate: 11 | { 12 | "key 1": "value 1", 13 | "key 2": "value 2", 14 | "key 3": <<<["array element", 2.6e-2, true, "false", ["a", "b"]]>>>, 15 | "key 4": {"subkey": null} 16 | } 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step10/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | string:8 3 | { 4 | "key 1": "hello world", 5 | "key 2": ["array value 1", "array value 2", <<<"array value 3">>>], 6 | } 7 | 8 | 9 | > merge: 10 | array 5 cachable >> 4 { 11 | value: string 6 >> 5 { 12 | value: "array value 1" 13 | } 14 | value: string 7 >> 5 { 15 | value: "array value 2" 16 | } 17 | value: string 8 >> 5 { 18 | value: "array value 3" 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step1/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 7. Fragment: 3 | { 4 | "key 1": "value 1", 5 | "key 2": "value 2", 6 | "key 3": <<<["array element", 2.6e-2, true, "false", ["a", "b"]]>>>, 7 | "key 4": {"subkey": null} 8 | } 9 | 10 | 11 | > create: 12 | Node 17. Fragment: 13 | { 14 | "key 1": "value 1", 15 | "key 2": "value 2", 16 | "key 3": ["array element", 2.6e-2, true, "false", <<<["a", "b"]>>>], 17 | "key 4": {"subkey": null} 18 | } 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step9/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 { 3 | entry: entry 2 >> 1 { 4 | key: "key 1" 5 | value: string 3 >> 2 { 6 | value: "hello world" 7 | } 8 | } 9 | entry: entry 4 >> 1 { 10 | key: "key 2" 11 | value: array 5 cachable >> 4 { 12 | value: string 6 >> 5 { 13 | value: "array value 1" 14 | } 15 | value: string 7 >> 5 { 16 | value: "array value 2" 17 | } 18 | } 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /doc/add-sbt-plugin-sbt-updates.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p "$HOME/.sbt/1.0/plugins" 4 | 5 | cat >>"$HOME/.sbt/1.0/plugins/sbt-updates.sbt" <<'EOF' 6 | 7 | // https://github.com/rtimush/sbt-updates 8 | addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "latest.integration") 9 | 10 | EOF 11 | 12 | # now run `sbt`, wait for plugin download 13 | # and in the sbt-console run `dependencyUpdates` 14 | 15 | # when install fails, try 16 | # rm -rf "$HOME/.sbt/1.0/plugins"/{project,target} 17 | # and run `sbt` again 18 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/recovery/prototype/step0/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | *:2 3 | 1 <<<*>>> * 3 4 | 5 | 6 | > create: 7 | number:3 8 | <<<1>>> * * 3 9 | 10 | 11 | > create: 12 | placeholder:4 13 | 1 * <<<>>>* 3 14 | 15 | 16 | > create: 17 | number:5 18 | 1 * * <<<3>>> 19 | 20 | 21 | > merge: 22 | * 1 { 23 | left: * 2 >> 1 { 24 | left: number 3 >> 2 { 25 | value: 1 26 | } 27 | right: placeholder 4 >> 2 28 | } 29 | right: number 5 >> 1 { 30 | value: 3 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery/prototype/step1/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~alphanum number 2 | {~2:2:0~alphanum numbernumber 3 | {~2:2:2:0~alphanum numbernumbernumber 4 | 5 | 6 | ~2:2:0~ 7 | {~2:2:2:0~alphanum numbernumbernumber 8 | 9 | } 10 | ~2:2:0~ } 11 | ~2:0~ 12 | {~2:2:0~alphanum numbernumber 13 | {~2:2:2:0~alphanum numbernumbernumber 14 | 15 | } 16 | ~2:2:0~ 17 | {~2:2:2:0~alphanum numbernumbernumber 18 | 19 | } 20 | ~2:2:0~ } 21 | ~2:0~} 22 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery-advanced/prototype/step0/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~alphanum number 2 | {~2:2:0~alphanum numbernumber 3 | {~2:2:2:0~alphanum numbernumbernumber 4 | 5 | } 6 | ~2:2:0~ 7 | {~2:2:2:0~alphanum numbernumbernumber 8 | 9 | } 10 | ~2:2:0~ } 11 | ~2:0~ 12 | {~2:2:0~alphanum numbernumber 13 | {~2:2:2:0~alphanum numbernumber 14 | 15 | } 16 | ~2:2:0~ 17 | {~2:2:2:0~alphanum numbernumber 18 | 19 | } 20 | ~2:2:0~ } 21 | ~2:0~} 22 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery-advanced/prototype/step1/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~alphanum number 2 | {~2:2:0~alphanum numbernumber 3 | {~2:2:2:0~alphanum numbernumbernumber 4 | 5 | } 6 | ~2:2:0~ 7 | {~2:2:2:0~alphanum numbernumbernumber 8 | } 9 | } 10 | ~2:2:0~ } 11 | ~2:0~ 12 | {~2:2:0~alphanum numbernumber 13 | {~2:2:2:0~alphanum numbernumber 14 | 15 | } 16 | ~2:2:0~ 17 | {~2:2:2:0~alphanum numbernumber 18 | 19 | } 20 | ~2:2:0~ } 21 | ~2:0~} 22 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery-advanced/prototype/step2/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~alphanum number 2 | {~2:2:0~alphanum numbernumber 3 | {~2:2:2:0~alphanum numbernumbernumber 4 | 5 | 6 | 7 | {~2:2:2:2:0~alphanum numbernumbernumber 8 | 9 | } 10 | ~2:2:2:0~ } 11 | ~2:2:0~ } 12 | ~2:0~ 13 | {~2:2:0~alphanum numbernumber 14 | {~2:2:2:0~alphanum numbernumber 15 | 16 | } 17 | ~2:2:0~ 18 | {~2:2:2:0~alphanum numbernumber 19 | 20 | } 21 | ~2:2:0~ } 22 | ~2:0~} 23 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery/prototype/step0/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~alphanum number 2 | {~2:2:0~alphanum numbernumber 3 | {~2:2:2:0~alphanum numbernumbernumber 4 | 5 | } 6 | ~2:2:0~ 7 | {~2:2:2:0~alphanum numbernumbernumber 8 | 9 | } 10 | ~2:2:0~ } 11 | ~2:0~ 12 | {~2:2:0~alphanum numbernumber 13 | {~2:2:2:0~alphanum numbernumbernumber 14 | 15 | } 16 | ~2:2:0~ 17 | {~2:2:2:0~alphanum numbernumbernumber 18 | 19 | } 20 | ~2:2:0~ } 21 | ~2:0~} 22 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery/prototype/step2/token.txt: -------------------------------------------------------------------------------- 1 | {~2:0~alphanum number 2 | {~2:2:0~alphanum numbernumber 3 | {~2:2:2:0~alphanum numbernumbernumber 4 | 5 | 6 | 7 | {~2:2:2:2:0~alphanum numbernumbernumber 8 | 9 | } 10 | ~2:2:2:0~ } 11 | ~2:2:0~ } 12 | ~2:0~ 13 | {~2:2:0~alphanum numbernumber 14 | {~2:2:2:0~alphanum numbernumbernumber 15 | 16 | } 17 | ~2:2:0~ 18 | {~2:2:2:0~alphanum numbernumbernumber 19 | 20 | } 21 | ~2:2:0~ } 22 | ~2:0~} 23 | ~0~ -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/prototype/step0/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | +:2 3 | 1 <<<+>>> 2 + 3 4 | 5 | 6 | > create: 7 | number:3 8 | <<<1>>> + 2 + 3 9 | 10 | 11 | > create: 12 | number:4 13 | 1 + <<<2>>> + 3 14 | 15 | 16 | > create: 17 | number:5 18 | 1 + 2 + <<<3>>> 19 | 20 | 21 | > merge: 22 | + 1 { 23 | left: + 2 >> 1 { 24 | left: number 3 >> 2 { 25 | value: 1 26 | } 27 | right: number 4 >> 2 { 28 | value: 2 29 | } 30 | } 31 | right: number 5 >> 1 { 32 | value: 3 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/large/prototype/step2/node.txt: -------------------------------------------------------------------------------- 1 | > remove: 2 | string:346 (233:9) - (233:13) 3 | 4 | > merge: 5 | array 343 cachable >> 342 { 6 | value: string 344 >> 343 { 7 | value: "id" 8 | } 9 | value: string 345 >> 343 { 10 | value: "Lorem" 11 | } 12 | value: string 347 >> 343 { 13 | value: "anim" 14 | } 15 | value: string 348 >> 343 { 16 | value: "esse" 17 | } 18 | value: string 349 >> 343 { 19 | value: "aute" 20 | } 21 | value: string 350 >> 343 { 22 | value: "ut" 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery/prototype/step1/fragment.txt: -------------------------------------------------------------------------------- 1 | > remove: 2 | {node 0 3 | {node 0-0 4 | <<<{node 0-0-0 5 | 6 | }>>> 7 | 8 | {node 0-0-1 9 | 10 | } 11 | } 12 | 13 | {node 0-1 14 | {node 0-1-0 15 | 16 | } 17 | 16... 18 | 19 | > invalidate: 20 | {node 0 21 | <<<{node 0-0 22 | {node 0-0-0 23 | 24 | 25 | 26 | {node 0-0-1 27 | 28 | } 29 | }>>> 30 | 31 | {node 0-1 32 | {node 0-1-0 33 | 34 | } 35 | 36 | {node 0-1-1 37 | 38 | } 39 | } 40 | 21... 41 | 42 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery/prototype/step2/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | {node 0 3 | {node 0-0 4 | <<<{node 0-0-0 5 | 6 | 7 | 8 | {node 0-0-1 9 | 10 | } 11 | }>>> 12 | } 13 | 14 | {node 0-1 15 | {node 0-1-0 16 | 17 | } 18 | 19 | {node 0-1-1 20 | 21 | } 22 | 21... 23 | 24 | > invalidate: 25 | {node 0 26 | <<<{node 0-0 27 | {node 0-0-0 28 | 29 | 30 | 31 | {node 0-0-1 32 | 33 | } 34 | } 35 | }>>> 36 | 37 | {node 0-1 38 | {node 0-1-0 39 | 40 | } 41 | 42 | {node 0-1-1 43 | 44 | } 45 | } 46 | 22... 47 | 48 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step0/cache.txt: -------------------------------------------------------------------------------- 1 | > invalidate: 2 | Node 1. Fragment: 3 | <<<{ 4 | "key 1": "value 1", 5 | "key 2": "value 2", 6 | "key 3": ["array element", 2.6e-2, true, false], 7 | "key 4": {"subkey": null} 8 | } 9 | >>> 10 | 11 | > create: 12 | Node 7. Fragment: 13 | { 14 | "key 1": "value 1", 15 | "key 2": "value 2", 16 | "key 3": <<<["array element", 2.6e-2, true, false]>>>, 17 | "key 4": {"subkey": null} 18 | } 19 | 20 | 21 | > create: 22 | Node 13. Fragment: 23 | { 24 | "key 1": "value 1", 25 | "key 2": "value 2", 26 | "key 3": ["array element", 2.6e-2, true, false], 27 | "key 4": <<<{"subkey": null}>>> 28 | } 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step13/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 { 3 | entry: entry 2 >> 1 { 4 | key: "key 1" 5 | value: string 3 >> 2 { 6 | value: "hello world" 7 | } 8 | } 9 | entry: entry 11 >> 1 { 10 | key: "key 1.1" 11 | value: string 12 >> 11 { 12 | value: "value 1.1" 13 | } 14 | } 15 | entry: entry 13 >> 1 { 16 | key: "key 2" 17 | value: array 14 cachable >> 13 { 18 | value: string 15 >> 14 { 19 | value: "array value 1" 20 | } 21 | value: string 16 >> 14 { 22 | value: "array value 2" 23 | } 24 | value: string 17 >> 14 { 25 | value: "array value 3" 26 | } 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "simple": { 3 | "steps": 4 4 | }, 5 | "error-recovery": { 6 | "steps": 17 7 | }, 8 | "large": { 9 | "steps": 3, 10 | "outputFrom": 1, 11 | "monitors": ["fragment", "cache", "node", "error"], 12 | "shortOutput": true 13 | }, 14 | "dense-fragments": { 15 | "steps": 5, 16 | "monitors": ["fragment"] 17 | }, 18 | "fragment-recovery": { 19 | "steps": 3, 20 | "monitors": ["token", "fragment"] 21 | }, 22 | "fragment-recovery-advanced": { 23 | "steps": 3, 24 | "monitors": ["token", "fragment"] 25 | }, 26 | "comments": { 27 | "independentSteps": true 28 | } 29 | } -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/recovery/prototype/step1/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | number:2 3 | <<<1>>> + 2 * (3 - 4 4 | 5 | 6 | > create: 7 | *:3 8 | 1 + 2 <<<*>>> (3 - 4 9 | 10 | 11 | > create: 12 | number:4 13 | 1 + <<<2>>> * (3 - 4 14 | 15 | 16 | > create: 17 | -:5 18 | 1 + 2 * (3 <<<->>> 4 19 | 20 | 21 | > create: 22 | number:6 23 | 1 + 2 * (<<<3>>> - 4 24 | 25 | 26 | > create: 27 | number:7 28 | 1 + 2 * (3 - <<<4>>> 29 | 30 | 31 | > merge: 32 | + 1 { 33 | left: number 2 >> 1 { 34 | value: 1 35 | } 36 | right: * 3 >> 1 { 37 | left: number 4 >> 3 { 38 | value: 2 39 | } 40 | right: - 5 >> 3 { 41 | left: number 6 >> 5 { 42 | value: 3 43 | } 44 | right: number 7 >> 5 { 45 | value: 4 46 | } 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step0/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | { 3 | "key 1": "value 1", 4 | "key 2": "value 2", 5 | "key 3": <<<["array element", 2.6e-2, true, false]>>>, 6 | "key 4": {"subkey": null} 7 | } 8 | 9 | 10 | > create: 11 | { 12 | "key 1": "value 1", 13 | "key 2": "value 2", 14 | "key 3": ["array element", 2.6e-2, true, false], 15 | "key 4": <<<{"subkey": null}>>> 16 | } 17 | 18 | 19 | > create: 20 | <<<{ 21 | "key 1": "value 1", 22 | "key 2": "value 2", 23 | "key 3": ["array element", 2.6e-2, true, false], 24 | "key 4": {"subkey": null} 25 | }>>> 26 | 27 | 28 | > invalidate: 29 | <<<{ 30 | "key 1": "value 1", 31 | "key 2": "value 2", 32 | "key 3": ["array element", 2.6e-2, true, false], 33 | "key 4": {"subkey": null} 34 | } 35 | >>> 36 | 37 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/prototype/step2/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | +:2 3 | (1 <<<+>>> 2) * ((3) + 4) 4 | 5 | 6 | > create: 7 | number:3 8 | (<<<1>>> + 2) * ((3) + 4) 9 | 10 | 11 | > create: 12 | number:4 13 | (1 + <<<2>>>) * ((3) + 4) 14 | 15 | 16 | > create: 17 | +:5 18 | (1 + 2) * ((3) <<<+>>> 4) 19 | 20 | 21 | > create: 22 | number:6 23 | (1 + 2) * ((<<<3>>>) + 4) 24 | 25 | 26 | > create: 27 | number:7 28 | (1 + 2) * ((3) + <<<4>>>) 29 | 30 | 31 | > merge: 32 | * 1 { 33 | left: + 2 >> 1 { 34 | left: number 3 >> 2 { 35 | value: 1 36 | } 37 | right: number 4 >> 2 { 38 | value: 2 39 | } 40 | } 41 | right: + 5 >> 1 { 42 | left: number 6 >> 5 { 43 | value: 3 44 | } 45 | right: number 7 >> 5 { 46 | value: 4 47 | } 48 | } 49 | } 50 | 51 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step0/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | entry:2 3 | { 4 | <<<"hello": "world">>>, // comment 1 5 | "second": "line" 6 | } 7 | 8 | 9 | > create: 10 | string:3 11 | { 12 | "hello": <<<"world">>>, // comment 1 13 | "second": "line" 14 | } 15 | 16 | 17 | > create: 18 | entry:4 19 | { 20 | "hello": "world", // comment 1 21 | <<<"second": "line">>> 22 | } 23 | 24 | 25 | > create: 26 | string:5 27 | { 28 | "hello": "world", // comment 1 29 | "second": <<<"line">>> 30 | } 31 | 32 | 33 | > merge: 34 | object 1 { 35 | entry: entry 2 >> 1 { 36 | key: "hello" 37 | value: string 3 >> 2 { 38 | value: "world" 39 | } 40 | } 41 | entry: entry 4 >> 1 { 42 | key: "second" 43 | value: string 5 >> 4 { 44 | value: "line" 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step15/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 { 3 | entry: entry 2 >> 1 { 4 | key: "key 1" 5 | value: string 3 >> 2 { 6 | value: "hello world" 7 | } 8 | } 9 | entry: entry 11 >> 1 { 10 | key: "key 1.1" 11 | value: string 12 >> 11 { 12 | value: "value 1.1" 13 | } 14 | } 15 | entry: entry 13 >> 1 { 16 | key: "key 2" 17 | value: array 14 cachable >> 13 { 18 | value: string 15 >> 14 { 19 | value: "array value 1" 20 | } 21 | value: string 16 >> 14 { 22 | value: "array value 2" 23 | } 24 | value: string 17 >> 14 { 25 | value: "array value 3" 26 | } 27 | } 28 | } 29 | entry: entry 18 >> 1 { 30 | key: "key 3" 31 | value: number 19 >> 18 { 32 | value: 100 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step16/node.txt: -------------------------------------------------------------------------------- 1 | > merge: 2 | object 1 { 3 | entry: entry 2 >> 1 { 4 | key: "key 1" 5 | value: string 3 >> 2 { 6 | value: "hello world" 7 | } 8 | } 9 | entry: entry 11 >> 1 { 10 | key: "key 1.1" 11 | value: string 12 >> 11 { 12 | value: "value 1.1" 13 | } 14 | } 15 | entry: entry 13 >> 1 { 16 | key: "key 2" 17 | value: array 14 cachable >> 13 { 18 | value: string 15 >> 14 { 19 | value: "array value 1" 20 | } 21 | value: string 16 >> 14 { 22 | value: "array value 2" 23 | } 24 | value: string 17 >> 14 { 25 | value: "array value 3" 26 | } 27 | } 28 | } 29 | entry: entry 18 >> 1 { 30 | key: "key 3" 31 | value: number 19 >> 18 { 32 | value: 100 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | // https://github.com/sbt/sbt-pgp 2 | addSbtPlugin("com.jsuereth" % "sbt-pgp" % "2.1.1") 3 | 4 | // https://www.scala-js.org/doc/project/index.html 5 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.3.1") 6 | 7 | // https://github.com/ceedubs/sbt-ctags 8 | addSbtPlugin("net.ceedubs" %% "sbt-ctags" % "0.3.0") 9 | // TODO sbt-ctags is abandoned, replace with metals 10 | // https://scalameta.org/metals/docs/build-tools/sbt.html 11 | 12 | 13 | 14 | // optional plugins for auto-correction and formatting 15 | 16 | // https://scalacenter.github.io/scalafix/docs/users/installation.html 17 | // CHORE to activate this, also enable `ThisBuild / semanticdb` in `build.sbt` 18 | //addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.24") 19 | 20 | // https://github.com/lucidsoftware/neo-sbt-scalafmt 21 | //addSbtPlugin("com.lucidchart" % "sbt-scalafmt" % "1.16") 22 | -------------------------------------------------------------------------------- /js/demo/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": 1, 3 | "guid": "866b81f6-dda1-4c47-945a-005ae6f55996", 4 | "isActive": true, 5 | "balance": "$1,832.00", 6 | "picture": "http://example.com/32x32", 7 | "age": 34, 8 | "name": "Joyce Sargent", 9 | "gender": "female", 10 | "company": "Hometown", 11 | "email": "joycesargent@example.com", 12 | "phone": "+1 (937) 515-2457", 13 | "registered": "2005-11-02T22:22:53 -07:00", 14 | "latitude": 67.658935, 15 | "longitude": 164.839993, 16 | "tags": [ 17 | "nisi", 18 | "est", 19 | "eu", 20 | "amet", 21 | "aute", 22 | "enim", 23 | "ipsum" 24 | ], 25 | "friends": [ 26 | { 27 | "id": 0, 28 | "name": "Minerva Hahn" 29 | }, 30 | { 31 | "id": 1, 32 | "name": "Brandy Jacobson" 33 | }, 34 | { 35 | "id": 2, 36 | "name": "Lillie Kent" 37 | } 38 | ] 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step1/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | entry:2 3 | { 4 | <<<"hello": "world">>>, // comment 1 // subcomment 5 | "second": "line" 6 | } 7 | 8 | 9 | > create: 10 | string:3 11 | { 12 | "hello": <<<"world">>>, // comment 1 // subcomment 13 | "second": "line" 14 | } 15 | 16 | 17 | > create: 18 | entry:4 19 | { 20 | "hello": "world", // comment 1 // subcomment 21 | <<<"second": "line">>> 22 | } 23 | 24 | 25 | > create: 26 | string:5 27 | { 28 | "hello": "world", // comment 1 // subcomment 29 | "second": <<<"line">>> 30 | } 31 | 32 | 33 | > merge: 34 | object 1 { 35 | entry: entry 2 >> 1 { 36 | key: "hello" 37 | value: string 3 >> 2 { 38 | value: "world" 39 | } 40 | } 41 | entry: entry 4 >> 1 { 42 | key: "second" 43 | value: string 5 >> 4 { 44 | value: "line" 45 | } 46 | } 47 | } 48 | 49 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/Result.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax 19 | 20 | object Result { 21 | val Successful = 1 22 | val Recoverable = 0 23 | val Failed = -1 24 | } 25 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step2/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | entry:2 3 | { 4 | <<<"hello": "world">>>, // comment 1 // subcomment 5 | 6 | "second": "line" 7 | } 8 | 9 | 10 | > create: 11 | string:3 12 | { 13 | "hello": <<<"world">>>, // comment 1 // subcomment 14 | 15 | "second": "line" 16 | } 17 | 18 | 19 | > create: 20 | entry:4 21 | { 22 | "hello": "world", // comment 1 // subcomment 23 | 24 | <<<"second": "line">>> 25 | } 26 | 27 | 28 | > create: 29 | string:5 30 | { 31 | "hello": "world", // comment 1 // subcomment 32 | 33 | "second": <<<"line">>> 34 | } 35 | 36 | 37 | > merge: 38 | object 1 { 39 | entry: entry 2 >> 1 { 40 | key: "hello" 41 | value: string 3 >> 2 { 42 | value: "world" 43 | } 44 | } 45 | entry: entry 4 >> 1 { 46 | key: "second" 47 | value: string 5 >> 4 { 48 | value: "line" 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/comments/prototype/step3/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | entry:2 3 | { 4 | <<<"hello": "world">>>, /* 5 | "second": "line", */ 6 | "third": "line" 7 | } 8 | 9 | 10 | > create: 11 | string:3 12 | { 13 | "hello": <<<"world">>>, /* 14 | "second": "line", */ 15 | "third": "line" 16 | } 17 | 18 | 19 | > create: 20 | entry:4 21 | { 22 | "hello": "world", /* 23 | "second": "line", */ 24 | <<<"third": "line">>> 25 | } 26 | 27 | 28 | > create: 29 | string:5 30 | { 31 | "hello": "world", /* 32 | "second": "line", */ 33 | "third": <<<"line">>> 34 | } 35 | 36 | 37 | > merge: 38 | object 1 { 39 | entry: entry 2 >> 1 { 40 | key: "hello" 41 | value: string 3 >> 2 { 42 | value: "world" 43 | } 44 | } 45 | entry: entry 4 >> 1 { 46 | key: "third" 47 | value: string 5 >> 4 { 48 | value: "line" 49 | } 50 | } 51 | } 52 | 53 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery-advanced/prototype/step1/fragment.txt: -------------------------------------------------------------------------------- 1 | > remove: 2 | {node 0 3 | {node 00 4 | {node 000 5 | 6 | } 7 | 8 | <<<{node 001 9 | } 10 | }>>> 11 | } 12 | 13 | {node 01 14 | {node 010 15 | 16 | } 17 | 18 | {node 011 19 | 20 | } 21 | 20... 22 | 23 | > create: 24 | {node 0 25 | {node 00 26 | {node 000 27 | 28 | } 29 | 30 | <<<{node 001 31 | }>>> 32 | } 33 | } 34 | 35 | {node 01 36 | {node 010 37 | 38 | } 39 | 40 | {node 011 41 | 42 | 19... 43 | 44 | > invalidate: 45 | {node 0 46 | <<<{node 00 47 | {node 000 48 | 49 | } 50 | 51 | {node 001 52 | } 53 | } 54 | }>>> 55 | 56 | {node 01 57 | {node 010 58 | 59 | } 60 | 61 | {node 011 62 | 63 | } 64 | } 65 | 21... 66 | 67 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/Issue.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax 19 | 20 | import name.lakhin.eliah.projects.papacarlo.utils.Bounds 21 | 22 | final case class Issue(range: Bounds, description: String) 23 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step2/node.txt: -------------------------------------------------------------------------------- 1 | > remove: 2 | number:9 3 | { 4 | "key 1": "value 1", 5 | "key 2": "value 2", 6 | "key 3": ["array element", <<<100>>>, true, "false", ["a", "b"]], 7 | "key 4": {"subkey": null} 8 | } 9 | 10 | 11 | > create: 12 | number:20 13 | { 14 | "key 1": "value 1", 15 | "key 2": "value 2", 16 | "key 3": ["array element", <<<100>>>, true, "false", ["a", "b"]], 17 | "key 4": {"subkey": null} 18 | } 19 | 20 | 21 | > merge: 22 | array 7 cachable >> 6 { 23 | value: string 8 >> 7 { 24 | value: "array element" 25 | } 26 | value: number 20 >> 7 { 27 | value: 100 28 | } 29 | value: boolean 10 >> 7 { 30 | value: true 31 | } 32 | value: string 16 >> 7 { 33 | value: "false" 34 | } 35 | value: array 17 cachable >> 7 { 36 | value: string 18 >> 17 { 37 | value: "a" 38 | } 39 | value: string 19 >> 17 { 40 | value: "b" 41 | } 42 | } 43 | } 44 | 45 | -------------------------------------------------------------------------------- /.scalafix.conf: -------------------------------------------------------------------------------- 1 | // https://scalacenter.github.io/scalafix/docs/rules/overview.html 2 | 3 | rules = [ 4 | 5 | // semantic rules 6 | DisableSyntax, // Reports an error for disabled features such as var or XML literals. 7 | LeakingImplicitClassVal, // Adds 'private' to val parameters of implicit value classes 8 | NoValInForComprehension, // Removes deprecated val inside for-comprehension binders 9 | ProcedureSyntax, // Replaces deprecated procedure syntax with explicit ': Unit =' 10 | 11 | //ExplicitResultTypes, // Inserts type annotations for inferred public members. 12 | // Only compatible with Scala 2.11.12, 2.12.10, 2.12.11, 2.12.12, 2.13.2, 2.13.3, 2.13.4. 13 | // expect: inputBinaryScalaVersion == runtimeBinaryScalaVersion 14 | 15 | NoAutoTupling, // Inserts explicit tuples for adapted argument lists for compatibility with -Yno-adapted-args 16 | RemoveUnused, // Removes unused imports and terms that reported by the compiler under -Ywarn-unused 17 | 18 | ] 19 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/Packrat.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax 19 | 20 | import name.lakhin.eliah.projects.papacarlo.utils.Bounds 21 | 22 | final case class Packrat(rule: String, range: Bounds, result: Int, state: State) 23 | -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/SyntaxMonitor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | import name.lakhin.eliah.projects.papacarlo.{Syntax, Lexer} 21 | 22 | abstract class SyntaxMonitor(lexer: Lexer, syntax: Syntax) 23 | extends Monitor(lexer) -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/lexis/SkipLevel.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.lexis 19 | 20 | sealed abstract class SkipLevel() 21 | 22 | case object OriginalSkipping extends SkipLevel 23 | 24 | case object ForceSkip extends SkipLevel 25 | 26 | case object ForceUse extends SkipLevel 27 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/lexis/SeamType.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.lexis 19 | 20 | sealed abstract class SeamType 21 | 22 | case object RegularSeam extends SeamType 23 | 24 | case object UnexpectedSeam extends SeamType 25 | 26 | case object EnterContext extends SeamType 27 | 28 | case object LeaveContext extends SeamType 29 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step8/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | entry:4 3 | { 4 | "key 1": "hello world", 5 | <<<"key 2": ["array value 1", "array value 2"]>>> 6 | } 7 | 8 | 9 | > create: 10 | array:5 cachable 11 | { 12 | "key 1": "hello world", 13 | "key 2": <<<["array value 1", "array value 2"]>>> 14 | } 15 | 16 | 17 | > create: 18 | string:6 19 | { 20 | "key 1": "hello world", 21 | "key 2": [<<<"array value 1">>>, "array value 2"] 22 | } 23 | 24 | 25 | > create: 26 | string:7 27 | { 28 | "key 1": "hello world", 29 | "key 2": ["array value 1", <<<"array value 2">>>] 30 | } 31 | 32 | 33 | > merge: 34 | object 1 { 35 | entry: entry 2 >> 1 { 36 | key: "key 1" 37 | value: string 3 >> 2 { 38 | value: "hello world" 39 | } 40 | } 41 | entry: entry 4 >> 1 { 42 | key: "key 2" 43 | value: array 5 cachable >> 4 { 44 | value: string 6 >> 5 { 45 | value: "array value 1" 46 | } 47 | value: string 7 >> 5 { 48 | value: "array value 2" 49 | } 50 | } 51 | } 52 | } 53 | 54 | -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/EmptyMonitor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | import name.lakhin.eliah.projects.papacarlo.{Lexer, Syntax} 21 | 22 | final class EmptyMonitor(lexer: Lexer, syntax: Syntax) 23 | extends SyntaxMonitor(lexer, syntax) { 24 | 25 | def getResult = "" 26 | 27 | def prepare() {} 28 | 29 | def release() {} 30 | } -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/JsonParserSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test 19 | 20 | import name.lakhin.eliah.projects.papacarlo.test.utils.ParserSpec 21 | import name.lakhin.eliah.projects.papacarlo.examples.Json 22 | 23 | class JsonParserSpec extends ParserSpec("json") { 24 | override def lexer = Json.lexer 25 | override def parser = { 26 | val lexer = Json.lexer 27 | val syntax = Json.syntax(lexer) 28 | 29 | (lexer, syntax) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/CalculatorSpec.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test 19 | 20 | import name.lakhin.eliah.projects.papacarlo.examples.Calculator 21 | import name.lakhin.eliah.projects.papacarlo.test.utils.ParserSpec 22 | 23 | class CalculatorSpec extends ParserSpec("calculator") { 24 | override def lexer = Calculator.lexer 25 | override def parser = { 26 | val lexer = Calculator.lexer 27 | val syntax = Calculator.syntax(lexer) 28 | 29 | (lexer, syntax) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/Error.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax 19 | 20 | import name.lakhin.eliah.projects.papacarlo.lexis.TokenReference 21 | import name.lakhin.eliah.projects.papacarlo.utils.Bounds 22 | 23 | final case class Error(from: TokenReference, 24 | to: TokenReference, 25 | description: String, 26 | cursor: Boolean = false) { 27 | def range = 28 | if (cursor) Bounds.cursor(from.index) 29 | else Bounds(from.index, to.index + 1) 30 | } 31 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step14/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | entry:18 3 | { 4 | "key 1": "hello world", 5 | "key 1.1": "value 1.1", 6 | "key 2": ["array value 1", "array value 2", "array value 3"], 7 | <<<"key 3": 100>>> 8 | } 9 | 10 | 11 | > create: 12 | number:19 13 | { 14 | "key 1": "hello world", 15 | "key 1.1": "value 1.1", 16 | "key 2": ["array value 1", "array value 2", "array value 3"], 17 | "key 3": <<<100>>> 18 | } 19 | 20 | 21 | > merge: 22 | object 1 { 23 | entry: entry 2 >> 1 { 24 | key: "key 1" 25 | value: string 3 >> 2 { 26 | value: "hello world" 27 | } 28 | } 29 | entry: entry 11 >> 1 { 30 | key: "key 1.1" 31 | value: string 12 >> 11 { 32 | value: "value 1.1" 33 | } 34 | } 35 | entry: entry 13 >> 1 { 36 | key: "key 2" 37 | value: array 14 cachable >> 13 { 38 | value: string 15 >> 14 { 39 | value: "array value 1" 40 | } 41 | value: string 16 >> 14 { 42 | value: "array value 2" 43 | } 44 | value: string 17 >> 14 { 45 | value: "array value 3" 46 | } 47 | } 48 | } 49 | entry: entry 18 >> 1 { 50 | key: "key 3" 51 | value: number 19 >> 18 { 52 | value: 100 53 | } 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/utils/Signal.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.utils 19 | 20 | final class Signal[T] { 21 | private var slots = List.empty[T => Any] 22 | 23 | def isEmpty = slots.isEmpty 24 | 25 | def nonEmpty = slots.nonEmpty 26 | 27 | def bind(slot: T => Any): Unit = { 28 | slots ::= slot 29 | } 30 | 31 | def unbind(slot: T => Any): Unit = { 32 | slots = slots.filter(_ != slot) 33 | } 34 | 35 | def unbindAll(): Unit = { 36 | slots = Nil 37 | } 38 | 39 | def trigger(value: T): Unit = { 40 | slots.foreach(_(value)) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /doc/chores.md: -------------------------------------------------------------------------------- 1 | # chores 2 | 3 | the boring but necessary work to keep the project running 4 | 5 | ## keep dependencies up to date 6 | 7 | in sbt console, run 8 | 9 | ``` 10 | ; dependencyUpdates; reload plugins; dependencyUpdates; reload return 11 | ``` 12 | 13 | and (try to) use the latest versions of dependencies 14 | in [build.sbt](../build.sbt) and [project/plugins.sbt](../project/plugins.sbt) 15 | 16 | see [add-sbt-plugin-sbt-updates.sh](add-sbt-plugin-sbt-updates.sh) 17 | to install the `sbt-updates` plugin, providing the `dependencyUpdates` command 18 | 19 | reference: [getting updates for sbt plugins](https://github.com/rtimush/sbt-updates/issues/10) 20 | 21 | ## avoid version conflicts 22 | 23 | in sbt console, run `evicted` to check for version conflicts 24 | 25 | ## optional chores 26 | 27 | these chores are optional: 28 | they are not needed to compile the project, 29 | and need significant extra downloads 30 | 31 | the corresponding plugins must be activated (uncommented) 32 | in [project/plugins.sbt](../project/plugins.sbt) 33 | 34 | ### replace deprecated code 35 | 36 | in [build.sbt](../build.sbt), use 37 | 38 | ``` 39 | ThisBuild / scalacOptions ++= Seq("-unchecked", "-deprecation") 40 | ``` 41 | 42 | to make sbt more verbose 43 | 44 | ### prettify code 45 | 46 | run `scalafmt` in the sbt console 47 | -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/ErrorMonitor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | import name.lakhin.eliah.projects.papacarlo.{Lexer, Syntax} 21 | 22 | final class ErrorMonitor(lexer: Lexer, syntax: Syntax) 23 | extends SyntaxMonitor(lexer, syntax) { 24 | 25 | def getResult = syntax 26 | .getErrors 27 | .map(error => " > " + error.description + 28 | ( 29 | if (shortOutput) " " + lexer.rangeToString(error.range) 30 | else ":\n" + lexer.highlight(error.range, Some(10))) 31 | ) 32 | .mkString("\n\n") 33 | 34 | def prepare() {} 35 | 36 | def release() {} 37 | } -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/State.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax 19 | 20 | import name.lakhin.eliah.projects.papacarlo.utils.Bounds 21 | 22 | final case class State(virtualPosition: Int = 0, 23 | issues: List[Issue] = Nil, 24 | products: List[(String, Node)] = Nil, 25 | captures: List[(String, Bounds)] = Nil) { 26 | def issue(description: String) = 27 | copy(issues = Issue(Bounds.cursor(virtualPosition), description) :: issues) 28 | 29 | def issue(range: Bounds, description: String) = 30 | copy(issues = Issue(range, description) :: issues) 31 | } 32 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/utils/Difference.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.utils 19 | 20 | object Difference { 21 | def head[A](first: Seq[A], 22 | second: Seq[A], 23 | comparator: Tuple2[A, A] => Boolean) = 24 | first.zip(second).takeWhile(comparator).length 25 | 26 | def double[A](first: Seq[A], 27 | second: Seq[A], 28 | comparator: Tuple2[A, A] => Boolean) = { 29 | val pairs = first.zip(second) 30 | val head = pairs.takeWhile(comparator).length 31 | ( 32 | head, 33 | first 34 | .drop(head) 35 | .reverse 36 | .zip(second.drop(head).reverse) 37 | .takeWhile(comparator) 38 | .length 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery-advanced/prototype/step2/fragment.txt: -------------------------------------------------------------------------------- 1 | > remove: 2 | {node 0 3 | {node 00 4 | <<<{node 000 5 | 6 | }>>> 7 | 8 | {node 001 9 | } 10 | } 11 | } 12 | 13 | {node 01 14 | {node 010 15 | 16 | } 17 | 16... 18 | 19 | > remove: 20 | {node 0 21 | {node 00 22 | {node 000 23 | 24 | } 25 | 26 | <<<{node 001 27 | }>>> 28 | } 29 | } 30 | 31 | {node 01 32 | {node 010 33 | 34 | } 35 | 36 | {node 011 37 | 38 | 19... 39 | 40 | > create: 41 | {node 0 42 | {node 00 43 | {node 000 44 | 45 | 46 | 47 | <<<{node 001 48 | 49 | }>>> 50 | } 51 | } 52 | 53 | {node 01 54 | {node 010 55 | 56 | } 57 | 58 | {node 011 59 | 60 | 20... 61 | 62 | > create: 63 | {node 0 64 | {node 00 65 | <<<{node 000 66 | 67 | 68 | 69 | {node 001 70 | 71 | } 72 | }>>> 73 | } 74 | 75 | {node 01 76 | {node 010 77 | 78 | } 79 | 80 | {node 011 81 | 82 | } 83 | 21... 84 | 85 | > invalidate: 86 | {node 0 87 | <<<{node 00 88 | {node 000 89 | 90 | 91 | 92 | {node 001 93 | 94 | } 95 | } 96 | }>>> 97 | 98 | {node 01 99 | {node 010 100 | 101 | } 102 | 103 | {node 011 104 | 105 | } 106 | } 107 | 22... 108 | 109 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step11/node.txt: -------------------------------------------------------------------------------- 1 | > remove: 2 | string:6 3 | { 4 | "key 1": "hello world", 5 | "key 1.1": 6 | "key 2": [<<<"array value 1">>>, "array value 2", "array value 3"], 7 | } 8 | 9 | 10 | > remove: 11 | string:7 12 | { 13 | "key 1": "hello world", 14 | "key 1.1": 15 | "key 2": ["array value 1", <<<"array value 2">>>, "array value 3"], 16 | } 17 | 18 | 19 | > remove: 20 | string:8 21 | { 22 | "key 1": "hello world", 23 | "key 1.1": 24 | "key 2": ["array value 1", "array value 2", <<<"array value 3">>>], 25 | } 26 | 27 | 28 | > remove: 29 | array:5 cachable 30 | { 31 | "key 1": "hello world", 32 | "key 1.1": 33 | "key 2": <<<["array value 1", "array value 2", "array value 3"]>>>, 34 | } 35 | 36 | 37 | > remove: 38 | entry:4 39 | { 40 | "key 1": "hello world", 41 | "key 1.1": 42 | <<<"key 2": ["array value 1", "array value 2", "array value 3"]>>>, 43 | } 44 | 45 | 46 | > create: 47 | entry:9 48 | { 49 | "key 1": "hello world", 50 | <<<"key 1.1": 51 | "key 2">>>: ["array value 1", "array value 2", "array value 3"], 52 | } 53 | 54 | 55 | > create: 56 | string:10 57 | { 58 | "key 1": "hello world", 59 | "key 1.1": 60 | <<<"key 2">>>: ["array value 1", "array value 2", "array value 3"], 61 | } 62 | 63 | 64 | > merge: 65 | object 1 { 66 | entry: entry 2 >> 1 { 67 | key: "key 1" 68 | value: string 3 >> 2 { 69 | value: "hello world" 70 | } 71 | } 72 | entry: entry 9 >> 1 { 73 | key: "key 1.1" 74 | value: string 10 >> 9 { 75 | value: "key 2" 76 | } 77 | } 78 | } 79 | 80 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step1/node.txt: -------------------------------------------------------------------------------- 1 | > remove: 2 | boolean:11 3 | { 4 | "key 1": "value 1", 5 | "key 2": "value 2", 6 | "key 3": ["array element", 2.6e-2, true, <<<"false">>>, ["a", "b"]], 7 | "key 4": {"subkey": null} 8 | } 9 | 10 | 11 | > create: 12 | string:16 13 | { 14 | "key 1": "value 1", 15 | "key 2": "value 2", 16 | "key 3": ["array element", 2.6e-2, true, <<<"false">>>, ["a", "b"]], 17 | "key 4": {"subkey": null} 18 | } 19 | 20 | 21 | > create: 22 | array:17 cachable 23 | { 24 | "key 1": "value 1", 25 | "key 2": "value 2", 26 | "key 3": ["array element", 2.6e-2, true, "false", <<<["a", "b"]>>>], 27 | "key 4": {"subkey": null} 28 | } 29 | 30 | 31 | > create: 32 | string:18 33 | { 34 | "key 1": "value 1", 35 | "key 2": "value 2", 36 | "key 3": ["array element", 2.6e-2, true, "false", [<<<"a">>>, "b"]], 37 | "key 4": {"subkey": null} 38 | } 39 | 40 | 41 | > create: 42 | string:19 43 | { 44 | "key 1": "value 1", 45 | "key 2": "value 2", 46 | "key 3": ["array element", 2.6e-2, true, "false", ["a", <<<"b">>>]], 47 | "key 4": {"subkey": null} 48 | } 49 | 50 | 51 | > merge: 52 | array 7 cachable >> 6 { 53 | value: string 8 >> 7 { 54 | value: "array element" 55 | } 56 | value: number 9 >> 7 { 57 | value: 2.6e-2 58 | } 59 | value: boolean 10 >> 7 { 60 | value: true 61 | } 62 | value: string 16 >> 7 { 63 | value: "false" 64 | } 65 | value: array 17 cachable >> 7 { 66 | value: string 18 >> 17 { 67 | value: "a" 68 | } 69 | value: string 19 >> 17 { 70 | value: "b" 71 | } 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/rules/RequiredRule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax.rules 19 | 20 | import name.lakhin.eliah.projects.papacarlo.syntax.{Session, Rule} 21 | import name.lakhin.eliah.projects.papacarlo.syntax.Result._ 22 | 23 | final case class RequiredRule(rule: Rule) extends Rule { 24 | def apply(session: Session): Int = { 25 | session.syntax.onRuleEnter.trigger(this, session.state) 26 | 27 | val initialPosition = session.state.virtualPosition 28 | var result = rule(session) 29 | 30 | if (result == Recoverable && 31 | session.state.virtualPosition == initialPosition) 32 | result = Failed 33 | 34 | session.syntax.onRuleLeave.trigger(this, session.state, result) 35 | result 36 | } 37 | 38 | override val show = "require(" + rule.showOperand(0) + ")" -> Int.MaxValue 39 | 40 | override val captures = rule.captures 41 | 42 | override val branches = rule.branches 43 | } 44 | -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/TokenizerMonitor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | import name.lakhin.eliah.projects.papacarlo.Lexer 21 | 22 | final class TokenizerMonitor(lexer: Lexer) extends Monitor(lexer) { 23 | def getResult = { 24 | var context = "" 25 | val result = new StringBuilder 26 | 27 | for (token <- lexer.fragments.rootFragment.getTokens) { 28 | token.getContext.view 29 | if (token.isMutable) result ++= "`" 30 | 31 | if (token.isSkippable) result ++= token.value 32 | else result ++= token.kind 33 | 34 | if (token.isMutable) result ++= "`" 35 | 36 | val tokenContext = token.getContext.view 37 | if (context != tokenContext) { 38 | context = tokenContext 39 | result ++= "~" + context + "~" 40 | } 41 | } 42 | 43 | result.toString() 44 | } 45 | 46 | def prepare() : Unit = {} 47 | 48 | def release() : Unit = {} 49 | } 50 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/lexis/TokenReference.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.lexis 19 | 20 | import name.lakhin.eliah.projects.papacarlo.utils.Signal 21 | 22 | final class TokenReference(val collection: TokenCollection, var index: Int) { 23 | private[lexis] var fragment = Option.empty[Fragment] 24 | private var removed: Boolean = false 25 | 26 | val onUpdate = new Signal[TokenReference] 27 | val onRemove = new Signal[TokenReference] 28 | 29 | def exists = !removed 30 | 31 | def token = { 32 | if (removed) throw new RuntimeException("Token was removed") 33 | collection.descriptions(index) 34 | } 35 | 36 | def getFragment = fragment 37 | 38 | private[lexis] def remove(): Unit = { 39 | removed = true 40 | onRemove.trigger(this) 41 | } 42 | 43 | override def toString = 44 | if (removed) "removed" 45 | else { 46 | val cursor = collection.cursor(index) 47 | 48 | cursor._1 + ":" + cursor._2 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/utils/Registry.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.utils 19 | 20 | final class Registry[A] { 21 | private var records = Map.empty[Int, A] 22 | private var index = 0 23 | 24 | val onAdd = new Signal[A] 25 | val onRemove = new Signal[A] 26 | 27 | def elements = records.values 28 | 29 | def size = records.size 30 | 31 | def add(element: A) = { 32 | val name = generateName 33 | records += name -> element 34 | onAdd.trigger(element) 35 | name 36 | } 37 | 38 | def add(element: Int => A) = { 39 | val name = generateName 40 | val applied = element(name) 41 | records += name -> applied 42 | onAdd.trigger(applied) 43 | applied 44 | } 45 | 46 | def get(id: Int) = records.get(id) 47 | 48 | def remove(id: Int) = { 49 | val value = records.get(id) 50 | 51 | for (value <- value) { 52 | records -= id 53 | onRemove.trigger(value) 54 | } 55 | 56 | value 57 | } 58 | 59 | private def generateName = { index += 1; index } 60 | } 61 | -------------------------------------------------------------------------------- /doc/compile.md: -------------------------------------------------------------------------------- 1 | # compile 2 | 3 | to compile `papa-carlo`, you need 4 | 5 | * JDK, the Java Development Kit. 6 | `sbt` recommends [AdoptOpenJDK JDK 11](https://adoptopenjdk.net/?variant=openjdk11) or [AdoptOpenJDK JDK 8](https://adoptopenjdk.net/?variant=openjdk8) 7 | * [sbt](https://www.scala-sbt.org/release/docs/Setup.html), the Scala Build Tool, version 1.x 8 | 9 | the folder of the `sbt` executable should now be in your PATH environment variable 10 | 11 | navigate to the `papa-carlo` project folder, open a terminal, and run 12 | 13 | ``` 14 | sbt 15 | ``` 16 | 17 | `sbt` should download dependencies, and show the sbt console 18 | 19 | ``` 20 | [info] welcome to sbt 21 | .... 22 | [info] started sbt server 23 | sbt:root> 24 | ``` 25 | 26 | enter `; reload; jvm/test` to compile the papa-carlo library for java. 27 | to compile again, press `arrow up` and hit enter 28 | 29 | enter `jvm/packageBin` to build a JAR package to [jvm/target/](../jvm/target/) 30 | 31 | enter `js-demo/fullLinkJS` to build the JavaScript demo 32 | with [scala-js](https://www.scala-js.org/doc/tutorial/basic/). 33 | This will generate `js/demo/target/scala-2.13/js-demo-opt/main.js`, 34 | which is included by `js/demo/client.js` and `js/demo/server.js`, 35 | which are included by `js/demo/index.html`. 36 | To use the demo, [start an HTTP server](https://gist.github.com/willurd/5720255) 37 | in the folder [js/demo/](../js/demo). 38 | for example on Linux: 39 | 40 | ``` 41 | cd js/demo/ 42 | python -m http.server 8000 & 43 | xdg-open http://localhost:8000/index.html 44 | ``` 45 | 46 | enter `js-demo/fastLinkJS` to build the JavaScript demo in development mode. 47 | in `js/demo/client.js` and `js/demo/server.js`, 48 | replace `js-demo-opt/main.js` with `js-demo-fastopt/main.js` 49 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/large/prototype/step1/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | object:919 cachable (580:11) - (580:52) 3 | 4 | > create: 5 | entry:920 (580:12) - (580:21) 6 | 7 | > create: 8 | number:921 (580:18) - (580:21) 9 | 10 | > create: 11 | entry:922 (580:23) - (580:51) 12 | 13 | > create: 14 | string:923 (580:31) - (580:51) 15 | 16 | > merge: 17 | array 840 cachable >> 839 { 18 | value: object 841 cachable >> 840 { 19 | entry: entry 842 >> 841 { 20 | key: "id" 21 | value: number 843 >> 842 { 22 | value: 0 23 | } 24 | } 25 | entry: entry 844 >> 841 { 26 | key: "name" 27 | value: string 845 >> 844 { 28 | value: "Rios Duffy" 29 | } 30 | } 31 | } 32 | value: object 919 cachable >> 840 { 33 | entry: entry 920 >> 919 { 34 | key: "id" 35 | value: number 921 >> 920 { 36 | value: 0.5 37 | } 38 | } 39 | entry: entry 922 >> 919 { 40 | key: "name" 41 | value: string 923 >> 922 { 42 | value: "another one friend" 43 | } 44 | } 45 | } 46 | value: object 846 cachable >> 840 { 47 | entry: entry 847 >> 846 { 48 | key: "id" 49 | value: number 848 >> 847 { 50 | value: 1 51 | } 52 | } 53 | entry: entry 849 >> 846 { 54 | key: "name" 55 | value: string 850 >> 849 { 56 | value: "Katheryn Foreman" 57 | } 58 | } 59 | } 60 | value: object 851 cachable >> 840 { 61 | entry: entry 852 >> 851 { 62 | key: "id" 63 | value: number 853 >> 852 { 64 | value: 2 65 | } 66 | } 67 | entry: entry 854 >> 851 { 68 | key: "name" 69 | value: string 855 >> 854 { 70 | value: "Maldonado Herring" 71 | } 72 | } 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/rules/NamedRule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax.rules 19 | 20 | import name.lakhin.eliah.projects.papacarlo.syntax.{Result, Session, Rule} 21 | 22 | final case class NamedRule(label: String, rule: Rule, trace: Boolean = false) 23 | extends Rule { 24 | def apply(session: Session) = { 25 | session.syntax.onRuleEnter.trigger(this, session.state) 26 | 27 | val initialState = session.state 28 | val result = rule(session) 29 | 30 | if (result == Result.Failed) 31 | session.state = initialState.issue(label + " expected") 32 | 33 | session.syntax.onRuleLeave.trigger(this, session.state, result) 34 | result 35 | } 36 | 37 | def debug = copy(trace = true) 38 | 39 | override val show = 40 | rule match { 41 | case ReferentialRule(name, tag) if name == label => rule.show 42 | case _ => 43 | rule.showOperand(Int.MaxValue) + ".name(" + label + ")" -> Int.MaxValue 44 | } 45 | 46 | override val captures = rule.captures 47 | 48 | override val branches = rule.branches 49 | } 50 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step3/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | entry:21 3 | { 4 | "key 1": "value 1", 5 | "key 2": "value 2", 6 | "key 3": ["array element", 100, true, "false", ["a", "b"]], 7 | <<<"key 3.5": "value 3.5">>>, 8 | "key 4": {"subkey": null} 9 | } 10 | 11 | 12 | > create: 13 | string:22 14 | { 15 | "key 1": "value 1", 16 | "key 2": "value 2", 17 | "key 3": ["array element", 100, true, "false", ["a", "b"]], 18 | "key 3.5": <<<"value 3.5">>>, 19 | "key 4": {"subkey": null} 20 | } 21 | 22 | 23 | > merge: 24 | object 1 { 25 | entry: entry 2 >> 1 { 26 | key: "key 1" 27 | value: string 3 >> 2 { 28 | value: "value 1" 29 | } 30 | } 31 | entry: entry 4 >> 1 { 32 | key: "key 2" 33 | value: string 5 >> 4 { 34 | value: "value 2" 35 | } 36 | } 37 | entry: entry 6 >> 1 { 38 | key: "key 3" 39 | value: array 7 cachable >> 6 { 40 | value: string 8 >> 7 { 41 | value: "array element" 42 | } 43 | value: number 20 >> 7 { 44 | value: 100 45 | } 46 | value: boolean 10 >> 7 { 47 | value: true 48 | } 49 | value: string 16 >> 7 { 50 | value: "false" 51 | } 52 | value: array 17 cachable >> 7 { 53 | value: string 18 >> 17 { 54 | value: "a" 55 | } 56 | value: string 19 >> 17 { 57 | value: "b" 58 | } 59 | } 60 | } 61 | } 62 | entry: entry 21 >> 1 { 63 | key: "key 3.5" 64 | value: string 22 >> 21 { 65 | value: "value 3.5" 66 | } 67 | } 68 | entry: entry 12 >> 1 { 69 | key: "key 4" 70 | value: object 13 cachable >> 12 { 71 | entry: entry 14 >> 13 { 72 | key: "subkey" 73 | value: null 15 >> 14 74 | } 75 | } 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /js/demo/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | var parser = null; 18 | 19 | function getStats(data) { 20 | return { 21 | nodes: parser.getAST(data.ast), 22 | errors: parser.getErrors() 23 | } 24 | } 25 | 26 | onmessage = function(event) { 27 | switch (event.data.kind) { 28 | case 'init': 29 | if (!parser) { 30 | importScripts('./target/scala-2.13/js-demo-opt/main.js'); // produced by `sbt js-demo/fullLinkJS` 31 | //importScripts('./target/scala-2.13/js-demo-fastopt/main.js'); // produced by `sbt js-demo/fastLinkJS` 32 | parser = PapaCarloDemoParser; 33 | } 34 | 35 | postMessage({kind: 'ready'}); 36 | 37 | break; 38 | 39 | case 'fragment': 40 | postMessage({ 41 | kind: 'fragment', 42 | response: parser.getNodeFragment(event.data.id) 43 | }); 44 | 45 | break; 46 | 47 | case 'input': 48 | var start = new Date().getTime(); 49 | parser.inputAll(event.data.code); 50 | var end = new Date().getTime(); 51 | 52 | postMessage({ 53 | kind: 'response', 54 | delta: end - start, 55 | stats: getStats(event.data) 56 | }); 57 | 58 | break; 59 | } 60 | }; 61 | 62 | -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/Monitor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | import name.lakhin.eliah.projects.papacarlo.Lexer 21 | 22 | abstract class Monitor(lexer: Lexer) { 23 | var shortOutput = false 24 | 25 | final def input(text: String) = { 26 | val start = System.currentTimeMillis() 27 | lexer.input(text) 28 | val end = System.currentTimeMillis() 29 | 30 | end - start 31 | } 32 | 33 | def prepare() 34 | def getResult: String 35 | def release() 36 | 37 | protected final def unionLog(log: List[(Symbol, String)], 38 | enterLeaveIndentation: Boolean = false) = { 39 | val result = new StringBuilder 40 | var indent = 0 41 | 42 | for ((title, details) <- log.reverse) { 43 | if (title == 'leave) indent -= 1 44 | 45 | val offset = " " * (indent * 2) 46 | 47 | result ++= offset + " > " + title.name + ":\n" 48 | result ++= details.split("\n", -1).map(offset + _).mkString("\n") 49 | result ++= "\n\n" 50 | 51 | if (title == 'enter) indent += 1 52 | } 53 | 54 | result.toString() 55 | } 56 | } -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/lexis/Context.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.lexis 19 | 20 | final case class Context(kind: Int, parent: Option[Context] = None) { 21 | val view: String = kind + parent.map(":" + _.view).getOrElse("") 22 | 23 | val depth: Int = parent.map(_.depth + 1).getOrElse(0) 24 | 25 | def branch(childKind: Int) = Context(childKind, Some(this)) 26 | 27 | def intersect(another: Context) = { 28 | var first = this 29 | var second = another 30 | 31 | while (first.depth > second.depth) { 32 | first = first.parent.getOrElse(second) 33 | } 34 | 35 | while (second.depth > first.depth) { 36 | second = second.parent.getOrElse(first) 37 | } 38 | 39 | while (first.view != second.view) { 40 | first = first.parent.getOrElse(Context.Base) 41 | second = second.parent.getOrElse(Context.Base) 42 | } 43 | 44 | first 45 | } 46 | 47 | override def equals(another: Any) = 48 | another.isInstanceOf[Context] && view == another.asInstanceOf[Context].view 49 | 50 | override def toString = view 51 | } 52 | 53 | object Context { 54 | val Base: Context = Context(0) 55 | } 56 | -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/Test.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | final case class Test( 21 | resources: Resources, 22 | parserName: String, 23 | testName: String, 24 | steps: Int, 25 | monitors: Set[String], 26 | shortOutput: Boolean, 27 | outputFrom: Int, 28 | independentSteps: Boolean 29 | ) { 30 | 31 | val inputs = read("input") 32 | 33 | val prototypes = monitors.map( 34 | monitorName => monitorName -> read("prototype", monitorName) 35 | ).toMap 36 | 37 | private def read(dir: String, monitorName: String = "") = ( 38 | for (step <- 0 until steps) 39 | yield ( 40 | step, 41 | if (monitorName.nonEmpty) { 42 | resources.input( 43 | parserName + "/" + testName + "/" + dir + "/step" + step, 44 | monitorName + ".txt" 45 | ) 46 | } 47 | else { 48 | resources.input( 49 | parserName + "/" + testName + "/" + dir, 50 | "step" + step + ".txt" 51 | ) 52 | } 53 | ) 54 | ).toMap 55 | 56 | def write(monitorName: String, step: Int, value: String): Unit = { 57 | resources.update( 58 | parserName + "/" + testName + "/output/step" + step, 59 | monitorName + ".txt", 60 | value 61 | ) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/rules/SequentialRule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax.rules 19 | 20 | import name.lakhin.eliah.projects.papacarlo.syntax.{Session, Rule} 21 | import name.lakhin.eliah.projects.papacarlo.syntax.Result._ 22 | 23 | final case class SequentialRule(steps: List[Rule]) extends Rule { 24 | def apply(session: Session): Int = { 25 | session.syntax.onRuleEnter.trigger(this, session.state) 26 | 27 | var result = Successful 28 | val initialState = session.state 29 | 30 | for (step <- steps) 31 | step(session) match { 32 | case Failed => 33 | session.state = initialState.copy(issues = session.state.issues) 34 | session.syntax.onRuleLeave.trigger(this, session.state, Failed) 35 | return Failed 36 | 37 | case Recoverable => result = Recoverable 38 | 39 | case _ => 40 | } 41 | 42 | session.syntax.onRuleLeave.trigger(this, session.state, result) 43 | result 44 | } 45 | 46 | override val show = steps.map(_.showOperand(3)).mkString(" & ") -> 3 47 | 48 | override val captures = steps.map(_.captures).reduce(_ ++ _) 49 | 50 | override val branches = steps.map(_.branches).reduce { (left, right) => 51 | var result = left 52 | for ((key, values) <- right) 53 | result += key -> (result.getOrElse(key, Set.empty) ++ values) 54 | result 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/rules/CapturingRule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax.rules 19 | 20 | import name.lakhin.eliah.projects.papacarlo.utils.Bounds 21 | import name.lakhin.eliah.projects.papacarlo.syntax.{Rule, Session} 22 | import name.lakhin.eliah.projects.papacarlo.syntax.Result._ 23 | 24 | final case class CapturingRule(tag: String, rule: Rule) extends Rule { 25 | def apply(session: Session) = { 26 | session.syntax.onRuleEnter.trigger(this, session.state) 27 | 28 | val initialState = session.state 29 | var result = rule(session) 30 | 31 | if (initialState.virtualPosition < session.state.virtualPosition) { 32 | session.state = session.state.copy( 33 | captures = 34 | ( 35 | tag, 36 | session.relativeSegmentOf( 37 | Bounds( 38 | initialState.virtualPosition, 39 | session.state.virtualPosition 40 | )) 41 | ) :: session.state.captures) 42 | } else if (result != Failed) { 43 | session.state = initialState.copy(issues = session.state.issues) 44 | result = Failed 45 | } 46 | 47 | session.syntax.onRuleLeave.trigger(this, session.state, result) 48 | result 49 | } 50 | 51 | override val show = rule.showOperand(1) + " -> " + tag -> 1 52 | 53 | override val captures = rule.captures + tag 54 | 55 | override val branches = rule.branches 56 | } 57 | -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/FragmentationMonitor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | import name.lakhin.eliah.projects.papacarlo.Lexer 21 | import name.lakhin.eliah.projects.papacarlo.lexis.Fragment 22 | import name.lakhin.eliah.projects.papacarlo.utils.Bounds 23 | 24 | private final class FragmentationMonitor(lexer: Lexer) extends Monitor(lexer) { 25 | private var fragmentLog = List.empty[(Symbol, String)] 26 | 27 | val onCreate = (fragment: Fragment) => 28 | fragmentLog ::= ('create, fragmentToString(fragment)) 29 | 30 | val onInvalidate = (pair: (Fragment, Bounds)) => 31 | fragmentLog ::= ('invalidate, fragmentToString(pair._1)) 32 | 33 | val onRemove = (fragment: Fragment) => 34 | fragmentLog ::= ('remove, fragmentToString(fragment)) 35 | 36 | private def fragmentToString(fragment: Fragment) = 37 | if (shortOutput) fragment.toString 38 | else fragment.highlight(Option(10)) 39 | 40 | def getResult = unionLog(fragmentLog) 41 | 42 | def prepare() { 43 | fragmentLog = Nil 44 | lexer.fragments.onCreate.bind(onCreate) 45 | lexer.fragments.onInvalidate.bind(onInvalidate) 46 | lexer.fragments.onRemove.bind(onRemove) 47 | } 48 | 49 | def release() { 50 | fragmentLog = Nil 51 | lexer.fragments.onCreate.unbind(onCreate) 52 | lexer.fragments.onInvalidate.unbind(onInvalidate) 53 | lexer.fragments.onRemove.unbind(onRemove) 54 | } 55 | } -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/CacheMonitor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | import name.lakhin.eliah.projects.papacarlo.{Lexer, Syntax} 21 | import name.lakhin.eliah.projects.papacarlo.syntax.Cache 22 | 23 | final class CacheMonitor(lexer: Lexer, syntax: Syntax) 24 | extends SyntaxMonitor(lexer, syntax) { 25 | 26 | private var cacheLog = List.empty[(Symbol, String)] 27 | 28 | private val onCacheCreate = (cache: Cache) => 29 | cacheLog ::= ('create, cacheInfo(cache)) 30 | 31 | private val onCacheInvalidate = (cache: Cache) => 32 | cacheLog ::= ('invalidate, cacheInfo(cache)) 33 | 34 | private val onCacheRemove = (cache: Cache) => 35 | cacheLog ::= ('remove, cacheInfo(cache)) 36 | 37 | private def cacheInfo(cache: Cache) = 38 | "Node " + cache.node.id + ". Fragment:" + 39 | ( 40 | if (this.shortOutput) " " + cache.fragment + "." 41 | else "\n" + cache.fragment.highlight(Some(10)) 42 | ) 43 | 44 | def getResult = unionLog(cacheLog) 45 | 46 | def prepare() { 47 | cacheLog = Nil 48 | syntax.onCacheCreate.bind(onCacheCreate) 49 | syntax.onCacheInvalidate.bind(onCacheInvalidate) 50 | syntax.onCacheRemove.bind(onCacheRemove) 51 | } 52 | 53 | def release() { 54 | cacheLog = Nil 55 | syntax.onCacheCreate.unbind(onCacheCreate) 56 | syntax.onCacheInvalidate.unbind(onCacheInvalidate) 57 | syntax.onCacheRemove.unbind(onCacheRemove) 58 | } 59 | } -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/NodeMonitor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | import name.lakhin.eliah.projects.papacarlo.syntax.rules.RecoveryRule 21 | import name.lakhin.eliah.projects.papacarlo.utils.Bounds 22 | import name.lakhin.eliah.projects.papacarlo.{Syntax, Lexer} 23 | import name.lakhin.eliah.projects.papacarlo.syntax.Node 24 | 25 | final class NodeMonitor(lexer: Lexer, syntax: Syntax) 26 | extends SyntaxMonitor(lexer, syntax) { 27 | 28 | private var nodeLog = List.empty[(Symbol, String)] 29 | 30 | val onNodeCreate = (node: Node) => nodeLog ::= ('create, nodeInfo(node)) 31 | 32 | val onNodeMerge = (node: Node) => nodeLog ::= ('merge, node.prettyPrint()) 33 | 34 | val onNodeRemove = (node: Node) => nodeLog ::= ('remove, nodeInfo(node)) 35 | 36 | private def nodeInfo(node: Node) = 37 | node.toString + ( 38 | if (shortOutput) " " + lexer.rangeToString(node.getRange) 39 | else { 40 | var range = node.getRange 41 | 42 | if (node.getKind == RecoveryRule.PlaceholderKind) { 43 | range = Bounds.cursor(range.from) 44 | } 45 | 46 | "\n" + lexer.highlight(range, Some(10)) 47 | } 48 | ) 49 | 50 | def getResult = unionLog(nodeLog) 51 | 52 | def prepare() { 53 | nodeLog = Nil 54 | syntax.onNodeCreate.bind(onNodeCreate) 55 | syntax.onNodeMerge.bind(onNodeMerge) 56 | syntax.onNodeRemove.bind(onNodeRemove) 57 | } 58 | 59 | def release() { 60 | nodeLog = Nil 61 | syntax.onNodeCreate.unbind(onNodeCreate) 62 | syntax.onNodeMerge.unbind(onNodeMerge) 63 | syntax.onNodeRemove.unbind(onNodeRemove) 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/examples/Calculator.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.examples 19 | 20 | import name.lakhin.eliah.projects.papacarlo.{Syntax, Lexer} 21 | import name.lakhin.eliah.projects.papacarlo.lexis.{ 22 | Contextualizer, 23 | Matcher, 24 | Tokenizer 25 | } 26 | import name.lakhin.eliah.projects.papacarlo.syntax.{Expressions, Rule} 27 | 28 | object Calculator { 29 | private def tokenizer = { 30 | val tokenizer = new Tokenizer() 31 | 32 | import tokenizer._ 33 | import Matcher._ 34 | 35 | tokenCategory( 36 | "whitespace", 37 | oneOrMore(anyOf(" \t\f\n")) 38 | ).skip 39 | 40 | tokenCategory( 41 | "number", 42 | choice( 43 | chunk("0"), 44 | sequence(rangeOf('1', '9'), zeroOrMore(rangeOf('0', '9'))) 45 | ) 46 | ) 47 | 48 | terminals("(", ")", "%", "+", "-", "*", "/") 49 | 50 | tokenizer 51 | } 52 | 53 | def lexer = new Lexer(tokenizer, new Contextualizer) 54 | 55 | def syntax(lexer: Lexer) = 56 | new { 57 | val syntax = new Syntax(lexer) 58 | 59 | import syntax._ 60 | import Rule._ 61 | import Expressions._ 62 | 63 | rule("expression").main { 64 | val rule = 65 | expression(branch("operand", recover(number, "operand required"))) 66 | 67 | group(rule, "(", ")") 68 | postfix(rule, "%", 1) 69 | prefix(rule, "+", 2) 70 | prefix(rule, "-", 2) 71 | infix(rule, "*", 3) 72 | infix(rule, "/", 3, rightAssociativity = true) 73 | infix(rule, "+", 4) 74 | infix(rule, "-", 4) 75 | 76 | rule 77 | } 78 | 79 | val number = rule("number") { capture("value", token("number")) } 80 | }.syntax 81 | } 82 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery-advanced/prototype/step0/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | {node 0 3 | {node 00 4 | <<<{node 000 5 | 6 | }>>> 7 | 8 | {node 001 9 | 10 | } 11 | } 12 | 13 | {node 01 14 | {node 010 15 | 16 | } 17 | 16... 18 | 19 | > create: 20 | {node 0 21 | {node 00 22 | {node 000 23 | 24 | } 25 | 26 | <<<{node 001 27 | 28 | }>>> 29 | } 30 | 31 | {node 01 32 | {node 010 33 | 34 | } 35 | 36 | {node 011 37 | 38 | } 39 | 20... 40 | 41 | > create: 42 | {node 0 43 | <<<{node 00 44 | {node 000 45 | 46 | } 47 | 48 | {node 001 49 | 50 | } 51 | }>>> 52 | 53 | {node 01 54 | {node 010 55 | 56 | } 57 | 58 | {node 011 59 | 60 | } 61 | } 62 | 21... 63 | 64 | > create: 65 | ...2 66 | {node 000 67 | 68 | } 69 | 70 | {node 001 71 | 72 | } 73 | } 74 | 75 | {node 01 76 | <<<{node 010 77 | 78 | }>>> 79 | 80 | {node 011 81 | 82 | } 83 | } 84 | } 85 | 86 | 87 | > create: 88 | ...6 89 | {node 001 90 | 91 | } 92 | } 93 | 94 | {node 01 95 | {node 010 96 | 97 | } 98 | 99 | <<<{node 011 100 | 101 | }>>> 102 | } 103 | } 104 | 105 | 106 | > create: 107 | ...1 108 | {node 00 109 | {node 000 110 | 111 | } 112 | 113 | {node 001 114 | 115 | } 116 | } 117 | 118 | <<<{node 01 119 | {node 010 120 | 121 | } 122 | 123 | {node 011 124 | 125 | } 126 | }>>> 127 | } 128 | 129 | 130 | > create: 131 | <<<{node 0 132 | {node 00 133 | {node 000 134 | 135 | } 136 | 137 | {node 001 138 | 139 | } 140 | } 141 | 142 | {node 01 143 | {node 010 144 | 145 | } 146 | 147 | {node 011 148 | 149 | } 150 | } 151 | }>>> 152 | 153 | 154 | > invalidate: 155 | <<<{node 0 156 | {node 00 157 | {node 000 158 | 159 | } 160 | 161 | {node 001 162 | 163 | } 164 | } 165 | 166 | {node 01 167 | {node 010 168 | 169 | } 170 | 171 | {node 011 172 | 173 | } 174 | } 175 | } 176 | >>> 177 | 178 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/rules/ChoiceRule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax.rules 19 | 20 | import name.lakhin.eliah.projects.papacarlo.syntax.{Rule, Session} 21 | import name.lakhin.eliah.projects.papacarlo.syntax.Result._ 22 | 23 | final case class ChoiceRule(choices: List[Rule]) extends Rule { 24 | def apply(session: Session): Int = { 25 | session.syntax.onRuleEnter.trigger(this, session.state) 26 | 27 | val initialState = session.state 28 | var bestResult = (Failed, session.state) 29 | 30 | for (choice <- choices) { 31 | choice(session) match { 32 | case Successful => 33 | session.syntax.onRuleLeave.trigger(this, session.state, Successful) 34 | return Successful 35 | 36 | case Recoverable => 37 | if (bestResult._1 < Recoverable || 38 | bestResult._2.virtualPosition < session.state.virtualPosition) 39 | bestResult = (Recoverable, session.state) 40 | 41 | session.state = initialState 42 | 43 | case Failed => 44 | if (bestResult._1 == Failed) bestResult = (Failed, session.state) 45 | 46 | session.state = initialState 47 | } 48 | } 49 | 50 | session.state = bestResult._2 51 | 52 | session.syntax.onRuleLeave.trigger(this, session.state, bestResult._1) 53 | bestResult._1 54 | } 55 | 56 | override val show = choices.map(_.showOperand(2)).mkString(" | ") -> 2 57 | 58 | override val captures = choices.map(_.captures).reduce(_ ++ _) 59 | 60 | override val branches = choices.map(_.branches).reduce { (left, right) => 61 | var result = left 62 | for ((key, values) <- right) 63 | result += key -> (result.getOrElse(key, Set.empty) ++ values) 64 | result 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/rules/TokenRule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax.rules 19 | 20 | import name.lakhin.eliah.projects.papacarlo.syntax.{Rule, Session} 21 | import name.lakhin.eliah.projects.papacarlo.utils.Bounds 22 | import name.lakhin.eliah.projects.papacarlo.syntax.Result._ 23 | 24 | final case class TokenRule(kind: String, matchUntil: Boolean = false) 25 | extends Rule { 26 | def apply(session: Session) = { 27 | session.syntax.onRuleEnter.trigger(this, session.state) 28 | 29 | var index = session.state.virtualPosition 30 | 31 | if (matchUntil) { 32 | while (session.tokens.lift(index).exists(_.kind != kind)) index += 1 33 | } 34 | 35 | val actualKind = session.tokens 36 | .lift(index) 37 | .map(_.kind) 38 | .getOrElse(TokenRule.EndOfFragmentKind) 39 | 40 | val result = if (actualKind == kind) { 41 | session.state = session.state.copy(virtualPosition = index + 1) 42 | Successful 43 | } else { 44 | session.state = session.state.issue( 45 | Bounds(session.state.virtualPosition, index + 1), 46 | kind + " expected, but " + actualKind + " found" 47 | ) 48 | Failed 49 | } 50 | 51 | session.syntax.onRuleLeave.trigger(this, session.state, result) 52 | result 53 | } 54 | 55 | override val show = { 56 | val terminal = !kind.toLowerCase.forall(char => 'a' <= char && char <= 'z') 57 | (if (terminal) "'" + kind + "'" else kind) + 58 | (if (matchUntil) ".matchUntil" else "") -> Int.MaxValue 59 | } 60 | 61 | override val captures = Set.empty[String] 62 | 63 | override val branches = Map.empty[String, Set[String]] 64 | } 65 | 66 | object TokenRule { 67 | val EndOfFragmentKind = "end of fragment" 68 | } 69 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/error-recovery/prototype/step12/node.txt: -------------------------------------------------------------------------------- 1 | > remove: 2 | string:10 3 | { 4 | "key 1": "hello world", 5 | "key 1.1": "value 1.1" 6 | <<<"key 2">>>: ["array value 1", "array value 2", "array value 3"], 7 | } 8 | 9 | 10 | > remove: 11 | entry:9 12 | { 13 | "key 1": "hello world", 14 | <<<"key 1.1": "value 1.1" 15 | "key 2">>>: ["array value 1", "array value 2", "array value 3"], 16 | } 17 | 18 | 19 | > create: 20 | entry:11 21 | { 22 | "key 1": "hello world", 23 | <<<"key 1.1": "value 1.1">>> 24 | "key 2": ["array value 1", "array value 2", "array value 3"], 25 | } 26 | 27 | 28 | > create: 29 | string:12 30 | { 31 | "key 1": "hello world", 32 | "key 1.1": <<<"value 1.1">>> 33 | "key 2": ["array value 1", "array value 2", "array value 3"], 34 | } 35 | 36 | 37 | > create: 38 | entry:13 39 | { 40 | "key 1": "hello world", 41 | "key 1.1": "value 1.1" 42 | <<<"key 2": ["array value 1", "array value 2", "array value 3"]>>>, 43 | } 44 | 45 | 46 | > create: 47 | array:14 cachable 48 | { 49 | "key 1": "hello world", 50 | "key 1.1": "value 1.1" 51 | "key 2": <<<["array value 1", "array value 2", "array value 3"]>>>, 52 | } 53 | 54 | 55 | > create: 56 | string:15 57 | { 58 | "key 1": "hello world", 59 | "key 1.1": "value 1.1" 60 | "key 2": [<<<"array value 1">>>, "array value 2", "array value 3"], 61 | } 62 | 63 | 64 | > create: 65 | string:16 66 | { 67 | "key 1": "hello world", 68 | "key 1.1": "value 1.1" 69 | "key 2": ["array value 1", <<<"array value 2">>>, "array value 3"], 70 | } 71 | 72 | 73 | > create: 74 | string:17 75 | { 76 | "key 1": "hello world", 77 | "key 1.1": "value 1.1" 78 | "key 2": ["array value 1", "array value 2", <<<"array value 3">>>], 79 | } 80 | 81 | 82 | > merge: 83 | object 1 { 84 | entry: entry 2 >> 1 { 85 | key: "key 1" 86 | value: string 3 >> 2 { 87 | value: "hello world" 88 | } 89 | } 90 | entry: entry 11 >> 1 { 91 | key: "key 1.1" 92 | value: string 12 >> 11 { 93 | value: "value 1.1" 94 | } 95 | } 96 | entry: entry 13 >> 1 { 97 | key: "key 2" 98 | value: array 14 cachable >> 13 { 99 | value: string 15 >> 14 { 100 | value: "array value 1" 101 | } 102 | value: string 16 >> 14 { 103 | value: "array value 2" 104 | } 105 | value: string 17 >> 14 { 106 | value: "array value 3" 107 | } 108 | } 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/fragment-recovery/prototype/step0/fragment.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | {node 0 3 | {node 0-0 4 | <<<{node 0-0-0 5 | 6 | }>>> 7 | 8 | {node 0-0-1 9 | 10 | } 11 | } 12 | 13 | {node 0-1 14 | {node 0-1-0 15 | 16 | } 17 | 16... 18 | 19 | > create: 20 | {node 0 21 | {node 0-0 22 | {node 0-0-0 23 | 24 | } 25 | 26 | <<<{node 0-0-1 27 | 28 | }>>> 29 | } 30 | 31 | {node 0-1 32 | {node 0-1-0 33 | 34 | } 35 | 36 | {node 0-1-1 37 | 38 | } 39 | 20... 40 | 41 | > create: 42 | {node 0 43 | <<<{node 0-0 44 | {node 0-0-0 45 | 46 | } 47 | 48 | {node 0-0-1 49 | 50 | } 51 | }>>> 52 | 53 | {node 0-1 54 | {node 0-1-0 55 | 56 | } 57 | 58 | {node 0-1-1 59 | 60 | } 61 | } 62 | 21... 63 | 64 | > create: 65 | ...2 66 | {node 0-0-0 67 | 68 | } 69 | 70 | {node 0-0-1 71 | 72 | } 73 | } 74 | 75 | {node 0-1 76 | <<<{node 0-1-0 77 | 78 | }>>> 79 | 80 | {node 0-1-1 81 | 82 | } 83 | } 84 | } 85 | 86 | 87 | > create: 88 | ...6 89 | {node 0-0-1 90 | 91 | } 92 | } 93 | 94 | {node 0-1 95 | {node 0-1-0 96 | 97 | } 98 | 99 | <<<{node 0-1-1 100 | 101 | }>>> 102 | } 103 | } 104 | 105 | 106 | > create: 107 | ...1 108 | {node 0-0 109 | {node 0-0-0 110 | 111 | } 112 | 113 | {node 0-0-1 114 | 115 | } 116 | } 117 | 118 | <<<{node 0-1 119 | {node 0-1-0 120 | 121 | } 122 | 123 | {node 0-1-1 124 | 125 | } 126 | }>>> 127 | } 128 | 129 | 130 | > create: 131 | <<<{node 0 132 | {node 0-0 133 | {node 0-0-0 134 | 135 | } 136 | 137 | {node 0-0-1 138 | 139 | } 140 | } 141 | 142 | {node 0-1 143 | {node 0-1-0 144 | 145 | } 146 | 147 | {node 0-1-1 148 | 149 | } 150 | } 151 | }>>> 152 | 153 | 154 | > invalidate: 155 | <<<{node 0 156 | {node 0-0 157 | {node 0-0-0 158 | 159 | } 160 | 161 | {node 0-0-1 162 | 163 | } 164 | } 165 | 166 | {node 0-1 167 | {node 0-1-0 168 | 169 | } 170 | 171 | {node 0-1-1 172 | 173 | } 174 | } 175 | } 176 | >>> 177 | 178 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/lexis/Token.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.lexis 19 | 20 | final class Token(val kind: String, 21 | val value: String, 22 | private var skipped: Boolean = false, 23 | private var mutable: Boolean = false, 24 | private var indentation: Boolean = false) { 25 | private val originallySkipped = skipped 26 | private val originallyMutable = mutable 27 | 28 | private[lexis] var context = Context.Base 29 | private[lexis] var seam: SeamType = RegularSeam 30 | 31 | def isSkippable = skipped 32 | def isMutable = mutable 33 | def getContext = context 34 | 35 | private[lexis] def applySkipLevel(level: SkipLevel): Unit = { 36 | level match { 37 | case ForceSkip => skipped = true 38 | case ForceUse => skipped = false 39 | case OriginalSkipping => skipped = originallySkipped 40 | } 41 | } 42 | 43 | private[lexis] def revertMutability(): Unit = { 44 | mutable = originallyMutable 45 | } 46 | 47 | private[lexis] def sameAs(another: Token) = { 48 | value == another.value || kind == another.kind && 49 | (mutable || another.mutable) 50 | } 51 | } 52 | 53 | object Token { 54 | val LineBreakKind = "lineBreak" 55 | val UnknownKind = "unknown" 56 | 57 | def unknown(value: String) = 58 | new Token( 59 | kind = UnknownKind, 60 | value = value, 61 | skipped = false, 62 | mutable = false 63 | ) 64 | 65 | def terminal(value: String) = 66 | new Token( 67 | kind = value, 68 | value = value, 69 | skipped = false, 70 | mutable = false 71 | ) 72 | 73 | def lineBreak = 74 | new Token( 75 | kind = LineBreakKind, 76 | value = "\n", 77 | skipped = true, 78 | mutable = false 79 | ) 80 | } 81 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/lexis/Fragment.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.lexis 19 | 20 | import name.lakhin.eliah.projects.papacarlo.utils.{Bounds, Signal} 21 | 22 | final case class Fragment(id: Int, begin: TokenReference, end: TokenReference) { 23 | private var defined = true 24 | private val tokenRemoveReaction = (reference: TokenReference) => remove() 25 | 26 | val onInvalidate = new Signal[Fragment] 27 | val onRemove = new Signal[Fragment] 28 | 29 | def parent: Option[Fragment] = 30 | if (begin.index > 0) { 31 | val references = begin.collection.references.lift 32 | 33 | for (index <- (0 until begin.index).reverse; 34 | candidate <- references(index).flatMap(_.getFragment)) 35 | if (candidate.end.index >= end.index) return Some(candidate) 36 | 37 | None 38 | } else None 39 | 40 | def range = 41 | if (defined) Bounds(begin.index, end.index + 1) 42 | else Bounds.undefined 43 | 44 | private[lexis] def remove(): Unit = { 45 | if (defined) { 46 | onRemove.trigger(this) 47 | defined = false 48 | begin.fragment = None 49 | onInvalidate.unbindAll() 50 | onRemove.unbindAll() 51 | begin.onRemove.unbind(tokenRemoveReaction) 52 | end.onRemove.unbind(tokenRemoveReaction) 53 | } 54 | } 55 | 56 | begin.onRemove.bind(tokenRemoveReaction) 57 | end.onRemove.bind(tokenRemoveReaction) 58 | 59 | def getTokens = 60 | Bounds(begin.index, end.index + 1).slice(begin.collection.descriptions) 61 | 62 | def highlight(limit: Option[Int] = None): String = 63 | if (defined) 64 | begin.collection.highlight(Bounds(begin.index, end.index + 1), limit) 65 | else "" 66 | 67 | override def toString = 68 | id + " " + 69 | this.begin.collection.range(Bounds(this.begin.index, this.end.index)) 70 | } 71 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/Cache.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax 19 | 20 | import name.lakhin.eliah.projects.papacarlo.Syntax 21 | import name.lakhin.eliah.projects.papacarlo.lexis.Fragment 22 | import name.lakhin.eliah.projects.papacarlo.utils.Bounds 23 | 24 | final class Cache(syntax: Syntax, val fragment: Fragment, val node: Node) { 25 | private[papacarlo] var errors = List.empty[Error] 26 | private[syntax] var ready = true 27 | 28 | for (oldCache <- syntax.cache.get(fragment.id)) oldCache.remove() 29 | 30 | syntax.cache += fragment.id -> this 31 | 32 | private val fragmentRemove = (_: Fragment) => remove() 33 | private val nodeRemove = (_: Node) => remove() 34 | 35 | fragment.onRemove.bind(fragmentRemove) 36 | node.onRemove.bind(nodeRemove) 37 | 38 | syntax.onCacheCreate.trigger(this) 39 | 40 | private[papacarlo] def invalidate(range: Bounds): Unit = { 41 | val session = new Session(syntax, fragment) 42 | 43 | ready = false 44 | val result = session.parse(node.kind) 45 | ready = true 46 | 47 | errors = result._2 48 | 49 | for (replacement <- result._1) { 50 | val errorBounds = errors.map(_.range) 51 | for (descendant <- node.merge(syntax.nodes, replacement, range)) 52 | if (descendant.cachable) 53 | for (descendantFragment <- descendant.begin.getFragment) 54 | if (descendantFragment.end.index == descendant.end.index 55 | && !errorBounds.exists(_.intersects(descendant.range))) 56 | new Cache(syntax, descendantFragment, descendant) 57 | 58 | syntax.onNodeMerge.trigger(node) 59 | node.update() 60 | } 61 | } 62 | 63 | private def remove(): Unit = { 64 | syntax.onCacheRemove.trigger(this) 65 | syntax.cache -= fragment.id 66 | node.onRemove.unbind(nodeRemove) 67 | fragment.onRemove.unbind(fragmentRemove) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/NodeAccessor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax 19 | 20 | import name.lakhin.eliah.projects.papacarlo.lexis.TokenReference 21 | 22 | final class NodeAccessor(val node: Node) { 23 | def setKind(kind: String) = { 24 | if (!node.bound) { 25 | node.kind = kind 26 | change() 27 | } 28 | 29 | this 30 | } 31 | 32 | def setBegin(reference: TokenReference) = { 33 | if (!node.bound) { 34 | node.begin = reference 35 | change() 36 | } 37 | 38 | this 39 | } 40 | 41 | def setEnd(reference: TokenReference) = { 42 | if (!node.bound) { 43 | node.end = reference 44 | change() 45 | } 46 | 47 | this 48 | } 49 | 50 | def setBranches(branches: Map[String, List[Node]]) = { 51 | if (!node.bound) { 52 | node.branches = branches 53 | change() 54 | } 55 | 56 | this 57 | } 58 | 59 | def setBranches(tag: String, branches: List[Node]) = { 60 | if (!node.bound) { 61 | node.branches += tag -> branches 62 | change() 63 | } 64 | 65 | this 66 | } 67 | 68 | def setReferences(references: Map[String, List[TokenReference]]) = { 69 | if (!node.bound) { 70 | node.references = references 71 | change() 72 | } 73 | 74 | this 75 | } 76 | 77 | def setReferences(tag: String, references: List[TokenReference]) = { 78 | if (!node.bound) { 79 | node.references += tag -> references 80 | change() 81 | } 82 | 83 | this 84 | } 85 | 86 | def setConstant(tag: String, value: String) = { 87 | if (!node.bound) { 88 | node.constants += tag -> value 89 | change() 90 | } 91 | 92 | this 93 | } 94 | 95 | private def change(): Unit = { 96 | node.cachable = false 97 | node.producer = None 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/calculator/expressions/prototype/step1/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | +:2 3 | 1 - -2 / 3 * +4 / 5 + 6 <<<+>>> 7% + 8 4 | 5 | 6 | > create: 7 | +:3 8 | 1 - -2 / 3 * +4 / 5 <<<+>>> 6 + 7% + 8 9 | 10 | 11 | > create: 12 | -:4 13 | 1 <<<->>> -2 / 3 * +4 / 5 + 6 + 7% + 8 14 | 15 | 16 | > create: 17 | number:5 18 | <<<1>>> - -2 / 3 * +4 / 5 + 6 + 7% + 8 19 | 20 | 21 | > create: 22 | /:6 23 | 1 - -2 <<>> 3 * +4 / 5 + 6 + 7% + 8 24 | 25 | 26 | > create: 27 | -:7 28 | 1 - <<<->>>2 / 3 * +4 / 5 + 6 + 7% + 8 29 | 30 | 31 | > create: 32 | number:8 33 | 1 - -<<<2>>> / 3 * +4 / 5 + 6 + 7% + 8 34 | 35 | 36 | > create: 37 | /:9 38 | 1 - -2 / 3 * +4 <<>> 5 + 6 + 7% + 8 39 | 40 | 41 | > create: 42 | *:10 43 | 1 - -2 / 3 <<<*>>> +4 / 5 + 6 + 7% + 8 44 | 45 | 46 | > create: 47 | number:11 48 | 1 - -2 / <<<3>>> * +4 / 5 + 6 + 7% + 8 49 | 50 | 51 | > create: 52 | +:12 53 | 1 - -2 / 3 * <<<+>>>4 / 5 + 6 + 7% + 8 54 | 55 | 56 | > create: 57 | number:13 58 | 1 - -2 / 3 * +<<<4>>> / 5 + 6 + 7% + 8 59 | 60 | 61 | > create: 62 | number:14 63 | 1 - -2 / 3 * +4 / <<<5>>> + 6 + 7% + 8 64 | 65 | 66 | > create: 67 | number:15 68 | 1 - -2 / 3 * +4 / 5 + <<<6>>> + 7% + 8 69 | 70 | 71 | > create: 72 | %:16 73 | 1 - -2 / 3 * +4 / 5 + 6 + 7<<<%>>> + 8 74 | 75 | 76 | > create: 77 | number:17 78 | 1 - -2 / 3 * +4 / 5 + 6 + <<<7>>>% + 8 79 | 80 | 81 | > create: 82 | number:18 83 | 1 - -2 / 3 * +4 / 5 + 6 + 7% + <<<8>>> 84 | 85 | 86 | > merge: 87 | + 1 { 88 | left: + 2 >> 1 { 89 | left: + 3 >> 2 { 90 | left: - 4 >> 3 { 91 | left: number 5 >> 4 { 92 | value: 1 93 | } 94 | right: / 6 >> 4 { 95 | left: - 7 >> 6 { 96 | operand: number 8 >> 7 { 97 | value: 2 98 | } 99 | } 100 | right: / 9 >> 6 { 101 | left: * 10 >> 9 { 102 | left: number 11 >> 10 { 103 | value: 3 104 | } 105 | right: + 12 >> 10 { 106 | operand: number 13 >> 12 { 107 | value: 4 108 | } 109 | } 110 | } 111 | right: number 14 >> 9 { 112 | value: 5 113 | } 114 | } 115 | } 116 | } 117 | right: number 15 >> 3 { 118 | value: 6 119 | } 120 | } 121 | right: % 16 >> 2 { 122 | operand: number 17 >> 16 { 123 | value: 7 124 | } 125 | } 126 | } 127 | right: number 18 >> 1 { 128 | value: 8 129 | } 130 | } 131 | 132 | -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/Resources.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | import scala.io.Source 21 | import java.io.{FileWriter, File} 22 | import net.liftweb.json.{NoTypeHints, Serialization} 23 | 24 | final class Resources( 25 | inputBase: String = Resources.DefaultResourceBase, 26 | outputBase: String = Resources.DefaultResourceBase 27 | ) { 28 | 29 | private implicit val formats = Serialization.formats(NoTypeHints) 30 | 31 | def exist(category: String, name: String) = 32 | getClass.getResource(inputBase + fileName(category, name)) != null 33 | 34 | def input(category: String, name: String) : String = 35 | try { 36 | val filePath = "src/test/resources" + inputBase + fileName(category, name) 37 | val textSource = scala.io.Source.fromFile(filePath) 38 | val str = textSource.mkString 39 | textSource.close 40 | return str 41 | } catch { 42 | case _: java.io.FileNotFoundException => return "" 43 | } 44 | 45 | def update(category: String, name: String, content: String) { 46 | try { 47 | val resource = getClass.getResource(outputBase) 48 | 49 | if (resource.getProtocol == "file") { 50 | val file = new File(resource.getPath + fileName(category, name)) 51 | 52 | if (!file.exists()) { 53 | val parent = file.getParentFile 54 | if (!parent.exists()) parent.mkdirs() 55 | file.createNewFile() 56 | } 57 | 58 | val writer = new FileWriter(file, false) 59 | 60 | writer.write(content) 61 | 62 | writer.close() 63 | } 64 | } catch { 65 | case _: RuntimeException => 66 | } 67 | } 68 | 69 | def json[A](category: String, name: String) 70 | (implicit mf : scala.reflect.Manifest[A]) = 71 | Serialization.read[A](input(category, name)) 72 | 73 | private def fileName(category: String, name: String) = category + "/" + name 74 | } 75 | 76 | object Resources { 77 | val DefaultResourceBase = "/fixtures/" 78 | } 79 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/Expressions.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax 19 | 20 | import name.lakhin.eliah.projects.papacarlo.syntax.rules.ExpressionRule 21 | 22 | object Expressions { 23 | def infix(rule: ExpressionRule, 24 | operator: String, 25 | precedence: Int, 26 | rightAssociativity: Boolean = false): Unit = { 27 | val leftBindingPower = Int.MaxValue - precedence * 10 28 | val rightBindingPower = 29 | leftBindingPower - (if (rightAssociativity) 1 else 0) 30 | 31 | rule 32 | .parselet(operator) 33 | .leftBindingPower(leftBindingPower) 34 | .leftDenotation { (expression, left, operatorReference) => 35 | val node = new Node(operator, operatorReference, operatorReference) 36 | 37 | node.branches += "left" -> List(left) 38 | 39 | for (right <- expression.parseRight(rightBindingPower)) 40 | node.branches += "right" -> List(right) 41 | 42 | node 43 | } 44 | } 45 | 46 | def postfix(rule: ExpressionRule, operator: String, precedence: Int): Unit = { 47 | rule 48 | .parselet(operator) 49 | .leftBindingPower(Int.MaxValue - precedence * 10) 50 | .leftDenotation { (expression, left, operatorReference) => 51 | val node = new Node(operator, operatorReference, operatorReference) 52 | node.branches += ExpressionRule.Operand -> List(left) 53 | node 54 | } 55 | } 56 | 57 | def prefix(rule: ExpressionRule, operator: String, precedence: Int): Unit = { 58 | val power = Int.MaxValue - precedence * 10 59 | 60 | rule.parselet(operator).nullDenotation { (expression, operatorReference) => 61 | val node = new Node(operator, operatorReference, operatorReference) 62 | for (right <- expression.parseRight(power)) 63 | node.branches += ExpressionRule.Operand -> List(right) 64 | node 65 | } 66 | } 67 | 68 | def group(rule: ExpressionRule, open: String, close: String): Unit = { 69 | rule.parselet(open).nullDenotation { (expression, _) => 70 | val result = expression.parseRight() 71 | 72 | expression.consume(")") 73 | 74 | result.getOrElse(expression.placeholder) 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/rules/RecoveryRule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax.rules 19 | 20 | import name.lakhin.eliah.projects.papacarlo.syntax.{Node, Issue, Session, Rule} 21 | import name.lakhin.eliah.projects.papacarlo.utils.Bounds 22 | import name.lakhin.eliah.projects.papacarlo.syntax.Result._ 23 | 24 | final case class RecoveryRule(rule: Rule, 25 | exception: Option[String] = None, 26 | branch: Option[String] = None) 27 | extends Rule { 28 | 29 | def apply(session: Session) = { 30 | session.syntax.onRuleEnter.trigger(this, session.state) 31 | 32 | val initialState = session.state 33 | var result = rule(session) 34 | 35 | if (result == Failed) { 36 | val issues = 37 | exception 38 | .map { description => 39 | Issue( 40 | Bounds.cursor(initialState.virtualPosition), 41 | description 42 | ) :: initialState.issues 43 | } 44 | .getOrElse(session.state.issues) 45 | 46 | branch match { 47 | case Some(tag) => 48 | val place = session.reference( 49 | session 50 | .relativeIndexOf(initialState.virtualPosition)) 51 | 52 | session.state = initialState.copy( 53 | issues = issues, 54 | products = ( 55 | tag, 56 | new Node(RecoveryRule.PlaceholderKind, place, place) 57 | ) :: initialState.products 58 | ) 59 | 60 | case None => session.state = initialState.copy(issues = issues) 61 | } 62 | result = Recoverable 63 | } 64 | 65 | session.syntax.onRuleLeave.trigger(this, session.state, result) 66 | result 67 | } 68 | 69 | override val show = { 70 | val atom = rule.showOperand(Int.MaxValue) + ".recover" + 71 | exception.map("(" + _ + ")").getOrElse("") 72 | 73 | branch match { 74 | case Some(branch: String) => branch + " -> " + atom -> 1 75 | case None => atom -> Int.MaxValue 76 | } 77 | } 78 | 79 | override val captures = rule.captures 80 | 81 | override val branches = 82 | branch match { 83 | case Some(tag) => 84 | rule.branches + (tag -> 85 | (rule.branches 86 | .getOrElse(tag, Set.empty) + RecoveryRule.PlaceholderKind)) 87 | 88 | case None => rule.branches 89 | } 90 | } 91 | 92 | object RecoveryRule { 93 | val PlaceholderKind = "placeholder" 94 | } 95 | -------------------------------------------------------------------------------- /js/demo/client.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | var initParser = function(main) { 18 | if (!!window.Worker) { 19 | var 20 | worker = new Worker('./server.js'), 21 | buffer = null, 22 | api = function(code) { 23 | if (api.busy) { 24 | buffer = code; 25 | return; 26 | } 27 | 28 | api.busy = true; 29 | 30 | worker.postMessage({ 31 | kind: 'input', 32 | code: code, 33 | ast: api.ast 34 | }); 35 | }; 36 | 37 | api.ast = true; 38 | api.async = true; 39 | api.busy = false; 40 | 41 | api.getFragment = function(id) { 42 | worker.postMessage({ 43 | kind: 'fragment', 44 | id: id 45 | }); 46 | } 47 | 48 | worker.onerror = function(error) { 49 | console.error(error); 50 | }; 51 | 52 | worker.onmessage = function(event) { 53 | switch (event.data.kind) { 54 | case 'ready': 55 | main(api); 56 | 57 | break; 58 | 59 | case 'fragment': 60 | if (!!api.receiveFragment) { 61 | api.receiveFragment(event.data.response); 62 | } 63 | 64 | break; 65 | 66 | case 'response': 67 | api.busy = false; 68 | 69 | if (buffer !== null) { 70 | var code = buffer; 71 | buffer = null; 72 | api(code); 73 | } 74 | 75 | if (!!api.response) { 76 | api.response(event.data.delta, event.data.stats); 77 | } 78 | } 79 | }; 80 | 81 | worker.postMessage({ kind: 'init' }); 82 | } else { 83 | d3.text( 84 | './target/scala-2.13/js-demo-opt/main.js', // produced by `sbt js-demo/fullLinkJS` 85 | //'./target/scala-2.13/js-demo-fastopt/main.js', // produced by `sbt js-demo/fastLinkJS` 86 | function(error, parserCode) { 87 | if (error) { 88 | console.error(error); 89 | return; 90 | } 91 | eval(parserCode); 92 | var 93 | parser = PapaCarloDemoParser, 94 | buffer = null, 95 | api = function(code) { 96 | if (api.busy) { 97 | buffer = true; 98 | return; 99 | } 100 | 101 | api.busy = true; 102 | var start = new Date().getTime(); 103 | parser.inputAll(code); 104 | var end = new Date().getTime(); 105 | api.busy = false; 106 | 107 | if (!!api.response) { 108 | api.response(end - start, { 109 | nodes: parser.getAST(true), 110 | errors: parser.getErrors() 111 | }); 112 | } 113 | 114 | if (buffer !== null) { 115 | code = buffer; 116 | buffer = null; 117 | api(code); 118 | } 119 | }; 120 | 121 | api.ast = true; 122 | api.async = false; 123 | api.busy = false; 124 | 125 | api.getFragment = function(id) { 126 | if (!!api.receiveFragment) { 127 | api.receiveFragment(parser.getNodeFragment(id)); 128 | } 129 | } 130 | 131 | main(api); 132 | } 133 | ); 134 | } 135 | }; 136 | 137 | -------------------------------------------------------------------------------- /src/test/resources/fixtures/json/simple/prototype/step0/node.txt: -------------------------------------------------------------------------------- 1 | > create: 2 | entry:2 3 | { 4 | <<<"key 1": "value 1">>>, 5 | "key 2": "value 2", 6 | "key 3": ["array element", 2.6e-2, true, false], 7 | "key 4": {"subkey": null} 8 | } 9 | 10 | 11 | > create: 12 | string:3 13 | { 14 | "key 1": <<<"value 1">>>, 15 | "key 2": "value 2", 16 | "key 3": ["array element", 2.6e-2, true, false], 17 | "key 4": {"subkey": null} 18 | } 19 | 20 | 21 | > create: 22 | entry:4 23 | { 24 | "key 1": "value 1", 25 | <<<"key 2": "value 2">>>, 26 | "key 3": ["array element", 2.6e-2, true, false], 27 | "key 4": {"subkey": null} 28 | } 29 | 30 | 31 | > create: 32 | string:5 33 | { 34 | "key 1": "value 1", 35 | "key 2": <<<"value 2">>>, 36 | "key 3": ["array element", 2.6e-2, true, false], 37 | "key 4": {"subkey": null} 38 | } 39 | 40 | 41 | > create: 42 | entry:6 43 | { 44 | "key 1": "value 1", 45 | "key 2": "value 2", 46 | <<<"key 3": ["array element", 2.6e-2, true, false]>>>, 47 | "key 4": {"subkey": null} 48 | } 49 | 50 | 51 | > create: 52 | array:7 cachable 53 | { 54 | "key 1": "value 1", 55 | "key 2": "value 2", 56 | "key 3": <<<["array element", 2.6e-2, true, false]>>>, 57 | "key 4": {"subkey": null} 58 | } 59 | 60 | 61 | > create: 62 | string:8 63 | { 64 | "key 1": "value 1", 65 | "key 2": "value 2", 66 | "key 3": [<<<"array element">>>, 2.6e-2, true, false], 67 | "key 4": {"subkey": null} 68 | } 69 | 70 | 71 | > create: 72 | number:9 73 | { 74 | "key 1": "value 1", 75 | "key 2": "value 2", 76 | "key 3": ["array element", <<<2.6e-2>>>, true, false], 77 | "key 4": {"subkey": null} 78 | } 79 | 80 | 81 | > create: 82 | boolean:10 83 | { 84 | "key 1": "value 1", 85 | "key 2": "value 2", 86 | "key 3": ["array element", 2.6e-2, <<>>, false], 87 | "key 4": {"subkey": null} 88 | } 89 | 90 | 91 | > create: 92 | boolean:11 93 | { 94 | "key 1": "value 1", 95 | "key 2": "value 2", 96 | "key 3": ["array element", 2.6e-2, true, <<>>], 97 | "key 4": {"subkey": null} 98 | } 99 | 100 | 101 | > create: 102 | entry:12 103 | { 104 | "key 1": "value 1", 105 | "key 2": "value 2", 106 | "key 3": ["array element", 2.6e-2, true, false], 107 | <<<"key 4": {"subkey": null}>>> 108 | } 109 | 110 | 111 | > create: 112 | object:13 cachable 113 | { 114 | "key 1": "value 1", 115 | "key 2": "value 2", 116 | "key 3": ["array element", 2.6e-2, true, false], 117 | "key 4": <<<{"subkey": null}>>> 118 | } 119 | 120 | 121 | > create: 122 | entry:14 123 | { 124 | "key 1": "value 1", 125 | "key 2": "value 2", 126 | "key 3": ["array element", 2.6e-2, true, false], 127 | "key 4": {<<<"subkey": null>>>} 128 | } 129 | 130 | 131 | > create: 132 | null:15 133 | { 134 | "key 1": "value 1", 135 | "key 2": "value 2", 136 | "key 3": ["array element", 2.6e-2, true, false], 137 | "key 4": {"subkey": <<>>} 138 | } 139 | 140 | 141 | > merge: 142 | object 1 { 143 | entry: entry 2 >> 1 { 144 | key: "key 1" 145 | value: string 3 >> 2 { 146 | value: "value 1" 147 | } 148 | } 149 | entry: entry 4 >> 1 { 150 | key: "key 2" 151 | value: string 5 >> 4 { 152 | value: "value 2" 153 | } 154 | } 155 | entry: entry 6 >> 1 { 156 | key: "key 3" 157 | value: array 7 cachable >> 6 { 158 | value: string 8 >> 7 { 159 | value: "array element" 160 | } 161 | value: number 9 >> 7 { 162 | value: 2.6e-2 163 | } 164 | value: boolean 10 >> 7 { 165 | value: true 166 | } 167 | value: boolean 11 >> 7 { 168 | value: false 169 | } 170 | } 171 | } 172 | entry: entry 12 >> 1 { 173 | key: "key 4" 174 | value: object 13 cachable >> 12 { 175 | entry: entry 14 >> 13 { 176 | key: "subkey" 177 | value: null 15 >> 14 178 | } 179 | } 180 | } 181 | } 182 | 183 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/utils/Bounds.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.utils 19 | 20 | final case class Bounds(from: Int, until: Int) { 21 | val defined = from <= until 22 | val length = until - from 23 | 24 | def iterator = (if (defined) from until until else 0 until 0).iterator 25 | 26 | def pair = from -> until 27 | 28 | def map(from: Int => Int, until: Int => Int) = 29 | if (defined) Bounds(from(this.from), until(this.until)) 30 | else this 31 | 32 | def map(offset: Int => Int) = 33 | if (defined) Bounds(offset(this.from), offset(this.until)) 34 | else this 35 | 36 | def union(point: Int) = 37 | if (defined) Bounds(point min from, (point + 1) max until) 38 | else Bounds(point, point + 1) 39 | 40 | def union(another: Bounds) = 41 | if (defined && another.defined) 42 | Bounds(from min another.from, until max another.until) 43 | else if (defined) this 44 | else another 45 | 46 | def inject(injection: Bounds) = 47 | if (defined && injection.defined) { 48 | if (injection.from <= from) 49 | Bounds(injection.from, until + injection.length) 50 | else if (injection.from <= until) 51 | Bounds(from, until + injection.length) 52 | else 53 | Bounds(from, injection.until) 54 | } else if (defined) this 55 | else injection 56 | 57 | def takeout(another: Bounds) = 58 | if (defined && another.defined) { 59 | if (until <= another.from) this 60 | else if (another.until <= from) 61 | Bounds(from - another.length, until - another.length) 62 | else if (from <= another.from) 63 | Bounds(from, (until - another.length) max another.from) 64 | else 65 | Bounds(another.until, until - another.from) 66 | } else this 67 | 68 | def enlarge(leftRadius: Int, rightRadius: Int) = 69 | if (defined) Bounds(from - leftRadius, until + rightRadius) 70 | else this 71 | 72 | def shift(offset: Int) = 73 | if (defined) Bounds(from + offset, until + offset) 74 | else this 75 | 76 | def slice[A](seq: IndexedSeq[A]) = 77 | if (defined) seq.slice(from, until) 78 | else seq 79 | 80 | def slice[A](seq: List[A]) = 81 | if (defined) seq.slice(from, until) 82 | else seq 83 | 84 | def substring(string: String) = 85 | if (defined) string.substring(from, until) 86 | else string 87 | 88 | def replace[A](target: IndexedSeq[A], replacement: Seq[A]) = 89 | if (defined) target.take(from) ++ replacement ++ target.drop(until) 90 | else target 91 | 92 | def replace[A](target: List[A], replacement: List[A]) = 93 | if (defined) target.take(from) ::: replacement ::: target.drop(until) 94 | else target 95 | 96 | def replace[A](target: String, replacement: String) = 97 | if (defined) 98 | target.substring(0, from) + replacement + target.substring(until) 99 | else target 100 | 101 | def includes(value: Int) = defined && from <= value && value < until 102 | 103 | def intersects(another: Bounds) = 104 | defined && another.defined && from < another.until && another.from < until 105 | 106 | def touches(another: Bounds) = 107 | defined && another.defined && from <= another.until && another.from <= until 108 | } 109 | 110 | object Bounds { 111 | val undefined = Bounds(0, -1) 112 | 113 | def point(position: Int) = Bounds(position, position + 1) 114 | 115 | def cursor(position: Int) = Bounds(position, position) 116 | } 117 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/Rule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax 19 | 20 | import name.lakhin.eliah.projects.papacarlo.syntax.rules._ 21 | 22 | abstract class Rule { 23 | def apply(session: Session): Int 24 | 25 | def show: (String, Int) 26 | 27 | def captures: Set[String] 28 | 29 | def branches: Map[String, Set[String]] 30 | 31 | final def showOperand(parentPrecedence: Int): String = { 32 | val (string, currentPrecedence) = show 33 | 34 | if (currentPrecedence < parentPrecedence) "(" + string + ")" 35 | else string 36 | } 37 | 38 | final def permissive = RecoveryRule(this) 39 | 40 | final def permissive(description: String) = 41 | RecoveryRule(this, Some(description)) 42 | 43 | final def required = RequiredRule(this) 44 | 45 | final def name(label: String) = this match { 46 | case NamedRule(_, rule: Rule, trace) => NamedRule(label, rule, trace) 47 | case _ => NamedRule(label, this) 48 | } 49 | } 50 | 51 | object Rule { 52 | def token(kind: String) = TokenRule(kind) 53 | 54 | def tokensUntil(lastKind: String) = TokenRule(lastKind, matchUntil = true) 55 | 56 | def capture(tag: String, rule: Rule) = rule match { 57 | case CapturingRule(_, rule: Rule) => CapturingRule(tag, rule) 58 | case _ => CapturingRule(tag, rule) 59 | } 60 | 61 | def branch(tag: String, rule: Rule): Rule = rule match { 62 | case rule: ReferentialRule => rule.copy(tag = Some(tag)) 63 | 64 | case ChoiceRule(cases) => 65 | ChoiceRule(cases.map(choice => branch(tag, choice))) 66 | 67 | case SequentialRule(steps) => 68 | ChoiceRule(steps.map(step => branch(tag, step))) 69 | 70 | case rule @ RepetitionRule(element, _, _, _) => 71 | rule.copy(element = branch(tag, element)) 72 | 73 | case RecoveryRule(rule: Rule, exception, recoveryBranch) => 74 | RecoveryRule(branch(tag, rule), exception, Some(tag)) 75 | 76 | case NamedRule(label: String, rule: Rule, trace) => 77 | NamedRule(label, branch(tag, rule), trace) 78 | 79 | case _ => rule 80 | } 81 | 82 | def subrule(label: String)(body: => Rule) = body.name(label) 83 | 84 | def optional(rule: Rule) = RepetitionRule(rule, max = Some(1)) 85 | 86 | def zeroOrMore(rule: Rule) = RepetitionRule(rule) 87 | 88 | def oneOrMore(rule: Rule) = RepetitionRule(rule, min = Some(1)) 89 | 90 | def repeat(rule: Rule, times: Int) = 91 | RepetitionRule(rule, min = Some(times), max = Some(times)) 92 | 93 | def zeroOrMore(rule: Rule, separator: Rule) = 94 | RepetitionRule(rule, separator = Some(separator)) 95 | 96 | def oneOrMore(rule: Rule, separator: Rule) = 97 | RepetitionRule(rule, separator = Some(separator), min = Some(1)) 98 | 99 | def repeat(rule: Rule, separator: Rule, times: Int) = 100 | RepetitionRule(rule, 101 | separator = Some(separator), 102 | min = Some(times), 103 | max = Some(times)) 104 | 105 | def sequence(steps: Rule*) = SequentialRule(steps.toList) 106 | 107 | def choice(cases: Rule*) = ChoiceRule(cases.toList) 108 | 109 | def recover(rule: Rule, exception: String) = 110 | RecoveryRule(rule, Some(exception)) 111 | 112 | def recover(rule: Rule, nodeTag: String, exception: String) = 113 | RecoveryRule(rule, Some(exception), Some(nodeTag)) 114 | 115 | def expression(tag: String, atom: Rule) = ExpressionRule(tag, atom) 116 | 117 | def expression(atom: Rule) = ExpressionRule(ReferentialRule.Result, atom) 118 | } 119 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/syntax/rules/RepetitionRule.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.syntax.rules 19 | 20 | import name.lakhin.eliah.projects.papacarlo.syntax.{Session, Rule} 21 | import name.lakhin.eliah.projects.papacarlo.syntax.Result._ 22 | 23 | final case class RepetitionRule(element: Rule, 24 | separator: Option[Rule] = None, 25 | min: Option[Int] = None, 26 | max: Option[Int] = None) 27 | extends Rule { 28 | def apply(session: Session): Int = { 29 | session.syntax.onRuleEnter.trigger(this, session.state) 30 | 31 | val min = this.min.getOrElse(0) 32 | val max = this.max.getOrElse(Int.MaxValue) 33 | val initialState = session.state 34 | 35 | if (element(session) == Failed) 36 | if (min <= 0) { 37 | session.state = initialState 38 | session.syntax.onRuleLeave.trigger(this, session.state, Successful) 39 | return Successful 40 | } else { 41 | session.syntax.onRuleLeave.trigger(this, session.state, Failed) 42 | return Failed 43 | } 44 | 45 | var counter = 1 46 | var lastIssues = session.state.issues 47 | 48 | this.separator match { 49 | case Some(division: Rule) => 50 | var finished = counter >= max 51 | 52 | while (!finished) { 53 | val lastState = session.state 54 | 55 | if (division(session) == Failed) { 56 | lastIssues = session.state.issues 57 | session.state = lastState 58 | finished = true 59 | } 60 | 61 | if (!finished && element(session) == Failed) { 62 | lastIssues = session.state.issues 63 | session.state = lastState 64 | finished = true 65 | } 66 | 67 | if (!finished) { 68 | counter += 1 69 | finished = counter >= max 70 | } 71 | } 72 | 73 | case _ => 74 | var finished = counter >= max 75 | 76 | while (!finished) { 77 | val lastState = session.state 78 | 79 | if (element(session) == Failed) { 80 | lastIssues = session.state.issues 81 | session.state = lastState 82 | finished = true 83 | } 84 | 85 | if (!finished) { 86 | counter += 1 87 | finished = counter >= max 88 | } 89 | } 90 | } 91 | 92 | val result = 93 | if (counter >= min) Successful 94 | else { 95 | session.state = initialState.copy(issues = lastIssues) 96 | Failed 97 | } 98 | 99 | session.syntax.onRuleLeave.trigger(this, session.state, result) 100 | result 101 | } 102 | 103 | override val show = 104 | (((min, max) match { 105 | case (None, None) => element.showOperand(4) + "*" 106 | case (Some(0), None) => element.showOperand(4) + "*" 107 | case (Some(1), None) => element.showOperand(4) + "+" 108 | case (Some(min: Int), None) => element.showOperand(4) + " * " + min 109 | case (Some(min: Int), Some(max: Int)) => 110 | element.showOperand(4) + " * (" + 111 | min + ", " + max + ")" 112 | case (None, Some(max: Int)) => 113 | element.showOperand(4) + " * (0, " + max + 114 | ")" 115 | }) + separator.map(" / " + _.showOperand(4)).getOrElse("")) -> 4 116 | 117 | override val captures = 118 | element.captures ++ separator.map(_.captures).getOrElse(Set.empty) 119 | 120 | override val branches = element.branches 121 | } 122 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/lexis/Tokenizer.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.lexis 19 | 20 | final class Tokenizer { 21 | private var rules = Map.empty[String, Matcher] 22 | private var order = List.empty[String] 23 | private var terminals = List.empty[(String, StringMatcher)] 24 | private var keywords = Set.empty[String] 25 | private var skips = Set.empty[String] 26 | private var mutables = Set.empty[String] 27 | private var indentations = Set.empty[String] 28 | 29 | /* 30 | [warn] papacarlo/lexis/Tokenizer.scala:28:20: 31 | The outer reference in this type test cannot be checked at run time. 32 | 33 | [warn] final case class RuleDefinition(name: String) { 34 | [warn] ^ 35 | 36 | https://stackoverflow.com/questions/16450008/typesafe-swing-events-the-outer-reference-in-this-type-test-cannot-be-checked-a/16466541 37 | the compiler can't find a way to check the outer reference, because you've declared your inner class as final. 38 | */ 39 | //final case class RuleDefinition(name: String) { 40 | case class RuleDefinition(name: String) { 41 | def skip = { 42 | skips += name 43 | 44 | this 45 | } 46 | 47 | def mutable = { 48 | mutables += name 49 | 50 | this 51 | } 52 | 53 | def indentation = { 54 | mutables += name 55 | 56 | this 57 | } 58 | } 59 | 60 | def tokenCategory(name: String, matcher: Matcher) = { 61 | if (!rules.contains(name)) { 62 | rules += Tuple2(name, matcher) 63 | order :+= name 64 | } 65 | 66 | RuleDefinition(name) 67 | } 68 | 69 | def terminals(patterns: String*): Unit = { 70 | for (pattern <- patterns) 71 | if (!terminals.exists(terminal => terminal._1 == pattern)) 72 | terminals = (Tuple2(pattern, StringMatcher(pattern)) :: terminals) 73 | .sortBy(_._1.length) 74 | 75 | terminals = terminals.sortBy(_._1).reverse 76 | } 77 | 78 | def keywords(patterns: String*): Unit = { 79 | keywords ++= patterns 80 | } 81 | 82 | def tokenize(input: String) = { 83 | val rules = (terminals ::: this.rules.toList).toIterable 84 | 85 | var output = List.empty[Token] 86 | var position = 0 87 | var inTheBegin = true 88 | while (position < input.length) { 89 | val start = position 90 | 91 | var application = Option.empty[(String, Int)] 92 | while (position < input.length && application.isEmpty) { 93 | application = rules 94 | .map(rule => (rule._1, rule._2(input, position))) 95 | .find(candidate => candidate._2.isDefined) 96 | .map(result => result.copy(_2 = result._2.getOrElse(position + 1))) 97 | if (application.isEmpty) position += 1 98 | } 99 | 100 | if (start < position) { 101 | output ::= Token.unknown(input.substring(start, position)) 102 | } 103 | 104 | for (successful <- application) { 105 | val value = input.substring(position, successful._2) 106 | 107 | if (keywords.contains(value)) output ::= Token.terminal(value) 108 | else { 109 | val kind = successful._1 110 | 111 | val indentation = inTheBegin && indentations.contains(kind) 112 | if (!indentation) inTheBegin = false 113 | 114 | output ::= new Token( 115 | kind = kind, 116 | value = value, 117 | skipped = skips.contains(kind), 118 | mutable = mutables.contains(kind), 119 | indentation = indentation 120 | ) 121 | } 122 | 123 | position = successful._2 124 | } 125 | } 126 | 127 | output.reverse 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | Development history 2 | =================== 3 | 4 | 0.7.0 5 | ----- 6 | _2014-05-27_ 7 | 8 | - In-browser demo of JSON parser. Compiled with ScalaJS. 9 | - Improvements: 10 | - Flat mode of syntax error recovery based on skips entire fragments. 11 | Turned on by default. 12 | - Ctags support. 13 | - Fragment.getTokens becomes a public method. 14 | - Rule Tree reflection features. Traversing structure of rules belong to 15 | Node. 16 | - Node.visit becomes a public method. 17 | - Bug fix: 18 | - Ignoring removed tokens of detached Nodes. 19 | - Wrong difference of files with trailing empty lines. 20 | - Lexer.input works wrong when the cursor bounds provided explicitly. 21 | - Top level fragments wrong parsing. Appropriate "comments" parsing test 22 | added. 23 | 24 | 25 | 0.6.1 26 | ----- 27 | _2014-02-20_ 28 | 29 | - Bug in packrat cache fixed. 30 | 31 | 32 | 0.6.0 33 | ----- 34 | _2014-02-16_ 35 | 36 | - Syntax rule construction API major rework. 37 | - New features: 38 | - `Node.getValues` method to retrieve captured parts separately. 39 | - Tracking child branches using `Node.onAddBranch` event. 40 | - AST Node random access by ID. 41 | - Direct access to the top node of the AST. 42 | - Bug fix: 43 | - Children node track in wrong order. 44 | - Minor issue in pretty print procedure. 45 | - Caching minor issues. 46 | 47 | 48 | 0.5.0 49 | ----- 50 | _2013-12-10_ 51 | 52 | - New features: 53 | - Debug Monitor that shows parse steps of the selected parse rule. 54 | - Node smart merging. Now merging procedure reuses branches of the previously 55 | parsed node. 56 | - Minor improvements: 57 | - Nodes support hash function. 58 | - Rule pretty printing. 59 | - Bug fix: 60 | - Zombie-monitors removing. 61 | - Major issue in Packrat cache fixed. 62 | 63 | 0.4.2 64 | ----- 65 | _2013-12-04_ 66 | 67 | - Bug fix: 68 | - Fragment forceful skipping/using, i.e comments erasing case. 69 | - Repetition rule bug. 70 | - Referential rule for without tag. 71 | - Choice Rule bug in selection of the suitable recoverable candidate. 72 | - Minor improvements: 73 | - Node construction in call-chain way. 74 | - Optional token consumption feature in Expression parser. 75 | - Line offset token tracking. 76 | - Syntax parser in ParserSpec can be instantiated separately from the lexer. 77 | - Empty Monitor that tracks nothing. For simple debug purposes. 78 | 79 | 0.4.1 80 | ----- 81 | _2013-11-30_ 82 | 83 | - Node's additional constructor. 84 | - Node's new property: "constant". When set forcefully overrides appropriate 85 | referenced token value. 86 | - Recover Rule constructor renamed to "permissive". 87 | - Require Rule introduced. 88 | 89 | 0.4.0 90 | ----- 91 | _2013-11-21_ 92 | 93 | - AST improvements: 94 | - Node's reference to it's parent. 95 | - Update event propagation up to root. 96 | - Node numeration order bug fixed. 97 | - Error recovery minor improvements. 98 | - SBT configuration major improvement. 99 | - Artifacts can be published to Sonatype. 100 | 101 | 0.3.0 102 | ----- 103 | _2013-10-15_ 104 | 105 | - Expression parsing rule based on Pratt's algorithm implemented. 106 | - Several parslet constructors for Expression parser implemented. 107 | - Calculator parser example implemented. As well as appropriate functional 108 | tests. 109 | - Minor improvements: 110 | - Avoid rule construction for simple rule nesting cases. 111 | - Some identifiers were renamed. 112 | 113 | 0.2.0 114 | ----- 115 | _2013-10-07_ 116 | 117 | - Lexer and Syntax parser grammar definition API changed from operator based to 118 | function based. 119 | - Three additional functional tests for Json example parser implemented. These 120 | three test were designed specifically to test various code fragmentation 121 | cases. 122 | - Minor refactoring: 123 | - Term "environment" renamed to "monitor" in context of functional tests. 124 | - Directory structure simplified. 125 | 126 | 0.1.0 127 | ----- 128 | _2013-10-05_ 129 | 130 | The project source code was derived from the internal project "Malvina" 131 | developed and copyrighted by Ilya Lakhin (Илья Александрович Лахин). The code is 132 | published under the terms of Apache Public License v2: 133 | http://www.apache.org/licenses/LICENSE-2.0.txt 134 | 135 | Last commit in the private repository where the "Papa Carlo" was 136 | derived from is: 137 | Eliah-Lakhin/malvina-prototype-b@d1d863f227e79ee27c4d69fdce1eb8c5146dacd4 138 | -------------------------------------------------------------------------------- /js/demo/Demo.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects.papacarlo.js.demo 18 | 19 | import scala.scalajs.js 20 | import js.annotation.{JSName, JSExport, JSExportTopLevel} 21 | 22 | import name.lakhin.eliah.projects.papacarlo.lexis.TokenReference 23 | import name.lakhin.eliah.projects.papacarlo.syntax.Node 24 | import name.lakhin.eliah.projects.papacarlo.examples.Json 25 | 26 | @JSExportTopLevel("PapaCarloDemoParser") 27 | object Demo { 28 | private val lexer = Json.lexer 29 | private val syntax = Json.syntax(lexer) 30 | private var addedNodes = List.empty[Int] 31 | private var removedNodes = List.empty[Int] 32 | 33 | syntax.onNodeCreate.bind { node => 34 | addedNodes ::= node.getId 35 | } 36 | syntax.onNodeRemove.bind { node => 37 | removedNodes ::= node.getId 38 | } 39 | 40 | @JSExport 41 | def inputAll(text: String) { 42 | lexer.input(text) 43 | } 44 | 45 | @JSExport 46 | def input(text: String, 47 | fromLine: Int, 48 | fromChar: Int, 49 | toLine: Int, 50 | toChar: Int) { 51 | lexer.input(text, fromLine -> fromChar, toLine -> toChar) 52 | } 53 | 54 | @JSExport 55 | def getErrors() = { 56 | toJsArray(syntax.getErrors.map { error => 57 | val from = tokenCursor(error.from) 58 | val to = tokenCursor(error.to, after = true) 59 | 60 | js.Dynamic.literal( 61 | "from" -> from, 62 | "to" -> to, 63 | "description" -> error.description 64 | ) 65 | }) 66 | } 67 | 68 | @JSExport 69 | def getNodeFragment(id: Int) = { 70 | syntax.nodes.get(id) match { 71 | case Some(node) => 72 | js.Dynamic.literal( 73 | "exists" -> true, 74 | "id" -> id, 75 | "from" -> tokenCursor(node.getBegin), 76 | "to" -> tokenCursor(node.getEnd, after = true) 77 | ) 78 | 79 | case None => 80 | js.Dynamic.literal( 81 | "exists" -> false, 82 | "id" -> id 83 | ) 84 | } 85 | } 86 | 87 | @JSExport 88 | def getAST(graph: Boolean = false) = { 89 | val result = js.Dictionary.empty[js.Any] 90 | 91 | result("total") = syntax.nodes.size 92 | result("added") = toJsArray(addedNodes.reverse.map(x => x: js.Any)) 93 | result("removed") = toJsArray(removedNodes.reverse.map(x => x: js.Any)) 94 | 95 | if (graph) { 96 | val ast = js.Dictionary.empty[js.Any] 97 | 98 | for (node <- syntax.nodes.elements) { 99 | ast(node.getId.toString) = exportNode(node) 100 | } 101 | 102 | result("all") = ast 103 | } 104 | 105 | addedNodes = Nil 106 | removedNodes = Nil 107 | 108 | result 109 | } 110 | 111 | private def toJsArray(iterable: Iterable[js.Any]) = { 112 | val result = new js.Array[js.Any] 113 | 114 | for (element <- iterable) result.push(element) 115 | 116 | result 117 | } 118 | 119 | private def mapToObject(map: Map[String, js.Array[js.Any]]) = { 120 | val result = js.Dictionary.empty[js.Any] 121 | 122 | for ((key, values) <- map) result(key) = values 123 | 124 | result 125 | } 126 | 127 | private def exportNode(node: Node) = { 128 | val parentId = node.getParent.map(_.getId).getOrElse(-1) 129 | 130 | js.Dynamic.literal( 131 | "id" -> node.getId, 132 | "parent" -> parentId, 133 | "children" -> 134 | toJsArray(node.getBranches.map(_._2).flatten.map(_.getId: js.Any)), 135 | "kind" -> node.getKind, 136 | "values" -> mapToObject( 137 | node.getValues 138 | .map { 139 | case (key, values) => 140 | key -> toJsArray(values.map(s => s: String)) 141 | }) 142 | ) 143 | } 144 | 145 | private def tokenCursor(token: TokenReference, after: Boolean = false) = { 146 | val pair = token.collection.cursor(token.index + (if (after) 1 else 0)) 147 | 148 | js.Dynamic.literal("line" -> (pair._1 - 1), "ch" -> (pair._2 - 1)) 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/test/scala/name.lakhin.eliah.projects/papacarlo/test/utils/DebugMonitor.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.test.utils 19 | 20 | import name.lakhin.eliah.projects.papacarlo.lexis.Token 21 | import name.lakhin.eliah.projects.papacarlo.syntax.rules.NamedRule 22 | import name.lakhin.eliah.projects.papacarlo.syntax.{Result, Rule, State, Cache} 23 | import name.lakhin.eliah.projects.papacarlo.{Syntax, Lexer} 24 | 25 | final class DebugMonitor(lexer: Lexer, syntax: Syntax) 26 | extends SyntaxMonitor(lexer, syntax) { 27 | 28 | private var log = List.empty[(Symbol, String)] 29 | private var fragment = IndexedSeq.empty[Token] 30 | private var segment = IndexedSeq.empty[Token] 31 | private var deep = 0 32 | 33 | val onCacheInvalidate = (cache: Cache) => { 34 | log ::= Symbol("fragment of node " + cache.node.id) -> 35 | (if (shortOutput) cache.fragment.toString 36 | else cache.fragment.highlight(Some(10))) 37 | fragment = cache.fragment.getTokens 38 | } 39 | 40 | val onParseStep = (tokens: Seq[Token]) => { 41 | segment = tokens.toIndexedSeq 42 | if (shortOutput) log ::= 'segment -> ("Length: " + segment.size) 43 | else log ::= 'segment -> segment.map(_.value).mkString(" ") 44 | } 45 | 46 | val onRuleEnter: ((Rule, State)) => Any = { 47 | case (rule: Rule, state: State) => { 48 | if (isTraceable(rule) || deep > 0) deep += 1 49 | 50 | if (rule.isInstanceOf[NamedRule] && deep > 0) 51 | log ::= 'enter -> (rule.show._1 + stateInfo(state)) 52 | } 53 | } 54 | 55 | val onRuleLeave: ((Rule, State, Int)) => Any = { 56 | case (rule: Rule, state: State, result: Int) => { 57 | if (rule.isInstanceOf[NamedRule] && deep > 0) 58 | log ::= 'leave -> (rule.show._1 + stateInfo(state) + (result match { 59 | case Result.Failed => "\nFailed" 60 | case Result.Recoverable => "\nRecoverable" 61 | case _ => "\nSuccessful" 62 | })) 63 | 64 | if (deep > 0) deep -= 1 65 | } 66 | } 67 | 68 | private def isTraceable(rule: Rule) = rule match { 69 | case NamedRule(_, _, true) => true 70 | case _ => false 71 | } 72 | 73 | private def stateInfo(state: State) = { 74 | var result = "" 75 | 76 | if (shortOutput) result += "\nposition: " + state.virtualPosition 77 | else result += "\n" + 78 | segment.take(state.virtualPosition).map(_.kind).mkString(" ") + 79 | ":|:" + segment.drop(state.virtualPosition).map(_.kind).mkString(" ") 80 | 81 | if (state.captures.nonEmpty) { 82 | result += "\ncaptures:" 83 | if (shortOutput) result += state.captures.size 84 | else for ((key, value) <- state.captures) 85 | result += "\n " + key + " = " + 86 | value.slice(fragment).map(_.value).mkString 87 | } 88 | 89 | if (state.products.nonEmpty) { 90 | result += "\nproducts:" 91 | if (shortOutput) result += state.products.size 92 | else for ((key, value) <- state.products) 93 | result += "\n " + key + " = " + value.toString 94 | } 95 | 96 | if (state.issues.nonEmpty) { 97 | result += "\nissues:" 98 | if (shortOutput) result += state.issues.size 99 | else for (issue <- state.issues) 100 | result += "\n \"" + issue.description + "\" = " + 101 | issue.range.slice(segment).map(_.kind).mkString(" ") 102 | } 103 | 104 | result 105 | } 106 | 107 | def getResult = unionLog(log, enterLeaveIndentation = true) 108 | 109 | def prepare() { 110 | log = Nil 111 | syntax.onCacheInvalidate.bind(onCacheInvalidate) 112 | syntax.onParseStep.bind(onParseStep) 113 | syntax.onRuleEnter.bind(onRuleEnter) 114 | syntax.onRuleLeave.bind(onRuleLeave) 115 | } 116 | 117 | def release() { 118 | log = Nil 119 | syntax.onCacheInvalidate.unbind(onCacheInvalidate) 120 | syntax.onParseStep.unbind(onParseStep) 121 | syntax.onRuleEnter.unbind(onRuleEnter) 122 | syntax.onRuleLeave.unbind(onRuleLeave) 123 | } 124 | } -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/Syntax.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo 19 | 20 | import name.lakhin.eliah.projects.papacarlo.lexis.Token 21 | import name.lakhin.eliah.projects.papacarlo.syntax.{State, Rule, Node, Cache} 22 | import name.lakhin.eliah.projects.papacarlo.utils.{Signal, Registry} 23 | import name.lakhin.eliah.projects.papacarlo.syntax.rules.{ 24 | NamedRule, 25 | ReferentialRule 26 | } 27 | 28 | final class Syntax(val lexer: Lexer) { 29 | final class RuleDefinition(val name: String) { 30 | private[papacarlo] var productKind: String = name 31 | private[papacarlo] var cachingFlag = false 32 | private[papacarlo] var transformer: Option[Node => Node] = None 33 | 34 | private var constructor = Option.empty[() => Rule] 35 | private[papacarlo] lazy val body = constructor match { 36 | case Some(bodyConstructor) => bodyConstructor() 37 | case _ => throw new RuntimeException("Rule " + name + " undefined") 38 | } 39 | 40 | def produce(kind: String) = { 41 | productKind = kind 42 | 43 | this 44 | } 45 | 46 | def main = { 47 | if (Syntax.this.mainRule.isEmpty) Syntax.this.mainRule = Some(name) 48 | 49 | this 50 | } 51 | 52 | def cachable = { 53 | cachingFlag = true 54 | transformer = None 55 | 56 | this 57 | } 58 | 59 | def transform(transformer: Node => Node) = { 60 | cachingFlag = false 61 | this.transformer = Some(transformer) 62 | 63 | this 64 | } 65 | 66 | def reference = NamedRule(name, ReferentialRule(name)) 67 | 68 | def apply(body: => Rule) = { 69 | constructor match { 70 | case None => 71 | constructor = Some(() => body) 72 | 73 | if (mainRule.exists(_ == name)) { 74 | val rootFragment = lexer.fragments.rootFragment 75 | val rootNode = new Node(name, rootFragment.begin, rootFragment.end) 76 | 77 | nodes.add(id => { rootNode.id = id; rootNode }) 78 | 79 | new Cache(Syntax.this, rootFragment, rootNode) 80 | 81 | Syntax.this.rootNode = Some(rootNode) 82 | } 83 | 84 | case _ => 85 | } 86 | 87 | reference 88 | } 89 | } 90 | 91 | private[papacarlo] var rules = Map.empty[String, RuleDefinition] 92 | private var rootNode: Option[Node] = None 93 | private var mainRule: Option[String] = None 94 | private[papacarlo] var nodes = new Registry[Node] 95 | private[papacarlo] var cache = Map.empty[Int, Cache] 96 | private[papacarlo] var deepRecoveryMode = false 97 | 98 | val onCacheCreate = new Signal[Cache] 99 | val onCacheRemove = new Signal[Cache] 100 | val onCacheInvalidate = new Signal[Cache] 101 | val onNodeCreate = nodes.onAdd 102 | val onNodeMerge = new Signal[Node] 103 | val onNodeRemove = nodes.onRemove 104 | val onParseStep = new Signal[Seq[Token]] 105 | val onRuleEnter = new Signal[(Rule, State)] 106 | val onRuleLeave = new Signal[(Rule, State, Int)] 107 | 108 | lexer.fragments.onInvalidate.bind { 109 | case (fragment, range) => 110 | var candidate = Option(fragment) 111 | 112 | while (candidate.exists(fragment => !cache.contains(fragment.id))) candidate = 113 | candidate.flatMap(_.parent) 114 | 115 | for (cache <- cache.get( 116 | candidate.getOrElse(lexer.fragments.rootFragment).id)) { 117 | onCacheInvalidate.trigger(cache) 118 | cache.invalidate(range) 119 | } 120 | } 121 | 122 | def getErrors = cache.values.map(cache => cache.errors).flatten.toList 123 | 124 | def rule(name: String) = 125 | rules.get(name) match { 126 | case Some(definition) => definition 127 | 128 | case None => 129 | val definition = new RuleDefinition(name) 130 | 131 | rules += name -> definition 132 | 133 | definition 134 | } 135 | 136 | def setDeepRecovery(mode: Boolean): Unit = { 137 | deepRecoveryMode = mode 138 | } 139 | 140 | def getRootNode = rootNode 141 | 142 | def getNode(id: Int) = nodes.get(id) 143 | } 144 | -------------------------------------------------------------------------------- /src/main/scala/name.lakhin.eliah.projects/papacarlo/lexis/Matcher.scala: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2013 Ilya Lakhin (Илья Александрович Лахин) 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package name.lakhin.eliah.projects 18 | package papacarlo.lexis 19 | 20 | sealed abstract class Matcher { 21 | def apply(code: String, position: Int): Option[Int] 22 | } 23 | 24 | final case class StringMatcher(pattern: String) extends Matcher { 25 | 26 | def apply(code: String, position: Int) = 27 | Some(position + pattern.length) 28 | .filter( 29 | next => 30 | next <= code.length 31 | && code.substring(position, next) == pattern) 32 | } 33 | 34 | final case class CharSetMatcher(set: Set[Char], positive: Boolean = true) 35 | extends Matcher { 36 | 37 | def apply(code: String, position: Int) = 38 | Some(position + 1) 39 | .filter(next => next <= code.length) 40 | .filter(_ => set.contains(code.charAt(position)) == positive) 41 | 42 | def sup = copy(positive = !positive) 43 | } 44 | 45 | final case class CharRangeMatcher(from: Char, 46 | to: Char, 47 | positive: Boolean = true) 48 | extends Matcher { 49 | 50 | def apply(code: String, position: Int) = 51 | Some(position + 1) 52 | .filter(next => next <= code.length) 53 | .filter(_ => 54 | (from <= code.charAt(position) 55 | && code.charAt(position) <= to) == positive) 56 | 57 | def sup = copy(positive = !positive) 58 | } 59 | 60 | final case class RepetitionMatcher(sub: Matcher, 61 | min: Int = 0, 62 | max: Int = Int.MaxValue) 63 | extends Matcher { 64 | 65 | def apply(code: String, position: Int) = { 66 | var count = 0 67 | var finished = false 68 | var current = position 69 | 70 | while (count < max && !finished) { 71 | sub(code, current) match { 72 | case Some(next) => 73 | current = next 74 | count += 1 75 | case None => finished = true 76 | } 77 | } 78 | 79 | Some(current).filter(_ => count >= min) 80 | } 81 | } 82 | 83 | final case class ChoiceMatcher(first: Matcher, second: Matcher) 84 | extends Matcher { 85 | def apply(code: String, position: Int) = 86 | first(code, position).orElse(second(code, position)) 87 | } 88 | 89 | final case class SequentialMatcher(first: Matcher, second: Matcher) 90 | extends Matcher { 91 | 92 | def apply(code: String, position: Int) = 93 | first(code, position).flatMap(next => second(code, next)) 94 | } 95 | 96 | final case class PredicativeMatcher(sub: Matcher, positive: Boolean = true) 97 | extends Matcher { 98 | 99 | def apply(code: String, position: Int) = 100 | Some(position) 101 | .filter(_ => sub.apply(code, position).isDefined == positive) 102 | } 103 | 104 | object Matcher { 105 | def zeroOrMore(sub: Matcher) = RepetitionMatcher(sub, 0) 106 | 107 | def oneOrMore(sub: Matcher) = RepetitionMatcher(sub, 1) 108 | 109 | def optional(sub: Matcher) = RepetitionMatcher(sub, 0, 1) 110 | 111 | def repeat(sub: Matcher, times: Int) = 112 | RepetitionMatcher(sub, times, times) 113 | 114 | def anyOf(pattern: String) = CharSetMatcher(pattern.toSet) 115 | 116 | def anyExceptOf(pattern: String) = CharSetMatcher(pattern.toSet).sup 117 | 118 | def nothing() = CharSetMatcher(Set.empty) 119 | 120 | def any() = CharSetMatcher(Set.empty).sup 121 | 122 | def rangeOf(from: Char, to: Char) = CharRangeMatcher(from, to) 123 | 124 | def chunk(pattern: String) = StringMatcher(pattern) 125 | 126 | def test(sub: Matcher) = PredicativeMatcher(sub, positive = true) 127 | 128 | def testNot(sub: Matcher) = PredicativeMatcher(sub, positive = false) 129 | 130 | def sequence(steps: Matcher*): Matcher = steps.toList match { 131 | case first :: Nil => first 132 | case first :: second :: tail => 133 | (sequence _).apply(SequentialMatcher(first, second) :: tail) 134 | case _ => nothing() 135 | } 136 | 137 | def choice(cases: Matcher*): Matcher = cases.toList match { 138 | case first :: Nil => first 139 | case first :: second :: tail => 140 | (choice _).apply(ChoiceMatcher(first, second) :: tail) 141 | case _ => nothing() 142 | } 143 | } 144 | --------------------------------------------------------------------------------