collie 0.1.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.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +333 -0
  6. data/Rakefile +9 -0
  7. data/collie.gemspec +37 -0
  8. data/docs/TUTORIAL.md +588 -0
  9. data/docs/index.html +56 -0
  10. data/docs/playground/README.md +134 -0
  11. data/docs/playground/build-collie-bundle.rb +85 -0
  12. data/docs/playground/css/styles.css +402 -0
  13. data/docs/playground/index.html +146 -0
  14. data/docs/playground/js/app.js +231 -0
  15. data/docs/playground/js/collie-bridge.js +186 -0
  16. data/docs/playground/js/editor.js +129 -0
  17. data/docs/playground/js/examples.js +80 -0
  18. data/docs/playground/js/ruby-runner.js +75 -0
  19. data/docs/playground/test-server.sh +18 -0
  20. data/exe/collie +15 -0
  21. data/lib/collie/analyzer/conflict.rb +114 -0
  22. data/lib/collie/analyzer/reachability.rb +83 -0
  23. data/lib/collie/analyzer/recursion.rb +96 -0
  24. data/lib/collie/analyzer/symbol_table.rb +67 -0
  25. data/lib/collie/ast.rb +183 -0
  26. data/lib/collie/cli.rb +249 -0
  27. data/lib/collie/config.rb +91 -0
  28. data/lib/collie/formatter/formatter.rb +196 -0
  29. data/lib/collie/formatter/options.rb +23 -0
  30. data/lib/collie/linter/base.rb +62 -0
  31. data/lib/collie/linter/registry.rb +34 -0
  32. data/lib/collie/linter/rules/ambiguous_precedence.rb +87 -0
  33. data/lib/collie/linter/rules/circular_reference.rb +89 -0
  34. data/lib/collie/linter/rules/consistent_tag_naming.rb +69 -0
  35. data/lib/collie/linter/rules/duplicate_token.rb +38 -0
  36. data/lib/collie/linter/rules/empty_action.rb +52 -0
  37. data/lib/collie/linter/rules/factorizable_rules.rb +67 -0
  38. data/lib/collie/linter/rules/left_recursion.rb +34 -0
  39. data/lib/collie/linter/rules/long_rule.rb +37 -0
  40. data/lib/collie/linter/rules/missing_start_symbol.rb +38 -0
  41. data/lib/collie/linter/rules/nonterminal_naming.rb +34 -0
  42. data/lib/collie/linter/rules/prec_improvement.rb +54 -0
  43. data/lib/collie/linter/rules/redundant_epsilon.rb +44 -0
  44. data/lib/collie/linter/rules/right_recursion.rb +35 -0
  45. data/lib/collie/linter/rules/token_naming.rb +39 -0
  46. data/lib/collie/linter/rules/trailing_whitespace.rb +46 -0
  47. data/lib/collie/linter/rules/undefined_symbol.rb +55 -0
  48. data/lib/collie/linter/rules/unreachable_rule.rb +49 -0
  49. data/lib/collie/linter/rules/unused_nonterminal.rb +93 -0
  50. data/lib/collie/linter/rules/unused_token.rb +82 -0
  51. data/lib/collie/parser/lexer.rb +349 -0
  52. data/lib/collie/parser/parser.rb +416 -0
  53. data/lib/collie/reporter/github.rb +35 -0
  54. data/lib/collie/reporter/json.rb +52 -0
  55. data/lib/collie/reporter/text.rb +97 -0
  56. data/lib/collie/version.rb +5 -0
  57. data/lib/collie.rb +52 -0
  58. metadata +145 -0
@@ -0,0 +1,134 @@
1
+ # Collie Playground
2
+
3
+ An interactive web-based playground for trying out Collie, the linter and formatter for Lrama Style BNF grammar files.
4
+
5
+ ## Features
6
+
7
+ - Interactive Monaco Editor (same as VS Code)
8
+ - Real-time linting with all 18 built-in rules
9
+ - Grammar formatting
10
+ - Auto-fix functionality
11
+ - Yacc/Bison/Lrama syntax highlighting
12
+ - Pre-loaded example grammar files
13
+ - Runs completely in the browser using Ruby.wasm
14
+
15
+ ## How It Works
16
+
17
+ The playground uses [Ruby.wasm](https://github.com/ruby/ruby.wasm) to run Ruby code directly in your browser. All Collie functionality (parsing, linting, formatting) executes client-side - your code never leaves your browser.
18
+
19
+ ### Architecture
20
+
21
+ ```
22
+ Browser
23
+ ├── Monaco Editor (UI)
24
+ ├── Ruby.wasm (Ruby runtime)
25
+ ├── collie-bundle.rb (All Collie code)
26
+ └── JavaScript bridge (UI ↔ Ruby)
27
+ ```
28
+
29
+ ## Local Development
30
+
31
+ ### Prerequisites
32
+
33
+ - Ruby 3.2 or higher
34
+ - Modern web browser
35
+
36
+ ### Setup
37
+
38
+ 1. Generate the Collie bundle:
39
+ ```bash
40
+ ruby build-collie-bundle.rb
41
+ ```
42
+
43
+ 2. Serve the playground locally:
44
+ ```bash
45
+ # Using Ruby
46
+ ruby -run -ehttpd . -p 8000
47
+
48
+ # Using Python
49
+ python3 -m http.server 8000
50
+
51
+ # Using Node.js (if you have http-server installed)
52
+ npx http-server -p 8000
53
+ ```
54
+
55
+ 3. Open http://localhost:8000/ in your browser
56
+
57
+ ### Rebuilding the Bundle
58
+
59
+ Whenever Collie's code changes, rebuild the bundle:
60
+
61
+ ```bash
62
+ ruby build-collie-bundle.rb
63
+ ```
64
+
65
+ ## Deployment
66
+
67
+ The playground is automatically deployed to GitHub Pages via GitHub Actions when changes are pushed to the main branch.
68
+
69
+ ### GitHub Actions Workflow
70
+
71
+ See `.github/workflows/deploy-playground.yml` for the deployment configuration.
72
+
73
+ The workflow:
74
+ 1. Checks out the repository
75
+ 2. Sets up Ruby
76
+ 3. Builds the Collie bundle
77
+ 4. Deploys to GitHub Pages
78
+
79
+ ### Accessing the Deployed Playground
80
+
81
+ Once deployed, the playground will be available at:
82
+ ```
83
+ https://ydah.github.io/collie/playground/
84
+ ```
85
+
86
+ ## Files
87
+
88
+ - `index.html` - Main HTML page
89
+ - `css/styles.css` - Stylesheet
90
+ - `js/app.js` - Main application logic
91
+ - `js/editor.js` - Monaco Editor integration
92
+ - `js/ruby-runner.js` - Ruby.wasm wrapper
93
+ - `js/collie-bridge.js` - JavaScript ↔ Ruby bridge
94
+ - `js/examples.js` - Example grammar files
95
+ - `collie-bundle.rb` - Bundled Collie code (generated)
96
+ - `build-collie-bundle.rb` - Bundle generation script
97
+
98
+ ## Limitations
99
+
100
+ - First load may be slow (Ruby.wasm is ~30MB)
101
+ - Large grammar files (>10,000 lines) may be slow
102
+ - Some Ruby features may not work in WebAssembly
103
+
104
+ ## Troubleshooting
105
+
106
+ ### "Failed to initialize Ruby.wasm"
107
+
108
+ - Ensure you have a stable internet connection (Ruby.wasm is loaded from CDN)
109
+ - Try refreshing the page
110
+ - Clear browser cache
111
+
112
+ ### "Collie bundle not found"
113
+
114
+ - Run `ruby build-collie-bundle.rb` to generate the bundle
115
+ - Ensure `collie-bundle.rb` exists in the playground directory
116
+
117
+ ### Linting/Formatting doesn't work
118
+
119
+ - Check browser console for JavaScript errors
120
+ - Ensure the bundle was generated correctly
121
+ - Try with a simple example first
122
+
123
+ ## Contributing
124
+
125
+ To add new features or fix bugs:
126
+
127
+ 1. Make changes to the playground files
128
+ 2. Test locally using a local web server
129
+ 3. If you changed Collie code, rebuild the bundle
130
+ 4. Submit a pull request
131
+
132
+ ## License
133
+
134
+ MIT License - Same as Collie
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "pathname"
5
+
6
+ OUTPUT_FILE = Pathname.new(__dir__) / "collie-bundle.rb"
7
+ LIB_DIR = Pathname.new(__dir__) / ".." / ".." / "lib"
8
+
9
+ files = [
10
+ "collie/version.rb",
11
+ "collie/ast.rb",
12
+ "collie/parser/lexer.rb",
13
+ "collie/parser/parser.rb",
14
+ "collie/analyzer/symbol_table.rb",
15
+ "collie/analyzer/reachability.rb",
16
+ "collie/analyzer/recursion.rb",
17
+ "collie/analyzer/conflict.rb",
18
+ "collie/linter/base.rb",
19
+ "collie/linter/registry.rb",
20
+ "collie/linter/rules/duplicate_token.rb",
21
+ "collie/linter/rules/undefined_symbol.rb",
22
+ "collie/linter/rules/unreachable_rule.rb",
23
+ "collie/linter/rules/circular_reference.rb",
24
+ "collie/linter/rules/missing_start_symbol.rb",
25
+ "collie/linter/rules/unused_nonterminal.rb",
26
+ "collie/linter/rules/unused_token.rb",
27
+ "collie/linter/rules/left_recursion.rb",
28
+ "collie/linter/rules/right_recursion.rb",
29
+ "collie/linter/rules/ambiguous_precedence.rb",
30
+ "collie/linter/rules/token_naming.rb",
31
+ "collie/linter/rules/nonterminal_naming.rb",
32
+ "collie/linter/rules/consistent_tag_naming.rb",
33
+ "collie/linter/rules/trailing_whitespace.rb",
34
+ "collie/linter/rules/empty_action.rb",
35
+ "collie/linter/rules/long_rule.rb",
36
+ "collie/linter/rules/factorizable_rules.rb",
37
+ "collie/linter/rules/redundant_epsilon.rb",
38
+ "collie/linter/rules/prec_improvement.rb",
39
+ "collie/formatter/options.rb",
40
+ "collie/formatter/formatter.rb",
41
+ "collie/reporter/text.rb",
42
+ "collie/reporter/json.rb",
43
+ "collie/reporter/github.rb",
44
+ "collie/config.rb",
45
+ "collie.rb"
46
+ ]
47
+
48
+ output = []
49
+ output << "# frozen_string_literal: true"
50
+ output << ""
51
+ output << "# Collie Bundle for Ruby.wasm Playground"
52
+ output << "# Auto-generated - DO NOT EDIT"
53
+ output << "# Generated at: #{Time.now}"
54
+ output << ""
55
+
56
+ files.each do |file_path|
57
+ full_path = LIB_DIR / file_path
58
+ unless full_path.exist?
59
+ warn "Warning: #{file_path} not found, skipping"
60
+ next
61
+ end
62
+
63
+ output << "# === #{file_path} ==="
64
+ content = File.read(full_path)
65
+
66
+ content = content.gsub(/^# frozen_string_literal: true\n/, "")
67
+ content = content.gsub(/^require ['"]collie.*$\n/, "")
68
+ content = content.gsub(/^require_relative .*$\n/, "")
69
+ content = content.gsub(/^require ["']pastel["']\n/, "")
70
+ content = content.gsub(/^require ["']thor["']\n/, "")
71
+ content = content.gsub(/^require ["']tty-table["']\n/, "")
72
+
73
+ content = content.gsub(
74
+ /begin\s+require ["']pastel["']\s+PASTEL_AVAILABLE = true\s+rescue LoadError\s+PASTEL_AVAILABLE = false\s+end/m,
75
+ "PASTEL_AVAILABLE = false"
76
+ )
77
+
78
+ output << content
79
+ output << ""
80
+ end
81
+
82
+ File.write(OUTPUT_FILE, output.join("\n"))
83
+ puts "Bundle created: #{OUTPUT_FILE}"
84
+ puts "Size: #{File.size(OUTPUT_FILE)} bytes (#{(File.size(OUTPUT_FILE) / 1024.0).round(2)} KB)"
85
+ puts "Files bundled: #{files.size}"
@@ -0,0 +1,402 @@
1
+ :root {
2
+ --primary-color: #4a90e2;
3
+ --success-color: #52c41a;
4
+ --error-color: #f5222d;
5
+ --warning-color: #faad14;
6
+ --info-color: #1890ff;
7
+ --bg-color: #f5f5f5;
8
+ --panel-bg: #ffffff;
9
+ --border-color: #d9d9d9;
10
+ --text-color: #262626;
11
+ --text-secondary: #8c8c8c;
12
+ }
13
+
14
+ * {
15
+ margin: 0;
16
+ padding: 0;
17
+ box-sizing: border-box;
18
+ }
19
+
20
+ body {
21
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
22
+ background: var(--bg-color);
23
+ color: var(--text-color);
24
+ line-height: 1.6;
25
+ }
26
+
27
+ .container {
28
+ max-width: 1400px;
29
+ margin: 0 auto;
30
+ padding: 0 20px;
31
+ }
32
+
33
+ /* Header */
34
+ .header {
35
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
36
+ color: white;
37
+ padding: 2rem 0;
38
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
39
+ }
40
+
41
+ .header h1 {
42
+ font-size: 2.5rem;
43
+ margin-bottom: 0.5rem;
44
+ }
45
+
46
+ .header p {
47
+ font-size: 1.1rem;
48
+ opacity: 0.9;
49
+ margin-bottom: 1rem;
50
+ }
51
+
52
+ .header-actions {
53
+ display: flex;
54
+ gap: 1rem;
55
+ }
56
+
57
+ /* Loading Screen */
58
+ .loading {
59
+ display: flex;
60
+ align-items: center;
61
+ justify-content: center;
62
+ min-height: 60vh;
63
+ text-align: center;
64
+ }
65
+
66
+ .loading-content {
67
+ max-width: 400px;
68
+ }
69
+
70
+ .spinner {
71
+ width: 60px;
72
+ height: 60px;
73
+ margin: 0 auto 1rem;
74
+ border: 4px solid rgba(0, 0, 0, 0.1);
75
+ border-left-color: var(--primary-color);
76
+ border-radius: 50%;
77
+ animation: spin 1s linear infinite;
78
+ }
79
+
80
+ @keyframes spin {
81
+ to { transform: rotate(360deg); }
82
+ }
83
+
84
+ .loading-detail {
85
+ color: var(--text-secondary);
86
+ font-size: 0.9rem;
87
+ margin-top: 0.5rem;
88
+ }
89
+
90
+ /* Main Content */
91
+ .main-content {
92
+ padding: 2rem 0;
93
+ }
94
+
95
+ /* Toolbar */
96
+ .toolbar {
97
+ display: flex;
98
+ justify-content: space-between;
99
+ align-items: center;
100
+ background: var(--panel-bg);
101
+ padding: 1rem;
102
+ border-radius: 8px;
103
+ margin-bottom: 1rem;
104
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
105
+ }
106
+
107
+ .toolbar-left,
108
+ .toolbar-right {
109
+ display: flex;
110
+ gap: 0.5rem;
111
+ }
112
+
113
+ /* Buttons */
114
+ .btn {
115
+ padding: 0.5rem 1rem;
116
+ border: none;
117
+ border-radius: 4px;
118
+ font-size: 0.9rem;
119
+ font-weight: 500;
120
+ cursor: pointer;
121
+ transition: all 0.2s;
122
+ text-decoration: none;
123
+ display: inline-block;
124
+ }
125
+
126
+ .btn-primary {
127
+ background: var(--primary-color);
128
+ color: white;
129
+ }
130
+
131
+ .btn-primary:hover {
132
+ background: #3a7bc8;
133
+ }
134
+
135
+ .btn-success {
136
+ background: var(--success-color);
137
+ color: white;
138
+ }
139
+
140
+ .btn-success:hover {
141
+ background: #49b318;
142
+ }
143
+
144
+ .btn-secondary {
145
+ background: #fff;
146
+ color: var(--text-color);
147
+ border: 1px solid var(--border-color);
148
+ }
149
+
150
+ .btn-secondary:hover {
151
+ background: #fafafa;
152
+ }
153
+
154
+ .btn:disabled {
155
+ opacity: 0.5;
156
+ cursor: not-allowed;
157
+ }
158
+
159
+ /* Select */
160
+ .select {
161
+ padding: 0.5rem;
162
+ border: 1px solid var(--border-color);
163
+ border-radius: 4px;
164
+ font-size: 0.9rem;
165
+ background: white;
166
+ cursor: pointer;
167
+ }
168
+
169
+ /* Workspace */
170
+ .workspace {
171
+ display: grid;
172
+ grid-template-columns: 1fr 1fr;
173
+ gap: 1rem;
174
+ margin-bottom: 1rem;
175
+ }
176
+
177
+ @media (max-width: 1024px) {
178
+ .workspace {
179
+ grid-template-columns: 1fr;
180
+ }
181
+ }
182
+
183
+ /* Panel */
184
+ .panel {
185
+ background: var(--panel-bg);
186
+ border-radius: 8px;
187
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
188
+ overflow: hidden;
189
+ display: flex;
190
+ flex-direction: column;
191
+ }
192
+
193
+ .panel-header {
194
+ display: flex;
195
+ justify-content: space-between;
196
+ align-items: center;
197
+ padding: 1rem;
198
+ border-bottom: 1px solid var(--border-color);
199
+ background: #fafafa;
200
+ }
201
+
202
+ .panel-header h2 {
203
+ font-size: 1.1rem;
204
+ font-weight: 600;
205
+ }
206
+
207
+ .panel-info {
208
+ color: var(--text-secondary);
209
+ font-size: 0.9rem;
210
+ }
211
+
212
+ /* Tabs */
213
+ .output-tabs {
214
+ display: flex;
215
+ gap: 0.5rem;
216
+ }
217
+
218
+ .tab-btn {
219
+ padding: 0.4rem 0.8rem;
220
+ border: none;
221
+ background: transparent;
222
+ cursor: pointer;
223
+ font-size: 0.9rem;
224
+ color: var(--text-secondary);
225
+ border-bottom: 2px solid transparent;
226
+ transition: all 0.2s;
227
+ }
228
+
229
+ .tab-btn:hover {
230
+ color: var(--text-color);
231
+ }
232
+
233
+ .tab-btn.active {
234
+ color: var(--primary-color);
235
+ border-bottom-color: var(--primary-color);
236
+ }
237
+
238
+ .tab-content {
239
+ display: none;
240
+ flex: 1;
241
+ overflow: auto;
242
+ }
243
+
244
+ .tab-content.active {
245
+ display: block;
246
+ }
247
+
248
+ /* Editor */
249
+ .editor-container {
250
+ height: 500px;
251
+ border-top: 1px solid var(--border-color);
252
+ }
253
+
254
+ /* Output */
255
+ .output-content {
256
+ padding: 1rem;
257
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
258
+ font-size: 0.9rem;
259
+ height: 500px;
260
+ overflow: auto;
261
+ }
262
+
263
+ .placeholder {
264
+ color: var(--text-secondary);
265
+ text-align: center;
266
+ padding: 2rem;
267
+ }
268
+
269
+ /* Diagnostics */
270
+ .diagnostic-item {
271
+ padding: 0.75rem;
272
+ margin-bottom: 0.5rem;
273
+ border-left: 4px solid;
274
+ background: #fafafa;
275
+ border-radius: 4px;
276
+ }
277
+
278
+ .diagnostic-item.error {
279
+ border-left-color: var(--error-color);
280
+ background: #fff2f0;
281
+ }
282
+
283
+ .diagnostic-item.warning {
284
+ border-left-color: var(--warning-color);
285
+ background: #fffbe6;
286
+ }
287
+
288
+ .diagnostic-item.convention {
289
+ border-left-color: var(--info-color);
290
+ background: #e6f7ff;
291
+ }
292
+
293
+ .diagnostic-item.info {
294
+ border-left-color: var(--text-secondary);
295
+ background: #f0f0f0;
296
+ }
297
+
298
+ .diagnostic-location {
299
+ font-weight: 600;
300
+ color: var(--text-color);
301
+ margin-bottom: 0.25rem;
302
+ }
303
+
304
+ .diagnostic-message {
305
+ color: var(--text-secondary);
306
+ }
307
+
308
+ .diagnostic-rule {
309
+ display: inline-block;
310
+ padding: 0.2rem 0.5rem;
311
+ background: white;
312
+ border-radius: 3px;
313
+ font-size: 0.85rem;
314
+ margin-top: 0.25rem;
315
+ }
316
+
317
+ .no-offenses {
318
+ color: var(--success-color);
319
+ font-weight: 600;
320
+ text-align: center;
321
+ padding: 2rem;
322
+ }
323
+
324
+ /* Rules List */
325
+ .rule-item {
326
+ padding: 0.75rem;
327
+ margin-bottom: 0.5rem;
328
+ border: 1px solid var(--border-color);
329
+ border-radius: 4px;
330
+ background: white;
331
+ }
332
+
333
+ .rule-header {
334
+ display: flex;
335
+ justify-content: space-between;
336
+ align-items: center;
337
+ margin-bottom: 0.5rem;
338
+ }
339
+
340
+ .rule-name {
341
+ font-weight: 600;
342
+ color: var(--primary-color);
343
+ }
344
+
345
+ .rule-severity {
346
+ display: inline-block;
347
+ padding: 0.2rem 0.5rem;
348
+ border-radius: 3px;
349
+ font-size: 0.8rem;
350
+ font-weight: 500;
351
+ }
352
+
353
+ .rule-severity.error {
354
+ background: #fff2f0;
355
+ color: var(--error-color);
356
+ }
357
+
358
+ .rule-severity.warning {
359
+ background: #fffbe6;
360
+ color: var(--warning-color);
361
+ }
362
+
363
+ .rule-severity.convention {
364
+ background: #e6f7ff;
365
+ color: var(--info-color);
366
+ }
367
+
368
+ .rule-severity.info {
369
+ background: #f0f0f0;
370
+ color: var(--text-secondary);
371
+ }
372
+
373
+ .rule-description {
374
+ color: var(--text-secondary);
375
+ font-size: 0.9rem;
376
+ }
377
+
378
+ /* Info Panel */
379
+ .info-panel {
380
+ background: var(--panel-bg);
381
+ padding: 1.5rem;
382
+ border-radius: 8px;
383
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
384
+ }
385
+
386
+ .info-panel h3 {
387
+ margin-bottom: 1rem;
388
+ }
389
+
390
+ .info-panel p {
391
+ margin-bottom: 1rem;
392
+ color: var(--text-secondary);
393
+ }
394
+
395
+ .info-panel a {
396
+ color: var(--primary-color);
397
+ text-decoration: none;
398
+ }
399
+
400
+ .info-panel a:hover {
401
+ text-decoration: underline;
402
+ }