Skip to content

Commit

Permalink
feat(fs): initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
denizenging committed Jun 27, 2023
1 parent fab7827 commit cbf9203
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 0 deletions.
25 changes: 25 additions & 0 deletions src/fs/lexer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { lex } from './lexer';
import { Identifier } from '../shared/tokens';
import { ThenI, SaveThe, IntoTheFile } from './tokens';

test('that lexing works', async () => {
// Given I have a contract with filesystem statements in it
const contract = `Rule unknown ignore
Given I have a 'string' named 'stringToWrite'
and I have a 'string' named 'nameOfTheFile'
Then I save the 'stringToWrite' into the file 'nameOfTheFile'
`;
// When I lex it
const lexed = await lex(contract, { data: { stringToWrite: 'foo', nameOfTheFile: 'bar' } });
// Then the result must have no errors
expect(lexed.errors).toHaveLength(0);
// and it must have 5 tokens
expect(lexed.tokens).toHaveLength(5);
// and those tokens must be these:
expect(lexed.tokens[0].tokenType).toStrictEqual(ThenI);
expect(lexed.tokens[1].tokenType).toStrictEqual(SaveThe);
expect(lexed.tokens[2].tokenType).toStrictEqual(Identifier);
expect(lexed.tokens[3].tokenType).toStrictEqual(IntoTheFile);
expect(lexed.tokens[4].tokenType).toStrictEqual(Identifier);
});
18 changes: 18 additions & 0 deletions src/fs/lexer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ZenroomParams } from '../shared/zenroom';
import { vocab } from './tokens';
import { getIgnoredStatements } from '../ignored';

import { Lexer } from 'chevrotain';

const FsLexer = new Lexer(vocab);

/**
* Lexes the input text for filesystem statements.
*
* @param contract the zencode contract to be lexed.
* @returns the lexed result.
*/
export const lex = async (contract: string, params?: ZenroomParams) => {
const ignored = await getIgnoredStatements(contract, params);
return FsLexer.tokenize(ignored.join('\n'));
};
38 changes: 38 additions & 0 deletions src/fs/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Identifier } from '../shared/tokens';
import { ZenroomParams } from '../shared/zenroom';
import { IntoTheFile, SaveThe, ThenI, vocab } from './tokens';
import { lex } from './lexer';

import { CstParser, IToken } from 'chevrotain';

export type FileOverrideStatementCtx = {
ThenI: [IToken];
SaveThe: [IToken];
content: [IToken];
IntoTheFile: [IToken];
filename: [IToken];
};

class Parser extends CstParser {
constructor() {
super(vocab);
this.performSelfAnalysis();
}

public fileOverrideStatement = this.RULE('fileOverrideStatement', () => {
this.CONSUME(ThenI);
this.CONSUME(SaveThe);
this.CONSUME(Identifier, { LABEL: 'content' });
this.CONSUME(IntoTheFile);
this.CONSUME2(Identifier, { LABEL: 'filename' });
});
}

export const FsParser = new Parser();
export const BaseFsVisitor = FsParser.getBaseCstVisitorConstructor();

export const parse = async (contract: string, params?: ZenroomParams) => {
const lexed = await lex(contract, params);
FsParser.input = lexed.tokens;
return FsParser.fileOverrideStatement();
};
34 changes: 34 additions & 0 deletions src/fs/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Whitespace, Comment, Identifier } from '../shared/tokens';

import { createToken } from 'chevrotain';

// TODO: maybe this should be put in shared and/or the "I" part should be split up.
/**
* The "Then I" statement.
*/
export const ThenI = createToken({
name: 'ThenI',
pattern: /Then I/
});

/**
* The "save the" statement, used to write files to the filesystems.
*/
export const SaveThe = createToken({
name: 'SaveThe',
pattern: /save the/
});

/**
* The "into the file" statement, used to indicate the file to which to
* write.
*/
export const IntoTheFile = createToken({
name: 'IntoTheFile',
pattern: /into the file/
});

/**
* Vocabulary to perform filesystems actions.
*/
export const vocab = [Whitespace, Comment, ThenI, SaveThe, IntoTheFile, Identifier];
16 changes: 16 additions & 0 deletions src/fs/visitor.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { visit } from './visitor';

test('that', async () => {
// Given I have a contract with filesystem statements in it
const contract = `Rule unknown ignore
Given I have a 'string' named 'stringToWrite'
and I have a 'string' named 'nameOfTheFile'
Then I save the 'stringToWrite' into the file 'nameOfTheFile'
`;
const ast = await visit(contract, {
data: { stringToWrite: 'hello world', nameOfTheFile: 'hello-world.txt' }
});

expect(true).toStrictEqual(true);
});
36 changes: 36 additions & 0 deletions src/fs/visitor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ZenroomParams } from '../shared/zenroom';
import { BaseFsVisitor, parse, type FileOverrideStatementCtx } from './parser';

import { type CstNode } from 'chevrotain';

export type FileOverrideStatement = {
content: string;
filename: string;
};

interface Visitor {
visit(params: CstNode): FileOverrideStatement;
}

class Visitor extends BaseFsVisitor {
constructor() {
super();
this.validateVisitor();
}

fileOverrideStatement(ctx: FileOverrideStatementCtx) {
const content = ctx.content[0].image;
const filename = ctx.filename[0].image;
return {
content: content,
filename: filename
};
}
}

export const FsVisitor = new Visitor();

export const visit = async (contract: string, params?: ZenroomParams) => {
const parsed = await parse(contract, params);
return FsVisitor.visit(parsed);
};

0 comments on commit cbf9203

Please sign in to comment.