diff --git a/.gitignore b/.gitignore index c9a052b..c6584a8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,10 @@ node_modules .idea +dist +src/**/*.d.ts.map +src/**/*.d.ts +src/**/*.js.map +src/**/*.js lib +.nyc_output +Develop diff --git a/package-lock.json b/package-lock.json index 6d204b6..ee7cefd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@drewsonne/maya-calculator-parser", - "version": "1.0.15", + "version": "1.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1033,11 +1033,12 @@ } }, "@drewsonne/maya-dates": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/@drewsonne/maya-dates/-/maya-dates-1.0.15.tgz", - "integrity": "sha512-88IncpJAeMrfkV5EiymUli0iTIKhH7CSyIe+q2dxKNPGp0U+YDOzEJWYRcw6jDVlWRcbv3Gbr99/d2sBpK2nNw==", + "version": "1.0.20", + "resolved": "https://registry.npmjs.org/@drewsonne/maya-dates/-/maya-dates-1.0.20.tgz", + "integrity": "sha512-dBR9GYuWUf1I1Hxwa0s62K9b8dc/2ODHYaqZsh+ZEIDPwsLkGFxWPMfb966CXv5heExCHSFHiWzEeRI3SIMwkQ==", "requires": { - "moonbeams": "^2.0.3" + "moonbeams": "^2.0.3", + "typescript": "^3.9.7" } }, "@istanbuljs/load-nyc-config": { @@ -3201,8 +3202,7 @@ "typescript": { "version": "3.9.7", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", - "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", - "dev": true + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==" }, "uglify-js": { "version": "3.10.1", diff --git a/package.json b/package.json index 0a6f280..7640fc0 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "typescript": "^3.9.7" }, "dependencies": { - "@drewsonne/maya-dates": "^1.0.15" + "@drewsonne/maya-dates": "^1.0.20" }, "scripts": { "test": "mocha -r ts-node/register 'src/**/*.spec.ts'", diff --git a/src/__tests__/primitive-parser.spec.ts b/src/__tests__/layer-0-parser.spec.ts similarity index 85% rename from src/__tests__/primitive-parser.spec.ts rename to src/__tests__/layer-0-parser.spec.ts index 0f443dd..77fb464 100644 --- a/src/__tests__/primitive-parser.spec.ts +++ b/src/__tests__/layer-0-parser.spec.ts @@ -1,17 +1,17 @@ import {expect} from 'chai' import 'mocha' -import PrimitiveParser from "../parsers/primitive"; -import {IToken} from "../tokens/base"; -import NumberToken from "../tokens/primitive/number-token"; -import PeriodToken from "../tokens/primitive/period-token"; +import Layer0Parser from "../parsers/layer-0-parser"; +import NumberToken from "../tokens/layer-0/number-token"; +import PeriodToken from "../tokens/layer-0/period-token"; import TokenCollection from "../tokens/collection"; -import LineEndToken from "../tokens/primitive/line-end-token"; -import SpaceToken from "../tokens/primitive/space-token"; -import WordToken from "../tokens/primitive/word-token"; -import WildcardToken from "../tokens/primitive/wildcard-token"; -import CommentStartToken from "../tokens/primitive/comment-start-token"; -import CommentToken from "../tokens/primitive/comment-token"; -import OperatorToken from "../tokens/primitive/operator-token"; +import LineEndToken from "../tokens/layer-0/line-end-token"; +import SpaceToken from "../tokens/layer-0/space-token"; +import WordToken from "../tokens/layer-0/word-token"; +import WildcardToken from "../tokens/layer-0/wildcard-token"; +import CommentStartToken from "../tokens/layer-0/comment-start-token"; +import CommentToken from "../tokens/layer-0/comment-token"; +import OperatorToken from "../tokens/layer-0/operator-token"; +import {IToken} from "../tokens/i-token"; const NT = (n: number) => new NumberToken(n) @@ -24,7 +24,7 @@ const ST = new SpaceToken() const WCT = new WildcardToken() const CST = new CommentStartToken() -describe('primitive parser', () => { +describe('layer-0 parser', () => { describe('should parse operators', () => { const looseOperations: [string, IToken[]][] = [ @@ -50,7 +50,7 @@ describe('primitive parser', () => { operations.forEach((pattern) => { const [rawString, expectedTokens]: [string, TokenCollection] = pattern it(`${rawString} -> ${expectedTokens}`, () => { - const tokenised = new PrimitiveParser().parse(rawString) + const tokenised = new Layer0Parser().parse(rawString) expect(tokenised.length).to.eq(expectedTokens.length) for (let i = 0; i < tokenised.length; i++) { expect( @@ -86,7 +86,7 @@ describe('primitive parser', () => { fullLines.forEach((pattern) => { const [rawString, expectedTokens]: [string, TokenCollection] = pattern it(`${rawString} -> ${expectedTokens}`, () => { - const tokenised = new PrimitiveParser().parse(rawString) + const tokenised = new Layer0Parser().parse(rawString) // expect(tokenised.length).to.eq(expectedTokens.length) for (let i = 0; i < tokenised.length; i++) { expect( @@ -110,7 +110,7 @@ describe('primitive parser', () => { fullDates.forEach((pattern) => { const [rawString, expectedTokens]: [string, TokenCollection] = pattern it(`${rawString} -> ${expectedTokens}`, () => { - const tokenised = new PrimitiveParser().parse(rawString) + const tokenised = new Layer0Parser().parse(rawString) expect(tokenised.length).to.eq(expectedTokens.length) for (let i = 0; i < tokenised.length; i++) { expect( @@ -141,7 +141,7 @@ describe('primitive parser', () => { crs.forEach((pattern) => { const [rawString, expectedTokens]: [string, TokenCollection] = pattern it(`${rawString} -> ${expectedTokens}`, () => { - const tokenised = new PrimitiveParser().parse(rawString) + const tokenised = new Layer0Parser().parse(rawString) expect(tokenised.length).to.eq(expectedTokens.length) for (let i = 0; i < tokenised.length; i++) { expect( @@ -169,14 +169,15 @@ describe('primitive parser', () => { NT(17), PT, ST, NT(2), PT, ST, NT(1), - ]] + ]], + ['*.*.*.7.13', [WCT, PT, WCT, PT, WCT, PT, NT(7), PT, NT(13)]] ] const parsed: [string, TokenCollection][] = dates.map((row: [string, IToken[]]) => [row[0], new TokenCollection(row[1])]) parsed.forEach((pattern) => { const [rawString, expectedTokens] = pattern it(`${rawString} -> ${expectedTokens}`, () => { - const tokenised = new PrimitiveParser().parse(rawString) + const tokenised = new Layer0Parser().parse(rawString) expect(tokenised.length).to.eq(expectedTokens.length) for (let i = 0; i < tokenised.length; i++) { expect( diff --git a/src/__tests__/layer-1-parser.spec.ts b/src/__tests__/layer-1-parser.spec.ts new file mode 100644 index 0000000..3ed9390 --- /dev/null +++ b/src/__tests__/layer-1-parser.spec.ts @@ -0,0 +1,203 @@ +import {expect} from 'chai' +import 'mocha' +import NumberToken from "../tokens/layer-0/number-token"; +import WordToken from "../tokens/layer-0/word-token"; +import CommentToken from "../tokens/layer-0/comment-token"; +import OperatorToken from "../tokens/layer-0/operator-token"; +import PeriodToken from "../tokens/layer-0/period-token"; +import LineEndToken from "../tokens/layer-0/line-end-token"; +import SpaceToken from "../tokens/layer-0/space-token"; +import WildcardToken from "../tokens/layer-0/wildcard-token"; +import CommentStartToken from "../tokens/layer-0/comment-start-token"; +import TokenCollection from "../tokens/collection"; +import CalendarRoundToken from "../tokens/layer-1/calendar-round-token"; +import LongCountToken from "../tokens/layer-1/long-count-token"; +import {IToken} from "../tokens/i-token"; +import Layer0Parser from "../parsers/layer-0-parser"; + +const NT = (n: number) => new NumberToken(n) +const WT = (w: string) => new WordToken(w) +const CT = (c: string) => new CommentToken(c) +const OT = (o: string) => new OperatorToken(o) +const PT = new PeriodToken() +const LET = new LineEndToken() +const ST = new SpaceToken() +const WCT = new WildcardToken() +const CST = new CommentStartToken() + + +describe('layer-1 parser', () => { + + describe('should parse operators', () => { + const looseOperations: [string, IToken[]][] = [ + [ + '4 Ajaw 8 Kumk\'u - 5 Kimi 4 Mol', + [ + CalendarRoundToken.parse([NT(4), WT('Ajaw'), NT(8), WT('Kumk\'u')]), + OT('-'), + CalendarRoundToken.parse([NT(5), WT('Kimi'), NT(4), WT('Mol')]), + + ] + ], + [ + '9.2.10.10.10 + 10.5.1', + [ + LongCountToken.parse([NT(9), PT, NT(2), PT, NT(10), PT, NT(10), PT, NT(10)]), + OT('+'), + LongCountToken.parse([NT(10), PT, NT(5), PT, NT(1)]) + ] + ] + ] + const operations: [string, TokenCollection][] = looseOperations.map((row: [string, IToken[]]) => { + return [row[0], new TokenCollection(row[1])]; + }) + operations.forEach((pattern) => { + const [rawText, expectedTokens]: [string, TokenCollection] = pattern + it(`${rawText} -> ${expectedTokens}`, () => { + + const layer1Tokens = new Layer0Parser().parse(rawText).processLayer1() + + expect(layer1Tokens.length).to.eq(expectedTokens.length) + for (let i = 0; i < layer1Tokens.length; i++) { + expect( + layer1Tokens.index(i).equal( + expectedTokens.index(i) + ), `Comparing ${i}` + ).to.be.true + } + }) + }); + }) + + describe('should parse calendar rounds', () => { + const looseCrs: [string, IToken[]][] = [ + [ + '4 Ajaw 8 Kumk\'u', + [CalendarRoundToken.parse([NT(4), WT('Ajaw'), NT(8), WT('Kumk\'u')])] + ], + [ + "4 Ajaw 8 Kumk\'u\n3 Kawak **", + [ + CalendarRoundToken.parse([NT(4), WT('Ajaw'), NT(8), WT('Kumk\'u')]), + CalendarRoundToken.parse([NT(3), WT('Kawak'), WCT, WCT]) + ] + ], + [ + '3 Kawak **', + [CalendarRoundToken.parse([NT(3), WT('Kawak'), WCT, WCT])] + ], + [ + '* Ajaw 8 Kumk\'u', + [CalendarRoundToken.parse([WCT, WT('Ajaw'), NT(8), WT('Kumk\'u')])] + ], + [ + '6 Manik\' 5 Mol', + [CalendarRoundToken.parse([NT(6), WT('Manik\''), NT(5), WT('Mol')])] + ], + [ + '6 Manik\' 5 Mol', + [CalendarRoundToken.parse([NT(6), WT('Manik\''), NT(5), WT('Mol')])] + ], + [ + '* * 12 Mol', + [CalendarRoundToken.parse([WCT, WCT, NT(12), WT('Mol')])] + ], + [ + '3 Kawak 7 Kumk\'u', + [CalendarRoundToken.parse([NT(3), WT('Kawak'), NT(7), WT('Kumk\'u')])] + ], + [ + '4 Ajaw 8 Kumk\'u', + [CalendarRoundToken.parse([NT(4), WT('Ajaw'), NT(8), WT('Kumk\'u')])] + ], + [ + '** 13 Xul', + [CalendarRoundToken.parse([WCT, WCT, NT(13), WT('Xul')])] + ], + [ + '6 Kimi * * ', + [CalendarRoundToken.parse([NT(6), WT('Kimi'), WCT, WCT])] + ], + [ + '5 Kimi 4 Mol', + [CalendarRoundToken.parse([NT(5), WT('Kimi'), NT(4), WT('Mol')])] + ], + [ + '* Chikchan 3 Mol #Hello, world', + [ + CalendarRoundToken.parse([WCT, WT('Chikchan'), NT(3), WT('Mol')]), + CT('Hello, world') + ] + ], + ] + const crs: [string, TokenCollection][] = looseCrs.map((row: [string, IToken[]]) => [row[0], new TokenCollection(row[1])]) + crs.forEach((pattern) => { + const [rawString, expectedTokens]: [string, TokenCollection] = pattern + it(`${rawString} -> ${expectedTokens}`, () => { + + const tokenised = new Layer0Parser().parse(rawString).processLayer1() + + expect(tokenised.length).to.eq(expectedTokens.length) + for (let i = 0; i < tokenised.length; i++) { + expect( + tokenised.index(i).equal( + expectedTokens.index(i) + ), `Comparing ${i}` + ).to.be.true + } + }) + }) + }) + + describe('should parse long counts', () => { + const looseLongCounts: [string, IToken[]][] = [ + // [ + // '7.13', + // [LongCountToken.parse([NT(7), PT, NT(13)])] + // ], + [ + '0.0.0.7.13', + [LongCountToken.parse([NT(0), PT, NT(0), PT, NT(0), PT, NT(7), PT, NT(13)])] + ], + [ + '9.16.19.17.19', + [LongCountToken.parse([NT(9), PT, NT(16), PT, NT(19), PT, NT(17), PT, NT(19)])] + ], + [ + "10.10\n9.9", + [LongCountToken.parse([NT(10), PT, NT(10)]), LongCountToken.parse([NT(9), PT, NT(9)])] + ], + [ + ' 8. 7. 6. 5. 4.17. 2. 1', + [LongCountToken.parse([ + NT(8), PT, + NT(7), PT, + NT(6), PT, + NT(5), PT, + NT(4), PT, + NT(17), PT, + NT(2), PT, + NT(1), + ])] + ] + ] + + const longcounts: [string, TokenCollection][] = looseLongCounts.map((row: [string, IToken[]]) => [row[0], new TokenCollection(row[1])]) + longcounts.forEach((pattern) => { + const [rawString, expectedTokens]: [string, TokenCollection] = pattern + it(`${rawString} -> ${expectedTokens}`, () => { + const tokenised = new Layer0Parser().parse(rawString).processLayer1() + expect(tokenised.length).to.eq(expectedTokens.length) + for (let i = 0; i < tokenised.length; i++) { + expect( + tokenised.index(i).equal( + expectedTokens.index(i) + ), `Comparing ${i}` + ).to.be.true + } + }) + }) + }) + +}) + diff --git a/src/__tests__/layer-1-tokens.spec.ts b/src/__tests__/layer-1-tokens.spec.ts new file mode 100644 index 0000000..31ca2fa --- /dev/null +++ b/src/__tests__/layer-1-tokens.spec.ts @@ -0,0 +1,61 @@ +import 'mocha' +import {expect} from 'chai' +import CalendarRoundToken from "../tokens/layer-1/calendar-round-token"; +import NumberToken from "../tokens/layer-0/number-token"; +import WordToken from "../tokens/layer-0/word-token"; +import WildcardToken from "../tokens/layer-0/wildcard-token"; +import LongCountToken from "../tokens/layer-1/long-count-token"; +import PeriodToken from "../tokens/layer-0/period-token"; +import CalendarRoundFactory from "@drewsonne/maya-dates/lib/factory/calendar-round"; +import {CalendarRound, origin} from "@drewsonne/maya-dates/lib/cr/calendar-round"; +import LongCount from "@drewsonne/maya-dates/lib/lc/long-count"; +import {Wildcard} from "@drewsonne/maya-dates/lib/wildcard"; + +const NT = (n: number) => new NumberToken(n) +const WT = (w: string) => new WordToken(w) +const WCT = new WildcardToken() +const PT = new PeriodToken() + +describe('calendar-round token', () => { + describe('should convert to calendar-round object', () => { + const crs: [CalendarRoundToken, CalendarRound][] = [ + [ + CalendarRoundToken.parse([NT(4), WT('Ajaw'), NT(8), WT('Kumk\'u')]), + origin + ], + [ + CalendarRoundToken.parse([WCT, WT('Ajaw'), NT(8), WT('Kumk\'u')]), + new CalendarRoundFactory().parse('*Ajaw 8 Kumk\'u') + ] + ] + crs.forEach((args) => { + const [crt, cr] = args + it(`${crt}.calendarRound -> ${cr}`, () => { + expect(crt.calendarRound).to.eq(cr) + }) + }) + }) +}) + +describe('long-count token', () => { + describe('should convert to long-count object', () => { + const lcs: [LongCountToken, LongCount][] = [ + [ + LongCountToken.parse([NT(9), PT, NT(2), PT, NT(10), PT, NT(10), PT, NT(10)]), + new LongCount(10, 10, 10, 2, 9) + ], + [ + LongCountToken.parse([WCT, PT, NT(2), PT, NT(10), PT, NT(10), PT, NT(10)]), + new LongCount(10, 10, 10, 2, new Wildcard()) + ] + ] + lcs.forEach((args) => { + const [lct, lc] = args + it(`${lct}.longCount --> ${lc}`, () => { + const parsedLc = lct.longCount + const result = parsedLc.equal(lc) + expect(result).to.be.true + }) + }) + }) +}) diff --git a/src/__tests__/layer-2-parser.spec.ts b/src/__tests__/layer-2-parser.spec.ts new file mode 100644 index 0000000..9cc6e4a --- /dev/null +++ b/src/__tests__/layer-2-parser.spec.ts @@ -0,0 +1,221 @@ +import {expect} from 'chai' +import 'mocha' +import NumberToken from "../tokens/layer-0/number-token"; +import WordToken from "../tokens/layer-0/word-token"; +import CommentToken from "../tokens/layer-0/comment-token"; +import OperatorToken from "../tokens/layer-0/operator-token"; +import PeriodToken from "../tokens/layer-0/period-token"; +import LineEndToken from "../tokens/layer-0/line-end-token"; +import SpaceToken from "../tokens/layer-0/space-token"; +import WildcardToken from "../tokens/layer-0/wildcard-token"; +import CommentStartToken from "../tokens/layer-0/comment-start-token"; +import TokenCollection from "../tokens/collection"; +import CalendarRoundToken from "../tokens/layer-1/calendar-round-token"; +import LongCountToken from "../tokens/layer-1/long-count-token"; +import {IToken} from "../tokens/i-token"; +import Layer0Parser from "../parsers/layer-0-parser"; +import LongCountWildcardOperationToken from "../tokens/layer-2/long-count-wildcard-operation-token"; +import CalendarRoundWildcardOperationToken from "../tokens/layer-2/calendar-round-wildcard-operation-token"; +import CalendarRoundOperationToken from "../tokens/layer-2/calendar-round-operation-token"; +import LongCountOperationToken from "../tokens/layer-2/long-count-operation-token"; + +const NT = (n: number) => new NumberToken(n) +const WT = (w: string) => new WordToken(w) +const CT = (c: string) => new CommentToken(c) +const OT = (o: string) => new OperatorToken(o) +const PT = new PeriodToken() +const WCT = new WildcardToken() + + +describe('layer-2 parser', () => { + + describe('should parse operators', () => { + const looseOperations: [string, IToken[]][] = [ + [ + '4 Ajaw 8 Kumk\'u - 5 Kimi 4 Mol', + [ + CalendarRoundOperationToken.parse( + CalendarRoundToken.parse([NT(4), WT('Ajaw'), NT(8), WT('Kumk\'u')]), + OT('-'), + CalendarRoundToken.parse([NT(5), WT('Kimi'), NT(4), WT('Mol')]), + ) + ] + ], + [ + '9.2.10.10.10 + 10.5.1', + [ + LongCountOperationToken.parse( + LongCountToken.parse([NT(9), PT, NT(2), PT, NT(10), PT, NT(10), PT, NT(10)]), + OT('+'), + LongCountToken.parse([NT(10), PT, NT(5), PT, NT(1)]) + ) + ] + ], + [ + '1Ok * *', + [ + CalendarRoundWildcardOperationToken.parse( + CalendarRoundToken.parse([NT(1), WT('Ok'), WCT, WCT]), + ) + ] + ], + [ + '9.*.10.10.10', + [ + LongCountWildcardOperationToken.parse( + LongCountToken.parse([NT(9), PT, WCT, PT, NT(10), PT, NT(10), PT, NT(10)]), + ) + ] + ] + ] + const operations: [string, TokenCollection][] = looseOperations.map((row: [string, IToken[]]) => { + return [row[0], new TokenCollection(row[1])]; + }) + operations.forEach((pattern) => { + const [rawText, expectedTokens]: [string, TokenCollection] = pattern + it(`${rawText} -> ${expectedTokens}`, () => { + + const layer1Tokens = new Layer0Parser().parse(rawText).processLayer1().processLayer2() + + expect(layer1Tokens.length).to.eq(expectedTokens.length) + for (let i = 0; i < layer1Tokens.length; i++) { + expect( + layer1Tokens.index(i).equal( + expectedTokens.index(i) + ), `Comparing ${i}` + ).to.be.true + } + }) + }); + }) + + describe('should parse calendar rounds', () => { + const looseCrs: [string, IToken[]][] = [ + [ + '4 Ajaw 8 Kumk\'u', + [CalendarRoundToken.parse([NT(4), WT('Ajaw'), NT(8), WT('Kumk\'u')])] + ], + [ + "4 Ajaw 8 Kumk\'u\n3 Kawak **", + [ + CalendarRoundToken.parse([NT(4), WT('Ajaw'), NT(8), WT('Kumk\'u')]), + CalendarRoundWildcardOperationToken.parse(CalendarRoundToken.parse([NT(3), WT('Kawak'), WCT, WCT])) + ] + ], + [ + '3 Kawak **', + [CalendarRoundWildcardOperationToken.parse(CalendarRoundToken.parse([NT(3), WT('Kawak'), WCT, WCT]))] + ], + [ + '* Ajaw 8 Kumk\'u', + [CalendarRoundWildcardOperationToken.parse(CalendarRoundToken.parse([WCT, WT('Ajaw'), NT(8), WT('Kumk\'u')]))] + ], + [ + '6 Manik\' 5 Mol', + [CalendarRoundToken.parse([NT(6), WT('Manik\''), NT(5), WT('Mol')])] + ], + [ + '* * 12 Mol', + [CalendarRoundWildcardOperationToken.parse(CalendarRoundToken.parse([WCT, WCT, NT(12), WT('Mol')]))] + ], + [ + '3 Kawak 7 Kumk\'u', + [CalendarRoundToken.parse([NT(3), WT('Kawak'), NT(7), WT('Kumk\'u')])] + ], + [ + '4 Ajaw 8 Kumk\'u', + [CalendarRoundToken.parse([NT(4), WT('Ajaw'), NT(8), WT('Kumk\'u')])] + ], + [ + '** 13 Xul', + [CalendarRoundWildcardOperationToken.parse(CalendarRoundToken.parse([WCT, WCT, NT(13), WT('Xul')]))] + ], + [ + '6 Kimi * * ', + [CalendarRoundWildcardOperationToken.parse(CalendarRoundToken.parse([NT(6), WT('Kimi'), WCT, WCT]))] + ], + [ + '5 Kimi 4 Mol', + [CalendarRoundToken.parse([NT(5), WT('Kimi'), NT(4), WT('Mol')])] + ], + [ + '* Chikchan 3 Mol #Hello, world', + [ + CalendarRoundWildcardOperationToken.parse( + CalendarRoundToken.parse([WCT, WT('Chikchan'), NT(3), WT('Mol')]) + ), + CT('Hello, world') + ] + ], + ] + const crs: [string, TokenCollection][] = looseCrs.map((row: [string, IToken[]]) => [row[0], new TokenCollection(row[1])]) + crs.forEach((pattern) => { + const [rawString, expectedTokens]: [string, TokenCollection] = pattern + it(`${rawString} -> ${expectedTokens}`, () => { + + const tokenised = new Layer0Parser().parse(rawString).processLayer1().processLayer2() + + expect(tokenised.length).to.eq(expectedTokens.length) + for (let i = 0; i < tokenised.length; i++) { + expect( + tokenised.index(i).equal( + expectedTokens.index(i) + ), `Comparing ${i}` + ).to.be.true + } + }) + }) + }) + + describe('should parse long counts', () => { + const looseLongCounts: [string, IToken[]][] = [ + [ + '7.13', + [LongCountToken.parse([NT(7), PT, NT(13)])] + ], + [ + '0.0.0.7.13', + [LongCountToken.parse([NT(0), PT, NT(0), PT, NT(0), PT, NT(7), PT, NT(13)])] + ], + [ + '9.16.19.17.19', + [LongCountToken.parse([NT(9), PT, NT(16), PT, NT(19), PT, NT(17), PT, NT(19)])] + ], + [ + "10.10\n9.9", + [LongCountToken.parse([NT(10), PT, NT(10)]), LongCountToken.parse([NT(9), PT, NT(9)])] + ], + [ + ' 8. 7. 6. 5. 4.17. 2. 1', + [LongCountToken.parse([ + NT(8), PT, + NT(7), PT, + NT(6), PT, + NT(5), PT, + NT(4), PT, + NT(17), PT, + NT(2), PT, + NT(1), + ])] + ] + ] + + const longcounts: [string, TokenCollection][] = looseLongCounts.map((row: [string, IToken[]]) => [row[0], new TokenCollection(row[1])]) + longcounts.forEach((pattern) => { + const [rawString, expectedTokens]: [string, TokenCollection] = pattern + it(`${rawString} -> ${expectedTokens}`, () => { + const tokenised = new Layer0Parser().parse(rawString).processLayer1() + expect(tokenised.length).to.eq(expectedTokens.length) + for (let i = 0; i < tokenised.length; i++) { + expect( + tokenised.index(i).equal( + expectedTokens.index(i) + ), `Comparing ${i}` + ).to.be.true + } + }) + }) + }) + +}) + diff --git a/src/__tests__/layer-3-parser.spec.ts b/src/__tests__/layer-3-parser.spec.ts new file mode 100644 index 0000000..73cfc22 --- /dev/null +++ b/src/__tests__/layer-3-parser.spec.ts @@ -0,0 +1,91 @@ +import 'mocha' +import {expect} from 'chai' +import {IToken} from "../tokens/i-token"; +import TokenCollection from "../tokens/collection"; +import Layer0Parser from "../parsers/layer-0-parser"; +import FullDateWildcardOperationToken from "../tokens/layer-3/full-date-wildcard-operation-token"; +import LongCountWildcardOperationToken from "../tokens/layer-2/long-count-wildcard-operation-token"; +import LongCountToken from "../tokens/layer-1/long-count-token"; +import CalendarRoundWildcardOperationToken from "../tokens/layer-2/calendar-round-wildcard-operation-token"; +import CalendarRoundToken from "../tokens/layer-1/calendar-round-token"; +import NumberToken from "../tokens/layer-0/number-token"; +import WordToken from "../tokens/layer-0/word-token"; +import PeriodToken from "../tokens/layer-0/period-token"; +import WildcardToken from "../tokens/layer-0/wildcard-token"; +import FullDateToken from "../tokens/layer-3/full-date-token"; + +const NT = (n: number) => new NumberToken(n) +const WT = (w: string) => new WordToken(w) +const PT = new PeriodToken() +const WCT = new WildcardToken() + + +describe('layer-3 parser', () => { + describe('should parse operators', () => { + const looseFullDates: [string, IToken[]][] = [ + [ + '1Ok * * 9.*.10.10.10', + [ + FullDateWildcardOperationToken.parse( + CalendarRoundWildcardOperationToken.parse( + CalendarRoundToken.parse([NT(1), WT('Ok'), WCT, WCT]), + ), + LongCountWildcardOperationToken.parse( + LongCountToken.parse([NT(9), PT, WCT, PT, NT(10), PT, NT(10), PT, NT(10)]), + ) + ) + ] + ], + [ + '1Ok * * 9.4.10.10.10', + [ + FullDateWildcardOperationToken.parse( + CalendarRoundWildcardOperationToken.parse( + CalendarRoundToken.parse([NT(1), WT('Ok'), WCT, WCT]), + ), + LongCountToken.parse([NT(9), PT, NT(4), PT, NT(10), PT, NT(10), PT, NT(10)]), + ) + ] + ], + [ + '1Ok 18 Kumk\'u 9.*.10.10.10', + [ + FullDateWildcardOperationToken.parse( + CalendarRoundToken.parse([NT(1), WT('Ok'), NT(18), WT('Kumk\'u')]), + LongCountWildcardOperationToken.parse( + LongCountToken.parse([NT(9), PT, WCT, PT, NT(10), PT, NT(10), PT, NT(10)]), + ) + ) + ] + ], + [ + '7 Chikchan 18 Sip 9.10. 2. 5. 5', + [ + FullDateToken.parse( + CalendarRoundToken.parse([NT(7), WT('Chikchan'), NT(18), WT('Sip')]), + LongCountToken.parse([NT(9), PT, NT(10), PT, NT(2), PT, NT(5), PT, NT(5)]) + ) + ] + ] + ] + const operations: [string, TokenCollection][] = looseFullDates.map((row: [string, IToken[]]) => { + return [row[0], new TokenCollection(row[1])]; + }); + operations.forEach((pattern) => { + const [rawText, expectedTokens]: [string, TokenCollection] = pattern + it(`${rawText} -> ${expectedTokens}`, () => { + + const layer3Tokens = new Layer0Parser().parse(rawText).processLayer1().processLayer2().processLayer3() + + expect(layer3Tokens.length).to.eq(expectedTokens.length) + for (let i = 0; i < layer3Tokens.length; i++) { + expect( + layer3Tokens.index(i).equal( + expectedTokens.index(i) + ), `Comparing ${i}` + ).to.be.true + } + }) + }); + }) +}) diff --git a/src/__tests__/primitive-types.spec.ts b/src/__tests__/primitive-types.spec.ts index 8f51ed4..ab7334d 100644 --- a/src/__tests__/primitive-types.spec.ts +++ b/src/__tests__/primitive-types.spec.ts @@ -1,16 +1,17 @@ import {expect} from 'chai' import 'mocha' -import NumberToken from "../tokens/primitive/number-token"; -import {IToken, Token} from "../tokens/base"; -import WildcardToken from "../tokens/primitive/wildcard-token"; -import WordToken from "../tokens/primitive/word-token"; -import PeriodToken from "../tokens/primitive/period-token"; -import SpaceToken from "../tokens/primitive/space-token"; -import CommentStartToken from "../tokens/primitive/comment-start-token"; -import LineEndToken from "../tokens/primitive/line-end-token"; +import NumberToken from "../tokens/layer-0/number-token"; +import {Token} from "../tokens/base"; +import WildcardToken from "../tokens/layer-0/wildcard-token"; +import WordToken from "../tokens/layer-0/word-token"; +import PeriodToken from "../tokens/layer-0/period-token"; +import SpaceToken from "../tokens/layer-0/space-token"; +import CommentStartToken from "../tokens/layer-0/comment-start-token"; +import LineEndToken from "../tokens/layer-0/line-end-token"; +import {IToken} from "../tokens/i-token"; -describe('primitive number parser', () => { +describe('layer-0 number parser', () => { describe('should parse numbers', () => { const dates: [string, NumberToken][] = [ diff --git a/src/parsers/primitive.ts b/src/parsers/layer-0-parser.ts similarity index 50% rename from src/parsers/primitive.ts rename to src/parsers/layer-0-parser.ts index ba756ec..7e1d6fb 100644 --- a/src/parsers/primitive.ts +++ b/src/parsers/layer-0-parser.ts @@ -1,17 +1,17 @@ -import {IToken} from "../tokens/base"; -import PrimitiveTest from "./simple-test"; -import NumberToken from "../tokens/primitive/number-token"; -import PeriodToken from "../tokens/primitive/period-token"; -import LineEndToken from "../tokens/primitive/line-end-token"; -import SpaceToken from "../tokens/primitive/space-token"; -import WordToken from "../tokens/primitive/word-token"; -import WildcardToken from "../tokens/primitive/wildcard-token"; +import Layer0Test from "./layer-0-test"; +import NumberToken from "../tokens/layer-0/number-token"; +import PeriodToken from "../tokens/layer-0/period-token"; +import LineEndToken from "../tokens/layer-0/line-end-token"; +import SpaceToken from "../tokens/layer-0/space-token"; +import WordToken from "../tokens/layer-0/word-token"; +import WildcardToken from "../tokens/layer-0/wildcard-token"; import TokenCollection from "../tokens/collection"; -import CommentStartToken from "../tokens/primitive/comment-start-token"; -import CommentToken from "../tokens/primitive/comment-token"; -import OperatorToken from "../tokens/primitive/operator-token"; +import CommentStartToken from "../tokens/layer-0/comment-start-token"; +import CommentToken from "../tokens/layer-0/comment-token"; +import OperatorToken from "../tokens/layer-0/operator-token"; +import {IToken} from "../tokens/i-token"; -enum PrimitiveParserStateValue { +enum Layer0ParserStateValue { WAITING, PARSING_NUMBER, PARSING_WORD, @@ -19,59 +19,59 @@ enum PrimitiveParserStateValue { PARSING_COMMENT_BODY, } -class PrimitiveParserState { - private state: PrimitiveParserStateValue +class Layer0ParserState { + private state: Layer0ParserStateValue constructor() { - this.state = PrimitiveParserStateValue.WAITING + this.state = Layer0ParserStateValue.WAITING + } + + reset() { + this.state = Layer0ParserStateValue.WAITING } isWaiting(): boolean { - return this.state === PrimitiveParserStateValue.WAITING + return this.state === Layer0ParserStateValue.WAITING } isParsingNumber(): boolean { - return this.state === PrimitiveParserStateValue.PARSING_NUMBER + return this.state === Layer0ParserStateValue.PARSING_NUMBER } isParsingWord(): boolean { - return this.state === PrimitiveParserStateValue.PARSING_WORD + return this.state === Layer0ParserStateValue.PARSING_WORD } isParsingCommentStart(): boolean { - return this.state === PrimitiveParserStateValue.PARSING_COMMENT_START + return this.state === Layer0ParserStateValue.PARSING_COMMENT_START } isParsingCommentBody(): boolean { - return this.state === PrimitiveParserStateValue.PARSING_COMMENT_BODY - } - - reset() { - this.state = PrimitiveParserStateValue.WAITING + return this.state === Layer0ParserStateValue.PARSING_COMMENT_BODY } startParsingNumber() { - this.state = PrimitiveParserStateValue.PARSING_NUMBER + this.state = Layer0ParserStateValue.PARSING_NUMBER } startParsingWord() { - this.state = PrimitiveParserStateValue.PARSING_WORD + this.state = Layer0ParserStateValue.PARSING_WORD } startParsingComment() { - this.state = PrimitiveParserStateValue.PARSING_COMMENT_START + this.state = Layer0ParserStateValue.PARSING_COMMENT_START } startParsingCommentBody() { - this.state = PrimitiveParserStateValue.PARSING_COMMENT_BODY + this.state = Layer0ParserStateValue.PARSING_COMMENT_BODY } } -export default class PrimitiveParser { - private state: PrimitiveParserState +export default class Layer0Parser { + private state: Layer0ParserState constructor() { - this.state = new PrimitiveParserState() + this.state = new Layer0ParserState() } parse(rawText: string): TokenCollection { @@ -80,90 +80,95 @@ export default class PrimitiveParser { for (let cursor = 0; cursor < rawText.length; cursor += 1) { const cell = rawText[cursor]; if (this.state.isWaiting()) { - if (PrimitiveTest.isLetter(cell)) { + if (Layer0Test.isLetter(cell)) { this.state.startParsingWord() cache.push(cell) - } else if (PrimitiveTest.isNumber(cell)) { + } else if (Layer0Test.isNumber(cell)) { this.state.startParsingNumber() cache.push(cell) - } else if (PrimitiveTest.isPeriod(cell)) { + } else if (Layer0Test.isPeriod(cell)) { tokens.push(PeriodToken.parse(cell)) - } else if (PrimitiveTest.isSpace(cell)) { + } else if (Layer0Test.isSpace(cell)) { tokens.push(SpaceToken.parse(cell)) - } else if (PrimitiveTest.isWildcard(cell)) { + } else if (Layer0Test.isWildcard(cell)) { tokens.push(WildcardToken.parse(cell)) - } else if (PrimitiveTest.isCommentStart(cell)) { + } else if (Layer0Test.isCommentStart(cell)) { tokens.push(CommentStartToken.parse(cell)) this.state.startParsingComment() - } else if (PrimitiveTest.isOperator(cell)) { + } else if (Layer0Test.isOperator(cell)) { tokens.push(OperatorToken.parse(cell)) this.state.reset() } else { throw new Error('Primitive parser in unexpected state') } } else if (this.state.isParsingNumber()) { - if (PrimitiveTest.isLetter(cell)) { + if (Layer0Test.isLetter(cell)) { tokens.push(NumberToken.parse(cache.join(''))) cache = [cell] this.state.startParsingWord() - } else if (PrimitiveTest.isNumber(cell)) { + } else if (Layer0Test.isNumber(cell)) { cache.push(cell) - } else if (PrimitiveTest.isPeriod(cell)) { + } else if (Layer0Test.isPeriod(cell)) { tokens.push(NumberToken.parse(cache.join(''))) tokens.push(PeriodToken.parse(cell)) cache = [] this.state.reset() - } else if (PrimitiveTest.isCarriageReturn(cell)) { + } else if (Layer0Test.isCarriageReturn(cell)) { tokens.push(NumberToken.parse(cache.join(''))) tokens.push(LineEndToken.parse(cell)) cache = [] this.state.reset() - } else if (PrimitiveTest.isSpace(cell)) { + } else if (Layer0Test.isSpace(cell)) { tokens.push(NumberToken.parse(cache.join(''))) tokens.push(SpaceToken.parse(cell)) cache = [] this.state.reset() } else { - throw new Error('Primitive parser in unexpected state') + throw new Error('Layer-0 parser in unexpected state') } } else if (this.state.isParsingWord()) { - if (PrimitiveTest.isLetter(cell)) { + if (Layer0Test.isLetter(cell)) { cache.push(cell) - } else if (PrimitiveTest.isNumber(cell)) { - throw new Error('Primitive parser in unexpected state') - } else if (PrimitiveTest.isPeriod(cell)) { - throw new Error('Primitive parser in unexpected state') - } else if (PrimitiveTest.isSpace(cell)) { + } else if (Layer0Test.isNumber(cell)) { + throw new Error('Layer-0 parser in unexpected state') + } else if (Layer0Test.isPeriod(cell)) { + throw new Error('Layer-0 parser in unexpected state') + } else if (Layer0Test.isSpace(cell)) { tokens.push(WordToken.parse(cache.join(''))) tokens.push(SpaceToken.parse(cell)) cache = [] this.state.reset() + } else if (Layer0Test.isCarriageReturn(cell)) { + tokens.push(WordToken.parse(cache.join(''))) + tokens.push(LineEndToken.parse(cell)) + cache = [] + this.state.reset() } else { - throw new Error('Primitive parser in unexpected state') + throw new Error('Layer-0 parser in unexpected state') } } else if (this.state.isParsingCommentStart()) { - if (PrimitiveTest.isSpace(cell)) { + if (Layer0Test.isSpace(cell)) { tokens.push(SpaceToken.parse(cell)) this.state.startParsingCommentBody() - } else if (PrimitiveTest.isCommentLetter(cell)) { + } else if (Layer0Test.isCommentLetter(cell)) { cache = [cell] this.state.startParsingCommentBody() } else { - throw new Error('Primitive parser in unexpected state') + throw new Error('Layer-0 parser in unexpected state') } } else if (this.state.isParsingCommentBody()) { - if (PrimitiveTest.isCommentLetter(cell)) { + if (Layer0Test.isCommentLetter(cell)) { cache.push(cell) - } else if (PrimitiveTest.isCarriageReturn(cell)) { + } else if (Layer0Test.isCarriageReturn(cell)) { tokens.push(CommentToken.parse(cache.join(''))) tokens.push(LineEndToken.parse(cell)) cache = [] this.state.reset() } else { - throw new Error('Primitive parser in unexpected state') + throw new Error('Layer-0 parser in unexpected state') } } else { - throw new Error('Primitive parser in unexpected state') + throw new Error('Layer-0 parser in unexpected state') } } if (cache.length > 0) { @@ -174,7 +179,7 @@ export default class PrimitiveParser { } else if (this.state.isParsingCommentBody()) { tokens.push(CommentToken.parse(cache.join(''))) } else { - throw new Error('Primitive parser in unexpected state') + throw new Error('Layer-0 parser in unexpected state') } } this.state.reset() diff --git a/src/parsers/simple-test.ts b/src/parsers/layer-0-test.ts similarity index 95% rename from src/parsers/simple-test.ts rename to src/parsers/layer-0-test.ts index 44e1381..9ba2187 100644 --- a/src/parsers/simple-test.ts +++ b/src/parsers/layer-0-test.ts @@ -1,4 +1,4 @@ -export default class PrimitiveTest { +export default class Layer0Test { static isLetter(raw: string): boolean { return this.regexTest(raw, /^['a-zA-Z]$/) } diff --git a/src/parsers/layer-1-parser.ts b/src/parsers/layer-1-parser.ts new file mode 100644 index 0000000..f380ce2 --- /dev/null +++ b/src/parsers/layer-1-parser.ts @@ -0,0 +1,166 @@ +import TokenCollection from "../tokens/collection"; +import NumberToken from "../tokens/layer-0/number-token"; +import SpaceToken from "../tokens/layer-0/space-token"; +import WordToken from "../tokens/layer-0/word-token"; +import CalendarRoundToken from "../tokens/layer-1/calendar-round-token"; +import WildcardToken from "../tokens/layer-0/wildcard-token"; +import {Comment} from "typedoc/dist/lib/models"; +import CommentToken from "../tokens/layer-0/comment-token"; +import PeriodToken from "../tokens/layer-0/period-token"; +import LongCountToken from "../tokens/layer-1/long-count-token"; +import LineEndToken from "../tokens/layer-0/line-end-token"; +import {IToken} from "../tokens/i-token"; +import OperatorToken from "../tokens/layer-0/operator-token"; +import CommentStartToken from "../tokens/layer-0/comment-start-token"; +import {isFullCR, isPartialCR} from "./layer-1-test"; + +enum Layer1ParserStateValue { + WAITING, + PARSING_DATE, + PARSING_CALENDAR_ROUND, + PARSING_LONG_COUNT, +} + +class Layer1ParserState { + private state: Layer1ParserStateValue + + constructor() { + this.state = Layer1ParserStateValue.WAITING + } + + reset() { + this.state = Layer1ParserStateValue.WAITING + } + + isWaiting(): boolean { + return this.state === Layer1ParserStateValue.WAITING + } + + isParsingDate(): boolean { + return this.state === Layer1ParserStateValue.PARSING_DATE + } + + isParsingCalendarRound(): boolean { + return this.state === Layer1ParserStateValue.PARSING_CALENDAR_ROUND + } + + isParsingLongCount(): boolean { + return this.state === Layer1ParserStateValue.PARSING_LONG_COUNT + } + + startParsingDate() { + this.state = Layer1ParserStateValue.PARSING_DATE + } + + startParsingCalendarRound() { + this.state = Layer1ParserStateValue.PARSING_CALENDAR_ROUND + } + + startParsingLongCount() { + this.state = Layer1ParserStateValue.PARSING_LONG_COUNT + } +} + + +export default class Layer1Parser { + private state: Layer1ParserState + + constructor() { + this.state = new Layer1ParserState() + } + + parse(layer0Tokens: TokenCollection) { + let tokens: IToken[] = [] + let cache: IToken[] = [] + for (let cursor = 0; cursor < layer0Tokens.length; cursor += 1) { + const layer0Token = layer0Tokens.index(cursor); + if (this.state.isWaiting()) { + if ((layer0Token instanceof NumberToken) || (layer0Token instanceof WildcardToken)) { + cache.push(layer0Token) + this.state.startParsingDate() + } else if ( + (layer0Token instanceof CommentToken) || + (layer0Token instanceof OperatorToken) + ) { + tokens.push(layer0Token) + } else if ((layer0Token instanceof SpaceToken) || (layer0Token instanceof CommentStartToken)) { + continue + } else if (layer0Token instanceof CommentToken) { + tokens.push(layer0Token) + } else { + throw new Error('Layer-1 parser in unexpected state') + } + } else if (this.state.isParsingDate()) { + if (layer0Token instanceof SpaceToken) { + cache.push(layer0Token) + } else if (layer0Token instanceof WordToken) { + cache.push(layer0Token) + this.state.startParsingCalendarRound() + } else if (layer0Token instanceof WildcardToken) { + cache.push(layer0Token) + } else if (layer0Token instanceof NumberToken) { + cache.push(layer0Token) + } else if (layer0Token instanceof PeriodToken) { + cache.push(layer0Token) + this.state.startParsingLongCount() + } else { + throw new Error('Layer-1 parser in unexpected state') + } + } else if (this.state.isParsingCalendarRound()) { + if (isFullCR(cache)) { + tokens.push( + CalendarRoundToken.parse(cache) + ) + cache = [] + this.state.reset() + } else if (isPartialCR(cache)) { + cache.push(layer0Token) + } else { + throw new Error('Layer-1 parser in unexpected state') + } + } else if (this.state.isParsingLongCount()) { + if ( + (layer0Token instanceof NumberToken) || + (layer0Token instanceof PeriodToken) || + (layer0Token instanceof WildcardToken) + ) { + cache.push(layer0Token) + } else if ((layer0Token instanceof LineEndToken)) { + tokens.push(LongCountToken.parse(cache)) + cache = [] + } else if (layer0Token instanceof SpaceToken) { + continue + } else if (layer0Token instanceof OperatorToken) { + tokens.push(LongCountToken.parse(cache)) + tokens.push(layer0Token) + cache = [] + this.state.reset() + } else { + throw new Error('Layer-1 parser in unexpected state') + } + } else { + throw new Error('Layer-1 parser in unexpected state') + } + } + if (cache.length > 0) { + if (this.state.isParsingCalendarRound()) { + if (isFullCR(cache)) { + tokens.push( + CalendarRoundToken.parse(cache) + ) + cache = [] + } else { + throw new Error('Layer-1 parser in unexpected state') + } + } else if (this.state.isParsingLongCount()) { + tokens.push( + LongCountToken.parse(cache) + ) + cache = [] + } else { + throw new Error('Layer-1 parser in unexpected state') + } + } + return new TokenCollection(tokens); + } +} diff --git a/src/parsers/layer-1-test.ts b/src/parsers/layer-1-test.ts new file mode 100644 index 0000000..9d195d4 --- /dev/null +++ b/src/parsers/layer-1-test.ts @@ -0,0 +1,61 @@ +import SpaceToken from "../tokens/layer-0/space-token"; +import NumberToken from "../tokens/layer-0/number-token"; +import WordToken from "../tokens/layer-0/word-token"; +import WildcardToken from "../tokens/layer-0/wildcard-token"; +import {IToken} from "../tokens/i-token"; +import PeriodToken from "../tokens/layer-0/period-token"; +import CommentToken from "../tokens/layer-0/comment-token"; + +export function isWordToken(t: IToken): t is WordToken { + return t instanceof WordToken +} + +export function isNumberToken(t: IToken): t is NumberToken { + return t instanceof NumberToken +} + +export function isWildcardToken(t: IToken): t is WildcardToken { + return t instanceof WildcardToken +} + +export function isPeriodToken(t: IToken): t is PeriodToken { + return t instanceof PeriodToken +} + +export function isCommentToken(t: IToken): t is CommentToken { + return t instanceof CommentToken +} + +export function isCRCoeff(c: IToken): boolean { + return isNumberToken(c) || isWildcardToken(c) +} + +export function isCRUnit(c: IToken): boolean { + return isWordToken(c) || isWildcardToken(c) +} + +export function isPartialCR(raw: IToken[]): boolean { + const tokens = raw.filter((t) => !(t instanceof SpaceToken)) + if (tokens.length === 0) { + return true + } else if (tokens.length === 1) { + return isCRCoeff(tokens[0]) + } else if (tokens.length === 2) { + return isCRCoeff(tokens[0]) && isCRUnit(tokens[1]) + } else if (tokens.length === 3) { + return isCRCoeff(tokens[0]) && isCRUnit(tokens[1]) && isCRCoeff(tokens[2]) + } else if (tokens.length === 4) { + return isFullCR(tokens) + } + return false +} + +export function isFullCR(raw: IToken[]): boolean { + const tokens = raw.filter((t) => !(t instanceof SpaceToken)) + if (tokens.length === 4) { + return isCRCoeff(tokens[0]) && isCRUnit(tokens[1]) && isCRCoeff(tokens[2]) && isCRUnit(tokens[3]) + } + return false +} + + diff --git a/src/parsers/layer-2-parser.ts b/src/parsers/layer-2-parser.ts new file mode 100644 index 0000000..6a06adf --- /dev/null +++ b/src/parsers/layer-2-parser.ts @@ -0,0 +1,100 @@ +import TokenCollection from "../tokens/collection"; +import {IToken} from "../tokens/i-token"; +import { + isCalendarRoundOperation, isCalendarRoundToken, + isFullOperation, isLongCountOperation, + isLongCountToken, isOperatorToken, + isPartialOperation +} from "./layer-2-test"; +import CalendarRoundOperationToken from "../tokens/layer-2/calendar-round-operation-token"; +import LongCountOperationToken from "../tokens/layer-2/long-count-operation-token"; +import CalendarRoundWildcardOperationToken from "../tokens/layer-2/calendar-round-wildcard-operation-token"; +import LongCountWildcardOperationToken from "../tokens/layer-2/long-count-wildcard-operation-token"; +import {isCommentToken} from "./layer-1-test"; + + +export default class Layer2Parser { + + parse(layer1Tokens: TokenCollection): TokenCollection { + let tokens: IToken[] = [] + let cache: IToken[] = [] + for (let cursor = 0; cursor < layer1Tokens.length; cursor += 1) { + const layer1Token = layer1Tokens.index(cursor); + if (isLongCountToken(layer1Token)) { + if (layer1Token.isPartial()) { + tokens.push(LongCountWildcardOperationToken.parse(layer1Token)) + } else if (isOperatorToken(cache[0])) { + if (tokens.length > 0) { + const lastToken = tokens.pop() + if (lastToken !== undefined) { + const operator = cache.pop() + if (operator !== undefined) { + if (isLongCountToken(lastToken)) { + tokens.push(LongCountOperationToken.parse(lastToken, operator, layer1Token)) + } else { + tokens.push(lastToken) + } + } else { + debugger + } + } else { + debugger + } + } else { + debugger + } + } else { + tokens.push(layer1Token) + } + } else if (isCalendarRoundToken(layer1Token)) { + if (layer1Token.isPartial()) { + tokens.push(CalendarRoundWildcardOperationToken.parse(layer1Token)) + } else if (isOperatorToken(cache[0])) { + if (tokens.length > 0) { + const lastToken = tokens.pop() + if (lastToken !== undefined) { + const operator = cache.pop() + if (operator !== undefined) { + if (isCalendarRoundToken(lastToken)) { + tokens.push(CalendarRoundOperationToken.parse(lastToken, operator, layer1Token)) + } else { + tokens.push(lastToken) + } + } else { + debugger + } + } else { + debugger + } + } else { + debugger + } + } else { + tokens.push(layer1Token) + } + } else if (isPartialOperation(cache, layer1Token)) { + cache.push(layer1Token) + } else if (isFullOperation(cache, layer1Token)) { + cache.push(layer1Token) + if (isLongCountOperation([cache[0], cache[1], cache[2]])) { + tokens.push(LongCountOperationToken.parse(cache[0], cache[1], cache[2])) + } else if (isCalendarRoundOperation([cache[0], cache[1], cache[2]])) { + tokens.push(CalendarRoundOperationToken.parse(cache[0], cache[1], cache[2])) + } else { + debugger + } + cache = [] + } else if (isOperatorToken(layer1Token)) { + cache.push(layer1Token) + } else if (isCommentToken(layer1Token)) { + tokens.push(layer1Token) + } else { + throw new Error('Layer-2 parser in unexpected state') + } + } + if (cache.length > 0) { + debugger + } + return new TokenCollection(tokens) + } +} diff --git a/src/parsers/layer-2-test.ts b/src/parsers/layer-2-test.ts new file mode 100644 index 0000000..7d4efa5 --- /dev/null +++ b/src/parsers/layer-2-test.ts @@ -0,0 +1,59 @@ +import {IToken} from "../tokens/i-token"; +import CalendarRoundToken from "../tokens/layer-1/calendar-round-token"; +import LongCountToken from "../tokens/layer-1/long-count-token"; +import OperatorToken from "../tokens/layer-0/operator-token"; +import CalendarRoundWildcardOperationToken from "../tokens/layer-2/calendar-round-wildcard-operation-token"; +import LongCountWildcardOperationToken from "../tokens/layer-2/long-count-wildcard-operation-token"; + + +export function isPartialOperation(cache: IToken[], layer1Token: IToken): boolean { + const full = cache.concat([layer1Token]) + if (full.length === 1) { + return isToken(full[0]) + } + if (full.length === 2) { + return isToken(full[0]) && isOperatorToken(full[1]) + } + return false; +} + +export function isFullOperation(cache: IToken[], layer1Token: IToken) { + const full = cache.concat([layer1Token]) + if (full.length === 3) { + return isToken(full[0]) && isOperatorToken(full[1]) && isToken(full[2]) + } + return false; +} + +export function isLongCountOperation(tokens: [IToken, IToken, IToken]): boolean { + return isLongCountToken(tokens[0]) && isOperatorToken(tokens[1]) && isLongCountToken(tokens[2]) +} + +export function isCalendarRoundOperation(tokens: [IToken, IToken, IToken]): boolean { + return isCalendarRoundToken(tokens[0]) && isOperatorToken(tokens[1]) && isCalendarRoundToken(tokens[2]) +} + +export function isLongCountToken(t: IToken): t is LongCountToken { + return (t instanceof LongCountToken) +} + +export function isCalendarRoundToken(t: IToken): t is CalendarRoundToken { + return (t instanceof CalendarRoundToken) +} + +export function isCalendarRoundWildcardOperationToken(t: IToken): t is CalendarRoundWildcardOperationToken { + return (t instanceof CalendarRoundWildcardOperationToken) +} + +export function isLongCountWildcardOperationToken(t: IToken): t is LongCountWildcardOperationToken { + return t instanceof LongCountWildcardOperationToken +} + +export function isToken(t: IToken): boolean { + return isCalendarRoundToken(t) || isLongCountToken(t) +} + + +export function isOperatorToken(t: IToken): t is OperatorToken { + return (t instanceof OperatorToken) +} diff --git a/src/parsers/layer-3-parser.ts b/src/parsers/layer-3-parser.ts new file mode 100644 index 0000000..daffaf1 --- /dev/null +++ b/src/parsers/layer-3-parser.ts @@ -0,0 +1,77 @@ +import TokenCollection from "../tokens/collection"; +import {IToken} from "../tokens/i-token"; +import { + isCalendarRoundToken, + isCalendarRoundWildcardOperationToken, isLongCountToken, + isLongCountWildcardOperationToken +} from "./layer-2-test"; +import FullDateWildcardOperationToken from "../tokens/layer-3/full-date-wildcard-operation-token"; +import FullDateToken from "../tokens/layer-3/full-date-token"; + +class FIFOQueue { + private readonly size: number; + private queue: T[]; + + constructor(length: number) { + this.size = length + this.queue = [] + } + + push(item: T): T | undefined { + this.queue.push(item) + if (this.queue.length <= this.size) { + return undefined + } else { + return this.queue.shift() + } + } + + get length(): number { + return this.queue.length + } + + isFull(): boolean { + return this.size === this.length + } + + peek(size: number): T[] { + return this.queue.slice(0, size) + } + + reset() { + this.queue = [] + } +} + + +export default class Layer3Parser { + + parse(layer2Tokens: TokenCollection): TokenCollection { + const tokens: IToken[] = [] + let window: FIFOQueue = new FIFOQueue(2); + for (let cursor = 0; cursor < layer2Tokens.length; cursor += 1) { + const currentToken = window.push(layer2Tokens.index(cursor)); + if (window.isFull()) { + if (currentToken !== undefined) { + tokens.push(currentToken) + } + const [former, latter]: IToken[] = window.peek(2) + if ( + isCalendarRoundWildcardOperationToken(former) || + isLongCountWildcardOperationToken(latter) + ) { + tokens.push(FullDateWildcardOperationToken.parse(former, latter)) + } else if ( + isCalendarRoundToken(former) && + isLongCountToken(latter) + ) { + tokens.push(FullDateToken.parse(former, latter)) + } else { + debugger + } + } + } + return new TokenCollection(tokens) + } +} + diff --git a/src/tokens/base.ts b/src/tokens/base.ts index 2537c27..eb8b014 100644 --- a/src/tokens/base.ts +++ b/src/tokens/base.ts @@ -1,3 +1,5 @@ +import {IToken} from "./i-token"; + export abstract class Token implements IToken { public value: T @@ -12,8 +14,3 @@ export abstract class Token implements IToken { } } -export interface IToken { - equal(otherToken: IToken): boolean - - value: any -} diff --git a/src/tokens/collection.ts b/src/tokens/collection.ts index 571a3e8..f92366c 100644 --- a/src/tokens/collection.ts +++ b/src/tokens/collection.ts @@ -1,4 +1,7 @@ -import {IToken} from "./base"; +import {IToken} from "./i-token"; +import Layer1Parser from "../parsers/layer-1-parser"; +import Layer2Parser from "../parsers/layer-2-parser"; +import Layer3Parser from "../parsers/layer-3-parser"; export default class TokenCollection { public tokens: IToken[] @@ -18,4 +21,16 @@ export default class TokenCollection { index(i: number): IToken { return this.tokens[i] } + + processLayer1(): TokenCollection { + return new Layer1Parser().parse(this) + } + + processLayer2(): TokenCollection { + return new Layer2Parser().parse(this) + } + + processLayer3(): TokenCollection { + return new Layer3Parser().parse(this) + } } diff --git a/src/tokens/i-token.ts b/src/tokens/i-token.ts new file mode 100644 index 0000000..8f3f22d --- /dev/null +++ b/src/tokens/i-token.ts @@ -0,0 +1,5 @@ +export interface IToken { + equal(otherToken: IToken): boolean + + value: any +} diff --git a/src/tokens/primitive/comment-start-token.ts b/src/tokens/layer-0/comment-start-token.ts similarity index 86% rename from src/tokens/primitive/comment-start-token.ts rename to src/tokens/layer-0/comment-start-token.ts index 834a216..fdc6f5d 100644 --- a/src/tokens/primitive/comment-start-token.ts +++ b/src/tokens/layer-0/comment-start-token.ts @@ -1,4 +1,5 @@ -import {IToken, Token} from "../base"; +import {Token} from "../base"; +import {IToken} from "../i-token"; export default class CommentStartToken extends Token { constructor() { diff --git a/src/tokens/primitive/comment-token.ts b/src/tokens/layer-0/comment-token.ts similarity index 87% rename from src/tokens/primitive/comment-token.ts rename to src/tokens/layer-0/comment-token.ts index 48f4785..16b4fd2 100644 --- a/src/tokens/primitive/comment-token.ts +++ b/src/tokens/layer-0/comment-token.ts @@ -1,4 +1,5 @@ -import {IToken, Token} from "../base"; +import {Token} from "../base"; +import {IToken} from "../i-token"; export default class CommentToken extends Token { equal(otherToken: IToken): boolean { diff --git a/src/tokens/primitive/line-end-token.ts b/src/tokens/layer-0/line-end-token.ts similarity index 84% rename from src/tokens/primitive/line-end-token.ts rename to src/tokens/layer-0/line-end-token.ts index 1ecc368..8f23424 100644 --- a/src/tokens/primitive/line-end-token.ts +++ b/src/tokens/layer-0/line-end-token.ts @@ -1,4 +1,5 @@ -import {IToken, Token} from "../base"; +import {Token} from "../base"; +import {IToken} from "../i-token"; export default class LineEndToken extends Token { constructor() { diff --git a/src/tokens/primitive/number-token.ts b/src/tokens/layer-0/number-token.ts similarity index 88% rename from src/tokens/primitive/number-token.ts rename to src/tokens/layer-0/number-token.ts index 1766646..95cd0b0 100644 --- a/src/tokens/primitive/number-token.ts +++ b/src/tokens/layer-0/number-token.ts @@ -1,4 +1,5 @@ -import {IToken, Token} from "../base"; +import {Token} from "../base"; +import {IToken} from "../i-token"; export default class NumberToken extends Token { static parse(raw: string): NumberToken { diff --git a/src/tokens/primitive/operator-token.ts b/src/tokens/layer-0/operator-token.ts similarity index 85% rename from src/tokens/primitive/operator-token.ts rename to src/tokens/layer-0/operator-token.ts index 138d35c..8f2651d 100644 --- a/src/tokens/primitive/operator-token.ts +++ b/src/tokens/layer-0/operator-token.ts @@ -1,4 +1,5 @@ -import {IToken, Token} from "../base"; +import {Token} from "../base"; +import {IToken} from "../i-token"; export default class OperatorToken extends Token { static parse(raw: string): OperatorToken { diff --git a/src/tokens/primitive/period-token.ts b/src/tokens/layer-0/period-token.ts similarity index 86% rename from src/tokens/primitive/period-token.ts rename to src/tokens/layer-0/period-token.ts index 14ce796..5cdbba4 100644 --- a/src/tokens/primitive/period-token.ts +++ b/src/tokens/layer-0/period-token.ts @@ -1,4 +1,5 @@ -import {IToken, Token} from "../base"; +import {Token} from "../base"; +import {IToken} from "../i-token"; export default class PeriodToken extends Token { constructor() { diff --git a/src/tokens/primitive/space-token.ts b/src/tokens/layer-0/space-token.ts similarity index 86% rename from src/tokens/primitive/space-token.ts rename to src/tokens/layer-0/space-token.ts index ebb5b87..1ffabb5 100644 --- a/src/tokens/primitive/space-token.ts +++ b/src/tokens/layer-0/space-token.ts @@ -1,4 +1,5 @@ -import {IToken, Token} from "../base"; +import {Token} from "../base"; +import {IToken} from "../i-token"; export default class SpaceToken extends Token { constructor() { diff --git a/src/tokens/primitive/wildcard-token.ts b/src/tokens/layer-0/wildcard-token.ts similarity index 86% rename from src/tokens/primitive/wildcard-token.ts rename to src/tokens/layer-0/wildcard-token.ts index c8c966a..ff9cfdd 100644 --- a/src/tokens/primitive/wildcard-token.ts +++ b/src/tokens/layer-0/wildcard-token.ts @@ -1,4 +1,5 @@ -import {IToken, Token} from "../base"; +import {Token} from "../base"; +import {IToken} from "../i-token"; export default class WildcardToken extends Token { constructor() { diff --git a/src/tokens/primitive/word-token.ts b/src/tokens/layer-0/word-token.ts similarity index 87% rename from src/tokens/primitive/word-token.ts rename to src/tokens/layer-0/word-token.ts index ccf68b6..325e7e6 100644 --- a/src/tokens/primitive/word-token.ts +++ b/src/tokens/layer-0/word-token.ts @@ -1,4 +1,5 @@ -import {IToken, Token} from "../base"; +import {Token} from "../base"; +import {IToken} from "../i-token"; export default class WordToken extends Token { static parse(raw: string): WordToken { diff --git a/src/tokens/layer-1/calendar-round-token.ts b/src/tokens/layer-1/calendar-round-token.ts new file mode 100644 index 0000000..b9a2fc5 --- /dev/null +++ b/src/tokens/layer-1/calendar-round-token.ts @@ -0,0 +1,45 @@ +import {Token} from "../base"; +import SpaceToken from "../layer-0/space-token"; +import WordToken from "../layer-0/word-token"; +import NumberToken from "../layer-0/number-token"; +import {IToken} from "../i-token"; +import WildcardToken from "../layer-0/wildcard-token"; +import {CalendarRound, getCalendarRound, origin} from "@drewsonne/maya-dates/lib/cr/calendar-round"; +import {getTzolkin, Tzolkin} from "@drewsonne/maya-dates/lib/cr/tzolkin"; +import {getHaab, Haab} from "@drewsonne/maya-dates/lib/cr/haab"; +import {coefficientParser as _} from "@drewsonne/maya-dates/lib/cr/component/coefficient"; +import {getTzolkinDay} from "@drewsonne/maya-dates/lib/cr/component/tzolkinDay"; +import {getHaabMonth} from "@drewsonne/maya-dates/lib/cr/component/haabMonth"; + +export default class CalendarRoundToken extends Token { + static parse(tokens: IToken[]): CalendarRoundToken { + const spaceLess: IToken[] = tokens.filter((t) => { + return !(t instanceof SpaceToken) && (t instanceof WordToken || t instanceof NumberToken || t instanceof WildcardToken) + }) + return new CalendarRoundToken(spaceLess) + } + + equal(otherToken: CalendarRoundToken): boolean { + if (this.value.length === otherToken.value.length) { + return otherToken.value.every( + (otherSubToken, index) => otherSubToken.equal(this.value[index]), + this + ) + } + throw new Error(`Could not parse: '${otherToken}'`) + } + + toString(): string { + return `[${this.value.map((t) => `${t}`).join(',')}]` + } + + get calendarRound(): CalendarRound { + const tzolkin: Tzolkin = getTzolkin(_(this.value[0].value), getTzolkinDay(this.value[1].value)) + const haab: Haab = getHaab(_(this.value[2].value), getHaabMonth(this.value[3].value)) + return getCalendarRound(tzolkin, haab) + } + + isPartial(): boolean { + return this.value.some((t) => t instanceof WildcardToken) + } +} diff --git a/src/tokens/layer-1/long-count-token.ts b/src/tokens/layer-1/long-count-token.ts new file mode 100644 index 0000000..f8551fb --- /dev/null +++ b/src/tokens/layer-1/long-count-token.ts @@ -0,0 +1,37 @@ +import {Token} from "../base"; +import NumberToken from "../layer-0/number-token"; +import PeriodToken from "../layer-0/period-token"; +import {IToken} from "../i-token"; +import WildcardToken from "../layer-0/wildcard-token"; +import LongCount from "@drewsonne/maya-dates/lib/lc/long-count"; +import {Wildcard} from "@drewsonne/maya-dates/lib/wildcard"; +import {isNumberToken, isPeriodToken, isWildcardToken} from "../../parsers/layer-1-test"; + +export default class LongCountToken extends Token { + equal(otherToken: LongCountToken): boolean { + return `${otherToken}` === `${this}` + } + + static parse(tokens: IToken[]): LongCountToken { + return new LongCountToken( + tokens.filter((t) => isNumberToken(t) || isPeriodToken(t) || isWildcardToken(t)) + ); + } + + toString(): string { + return this.value.map((t) => `${t}`).join('') + } + + get longCount(): LongCount { + const parts: (number | Wildcard)[] = this.value.filter( + (c) => isNumberToken(c) || isWildcardToken(c) + ).map( + (c) => isNumberToken(c) ? c.value : new Wildcard() + ); + return new LongCount(...parts.reverse()) + } + + isPartial(): boolean { + return this.value.some((t) => isWildcardToken(t)) + } +} diff --git a/src/tokens/layer-2/calendar-round-operation-token.ts b/src/tokens/layer-2/calendar-round-operation-token.ts new file mode 100644 index 0000000..d293829 --- /dev/null +++ b/src/tokens/layer-2/calendar-round-operation-token.ts @@ -0,0 +1,26 @@ +import {Token} from "../base"; +import {IToken} from "../i-token"; +import {isCalendarRoundToken, isOperatorToken} from "../../parsers/layer-2-test"; + + +export default class CalendarRoundOperationToken extends Token { + static parse(operand1: IToken, operator: IToken, operand2: IToken): CalendarRoundOperationToken { + if (isCalendarRoundToken(operand1)) { + if (isOperatorToken(operator)) { + if (isCalendarRoundToken(operand2)) { + return new CalendarRoundOperationToken([operand1, operator, operand2]) + } + } + } + throw new Error(`Could not parse: '${operand1}, ${operator}, ${operand2}'`) + } + + equal(otherToken: IToken): boolean { + if (otherToken instanceof CalendarRoundOperationToken) { + return this.value[0].equal(otherToken.value[0]) && + this.value[1].equal(otherToken.value[1]) && + this.value[2].equal(otherToken.value[2]) + } + return false; + } +} diff --git a/src/tokens/layer-2/calendar-round-wildcard-operation-token.ts b/src/tokens/layer-2/calendar-round-wildcard-operation-token.ts new file mode 100644 index 0000000..30792f0 --- /dev/null +++ b/src/tokens/layer-2/calendar-round-wildcard-operation-token.ts @@ -0,0 +1,20 @@ +import {Token} from "../base"; +import {IToken} from "../i-token"; +import CalendarRoundToken from "../layer-1/calendar-round-token"; +import {isCalendarRoundWildcardOperationToken} from "../../parsers/layer-2-test"; + +export default class CalendarRoundWildcardOperationToken extends Token { + equal(otherToken: IToken): boolean { + if (isCalendarRoundWildcardOperationToken(otherToken)) { + return otherToken.value.equal(this.value) + } + return false; + } + + static parse(calendarRoundToken: CalendarRoundToken): CalendarRoundWildcardOperationToken { + if (calendarRoundToken.isPartial()) { + return new CalendarRoundWildcardOperationToken(calendarRoundToken) + } + throw new Error(`Could not parse: '${calendarRoundToken}'`) + } +} diff --git a/src/tokens/layer-2/long-count-operation-token.ts b/src/tokens/layer-2/long-count-operation-token.ts new file mode 100644 index 0000000..1d49a51 --- /dev/null +++ b/src/tokens/layer-2/long-count-operation-token.ts @@ -0,0 +1,18 @@ +import {Token} from "../base"; +import {IToken} from "../i-token"; + + +export default class LongCountOperationToken extends Token { + static parse(operand1: IToken, operator: IToken, operand2: IToken): LongCountOperationToken { + return new LongCountOperationToken([operand1, operator, operand2]) + } + + equal(otherToken: IToken): boolean { + if (otherToken instanceof LongCountOperationToken) { + return this.value[0].equal(otherToken.value[0]) && + this.value[1].equal(otherToken.value[1]) && + this.value[2].equal(otherToken.value[2]) + } + return false; + } +} diff --git a/src/tokens/layer-2/long-count-wildcard-operation-token.ts b/src/tokens/layer-2/long-count-wildcard-operation-token.ts new file mode 100644 index 0000000..5cf691c --- /dev/null +++ b/src/tokens/layer-2/long-count-wildcard-operation-token.ts @@ -0,0 +1,20 @@ +import {Token} from "../base"; +import {IToken} from "../i-token"; +import LongCountToken from "../layer-1/long-count-token"; +import {isLongCountToken} from "../../parsers/layer-2-test"; + +export default class LongCountWildcardOperationToken extends Token { + static parse(token: IToken): LongCountWildcardOperationToken { + if (isLongCountToken(token)) { + return new LongCountWildcardOperationToken(token) + } + throw new Error(`Could not parse: '${token}'`) + } + + equal(otherToken: IToken): boolean { + if (otherToken instanceof LongCountWildcardOperationToken) { + return otherToken.value.equal(this.value) + } + return false; + } +} diff --git a/src/tokens/layer-3/full-date-token.ts b/src/tokens/layer-3/full-date-token.ts new file mode 100644 index 0000000..3a4415a --- /dev/null +++ b/src/tokens/layer-3/full-date-token.ts @@ -0,0 +1,18 @@ +import {Token} from "../base"; +import CalendarRoundToken from "../layer-1/calendar-round-token"; +import LongCountToken from "../layer-1/long-count-token"; +import {IToken} from "../i-token"; + +export default class FullDateToken extends Token<[CalendarRoundToken, LongCountToken]> { + equal(otherToken: IToken): boolean { + if (otherToken instanceof FullDateToken) { + return otherToken.value[0].equal(this.value[0]) && + otherToken.value[1].equal(this.value[1]) + } + return false; + } + + static parse(former: CalendarRoundToken, latter: LongCountToken): FullDateToken { + return new FullDateToken([former, latter]) + } +} diff --git a/src/tokens/layer-3/full-date-wildcard-operation-token.ts b/src/tokens/layer-3/full-date-wildcard-operation-token.ts new file mode 100644 index 0000000..69a8efe --- /dev/null +++ b/src/tokens/layer-3/full-date-wildcard-operation-token.ts @@ -0,0 +1,21 @@ +import {IToken} from "../i-token"; +import {Token} from "../base"; +import {isCalendarRoundWildcardOperationToken, isLongCountWildcardOperationToken} from "../../parsers/layer-2-test"; + +export default class FullDateWildcardOperationToken extends Token<[IToken, IToken]> { + + static parse(crWildcard: IToken, lcWildcard: IToken): FullDateWildcardOperationToken { + if (isCalendarRoundWildcardOperationToken(crWildcard) || isLongCountWildcardOperationToken(lcWildcard)) { + return new FullDateWildcardOperationToken([crWildcard, lcWildcard]) + } + throw new Error(`Could not parse: '${crWildcard}', '${lcWildcard}'`) + } + + equal(otherToken: IToken): boolean { + if (otherToken instanceof FullDateWildcardOperationToken) { + return this.value[0].equal(otherToken.value[0]) && + this.value[1].equal(otherToken.value[1]) + } + return false; + } +}