Skip to content

Commit

Permalink
docs(core): Document the core module (#60)
Browse files Browse the repository at this point in the history
* feat(core): rm unused file

* doc(core): add docs to lexer

* doc(core): add docs to parser

* doc(core): add docs to plugin

* doc(core): add docs to slangroom

* doc(core): add docs to visitor

* doc(core): add docs to the package

* doc(core): fix nitpicks of the package

---------

Co-authored-by: Puria Nafisi Azizi <[email protected]>
  • Loading branch information
denizenging and puria authored Jan 18, 2024
1 parent 946014e commit beeee85
Show file tree
Hide file tree
Showing 7 changed files with 808 additions and 99 deletions.
206 changes: 206 additions & 0 deletions pkg/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,209 @@ export * from '@slangroom/core/lexer';
export * from '@slangroom/core/visitor';
export * from '@slangroom/core/plugin';
export * from '@slangroom/core/slangroom';

/**
* The Core of Slangroom.
*
* @remarks
* - The lexer module defines the lexer that is used to split up strings of
* characters of a line by whitespace, while keeping the whitespace intact
* inside identifiers. It generates tokens with position information.
*
* - The parser module defines the parser that is used to parse the list of
* tokens of a line. It creates a CST (Concrete Syntax Tree) with a list of
* possible matches of plugin definitions.
*
* - The visitor module defines the visitor that is used to generate an AST
* (Abstract Syntax Tree) out of the given CST. It keeps the information of
* what needs to be provided to which plugin definition.
*
* - The plugin module defines the plugins subsystem, where a plugin can define
* multiple plugin definitions, each of which defines a unique plugin
* definition inside of that parcitular plugin.
*
* - The Slangroom module is the entrypoint to the whole system. It uses a list
* of plugins to execute a contract as if the custom statements defined in a
* contract is actually run by Zenroom itself, a seamless experience.
*
* @example
* Let's define an example plugin with a single, simple plugin definitons:
* ```ts
* // file: my-plugin.ts
* import {Plugin} from "@slangroom/core";
*
* const p = new Plugin();
*
* p.new("love asche", ctx => {
* return ctx.pass("everything is okay, and this is my return value");
* });
*
* export const myPlugin = p;
* ```
*
* The callback function of the plugin definition above would be ran when a
* custom statements of the following possible forms are matched (everything but
* the "I" is case-insensitive):
* ```
* Given I love Asche
* Then I love Asche
* Given I love Asche and output into 'result'
* Then I love Asche and output into 'other_result'
* ```
*
* The statements starting with Given are executed before the actual Zenroom
* execution takes place, and statements starting with Then are executed after
* the actual execution takes place.
*
* We can later use that definion with a Slangroom instance:
* ```ts
* import {Slangroom} from '@slangroom/core';
* import {myPlugin} from "my-plugin";
*
* const sl = new Slangroom(myPlugin);
*
* const {result, logs} = sl.execute(contract, params)
* ```
*
* @example
* Let's define an example plugin with parameters now:
* ```ts
* // file: my-plugin.ts
* import {Plugin} from "@slangroom/core";
*
* const p = new Plugin();
*
* p.new(["first_number", "second_number"], "add up", ctx => {
* const first = ctx.fetch("first_number");
* if (typeof first !== "number")
* return ctx.fail("first_number must be a number")
* const second = ctx.fetch("second_number");
* if (typeof second !== "number")
* return ctx.fail("second_number must be a number")
* return ctx.pass(first + second);
* });
*
* export const myPlugin = p;
* ```
*
* The callback function of the plugin definition above would be ran when a
* custom statements of the following possible forms are matched (everything but
* the "I" is case-insensitive):
* ```
* Given I send first_number 'ident1' and send second_number 'ident2' and add up
* Then I send first_number 'ident1' and send second_number 'ident2' and add up
* Given I send second_number 'ident1' and send first_number 'ident2' and add up
* Then I send second_number 'ident1' and send first_number 'ident2' and add up
* Given I send first_number 'ident1' and send second_number 'ident2' and add up and output into 'result'
* Then I send first_number 'ident1' and send second_number 'ident2' and add up and output into 'another_result'
* Given I send second_number 'ident1' and send first_number 'ident2' and add up and add up and output into 'result'
* Then I send second_number 'ident1' and send first_number 'ident2' and add up and add up and output into 'other_result'
* ```
*
* The statements starting with Given are executed before the actual Zenroom
* execution takes place, and statements starting with Then are executed after
* the actual execution takes place. The first four statements don't make much
* sense, as the whole reason we created this plugin is to use its return value,
* but, but this is allowed by design.
*
* We can later use that definion with a Slangroom instance:
* ```ts
* import {Slangroom} from '@slangroom/core';
* import {myPlugin} from "my-plugin";
*
* const sl = new Slangroom(myPlugin);
*
* const {result, logs} = sl.execute(contract, params)
* ```
*
* @example
* Let's define an example plugin with parameters and open now:
* ```ts
* // file: my-plugin.ts
* import {Plugin} from "@slangroom/core";
*
* const p = new Plugin();
*
* p.new("open", ["content"], "write to file", ctx => {
* const path = ctx.fetchOpen()[0];
* const cont = ctx.fetch("content");
* if (typeof cont !== "string")
* return ctx.fail("content must be a number");
* const {result, error} = fs.writeToFile(path, cont);
* if (error)
* return ctx.fail(error);
* return ctx.pass(result);
* });
*
* export const myPlugin = p;
* ```
*
* The callback function of the plugin definition above would be ran when a
* custom statements of the following possible forms are matched (everything but
* the "I" is case-insensitive):
* ```
* Given I open 'ident1' and send content 'ident1' and write to file
* Then I open 'ident1' and send content 'ident1' and write to file
* Given I open 'ident1' and send content 'ident1' and write to file and output into 'result'
* Then I open 'ident1' and send content 'ident1' and write to file and ountput into 'other_result'
* ```
*
* The statements starting with Given are executed before the actual Zenroom
* execution takes place, and statements starting with Then are executed after
* the actual execution takes place.
*
* We can later use that definion with a Slangroom instance:
* ```ts
* import {Slangroom} from '@slangroom/core';
* import {myPlugin} from "my-plugin";
*
* const sl = new Slangroom(myPlugin);
*
* const {result, logs} = sl.execute(contract, params)
* ```
*
* @example
* We can also just define a plugin that uses connect and a phrase:
* ```ts
* // file: my-plugin.ts
* import {Plugin} from "@slangroom/core";
*
* const p = new Plugin();
*
* p.new("connect", "ping once", ctx => {
* const host = ctx.fetchConnect()[0];
* const {result, error} = net.pingHost(host);
* if (error)
* return ctx.fail(error);
* return ctx.pass(result);
* });
*
* export const myPlugin = p;
* ```
*
* The callback function of the plugin definition above would be ran when a
* custom statements of the following possible forms are matched (everything but
* the "I" is case-insensitive):
* ```
* Given I connect to 'ident1' and ping once
* Then I connect to 'ident1' and ping once
* Given I connect to 'ident1' and ping once and output into 'result'
* Then I connect to 'ident1' and ping once and output into 'other_result'
* ```
*
* The statements starting with Given are executed before the actual Zenroom
* execution takes place, and statements starting with Then are executed after
* the actual execution takes place.
*
* We can later use that definion with a Slangroom instance:
* ```ts
* import {Slangroom} from '@slangroom/core';
* import {myPlugin} from "my-plugin";
*
* const sl = new Slangroom(myPlugin);
*
* const {result, logs} = sl.execute(contract, params)
* ```
*
* @packageDocumentation
*/
74 changes: 69 additions & 5 deletions pkg/core/src/lexer.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,75 @@
/**
* A whitespace-separated string of characters with position information.
*
* @remarks
* The whole instance of of this class must be thought as a concrete, immutable
* unit.
*
* When that string of characters corresponds to an identifier, which is wrapped
* in a pair of single-quotes, the whitespace inside the quotes remains intact.
*
* @example
* `love` and `Asche` are tokens, so are `'transfer id'` and `'http method'`.
*/
export class Token {
/**
* The raw string of a token, such as `Asche`.
*/
readonly raw: string;

/**
* The lowercased version of {@link raw} string, such as `asche`.
*/
readonly name: string;

/**
* The start position of the token in the given line, must be non-negative.
*/
readonly start: number;

/**
* The end position of the token in the given line, must be >= {@link start}.
*/
readonly end: number;

/**
* Whether this token is an identifier or not. It is an identifier if the
* first character of {@link raw} is a single-quote.
*/
readonly isIdent: boolean;

constructor(
readonly raw: string,
readonly start: number,
readonly end: number,
) {
/**
* Creates a new instance of {@link Token}.
*
* @throws {@link Error}
* If {@link raw} is empty.
*
* @throws {@link Error}
* If {@link start} is negative.
*
* @throws {@link Error}
* If {@link end} is less than {@link start}.
*/
constructor(raw: string, start: number, end: number) {
if (!raw) throw new Error('raw cannot be empty string');
if (start < 0) throw new Error('start cannot be negative');
if (end < start) throw new Error('end cannot be less than start');

this.raw = raw;
this.start = start;
this.end = end;
this.name = this.raw.toLowerCase();
this.isIdent = this.raw.charAt(0) === "'";
}
}

/**
* An error encountered during the lexican analysis phrase.
*
* @privateRemarks
* Currently, we only have an error of unclosed single-quotes. I don't know if
* we'll ever need anything other than this.
*/
export class LexError extends Error {
constructor(t: Token) {
super();
Expand All @@ -20,6 +78,12 @@ export class LexError extends Error {
}
}

/**
* Analyzes the given line lexically to generate an array of tokens.
*
* @throws {@link LexError}
* If any error during lexing is encountered.
*/
export const lex = (line: string): Token[] => {
const tokens: Token[] = [];
const c = [...line];
Expand Down
70 changes: 0 additions & 70 deletions pkg/core/src/lexicon.ts

This file was deleted.

Loading

0 comments on commit beeee85

Please sign in to comment.