@miliastry/quasar 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +119 -0
- package/README.md +45 -0
- package/dist/Visuals/lyne.css +1027 -0
- package/dist/Visuals/osu.css +257 -0
- package/dist/index.d.mts +4563 -0
- package/dist/index.d.ts +4563 -0
- package/dist/index.js +11291 -0
- package/dist/index.mjs +11197 -0
- package/package.json +51 -0
- package/src/Analysis/Contracts/AnalysisReport.ts +26 -0
- package/src/Analysis/Contracts/Contribution.ts +76 -0
- package/src/Analysis/Contracts/Pass.ts +76 -0
- package/src/Analysis/Contracts/PipelineContext.ts +48 -0
- package/src/Analysis/Passes/Analysis/GradientAnalyzer.ts +292 -0
- package/src/Analysis/Passes/Analysis/MergeableColorAnalyzer.ts +112 -0
- package/src/Analysis/Passes/Analysis/RainbowAnalyzer.ts +233 -0
- package/src/Analysis/Passes/Analysis/WaveAnalyzer.ts +211 -0
- package/src/Analysis/Passes/Analysis/__tests__/GradientAnalyzer.test.ts +135 -0
- package/src/Analysis/Passes/Analysis/__tests__/MergeableColorAnalyzer.test.ts +84 -0
- package/src/Analysis/Passes/Analysis/__tests__/RainbowAnalyzer.test.ts +99 -0
- package/src/Analysis/Passes/Analysis/__tests__/WaveAnalyzer.test.ts +119 -0
- package/src/Analysis/Passes/Decision/DefaultDecision.ts +139 -0
- package/src/Analysis/Passes/Decision/__tests__/DefaultDecision.test.ts +179 -0
- package/src/Analysis/Passes/Transform/CollapseGradientTransform.ts +176 -0
- package/src/Analysis/Passes/Transform/MergeColorsTransform.ts +126 -0
- package/src/Analysis/Passes/Transform/RainbowCollapseTransform.ts +83 -0
- package/src/Analysis/Passes/Transform/WaveCollapseTransform.ts +88 -0
- package/src/Analysis/Passes/Utility/CharacterCountAnalyzer.ts +45 -0
- package/src/Analysis/Pipeline/Pipeline.ts +133 -0
- package/src/Analysis/Pipeline/PipelineBuilder.ts +55 -0
- package/src/Analysis/Pipeline/PipelineStage.ts +19 -0
- package/src/Analysis/Utils/color-utils.ts +132 -0
- package/src/Analysis/__tests__/Integration.test.ts +162 -0
- package/src/Analysis/__tests__/Pipeline.test.ts +133 -0
- package/src/Analysis/index.ts +52 -0
- package/src/BBCode/BBCodeDocumentModel.ts +175 -0
- package/src/BBCode/BBCodeToGreenNode.ts +755 -0
- package/src/BBCode/Parser.ts +384 -0
- package/src/BBCode/index.ts +12 -0
- package/src/Collab/positions.ts +91 -0
- package/src/Commands/Command.ts +44 -0
- package/src/Commands/CommandRegistry.ts +78 -0
- package/src/Commands/DeleteNode.ts +20 -0
- package/src/Commands/InsertText.ts +21 -0
- package/src/Commands/SplitMerge.ts +28 -0
- package/src/Commands/WrapInTag.ts +21 -0
- package/src/Commands/index.ts +6 -0
- package/src/Diff/TreeDiffer.ts +264 -0
- package/src/Diff/__tests__/TreeDiffer.test.ts +65 -0
- package/src/Diff/index.ts +2 -0
- package/src/Events/EventBus.ts +160 -0
- package/src/Events/index.ts +2 -0
- package/src/Formatter/Formatter.ts +54 -0
- package/src/Formatter/index.ts +2 -0
- package/src/HTML/HTMLDocumentModel.ts +35 -0
- package/src/HTML/HTMLToGreenNode.ts +290 -0
- package/src/Incremental/ChangeTracker.ts +105 -0
- package/src/Incremental/IncrementalParser.ts +591 -0
- package/src/Incremental/__tests__/IncrementalParser.test.ts +164 -0
- package/src/Incremental/index.ts +4 -0
- package/src/Lexer/BBCodeLexer.ts +382 -0
- package/src/Lexer/Lexer.ts +181 -0
- package/src/Lexer/index.ts +10 -0
- package/src/Linter/Linter.ts +193 -0
- package/src/Linter/index.ts +2 -0
- package/src/Markdown/MarkdownAST.ts +112 -0
- package/src/Markdown/MarkdownDocumentModel.ts +55 -0
- package/src/Markdown/MarkdownLexer.ts +203 -0
- package/src/Markdown/MarkdownParser.ts +455 -0
- package/src/Markdown/MarkdownToGreenNode.ts +153 -0
- package/src/Model/DocumentModel.ts +694 -0
- package/src/Model/NodeFactory.ts +117 -0
- package/src/Model/TagRegistry.ts +495 -0
- package/src/Model/index.ts +5 -0
- package/src/Plugins/PluginAPI.ts +119 -0
- package/src/Plugins/PluginRegistry.ts +132 -0
- package/src/Plugins/index.ts +3 -0
- package/src/Queries/QueryEngine.ts +152 -0
- package/src/Queries/index.ts +1 -0
- package/src/RenderPipeline/RenderPipeline.ts +125 -0
- package/src/RenderPipeline/RenderTree.ts +134 -0
- package/src/RenderPipeline/index.ts +4 -0
- package/src/Semantic/SemanticAnalyzer.ts +506 -0
- package/src/Semantic/index.ts +2 -0
- package/src/Symbols/SymbolTable.ts +124 -0
- package/src/Symbols/index.ts +1 -0
- package/src/Syntax/GreenNode.ts +324 -0
- package/src/Syntax/GreenNodePool.ts +269 -0
- package/src/Syntax/NodeMatcher.ts +370 -0
- package/src/Syntax/RedNode.ts +569 -0
- package/src/Syntax/RedNodeStore.ts +184 -0
- package/src/Syntax/TreeBuilder.ts +214 -0
- package/src/Syntax/__tests__/GreenNode.test.ts +33 -0
- package/src/Syntax/__tests__/RedNode.test.ts +81 -0
- package/src/Syntax/__tests__/RedNodeStore.test.ts +104 -0
- package/src/Syntax/greenEdit.ts +110 -0
- package/src/Syntax/hash.ts +30 -0
- package/src/Syntax/index.ts +12 -0
- package/src/Syntax/partition.ts +161 -0
- package/src/Syntax/preserveNodeIds.ts +201 -0
- package/src/Tests/ASTOptimizerIdempotence.test.ts +77 -0
- package/src/Tests/BlockPatcher.test.ts +437 -0
- package/src/Tests/BlockPatcherWindowed.test.ts +364 -0
- package/src/Tests/BoxDrawer.test.ts +217 -0
- package/src/Tests/BoxRichTitle.test.ts +105 -0
- package/src/Tests/Chars500kBenchmark.test.ts +151 -0
- package/src/Tests/Chars500kEdits.test.ts +321 -0
- package/src/Tests/CollabPositions.test.ts +146 -0
- package/src/Tests/CompilerPathProfiling.test.ts +186 -0
- package/src/Tests/DOMMorpher.test.ts +142 -0
- package/src/Tests/DomPatchPerf.test.ts +60 -0
- package/src/Tests/EffectSegments.snapshot.json +616 -0
- package/src/Tests/EffectSegments.test.ts +68 -0
- package/src/Tests/FindNodeAtOffset.test.ts +65 -0
- package/src/Tests/Fuzzer.test.ts +166 -0
- package/src/Tests/GreenNodePool.test.ts +153 -0
- package/src/Tests/Lexer.test.ts +238 -0
- package/src/Tests/LyneMode.test.ts +187 -0
- package/src/Tests/ModelCoherence.test.ts +180 -0
- package/src/Tests/Partition.test.ts +238 -0
- package/src/Tests/PluginTags.test.ts +150 -0
- package/src/Tests/ProblematicSection.test.ts +46 -0
- package/src/Tests/ProblematicSectionHTML.test.ts +58 -0
- package/src/Tests/RedReuse.test.ts +134 -0
- package/src/Tests/ReproDelete20k.test.ts +62 -0
- package/src/Tests/SemanticValidators.test.ts +136 -0
- package/src/Tests/StableNodeIds.test.ts +210 -0
- package/src/Tests/StudioColorBloat.test.ts +25 -0
- package/src/Tests/StudioDebugText.test.ts +27 -0
- package/src/Tests/StudioTrailingChar.test.ts +25 -0
- package/src/Tests/StudioValidText.test.ts +25 -0
- package/src/Tests/UrlImgBug.test.ts +23 -0
- package/src/Tests/VisualBuilderFidelity.test.ts +105 -0
- package/src/Tests/referenceDocument.ts +119 -0
- package/src/Transactions/Transaction.ts +176 -0
- package/src/Transactions/UndoManager.ts +111 -0
- package/src/Transactions/index.ts +3 -0
- package/src/Transformers/ASTOptimizer.ts +315 -0
- package/src/Transformers/GradientTransformer.ts +143 -0
- package/src/Transformers/GrowTransformer.ts +115 -0
- package/src/Transformers/RainbowTransformer.ts +121 -0
- package/src/Transformers/SineWaveTransformer.ts +130 -0
- package/src/Transformers/Transformer.ts +22 -0
- package/src/Types/core.ts +270 -0
- package/src/Types/diagnostics.ts +156 -0
- package/src/Types/index.ts +23 -0
- package/src/Types/operations.ts +180 -0
- package/src/Types/queries.ts +121 -0
- package/src/Types/symbols.ts +69 -0
- package/src/Types/tokens.ts +186 -0
- package/src/Utils/BBCodeGenerator.ts +126 -0
- package/src/Utils/ColorMath.ts +276 -0
- package/src/Utils/color.ts +112 -0
- package/src/Utils/dom-to-svg.test.ts +86 -0
- package/src/Utils/dom-to-svg.ts +615 -0
- package/src/Utils/treeTransformers.ts +717 -0
- package/src/Visitors/BBBlocksExporter.ts +69 -0
- package/src/Visitors/BBCodeExporter.ts +318 -0
- package/src/Visitors/BlockPatcher.ts +963 -0
- package/src/Visitors/DOMMorpher.ts +134 -0
- package/src/Visitors/HTMLRenderer.ts +1077 -0
- package/src/Visitors/JSONExporter.ts +66 -0
- package/src/Visitors/MarkdownExporter.ts +99 -0
- package/src/Visitors/SVGRenderer.ts +35 -0
- package/src/Visitors/TiptapExporter.ts +145 -0
- package/src/Visitors/Visitor.ts +48 -0
- package/src/Visitors/index.ts +9 -0
- package/src/Visuals/BoxDrawer.ts +175 -0
- package/src/Visuals/index.ts +42 -0
- package/src/Visuals/lyne.css +1027 -0
- package/src/Visuals/osu.css +257 -0
- package/src/index.ts +197 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — Query Types
|
|
3
|
+
*
|
|
4
|
+
* CSS-inspired query selectors for the document tree.
|
|
5
|
+
* Enables queries like: `paragraph > bold`, `quote text`, `[color]`
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { NodeKind, NodeAttributes } from './core'
|
|
9
|
+
import type { RedNode } from '../Syntax/RedNode'
|
|
10
|
+
|
|
11
|
+
// ─── Query ─────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
export type QuerySelector =
|
|
14
|
+
| { type: 'kind'; kind: NodeKind }
|
|
15
|
+
| { type: 'tag'; tag: string }
|
|
16
|
+
| { type: 'attribute'; name: string; value?: string }
|
|
17
|
+
| { type: 'has_attribute'; name: string }
|
|
18
|
+
| { type: 'text'; pattern: string }
|
|
19
|
+
| { type: 'has_child'; selector: QuerySelector }
|
|
20
|
+
| { type: 'has_descendant'; selector: QuerySelector }
|
|
21
|
+
| { type: 'nth_child'; n: number }
|
|
22
|
+
| { type: 'first_child' }
|
|
23
|
+
| { type: 'last_child' }
|
|
24
|
+
| { type: 'position'; min?: number; max?: number }
|
|
25
|
+
| { type: 'depth'; min?: number; max?: number }
|
|
26
|
+
|
|
27
|
+
export type QueryCombinator =
|
|
28
|
+
| 'child' // > direct child
|
|
29
|
+
| 'descendant' // (space) any descendant
|
|
30
|
+
| 'adjacent' // + next sibling
|
|
31
|
+
| 'sibling' // ~ any sibling
|
|
32
|
+
|
|
33
|
+
export interface QueryStep {
|
|
34
|
+
selector: QuerySelector[]
|
|
35
|
+
combinator: QueryCombinator | 'root'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface Query {
|
|
39
|
+
steps: QueryStep[]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface QueryMatch {
|
|
43
|
+
node: RedNode
|
|
44
|
+
/** How well this node matches (for sorting results) */
|
|
45
|
+
score: number
|
|
46
|
+
/** The specific selectors that matched */
|
|
47
|
+
matchedSelectors: string[]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Query Builder ─────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
export function query(selector: string): Query {
|
|
53
|
+
// Parse simple CSS-like selector syntax
|
|
54
|
+
const parts = selector.split(/\s+(?![^\[]*\])\s*/g).filter(Boolean)
|
|
55
|
+
const steps: QueryStep[] = []
|
|
56
|
+
let combinator: QueryCombinator | 'root' = 'root'
|
|
57
|
+
|
|
58
|
+
for (const part of parts) {
|
|
59
|
+
if (part === '>') {
|
|
60
|
+
combinator = 'child'
|
|
61
|
+
continue
|
|
62
|
+
}
|
|
63
|
+
if (part === '+') {
|
|
64
|
+
combinator = 'adjacent'
|
|
65
|
+
continue
|
|
66
|
+
}
|
|
67
|
+
if (part === '~') {
|
|
68
|
+
combinator = 'sibling'
|
|
69
|
+
continue
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const selectors: QuerySelector[] = parseSimpleSelector(part)
|
|
73
|
+
steps.push({ selector: selectors, combinator })
|
|
74
|
+
combinator = 'descendant'
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return { steps }
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function parseSimpleSelector(part: string): QuerySelector[] {
|
|
81
|
+
const selectors: QuerySelector[] = []
|
|
82
|
+
|
|
83
|
+
// Tag/kind selector
|
|
84
|
+
const tagMatch = part.match(/^([a-zA-Z_*][a-zA-Z0-9_-]*)/)
|
|
85
|
+
if (tagMatch) {
|
|
86
|
+
selectors.push({ type: 'tag', tag: tagMatch[1] })
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Attribute selectors: [name], [name=value]
|
|
90
|
+
const attrRegex = /\[([^\]=]+)(?:=([^\]]*))?\]/g
|
|
91
|
+
let attrMatch
|
|
92
|
+
while ((attrMatch = attrRegex.exec(part)) !== null) {
|
|
93
|
+
if (attrMatch[2] !== undefined) {
|
|
94
|
+
selectors.push({ type: 'attribute', name: attrMatch[1], value: attrMatch[2] })
|
|
95
|
+
} else {
|
|
96
|
+
selectors.push({ type: 'has_attribute', name: attrMatch[1] })
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Pseudo-classes
|
|
101
|
+
if (part.includes(':first-child')) {
|
|
102
|
+
selectors.push({ type: 'first_child' })
|
|
103
|
+
}
|
|
104
|
+
if (part.includes(':last-child')) {
|
|
105
|
+
selectors.push({ type: 'last_child' })
|
|
106
|
+
}
|
|
107
|
+
const nthMatch = part.match(/:nth-child\((\d+)\)/)
|
|
108
|
+
if (nthMatch) {
|
|
109
|
+
selectors.push({ type: 'nth_child', n: parseInt(nthMatch[1], 10) })
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return selectors
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// ─── Query Result ─────────────────────────────────────────────
|
|
116
|
+
|
|
117
|
+
export interface QueryResult {
|
|
118
|
+
matches: QueryMatch[]
|
|
119
|
+
total: number
|
|
120
|
+
time: number
|
|
121
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — Symbol Types
|
|
3
|
+
*
|
|
4
|
+
* Symbol system for definitions and references in BBCode documents.
|
|
5
|
+
* Enables: go-to-definition, find-all-references, rename, etc.
|
|
6
|
+
*
|
|
7
|
+
* Inspired by Language Server Protocol (LSP).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { NodeId, NodeKind, SourceRange } from './core'
|
|
11
|
+
|
|
12
|
+
// ─── Symbol Kind ───────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
export type SymbolKind =
|
|
15
|
+
| 'tag'
|
|
16
|
+
| 'id_definition'
|
|
17
|
+
| 'id_reference'
|
|
18
|
+
| 'class'
|
|
19
|
+
| 'property'
|
|
20
|
+
| 'variable'
|
|
21
|
+
| 'function'
|
|
22
|
+
| 'module'
|
|
23
|
+
| 'snippet'
|
|
24
|
+
| 'template'
|
|
25
|
+
| 'other'
|
|
26
|
+
|
|
27
|
+
// ─── Symbol Info ───────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
export interface SymbolInfo {
|
|
30
|
+
/** Unique ID for this symbol */
|
|
31
|
+
id: string
|
|
32
|
+
/** Symbol name (e.g. 'hero', 'my-block') */
|
|
33
|
+
name: string
|
|
34
|
+
/** Symbol kind */
|
|
35
|
+
kind: SymbolKind
|
|
36
|
+
/** The node that defines this symbol */
|
|
37
|
+
definitionNodeId: NodeId
|
|
38
|
+
/** Source range of the definition */
|
|
39
|
+
definitionRange: SourceRange
|
|
40
|
+
/** Container node (e.g. the document) */
|
|
41
|
+
containerNodeId: NodeId
|
|
42
|
+
/** All references to this symbol */
|
|
43
|
+
references: Reference[]
|
|
44
|
+
/** Additional data */
|
|
45
|
+
data?: Record<string, unknown>
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─── Reference ─────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
export interface Reference {
|
|
51
|
+
nodeId: NodeId
|
|
52
|
+
range: SourceRange
|
|
53
|
+
kind: 'definition' | 'reference' | 'implementation'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Symbol Table ──────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
export interface SymbolTableData {
|
|
59
|
+
symbols: Map<string, SymbolInfo>
|
|
60
|
+
/** Quick lookup: node ID → symbol */
|
|
61
|
+
nodeToSymbol: Map<NodeId, string>
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// ─── Symbol Search ─────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
export interface SymbolSearchResult {
|
|
67
|
+
symbol: SymbolInfo
|
|
68
|
+
score: number
|
|
69
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DocumentEngine — Token & Trivia Types
|
|
3
|
+
*
|
|
4
|
+
* Tokens are the output of the Lexer and input to the Parser.
|
|
5
|
+
* Trivia is whitespace, newlines, formatting that is preserved
|
|
6
|
+
* but not semantically meaningful — inspired by Roslyn.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// ─── Trivia ────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export type TriviaKind =
|
|
12
|
+
| 'whitespace'
|
|
13
|
+
| 'newline'
|
|
14
|
+
| 'carriage_return'
|
|
15
|
+
| 'comment'
|
|
16
|
+
| 'line_continuation'
|
|
17
|
+
|
|
18
|
+
export interface Trivia {
|
|
19
|
+
kind: TriviaKind
|
|
20
|
+
text: string
|
|
21
|
+
range: Range
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// ─── Range ─────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
export interface Range {
|
|
27
|
+
start: number
|
|
28
|
+
end: number
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function range(start: number, end: number): Range {
|
|
32
|
+
return { start, end }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function rangeLength(r: Range): number {
|
|
36
|
+
return r.end - r.start
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function rangeContains(r: Range, pos: number): boolean {
|
|
40
|
+
return pos >= r.start && pos < r.end
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ─── Token ─────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
export type TokenKind =
|
|
46
|
+
// Structural
|
|
47
|
+
| 'open_tag'
|
|
48
|
+
| 'close_tag'
|
|
49
|
+
| 'self_closing_tag'
|
|
50
|
+
| 'text'
|
|
51
|
+
| 'newline'
|
|
52
|
+
| 'whitespace'
|
|
53
|
+
// Attribute values (inside tags)
|
|
54
|
+
| 'attribute_name'
|
|
55
|
+
| 'attribute_value'
|
|
56
|
+
| 'equals'
|
|
57
|
+
| 'quote'
|
|
58
|
+
// Special
|
|
59
|
+
| 'end_of_file'
|
|
60
|
+
| 'error'
|
|
61
|
+
|
|
62
|
+
export interface Token {
|
|
63
|
+
kind: TokenKind
|
|
64
|
+
text: string
|
|
65
|
+
range: Range
|
|
66
|
+
|
|
67
|
+
/** Tag name or attribute name if applicable */
|
|
68
|
+
name?: string
|
|
69
|
+
|
|
70
|
+
/** Full raw tag text including brackets: [b], [/color], [*] */
|
|
71
|
+
tagText?: string
|
|
72
|
+
|
|
73
|
+
/** Raw attribute string (everything between tag name and closing ]) */
|
|
74
|
+
attrs?: string
|
|
75
|
+
|
|
76
|
+
/** Trivia attached BEFORE this token (whitespace, newlines) */
|
|
77
|
+
leadingTrivia: Trivia[]
|
|
78
|
+
|
|
79
|
+
/** Trivia attached AFTER this token */
|
|
80
|
+
trailingTrivia: Trivia[]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ─── Token Stream ──────────────────────────────────────────────
|
|
84
|
+
|
|
85
|
+
export interface TokenStream {
|
|
86
|
+
/** Peek at the current token without consuming */
|
|
87
|
+
peek(): Token
|
|
88
|
+
/** Consume and return the current token, advance */
|
|
89
|
+
advance(): Token
|
|
90
|
+
/** Look ahead n tokens (0 = next) */
|
|
91
|
+
lookAhead(n: number): Token
|
|
92
|
+
/** Whether we've consumed all tokens */
|
|
93
|
+
isEof(): boolean
|
|
94
|
+
/** Get current position in the stream */
|
|
95
|
+
position(): number
|
|
96
|
+
/** Reset to a position */
|
|
97
|
+
seek(pos: number): void
|
|
98
|
+
/** Get all remaining tokens */
|
|
99
|
+
remaining(): Token[]
|
|
100
|
+
/** Get all tokens */
|
|
101
|
+
getAll(): Token[]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ─── Token Factory ─────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
export function createToken(
|
|
107
|
+
kind: TokenKind,
|
|
108
|
+
text: string,
|
|
109
|
+
start: number,
|
|
110
|
+
end: number,
|
|
111
|
+
options?: {
|
|
112
|
+
name?: string
|
|
113
|
+
tagText?: string
|
|
114
|
+
attrs?: string
|
|
115
|
+
leadingTrivia?: Trivia[]
|
|
116
|
+
trailingTrivia?: Trivia[]
|
|
117
|
+
},
|
|
118
|
+
): Token {
|
|
119
|
+
return {
|
|
120
|
+
kind,
|
|
121
|
+
text,
|
|
122
|
+
range: range(start, end),
|
|
123
|
+
name: options?.name,
|
|
124
|
+
tagText: options?.tagText,
|
|
125
|
+
attrs: options?.attrs,
|
|
126
|
+
leadingTrivia: options?.leadingTrivia ?? [],
|
|
127
|
+
trailingTrivia: options?.trailingTrivia ?? [],
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function createTrivia(kind: TriviaKind, text: string, start: number, end: number): Trivia {
|
|
132
|
+
return { kind, text, range: range(start, end) }
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ─── Token Stream Implementation ───────────────────────────────
|
|
136
|
+
|
|
137
|
+
export class ArrayTokenStream implements TokenStream {
|
|
138
|
+
private tokens: Token[]
|
|
139
|
+
private pos: number
|
|
140
|
+
|
|
141
|
+
constructor(tokens: Token[]) {
|
|
142
|
+
this.tokens = tokens
|
|
143
|
+
this.pos = 0
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
peek(): Token {
|
|
147
|
+
if (this.pos >= this.tokens.length) {
|
|
148
|
+
return createToken('end_of_file', '', 0, 0)
|
|
149
|
+
}
|
|
150
|
+
return this.tokens[this.pos]
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
advance(): Token {
|
|
154
|
+
const token = this.peek()
|
|
155
|
+
this.pos++
|
|
156
|
+
return token
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
lookAhead(n: number): Token {
|
|
160
|
+
const idx = this.pos + n
|
|
161
|
+
if (idx >= this.tokens.length) {
|
|
162
|
+
return createToken('end_of_file', '', 0, 0)
|
|
163
|
+
}
|
|
164
|
+
return this.tokens[idx]
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
isEof(): boolean {
|
|
168
|
+
return this.pos >= this.tokens.length
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
position(): number {
|
|
172
|
+
return this.pos
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
seek(pos: number): void {
|
|
176
|
+
this.pos = Math.max(0, Math.min(pos, this.tokens.length))
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
remaining(): Token[] {
|
|
180
|
+
return this.tokens.slice(this.pos)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
getAll(): Token[] {
|
|
184
|
+
return [...this.tokens]
|
|
185
|
+
}
|
|
186
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
export type GeneratorTheme = 'forum_post' | 'beatmap_desc' | 'random_chat'
|
|
2
|
+
|
|
3
|
+
export class BBCodeGenerator {
|
|
4
|
+
// ── Osu! Dictionaries ──
|
|
5
|
+
private nouns = ['beatmap', 'circles', 'sliders', 'streams', 'jumps', 'combo', 'PP', 'rank', 'choke', 'mods', 'FC', 'spinner', 'accuracy', 'cursor']
|
|
6
|
+
private adjectives = ['hard', 'insane', 'unranked', 'ranked', 'loved', 'fast', 'tricky', 'technical', 'smooth', 'impossible']
|
|
7
|
+
private verbs = ['click', 'hit', 'miss', 'retry', 'pass', 'fail', 'spin', 'farm']
|
|
8
|
+
private players = ['peppy', 'Cookiezi', 'mrekk', 'Vaxei', 'WhiteCat', 'BTMC']
|
|
9
|
+
private colors = ['red', 'blue', 'green', 'purple', '#FF5555', '#55FF55']
|
|
10
|
+
|
|
11
|
+
private randomInt(max: number): number {
|
|
12
|
+
return Math.floor(Math.random() * max)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
private pick<T>(arr: T[]): T {
|
|
16
|
+
return arr[this.randomInt(arr.length)]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// ── Basic Text Generators ──
|
|
20
|
+
|
|
21
|
+
private generateSentence(): string {
|
|
22
|
+
const templates = [
|
|
23
|
+
`I just got a ${this.pick(this.adjectives)} ${this.pick(this.nouns)} on this map!`,
|
|
24
|
+
`Why are the ${this.pick(this.nouns)} so ${this.pick(this.adjectives)} here?`,
|
|
25
|
+
`Don't forget to ${this.pick(this.verbs)} the ${this.pick(this.nouns)}...`,
|
|
26
|
+
`${this.pick(this.players)} could probably ${this.pick(this.verbs)} this with HardRock.`,
|
|
27
|
+
`My ${this.pick(this.nouns)} is completely ruined after that ${this.pick(this.nouns)}.`
|
|
28
|
+
]
|
|
29
|
+
return this.pick(templates)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private generateParagraph(sentences: number = 3): string {
|
|
33
|
+
let p = ''
|
|
34
|
+
for (let i = 0; i < sentences; i++) {
|
|
35
|
+
p += this.generateSentence() + ' '
|
|
36
|
+
}
|
|
37
|
+
return p.trim()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Formatted Blocks ──
|
|
41
|
+
|
|
42
|
+
private generateHeading(level: 1 | 2 | 3 = 1): string {
|
|
43
|
+
const text = `${this.pick(this.adjectives)} ${this.pick(this.nouns)}!`
|
|
44
|
+
const size = level === 1 ? 150 : level === 2 ? 120 : 100
|
|
45
|
+
return `[size=${size}][b]${text.toUpperCase()}[/b][/size]`
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private generateHighlight(): string {
|
|
49
|
+
return `[color=${this.pick(this.colors)}][b]${this.generateSentence()}[/b][/color]`
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private generateList(items: number = 3): string {
|
|
53
|
+
let list = '[list]\n'
|
|
54
|
+
for (let i = 0; i < items; i++) {
|
|
55
|
+
list += `[*] ${this.pick(this.adjectives)} ${this.pick(this.nouns)}\n`
|
|
56
|
+
}
|
|
57
|
+
list += '[/list]'
|
|
58
|
+
return list
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private generateNotice(): string {
|
|
62
|
+
return `[notice]\n[b]Update:[/b] ${this.generateSentence()}\n[/notice]`
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
private generateBox(): string {
|
|
66
|
+
return `[box=${this.pick(this.nouns)} details]\n${this.generateParagraph(2)}\n[/box]`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── High-Level Document Templates ──
|
|
70
|
+
|
|
71
|
+
public generate(theme: GeneratorTheme = 'forum_post'): string {
|
|
72
|
+
switch (theme) {
|
|
73
|
+
case 'forum_post': return this.generateForumPost()
|
|
74
|
+
case 'beatmap_desc': return this.generateBeatmapDesc()
|
|
75
|
+
case 'random_chat': return this.generateRandomChat()
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
private generateForumPost(): string {
|
|
80
|
+
return `
|
|
81
|
+
${this.generateHeading(1)}
|
|
82
|
+
|
|
83
|
+
Hey everyone!
|
|
84
|
+
${this.generateParagraph(3)}
|
|
85
|
+
|
|
86
|
+
${this.generateHighlight()}
|
|
87
|
+
|
|
88
|
+
Here is what I think about the ${this.pick(this.nouns)}:
|
|
89
|
+
${this.generateList(4)}
|
|
90
|
+
|
|
91
|
+
${this.generateBox()}
|
|
92
|
+
|
|
93
|
+
Thanks for reading!
|
|
94
|
+
[i]- ${this.pick(this.players)}[/i]
|
|
95
|
+
`.trim()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
private generateBeatmapDesc(): string {
|
|
99
|
+
return `
|
|
100
|
+
[center]
|
|
101
|
+
${this.generateHeading(1)}
|
|
102
|
+
[i]Mapped by ${this.pick(this.players)}[/i]
|
|
103
|
+
[/center]
|
|
104
|
+
|
|
105
|
+
${this.generateNotice()}
|
|
106
|
+
|
|
107
|
+
[size=120][b]Difficulties[/b][/size]
|
|
108
|
+
${this.generateList(3)}
|
|
109
|
+
|
|
110
|
+
[size=120][b]Modding[/b][/size]
|
|
111
|
+
${this.generateParagraph(2)}
|
|
112
|
+
If you want to mod, please focus on the ${this.pick(this.adjectives)} ${this.pick(this.nouns)}.
|
|
113
|
+
|
|
114
|
+
[color=#888888]Enjoy the map![/color]
|
|
115
|
+
`.trim()
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private generateRandomChat(): string {
|
|
119
|
+
return `
|
|
120
|
+
[b]PlayerA:[/b] omg I just had a ${this.pick(this.adjectives)} ${this.pick(this.nouns)}
|
|
121
|
+
[b]PlayerB:[/b] lol really? did you ${this.pick(this.verbs)} it?
|
|
122
|
+
[b]PlayerA:[/b] no... ${this.generateHighlight()}
|
|
123
|
+
[b]PlayerB:[/b] rip your ${this.pick(this.nouns)}.
|
|
124
|
+
`.trim()
|
|
125
|
+
}
|
|
126
|
+
}
|