Skip to content

Commit

Permalink
chore(Markdown): add code documentation for parser
Browse files Browse the repository at this point in the history
  • Loading branch information
jstarpl committed Dec 9, 2023
1 parent 2007e03 commit 51f8ee5
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 71 deletions.
2 changes: 1 addition & 1 deletion packages/apps/client/src/ScriptEditor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { fromMarkdown } from '../lib/prosemirrorDoc'

const LOREM_IPSUM =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent sollicitudin ipsum at lacinia sodales. *Sed* in **pharetra _mauris_**, id facilisis nibh. Curabitur eget erat bibendum, aliquam ligula ac, interdum orci. Curabitur non mollis nibh. Pellentesque ultrices suscipit diam ac fermentum. Morbi id velit consectetur, auctor ligula scelerisque, vulputate ante. Nunc mattis consectetur eleifend. Aenean vestibulum porta mollis. Cras ultrices facilisis turpis, et vulputate felis tempor at. Aliquam ultricies commodo odio at vehicula. Curabitur lobortis lectus at lacus commodo tincidunt. Donec vulputate urna efficitur, vehicula urna vel, porttitor urna.\n' +
'Duis congue molestie neque, et sollicitudin lacus porta eget. Etiam massa dui, cursus vitae lacus sit **amet**, aliquet bibendum elit. Morbi tincidunt quis metus ut luctus. Proin tincidunt suscipit vestibulum. In eu cursus quam. Praesent lacus mauris, euismod nec lacus in, tincidunt ultrices justo. Sed ac rhoncus quam. Praesent libero elit, convallis ut urna nec, interdum elementum diam. Pellentesque aliquet, mi vitae faucibus euismod, mauris lorem auctor felis, tincidunt bibendum erat nisl in nisi.\n' +
'Duis congue molestie neque, et sollicitudin lacus porta eget. *Etiam* massa dui, cursus vitae lacus sit **amet**, aliquet bibendum elit. Morbi tincidunt quis metus ut luctus. Proin tincidunt suscipit vestibulum. In eu cursus quam. Praesent lacus mauris, euismod nec lacus in, tincidunt ultrices justo. Sed ac rhoncus quam. Praesent libero elit, convallis ut urna nec, interdum elementum diam. Pellentesque aliquet, mi vitae faucibus euismod, mauris lorem auctor felis, tincidunt bibendum erat nisl in nisi.\n' +
'Donec ac rhoncus ex. Pellentesque eleifend ante id maximus *mollis*. Duis in mauris vel ligula venenatis gravida.\n\n\\*Mauris blandit arcu a lorem cursus ornare. Vestibulum at ligula vel nisi eleifend pretium. Vivamus et nunc scelerisque, suscipit dolor nec, ornare elit. Nam ut tristique est. Suspendisse sollicitudin tortor quam, eget cursus quam porttitor nec. Fusce convallis libero massa, a consequat tortor accumsan id. Pellentesque at diam sit amet tortor suscipit bibendum sed et elit. Etiam ac tellus tellus. Cras pulvinar sem et augue consequat mattis. \n' +
'Duis mollis ut enim vitae lobortis. ~Nulla mi libero~, blandit sit amet congue eu, vehicula vel sem. Donec maximus lacus \\~ac nisi blandit sodales. Fusce sed lectus iaculis, tempus quam lacinia, gravida velit. In imperdiet, sem sit amet commodo eleifend, turpis tellus lobortis metus, et rutrum mi sapien vel nisl. Pellentesque at est non tortor efficitur tincidunt vitae in ex. In gravida pulvinar ligula eget pellentesque. Nullam viverra orci velit, at dictum diam imperdiet sit amet. Morbi consequat est vitae mi consequat fringilla. Phasellus pharetra turpis nulla, at molestie nunc hendrerit ut. \n' +
'Aenean ut nulla ut diam imperdiet laoreet sed sed enim. **Vivamus bibendum** tempus metus ac consectetur. Aliquam ut nisl sed mauris sodales dignissim. Integer consectetur sapien quam, sit amet blandit quam cursus ac. Quisque vel convallis erat. Aliquam ac interdum nisi. Praesent id sapien vitae sem venenatis sollicitudin. '
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { NodeConstruct, ParserState } from '..'
import { EmphasisNode, StrongNode } from '../astNodes'
import { NodeConstruct, ParserState, CharHandlerResult } from '../parserState'
import { EmphasisNode, StrongNode, Node, ParentNodeBase } from '../astNodes'

export function emphasisAndStrong(): NodeConstruct {
function emphasisOrStrong(char: string, state: ParserState) {
function emphasisOrStrong(char: string, state: ParserState): CharHandlerResult | void {
if (state.nodeCursor === null) throw new Error('cursor === null assertion')
if ((state.nodeCursor.type === 'emphasis' || state.nodeCursor.type === 'strong') && 'code' in state.nodeCursor) {
if (state.nodeCursor.code === char) {
if (state.nodeCursor && isEmphasisOrStrongNode(state.nodeCursor)) {
if (state.nodeCursor.code.startsWith(char)) {
if (state.peek() === char) {
state.consume()
}

state.flushBuffer()
state.popNode()
return false
return CharHandlerResult.StopProcessingNoBuffer
}
}

Expand All @@ -22,7 +22,7 @@ export function emphasisAndStrong(): NodeConstruct {

if (state.peek() === char) {
type = 'strong'
state.consume()
char += state.consume()
}

const emphasisOrStrongNode: EmphasisNode | StrongNode = {
Expand All @@ -32,7 +32,7 @@ export function emphasisAndStrong(): NodeConstruct {
}
state.pushNode(emphasisOrStrongNode)

return false
return CharHandlerResult.StopProcessingNoBuffer
}

return {
Expand All @@ -43,3 +43,7 @@ export function emphasisAndStrong(): NodeConstruct {
},
}
}

function isEmphasisOrStrongNode(node: Node | ParentNodeBase): node is EmphasisNode | StrongNode {
return node.type === 'emphasis' || node.type === 'strong'
}
9 changes: 5 additions & 4 deletions packages/apps/client/src/lib/mdParser/constructs/escape.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { NodeConstruct, ParserState } from '..'
import { CharHandlerResult, NodeConstruct, ParserState } from '../parserState'

export function escape(): NodeConstruct {
function escapeChar(_: string, state: ParserState) {
function escapeChar(_: string, state: ParserState): CharHandlerResult | void {
state.dataStore['inEscape'] = true
return CharHandlerResult.StopProcessingNoBuffer
}

function passthroughChar(_: string, state: ParserState) {
function passthroughChar(_: string, state: ParserState): CharHandlerResult | void {
if (state.dataStore['inEscape'] !== true) return
state.dataStore['inEscape'] = false
return true
return CharHandlerResult.StopProcessing
}

return {
Expand Down
6 changes: 3 additions & 3 deletions packages/apps/client/src/lib/mdParser/constructs/paragraph.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { NodeConstruct, ParserState } from '..'
import { NodeConstruct, ParserState, CharHandlerResult } from '../parserState'
import { ParagraphNode } from '../astNodes'

export function paragraph(): NodeConstruct {
Expand All @@ -11,15 +11,15 @@ export function paragraph(): NodeConstruct {
state.replaceStack(newParagraph)
}

function paragraphEnd(char: string, state: ParserState) {
function paragraphEnd(char: string, state: ParserState): CharHandlerResult {
if (state.nodeCursor === null) {
paragraphStart(char, state)
}

state.flushBuffer()
state.nodeCursor = null

return false
return CharHandlerResult.StopProcessingNoBuffer
}

return {
Expand Down
8 changes: 4 additions & 4 deletions packages/apps/client/src/lib/mdParser/constructs/reverse.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { NodeConstruct, ParserState } from '..'
import { NodeConstruct, ParserState, CharHandlerResult } from '../parserState'
import { ReverseNode } from '../astNodes'

export function reverse(): NodeConstruct {
function reverse(char: string, state: ParserState) {
function reverse(char: string, state: ParserState): CharHandlerResult | void {
if (state.nodeCursor === null) throw new Error('cursor === null assertion')
if (state.nodeCursor.type === 'reverse' && 'code' in state.nodeCursor) {
if (state.nodeCursor.code === char) {
state.flushBuffer()
state.popNode()
return false
return CharHandlerResult.StopProcessingNoBuffer
}
}

Expand All @@ -23,7 +23,7 @@ export function reverse(): NodeConstruct {
}
state.pushNode(reverseNode)

return false
return CharHandlerResult.StopProcessingNoBuffer
}

return {
Expand Down
86 changes: 36 additions & 50 deletions packages/apps/client/src/lib/mdParser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,7 @@ import { emphasisAndStrong } from './constructs/emphasisAndStrong'
import { escape } from './constructs/escape'
import { paragraph } from './constructs/paragraph'
import { reverse } from './constructs/reverse'

export interface ParserState {
readonly nodeStack: ParentNodeBase[]
nodeCursor: ParentNodeBase | null
buffer: string
charCursor: number
readonly dataStore: Record<string, unknown>
flushBuffer: () => void
pushNode: (node: ParentNodeBase) => void
popNode: () => void
replaceStack: (node: ParentNodeBase) => void
peek: () => string | undefined
consume: () => void
}
import { CharHandler, CharHandlerResult, NodeConstruct, ParserState } from './parserState'

export class ParserStateImpl implements ParserState {
readonly nodeStack: ParentNodeBase[] = []
Expand Down Expand Up @@ -60,27 +47,15 @@ export class ParserStateImpl implements ParserState {
return this.text[this.charCursor + 1]
}
consume = () => {
if (this.text[this.charCursor + 1] === undefined) throw new Error('No more text available to parse')
this.charCursor++
return this.text[this.charCursor]
}
}

export type CharHandler = (char: string, state: ParserState) => void | undefined | boolean

export interface NodeConstruct {
name?: string
char: Record<string, CharHandler>
}

export function astFromMarkdownish(text: string): RootNode {
performance.mark('astFromMarkdownishBegin')

const document: RootNode = {
type: 'root',
children: [],
}

const state = new ParserStateImpl(document, text)
export type Parser = (text: string) => RootNode

export default function createParser(): Parser {
const nodeConstructs: NodeConstruct[] = [paragraph(), escape(), emphasisAndStrong(), reverse()]

const charHandlers: Record<string, CharHandler[]> = {}
Expand All @@ -92,36 +67,47 @@ export function astFromMarkdownish(text: string): RootNode {
}
}

function runAll(handlers: CharHandler[], char: string, state: ParserState): void | undefined | boolean {
function runAll(handlers: CharHandler[], char: string, state: ParserState): void | undefined | CharHandlerResult {
for (const handler of handlers) {
const result = handler(char, state)
if (typeof result === 'boolean') return result
if (typeof result === 'number') return result
}
}

for (state.charCursor = 0; state.charCursor < text.length; state.charCursor++) {
const char = text[state.charCursor]
let preventOthers = false
if (!preventOthers && charHandlers['any']) {
const result = runAll(charHandlers['any'], char, state)
if (result === false) continue
if (result === true) preventOthers = true
return function astFromMarkdownish(text: string): RootNode {
performance.mark('astFromMarkdownishBegin')

const document: RootNode = {
type: 'root',
children: [],
}
if (!preventOthers && charHandlers[char]) {
const result = runAll(charHandlers[char], char, state)
if (result === false) continue
if (result === true) preventOthers = true

const state = new ParserStateImpl(document, text)

for (state.charCursor = 0; state.charCursor < text.length; state.charCursor++) {
const char = text[state.charCursor]
let preventOthers = false
if (!preventOthers && charHandlers['any']) {
const result = runAll(charHandlers['any'], char, state)
if (result === CharHandlerResult.StopProcessingNoBuffer) continue
if (result === CharHandlerResult.StopProcessing) preventOthers = true
}
if (!preventOthers && charHandlers[char]) {
const result = runAll(charHandlers[char], char, state)
if (result === CharHandlerResult.StopProcessingNoBuffer) continue
if (result === CharHandlerResult.StopProcessing) preventOthers = true
}
state.buffer += char
}
state.buffer += char
}

if (charHandlers['end']) runAll(charHandlers['end'], 'end', state)
if (charHandlers['end']) runAll(charHandlers['end'], 'end', state)

performance.mark('astFromMarkdownishEnd')
performance.mark('astFromMarkdownishEnd')

console.log(performance.measure('astFromMarkdownish', 'astFromMarkdownishBegin', 'astFromMarkdownishEnd'))
console.log(performance.measure('astFromMarkdownish', 'astFromMarkdownishBegin', 'astFromMarkdownishEnd'))

console.log(document)
console.log(document)

return document
return document
}
}
41 changes: 41 additions & 0 deletions packages/apps/client/src/lib/mdParser/parserState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ParentNodeBase } from './astNodes'

export interface ParserState {
/** The current stack of nodes as set up leading to the current character */
readonly nodeStack: ParentNodeBase[]
/** The top of the nodeStack */
nodeCursor: ParentNodeBase | null
/** A buffer to collect characters that don't create or mutate nodes (text) */
buffer: string
/** The position of the current character */
charCursor: number
/** An dataStore that can be read and written to by NodeConstruct Handlers. */
readonly dataStore: Record<string, unknown>
/** Create a new text node and append as a child to the node under nodeCursor */
flushBuffer(): void
/** Append a new child node to the node at the top of the nodeStack, and push it onto the nodeStack */
pushNode(node: ParentNodeBase): void
/** Pop a ParentNode from the nodeStack */
popNode(): void
/** Append a new child node to the root node and clear the stack */
replaceStack(node: ParentNodeBase): void
/** Get the character immediately after the current one */
peek(): string | undefined
/** Move the charCursor to the next character */
consume(): string | undefined
}

export enum CharHandlerResult {
/** Stop all processing of this character and append it to the text buffer */
StopProcessing = 1,
/** Stop all processing of this character and don't append it to the text buffer */
StopProcessingNoBuffer = 2,
}

export type CharHandler = (char: string, state: ParserState) => void | undefined | CharHandlerResult

/** A NodeConstruct is a set of character handlers that process a type of a node */
export interface NodeConstruct {
name?: string
char: Record<string, CharHandler>
}
3 changes: 2 additions & 1 deletion packages/apps/client/src/lib/prosemirrorDoc.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Node as ProsemirrorNode } from 'prosemirror-model'
import { schema } from '../ScriptEditor/scriptSchema'
import { Node as MdAstNode } from './mdParser/astNodes'
import { astFromMarkdownish } from './mdParser'
import createMdParser from './mdParser'

export function fromMarkdown(text: string): ProsemirrorNode[] {
const astFromMarkdownish = createMdParser()
const ast = astFromMarkdownish(text)

return traverseMdAstNodes(ast.children)
Expand Down

0 comments on commit 51f8ee5

Please sign in to comment.