Skip to content

Commit

Permalink
Random generation of parse trees from grammar (untested yet)
Browse files Browse the repository at this point in the history
  • Loading branch information
ghadeeras committed Aug 24, 2024
1 parent 88dd213 commit a714760
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 45 deletions.
43 changes: 38 additions & 5 deletions src/prod/grammar.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as tokens from "./tokens";
import * as tokens from "./tokens.js";
import { randomInt } from "./utils.js";

export class Grammar<T> {

Expand Down Expand Up @@ -70,10 +71,10 @@ export interface Symbol<T> {

accept<R>(visitor: Visitor<R>): R

process(value: T): void

mapped<R>(toMapper: (v: T) => R, fromMapper: (v: R) => T): Mapped<T, R>

random(): T

}

export interface Optional<T> extends Symbol<T | null> {
Expand Down Expand Up @@ -133,9 +134,9 @@ export interface Mapped<S, T> extends Symbol<T> {
abstract class SymbolImpl<T> implements Symbol<T> {

abstract accept<R>(visitor: Visitor<R>): R

process(value: T) {}

abstract random(): T;

mapped<R>(toMapper: (v: T) => R, fromMapper: (v: R) => T): Mapped<T, R> {
return new MappedImpl(this, toMapper, fromMapper)
}
Expand All @@ -152,6 +153,10 @@ class OptionalImpl<T> extends SymbolImpl<T | null> implements Optional<T> {
return visitor.visitOptional(this)
}

random(): T | null {
return randomInt(2) === 1 ? this.symbol.random() : null
}

}

abstract class RepeatableImpl<T> extends SymbolImpl<T> implements Repeatable<T> {
Expand Down Expand Up @@ -179,6 +184,10 @@ class TerminalImpl<T> extends RepeatableImpl<T> implements Terminal<T> {
accept<R>(visitor: Visitor<R>): R {
return visitor.visitTerminal(this)
}

random(): T {
return this.tokenType.parse(this.tokenType.pattern.random())
}

}

Expand All @@ -194,6 +203,11 @@ class ChoiceImpl<P extends Node<any>[]> extends RepeatableImpl<InferFromNodes<P>
return visitor.visitChoice(this)
}

random(): InferFromNodes<P> {
const i = randomInt(this.productions.length)
return this.productions[i].random()
}

}

class ProductionImpl<T extends string, D extends Definition> extends RepeatableImpl<ParseTreeNode<T, Structure<D>>> implements Production<T, D> {
Expand All @@ -208,6 +222,17 @@ class ProductionImpl<T extends string, D extends Definition> extends RepeatableI
return visitor.visitProduction(this)
}

random(): ParseTreeNode<T, Structure<D>> {
const content: Partial<ParseTreeNode<T, Structure<D>>["content"]> = {}
for (const key of this.order) {
content[key] = this.definition[key].random()
}
return {
type: this.type,
content: content as ParseTreeNode<T, Structure<D>>["content"]
}
}

}

class LazyImpl<T> extends SymbolImpl<T> implements Lazy<T> {
Expand All @@ -228,6 +253,10 @@ class LazyImpl<T> extends SymbolImpl<T> implements Lazy<T> {
accept<R>(visitor: Visitor<R>): R {
return visitor.visitLazy(this)
}

random(): T {
return this.symbol.random()
}

}

Expand All @@ -241,6 +270,10 @@ class MappedImpl<S, T> extends SymbolImpl<T> implements Mapped<S, T> {
return visitor.visitMapped(this)
}

random(): T {
return this.toMapper(this.symbol.random())
}

}

export interface Visitor<R> {
Expand Down
18 changes: 9 additions & 9 deletions src/prod/scanning.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import * as utils from './utils.js'

export class Scanner {

readonly errorTokenType: tokens.TextualTokenType = new tokens.TextualTokenType(regex.oneOrMore(regex.charIn("\u0000-\uffff")))
readonly eofTokenType: tokens.BooleanTokenType = new tokens.BooleanTokenType(regex.word("EOF"))
readonly errorTokenType: tokens.TokenType<string> = tokens.textualToken(regex.oneOrMore(regex.charIn("\u0000-\uffff")))
readonly eofTokenType: tokens.TokenType<boolean> = tokens.booleanToken(regex.word("EOF")).parsedAs(lexeme => true)

private readonly tokenTypes: TokenTypeWrapper<any>[] = []
private readonly _tokenTypeNames: Map<tokens.TokenType<any>, string> = new Map()
Expand Down Expand Up @@ -64,31 +64,31 @@ export class Scanner {
}

protected string(pattern: regex.RegEx) {
return this.define(new tokens.TextualTokenType(pattern))
return this.define(tokens.textualToken(pattern))
}

protected float(pattern: regex.RegEx) {
return this.define(new tokens.FloatTokenType(pattern))
return this.define(tokens.floatToken(pattern))
}

protected integer(pattern: regex.RegEx) {
return this.define(new tokens.IntegerTokenType(pattern))
return this.define(tokens.integerToken(pattern))
}

protected boolean(pattern: regex.RegEx) {
return this.define(new tokens.BooleanTokenType(pattern))
return this.define(tokens.booleanToken(pattern))
}

protected keyword(word: string) {
return this.boolean(regex.word(word))
return this.boolean(regex.word(word)).parsedAs(lexeme => true)
}

protected op(op: string) {
return this.boolean(regex.word(op))
return this.boolean(regex.word(op)).parsedAs(lexeme => true)
}

protected delimiter(del: string) {
return this.boolean(regex.word(del))
return this.boolean(regex.word(del)).parsedAs(lexeme => true)
}

*iterator(stream: streams.InputStream<number>) {
Expand Down
34 changes: 9 additions & 25 deletions src/prod/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export interface TokenType<T> {

class TokenTypeImpl<T> implements TokenType<T> {

protected constructor(
constructor(
readonly pattern: regex.RegEx,
private readonly parser: (lexeme: string) => T
) {
Expand All @@ -38,36 +38,20 @@ class TokenTypeImpl<T> implements TokenType<T> {

}

export class TextualTokenType extends TokenTypeImpl<string> {

constructor(pattern: regex.RegEx) {
super(pattern, s => s)
}

export function textualToken(pattern: regex.RegEx): TokenType<string> {
return new TokenTypeImpl(pattern, s => s)
}

export class FloatTokenType extends TokenTypeImpl<number> {

constructor(pattern: regex.RegEx) {
super(pattern, s => Number.parseFloat(s))
}

export function floatToken(pattern: regex.RegEx): TokenType<number> {
return new TokenTypeImpl(pattern, s => Number.parseFloat(s))
}

export class IntegerTokenType extends TokenTypeImpl<number> {

constructor(pattern: regex.RegEx) {
super(pattern, s => Number.parseInt(s))
}

export function integerToken(pattern: regex.RegEx): TokenType<number> {
return new TokenTypeImpl(pattern, s => Number.parseInt(s))
}

export class BooleanTokenType extends TokenTypeImpl<boolean> {

constructor(pattern: regex.RegEx) {
super(pattern, s => pattern.matches(s))
}

export function booleanToken(pattern: regex.RegEx): TokenType<boolean> {
return new TokenTypeImpl(pattern, s => s === "true")
}

export class Token<T> {
Expand Down
10 changes: 4 additions & 6 deletions src/test/grammar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ import * as tokens from "../prod/tokens.js";
import * as gram from "../prod/grammar.js";
import { expect } from 'chai'

const booleanTerm = gram.terminal(new tokens.BooleanTokenType(rex.choice(
const booleanTerm = gram.terminal(tokens.booleanToken(rex.choice(
rex.word("true"),
rex.word("false")
)))
const intTerm = gram.terminal(new tokens.IntegerTokenType(rex.oneOrMore(rex.charIn("0-9"))))
const floatTerm = gram.terminal(new tokens.FloatTokenType(rex.concat(
const intTerm = gram.terminal(tokens.integerToken(rex.oneOrMore(rex.charIn("0-9"))))
const floatTerm = gram.terminal(tokens.floatToken(rex.concat(
rex.zeroOrMore(rex.charIn("0-9")),
rex.char("."),
rex.oneOrMore(rex.charIn("0-9"))
)))
const identifier = gram.terminal(new tokens.TextualTokenType(rex.concat(
const identifier = gram.terminal(tokens.textualToken(rex.concat(
rex.oneOrMore(rex.choice(
rex.charIn("a-z"),
rex.charIn("A-Z")
Expand Down Expand Up @@ -69,8 +69,6 @@ describe("Grammar", () => {
const exp = gram.choice(varExp, numExp)
const g = new gram.Grammar(exp)

exp.process({type: "num", content: {val: 123}})

expect(g).to.satisfy(containmentOf(exp))
expect(g).to.satisfy(containmentOf(varExp))
expect(g).to.satisfy(containmentOf(numExp))
Expand Down

0 comments on commit a714760

Please sign in to comment.