Skip to content

Commit

Permalink
docs: finished tutorial samples
Browse files Browse the repository at this point in the history
  • Loading branch information
bd82 committed Jul 7, 2023
1 parent b8767c9 commit 56bab8c
Show file tree
Hide file tree
Showing 13 changed files with 159 additions and 189 deletions.
1 change: 1 addition & 0 deletions examples/tutorial/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"name": "chevrotain_examples_tutorial",
"version": "10.5.0",
"type": "module",
"scripts": {
"ci": "pnpm run test",
"test": "mocha \"step*/*spec.js\""
Expand Down
2 changes: 1 addition & 1 deletion examples/tutorial/step1_lexing/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const lex = require("./step1_lexing").lex
import { lex } from "./step1_lexing.js"

const inputText = "SELECT column1 FROM table2"
const lexingResult = lex(inputText)
Expand Down
51 changes: 22 additions & 29 deletions examples/tutorial/step1_lexing/step1_lexing.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,49 @@

// Tutorial Step 1:
// Implementation of A lexer for a simple SELECT statement grammar
const chevrotain = require("chevrotain")
const Lexer = chevrotain.Lexer
const createToken = chevrotain.createToken
import { createToken, Lexer } from "chevrotain"

// the vocabulary will be exported and used in the Parser definition.
const tokenVocabulary = {}
// export const tokenVocabulary = {}

// createToken is used to create a TokenType
// The Lexer's output will contain an array of token Objects created by metadata
const Identifier = createToken({ name: "Identifier", pattern: /[a-zA-Z]\w*/ })
export const Identifier = createToken({
name: "Identifier",
pattern: /[a-zA-Z]\w*/
})

// We specify the "longer_alt" property to resolve keywords vs identifiers ambiguity.
// See: https://github.com/chevrotain/chevrotain/blob/master/examples/lexer/keywords_vs_identifiers/keywords_vs_identifiers.js
const Select = createToken({
export const Select = createToken({
name: "Select",
pattern: /SELECT/,
longer_alt: Identifier
})

const From = createToken({
export const From = createToken({
name: "From",
pattern: /FROM/,
longer_alt: Identifier
})
const Where = createToken({
export const Where = createToken({
name: "Where",
pattern: /WHERE/,
longer_alt: Identifier
})

const Comma = createToken({ name: "Comma", pattern: /,/ })
const Integer = createToken({ name: "Integer", pattern: /0|[1-9]\d*/ })
const GreaterThan = createToken({ name: "GreaterThan", pattern: />/ })
const LessThan = createToken({ name: "LessThan", pattern: /</ })
const WhiteSpace = createToken({
export const Comma = createToken({ name: "Comma", pattern: /,/ })
export const Integer = createToken({ name: "Integer", pattern: /0|[1-9]\d*/ })
export const GreaterThan = createToken({ name: "GreaterThan", pattern: />/ })
export const LessThan = createToken({ name: "LessThan", pattern: /</ })
export const WhiteSpace = createToken({
name: "WhiteSpace",
pattern: /\s+/,
group: Lexer.SKIPPED
})

// The order of tokens is important
const allTokens = [
export const allTokens = [
WhiteSpace,
// "keywords" appear before the Identifier
Select,
Expand All @@ -58,22 +59,14 @@ const allTokens = [
LessThan
]

const SelectLexer = new Lexer(allTokens)

allTokens.forEach((tokenType) => {
tokenVocabulary[tokenType.name] = tokenType
})

module.exports = {
tokenVocabulary: tokenVocabulary,

lex: function (inputText) {
const lexingResult = SelectLexer.tokenize(inputText)
export const selectLexer = new Lexer(allTokens)

if (lexingResult.errors.length > 0) {
throw Error("Sad Sad Panda, lexing errors detected")
}
export function lex(inputText) {
const lexingResult = selectLexer.tokenize(inputText)

return lexingResult
if (lexingResult.errors.length > 0) {
throw Error("Sad Sad Panda, lexing errors detected")
}

return lexingResult
}
19 changes: 9 additions & 10 deletions examples/tutorial/step1_lexing/step1_lexing_spec.js
Original file line number Diff line number Diff line change
@@ -1,28 +1,27 @@
import { expect } from "chai"
const tokenMatcher = require("chevrotain").tokenMatcher
const lex = require("./step1_lexing").lex
const tokenVocabulary = require("./step1_lexing").tokenVocabulary
import { tokenMatcher } from "chevrotain"
import { From, lex, Select, Identifier, From } from "./step1_lexing.js"

describe("Chevrotain Tutorial", () => {
context("Step 1 - Lexing", () => {
it("Can Lex a simple input", () => {
let inputText = "SELECT column1 FROM table2"
let lexingResult = lex(inputText)
const inputText = "SELECT column1 FROM table2"
const lexingResult = lex(inputText)

expect(lexingResult.errors).to.be.empty

let tokens = lexingResult.tokens
const tokens = lexingResult.tokens
expect(tokens).to.have.lengthOf(4)
expect(tokens[0].image).to.equal("SELECT")
expect(tokens[1].image).to.equal("column1")
expect(tokens[2].image).to.equal("FROM")
expect(tokens[3].image).to.equal("table2")

// tokenMatcher acts as an "instanceof" check for Tokens
expect(tokenMatcher(tokens[0], tokenVocabulary.Select)).to.be.true
expect(tokenMatcher(tokens[1], tokenVocabulary.Identifier)).to.be.true
expect(tokenMatcher(tokens[2], tokenVocabulary.From)).to.be.true
expect(tokenMatcher(tokens[3], tokenVocabulary.Identifier)).to.be.true
expect(tokenMatcher(tokens[0], Select)).to.be.true
expect(tokenMatcher(tokens[1], Identifier)).to.be.true
expect(tokenMatcher(tokens[2], From)).to.be.true
expect(tokenMatcher(tokens[3], Identifier)).to.be.true
})
})
})
2 changes: 1 addition & 1 deletion examples/tutorial/step2_parsing/main.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const parse = require("./step2_parsing").parse
import { parse } from "./step2_parsing.js"

let inputText = "SELECT column1 FROM table2"
// step into the parse function to debug the full flow
Expand Down
58 changes: 26 additions & 32 deletions examples/tutorial/step2_parsing/step2_parsing.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,24 @@
// Adding a Parser (grammar only, only reads the input without any actions).
// Using the Token Vocabulary defined in the previous step.

const selectLexer = require("../step1_lexing/step1_lexing")
const CstParser = require("chevrotain").CstParser
const tokenVocabulary = selectLexer.tokenVocabulary

// individual imports, prefer ES6 imports if supported in your runtime/transpiler...
const Select = tokenVocabulary.Select
const From = tokenVocabulary.From
const Where = tokenVocabulary.Where
const Identifier = tokenVocabulary.Identifier
const Integer = tokenVocabulary.Integer
const GreaterThan = tokenVocabulary.GreaterThan
const LessThan = tokenVocabulary.LessThan
const Comma = tokenVocabulary.Comma
import {
selectLexer,
allTokens,
Select,
From,
Where,
LessThan,
GreaterThan,
Comma,
Identifier,
Integer
} from "../step1_lexing/step1_lexing.js"
import { CstParser } from "chevrotain"

// ----------------- parser -----------------
class SelectParser extends CstParser {
export class SelectParser extends CstParser {
constructor() {
super(tokenVocabulary)
super(allTokens)

// for conciseness
const $ = this
Expand Down Expand Up @@ -90,25 +90,19 @@ class SelectParser extends CstParser {
// We only ever need one as the parser internal state is reset for each new input.
const parserInstance = new SelectParser()

module.exports = {
parserInstance: parserInstance,
export function parse(inputText) {
const lexResult = selectLexer.tokenize(inputText)

SelectParser: SelectParser,
// ".input" is a setter which will reset the parser's internal state.
parserInstance.input = lexResult.tokens

parse: function (inputText) {
const lexResult = selectLexer.lex(inputText)
// No semantic actions so this won't return anything yet.
parserInstance.selectStatement()

// ".input" is a setter which will reset the parser's internal's state.
parserInstance.input = lexResult.tokens

// No semantic actions so this won't return anything yet.
parserInstance.selectStatement()

if (parserInstance.errors.length > 0) {
throw Error(
"Sad sad panda, parsing errors detected!\n" +
parserInstance.errors[0].message
)
}
if (parserInstance.errors.length > 0) {
throw Error(
"Sad sad panda, parsing errors detected!\n" +
parserInstance.errors[0].message
)
}
}
2 changes: 1 addition & 1 deletion examples/tutorial/step2_parsing/step2_parsing_spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from "chai"
const parse = require("./step2_parsing").parse
import { parse } from "./step2_parsing.js"

describe("Chevrotain Tutorial", () => {
context("Step 2 - Parsing", () => {
Expand Down
6 changes: 3 additions & 3 deletions examples/tutorial/step3_actions/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const assert = require("assert")
const toAstVisitor = require("./step3a_actions_visitor").toAst
const toAstEmbedded = require("./step3b_actions_embedded").toAst
import assert from "assert"
import { toAstVisitor } from "./step3a_actions_visitor.js"
import { toAstEmbedded } from "./step3b_actions_embedded.js"

let inputText = "SELECT column1, column2 FROM table2 WHERE column2 > 3"

Expand Down
4 changes: 2 additions & 2 deletions examples/tutorial/step3_actions/step3_actions_spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from "chai"
const toAstVisitor = require("./step3a_actions_visitor").toAst
const toAstEmbedded = require("./step3b_actions_embedded").toAst
import { toAstVisitor } from "./step3a_actions_visitor.js"
import { toAstEmbedded } from "./step3b_actions_embedded.js"

describe("Chevrotain Tutorial", () => {
context("Step 3a - Actions (semantics) using CST Visitor", () => {
Expand Down
35 changes: 16 additions & 19 deletions examples/tutorial/step3_actions/step3a_actions_visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@

// Adding Actions(semantics) to our grammar using a CST Visitor.

const selectLexer = require("../step1_lexing/step1_lexing")
import { lex } from "../step1_lexing/step1_lexing.js"
// re-using the parser implemented in step two.
const parser = require("../step2_parsing/step2_parsing")
const SelectParser = parser.SelectParser
import { SelectParser } from "../step2_parsing/step2_parsing.js"

// A new parser instance with CST output (enabled by default).
const parserInstance = new SelectParser()
Expand Down Expand Up @@ -105,25 +104,23 @@ class SQLToAstVisitor extends BaseSQLVisitor {
// Our visitor has no state, so a single instance is sufficient.
const toAstVisitorInstance = new SQLToAstVisitor()

module.exports = {
toAst: function (inputText) {
const lexResult = selectLexer.lex(inputText)
export function toAstVisitor(inputText) {
const lexResult = lex(inputText)

// ".input" is a setter which will reset the parser's internal's state.
parserInstance.input = lexResult.tokens
// ".input" is a setter which will reset the parser's internal's state.
parserInstance.input = lexResult.tokens

// Automatic CST created when parsing
const cst = parserInstance.selectStatement()
// Automatic CST created when parsing
const cst = parserInstance.selectStatement()

if (parserInstance.errors.length > 0) {
throw Error(
"Sad sad panda, parsing errors detected!\n" +
parserInstance.errors[0].message
)
}
if (parserInstance.errors.length > 0) {
throw Error(
"Sad sad panda, parsing errors detected!\n" +
parserInstance.errors[0].message
)
}

const ast = toAstVisitorInstance.visit(cst)
const ast = toAstVisitorInstance.visit(cst)

return ast
}
return ast
}
58 changes: 28 additions & 30 deletions examples/tutorial/step3_actions/step3b_actions_embedded.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,24 @@
// This is the highest performance approach, but its also verbose and none modular
// Therefore using the CST Visitor is the recommended approach:
// https://chevrotain.io/docs/tutorial/step3a_adding_actions_visitor.html
const selectLexer = require("../step1_lexing/step1_lexing")
const EmbeddedActionsParser = require("chevrotain").EmbeddedActionsParser
const tokenVocabulary = selectLexer.tokenVocabulary

// individual imports, prefer ES6 imports if supported in your runtime/transpiler...
const Select = tokenVocabulary.Select
const From = tokenVocabulary.From
const Where = tokenVocabulary.Where
const Identifier = tokenVocabulary.Identifier
const Integer = tokenVocabulary.Integer
const GreaterThan = tokenVocabulary.GreaterThan
const LessThan = tokenVocabulary.LessThan
const Comma = tokenVocabulary.Comma
import {
lex,
Select,
Comma,
From,
GreaterThan,
Identifier,
Integer,
LessThan,
allTokens,
Where
} from "../step1_lexing/step1_lexing.js"
import { EmbeddedActionsParser } from "chevrotain"

// ----------------- parser -----------------
class SelectParserEmbedded extends EmbeddedActionsParser {
constructor() {
super(tokenVocabulary)
super(allTokens)
const $ = this

this.selectStatement = $.RULE("selectStatement", () => {
Expand Down Expand Up @@ -125,31 +125,29 @@ class SelectParserEmbedded extends EmbeddedActionsParser {

// very important to call this after all the rules have been defined.
// otherwise the parser may not work correctly as it will lack information
// derived during the self analysis phase.
// derived during the self-analysis phase.
this.performSelfAnalysis()
}
}

// We only ever need one as the parser internal state is reset for each new input.
const parserInstance = new SelectParserEmbedded()

module.exports = {
toAst: function (inputText) {
const lexResult = selectLexer.lex(inputText)
export function toAstEmbedded(inputText) {
const lexResult = lex(inputText)

// ".input" is a setter which will reset the parser's internal's state.
parserInstance.input = lexResult.tokens
// ".input" is a setter which will reset the parser's internal's state.
parserInstance.input = lexResult.tokens

// No semantic actions so this won't return anything yet.
const ast = parserInstance.selectStatement()
// No semantic actions so this won't return anything yet.
const ast = parserInstance.selectStatement()

if (parserInstance.errors.length > 0) {
throw Error(
"Sad sad panda, parsing errors detected!\n" +
parserInstance.errors[0].message
)
}

return ast
if (parserInstance.errors.length > 0) {
throw Error(
"Sad sad panda, parsing errors detected!\n" +
parserInstance.errors[0].message
)
}

return ast
}
Loading

0 comments on commit 56bab8c

Please sign in to comment.