Skip to content

Commit

Permalink
feat(nlcst-pattern-match): Add matchCST and testCST
Browse files Browse the repository at this point in the history
  • Loading branch information
azu committed Nov 4, 2017
1 parent ed5f565 commit 3ec96ec
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 19 deletions.
17 changes: 16 additions & 1 deletion packages/nlcst-pattern-match/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,22 @@ Install with [npm](https://www.npmjs.com/):

## Usage

You write Pattern of NLCST object in `patternMatcher.tag`${object}`.
### `patternMatcher.tag` : `TagPattern`

`patternMatcher.tag` is a tagged function.
It is used with template literal.

### `patternMatcher.match(text:string, pattern: TagPattern)`

match `text` with `pattern` that is result of `tag` function.

### `patternMatcher.matchCST(cst: Root, pattern: TagPattern)`

match `cst` with `pattern` that is result of `tag` function.

## Example

You write Pattern of NLCST object in `` patternMatcher.tag`${object}` ``.

```js
import { PatternMatcher } from "nlcst-pattern-match";
Expand Down
13 changes: 8 additions & 5 deletions packages/nlcst-pattern-match/src/matcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ export interface MatchResult {
nodeList: Node[];
}

export interface MatchCSTResult {
position: Position | undefined;
nodeList: Node[];
}

/**
* Match actualValue with expectedValue
* @param actualValue
Expand Down Expand Up @@ -72,12 +77,11 @@ export function matchNode(actualNode: Node, expectedNode: Node): boolean {

/**
* match actualSentence with expectedSentence
* @param {string} text
* @param {Sentence} actualSentence
* @param {Sentence} expectedSentence
* @returns {MatchResult[]}
* @returns {MatchCSTResult[]}
*/
export function match(text: string, actualSentence: Sentence, expectedSentence: Sentence): MatchResult[] {
export function match(actualSentence: Sentence, expectedSentence: Sentence): MatchCSTResult[] {
if (!isSentence(actualSentence)) {
throw new Error(`Expected sentence node: ${JSON.stringify(actualSentence)}`);
}
Expand All @@ -88,7 +92,7 @@ export function match(text: string, actualSentence: Sentence, expectedSentence:
const expectedChildren = expectedSentence.children;
const tokenCount = expectedChildren.length;
const matchTokens: Node[] = [];
const results: MatchResult[] = [];
const results: MatchCSTResult[] = [];
let currentTokenPosition = 0;
let index = 0;
for (index = 0; index < children.length; index++) {
Expand Down Expand Up @@ -147,7 +151,6 @@ export function match(text: string, actualSentence: Sentence, expectedSentence:
throw new Error(`The node has not position: ${firstNode}`);
}
results.push({
text: text.slice(firstNode.position.start.offset, lastNode.position.end.offset),
position: {
start: firstNode.position.start,
end: lastNode.position.end,
Expand Down
55 changes: 42 additions & 13 deletions packages/nlcst-pattern-match/src/nlcst-pattern-match.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Root } from "nlcst-types";
import { Parent, Node } from "unist-types";
import { isPunctuation, isSentence, isWhiteSpace, Sentence } from "nlcst-types";
import { PatternNode, TagNode } from "./NodeTypes";
import { match, MatchResult } from "./matcher";
import { match, MatchCSTResult, MatchResult } from "./matcher";

const walk = require("estree-walker").walk;
// Acceptable Node Types
Expand All @@ -13,6 +13,11 @@ export interface PatternMatcherArgs {
parser: { parse(text: string): Root };
}

/**
* tag function result
*/
export type TagPattern = Sentence

export class PatternMatcher {
private parser: { parse: ((text: string) => Root) };

Expand All @@ -23,23 +28,47 @@ export class PatternMatcher {
/**
* Return true If test is passed
*/
test(text: string, pattern: Sentence): boolean {
test(text: string, pattern: TagPattern): boolean {
return this.match(text, pattern).length !== 0;
}

match(text: string, pattern: Sentence): MatchResult[] {
match(text: string, pattern: TagPattern): MatchResult[] {
if (typeof text !== "string") {
throw new Error(
"Invalid Arguments: match(text: string, pattern: Sentence)\n" +
"matcher.match(text, matcher.tag`pattern`)"
"Invalid Arguments: match(text: string, pattern: TagPattern)\n" +
"matcher.match(text, matcher.tag`pattern`)"
);
}
let allResults: MatchResult[] = [];
const AST = this.parser.parse(text);
walk(AST, {
enter: function(node: Node) {
const CST = this.parser.parse(text);
const CSTResults = this.matchCST(CST, pattern);
return CSTResults.map(node => {
const firstNode = node.nodeList[0];
const lastNode = node.nodeList[node.nodeList.length - 1];
if (!firstNode || !lastNode) {
return {
text: "",
position: node.position,
nodeList: node.nodeList
}
}
return {
text: text.slice(firstNode.position!.start.offset, lastNode.position!.end.offset),
position: node.position,
nodeList: node.nodeList
}
});
}

testCST(cst: Root, pattern: TagPattern): boolean {
return this.matchCST(cst, pattern).length > 0;
}

matchCST(cst: Root, pattern: TagPattern): MatchCSTResult[] {
let allResults: MatchCSTResult[] = [];
walk(cst, {
enter: function (node: Node) {
if (isSentence(node)) {
const results = match(text, node, pattern);
const results = match(node, pattern);
allResults = allResults.concat(results);
this.skip();
}
Expand All @@ -52,7 +81,7 @@ export class PatternMatcher {
* Template tag function.
* Return pattern objects that are used for `matcher.match` method.
*/
tag(strings: TemplateStringsArray, ...values: NodeTypes[]): Sentence {
tag(strings: TemplateStringsArray, ...values: NodeTypes[]): TagPattern {
if (!Array.isArray(strings)) {
throw new Error(
"tag method is template tag function.\n" + 'For example matcher.tag`this is ${{ type: "WordNode" }}` .'
Expand Down Expand Up @@ -82,7 +111,7 @@ export class PatternMatcher {
});
const AST = this.parser.parse(allString);
walk(AST, {
enter: function(node: Node, parent: Parent) {
enter: function (node: Node, parent: Parent) {
replaceHolders
.filter(replaceHolder => {
return node.position!.start.offset === replaceHolder.start;
Expand All @@ -98,6 +127,6 @@ export class PatternMatcher {
}
});

return AST.children[0].children[0] as Sentence;
return AST.children[0].children[0] as TagPattern;
}
}
118 changes: 118 additions & 0 deletions packages/nlcst-pattern-match/test/nlcst-pattern-match-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -371,4 +371,122 @@ ${JSON.stringify(actual)}
);
});
});

describe("#matchCST", () => {
it("should return MatchCSTResult[]", () => {
const englishParser = new EnglishParser();
const patternMatcher = new PatternMatcher({
parser: englishParser
});
const pattern = patternMatcher.tag`Bob ${{
type: "*",
data: {
pos: /^VB/ // verb
}
}} it.`;
const text = "Bob does it.";
const CST = englishParser.parse(text);
const results = patternMatcher.matchCST(CST, pattern);
assert.equal(results.length, 1, "results should have 1");
const [result] = results;
assert.deepEqual(result.position, {
index: 0,
end: {
column: 13,
line: 1,
offset: 12
},
start: {
column: 1,
line: 1,
offset: 0
}
});
assert.deepEqual(
result.nodeList,
[
{
type: "WordNode",
children: [
{
type: "TextNode",
value: "Bob",
position: {
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 4, offset: 3 }
}
}
],
position: {
start: { line: 1, column: 1, offset: 0 },
end: { line: 1, column: 4, offset: 3 }
},
data: { pos: "NNP" }
},
{
type: "WhiteSpaceNode",
value: " ",
position: {
start: { line: 1, column: 4, offset: 3 },
end: { line: 1, column: 5, offset: 4 }
}
},
{
type: "WordNode",
children: [
{
type: "TextNode",
value: "does",
position: {
start: { line: 1, column: 5, offset: 4 },
end: { line: 1, column: 9, offset: 8 }
}
}
],
position: {
start: { line: 1, column: 5, offset: 4 },
end: { line: 1, column: 9, offset: 8 }
},
data: { pos: "VBZ" }
},
{
type: "WhiteSpaceNode",
value: " ",
position: {
start: { line: 1, column: 9, offset: 8 },
end: { line: 1, column: 10, offset: 9 }
}
},
{
type: "WordNode",
children: [
{
type: "TextNode",
value: "it",
position: {
start: { line: 1, column: 10, offset: 9 },
end: { line: 1, column: 12, offset: 11 }
}
}
],
position: {
start: { line: 1, column: 10, offset: 9 },
end: { line: 1, column: 12, offset: 11 }
},
data: { pos: "PRP" }
},
{
type: "PunctuationNode",
value: ".",
position: {
start: { line: 1, column: 12, offset: 11 },
end: { line: 1, column: 13, offset: 12 }
},
data: { pos: "." }
}
],
`\n${JSON.stringify(result.nodeList)}\n`
);
});
});
});

0 comments on commit 3ec96ec

Please sign in to comment.