feat: poc of the grammar

This commit is contained in:
Kacper Kula 2025-02-13 11:32:43 +00:00
parent a335444d73
commit d761e33c38
5 changed files with 186 additions and 0 deletions

View file

@ -61,6 +61,7 @@
"jsonpath": "^1.1.1",
"lodash": "^4.17.21",
"markdown-table-ts": "^1.0.3",
"ohm-js": "^17.1.0",
"papaparse": "^5.4.1",
"sql-parser-cst": "^0.32.0",
"unidecode": "^1.1.0",

View file

@ -56,6 +56,9 @@ importers:
markdown-table-ts:
specifier: ^1.0.3
version: 1.0.3
ohm-js:
specifier: ^17.1.0
version: 17.1.0
papaparse:
specifier: ^5.4.1
version: 5.4.1
@ -2616,6 +2619,10 @@ packages:
'@codemirror/state': ^6.0.0
'@codemirror/view': ^6.0.0
ohm-js@17.1.0:
resolution: {integrity: sha512-xc3B5dgAjTBQGHaH7B58M2Pmv6WvzrJ/3/7LeUzXNg0/sY3jQPdSd/S2SstppaleO77rifR1tyhdfFGNIwxf2Q==}
engines: {node: '>=0.12.1'}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@ -6156,6 +6163,8 @@ snapshots:
'@types/codemirror': 5.60.8
moment: 2.29.4
ohm-js@17.1.0: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2

22
src/grammar/ohmGrammar.ts Normal file
View file

@ -0,0 +1,22 @@
/***
SQLSealLang {
Grammar = (GrammarEntry | Flag)* SelectStmt*
SelectStmt = ("WITH" | "SELECT") any+
GrammarEntry = TableExpression | RendererExpression
Flag = "REPEAT" | "NO REPEAT" | "EXPLAIN"
TableExpression = "TABLE" identifier "=" "file(" filename ")"
identifier = alnum+
filename = (alnum | ".")+
RendererExpression = "GRID" anyObject -- grid
| "HTML" -- html
| "MARKDOWN" --markdown
NewLine = "\n"
sp = " "
blank = sp* nl // blank line has only newline
endline = (~nl any)+ end
objectContent = (alnum | "." | ":" | "," | sp | nl | tab)+
anyObject = "{" (~"}" any)* "}"
nl = "\n" // new line
tab = "\t"
}
*/

View file

@ -0,0 +1,65 @@
import { parse } from './parser'
describe('Ohm parser', () => {
it('should parse simple table expression', () => {
expect(parse('TABLE x = file(a.csv)')).toEqual({
query: '',
tables: [{ filename: 'a.csv', type: 'csv', identifier: 'x'}],
flags: {
explain: false,
refresh: true
},
renderer: {
name: 'GRID',
options: ''
}
})
})
it('should parse SELECT statement alone', () => {
expect(parse('SELECT * FROM files')).toEqual({
query: 'SELECT * FROM files',
tables: [],
flags: { explain: false, refresh: true },
renderer: { name: 'GRID', options: '' }
})
})
it('should not parse incorrect statement', () => {
expect(() => parse('this is not valid entry')).toThrow()
})
it('should parse statement with table and select', () => {
expect(parse(`TABLE x = file(a.csv)
SELECT * FROM x`)).toEqual({
tables: [{ type: 'csv', filename: 'a.csv', identifier: 'x' }],
flags: { explain: false, refresh: true },
renderer: { name: 'GRID', options: '' },
query: 'SELECT * FROM x'
})
})
it('should have table and flags', () => {
expect(parse(`
TABLE x = file(a.csv)
REFRESH
EXPLAIN
SELECT * FROM x`)).toEqual({
tables: [{
filename: 'a.csv',
identifier: 'x',
type: 'csv'
}],
renderer: {
name: 'GRID',
options: ''
},
flags: {
explain: false,
refresh: true
},
query: 'SELECT * FROM x'
})
})
})

89
src/grammar/parser.ts Normal file
View file

@ -0,0 +1,89 @@
import * as ohm from 'ohm-js';
const SQLSealGrammar = String.raw`
SQLSealLang {
Grammar = (TableExpression | RendererExpression | FlagExpression)* SelectStmt*
SelectStmt = ("WITH" | "SELECT") any+
FlagExpression = "REFRESH" -- refresh
| "NO REFRESH" -- norefresh
| "EXPLAIN" -- explain
TableExpression = "TABLE" identifier "=" "file(" filename ")"
identifier = alnum+
filename = (alnum | ".")+
RendererExpression = "GRID" anyObject -- grid
| "HTML" -- html
| "MARKDOWN" --markdown
anyObject = "{" (~"}" any)* "}"
}
`
const generateSemantic = (grammar: ohm.Grammar) => {
const s = grammar.createSemantics()
s.addOperation<any>('toObject', {
Grammar: (entries, selectStatement) => {
console.log('GRAMMAR', entries, selectStatement)
const res = {
flags: {
refresh: true,
explain: false
},
renderer: {
name: 'GRID',
options: ''
},
tables: [] as any[],
query: ''
}
if (entries.children.length) {
entries.children.forEach(c => {
console.log(c.ctorName)
switch (c.ctorName) {
case 'TableExpression':
res.tables.push(c.toObject())
break;
case 'RendererExpression':
res.renderer = c.toObject()
break
case 'Flag':
res.flags = {...res.flags, ...c.toObject()}
break
}
})
}
if (selectStatement) {
res.query = selectStatement.sourceString
}
return res
},
TableExpression: (_table, identifier, _eq, _file, filename, _close) => {
return { identifier: identifier.sourceString , filename: filename.sourceString, type: 'csv' }
},
FlagExpression_refresh: (v) => {
return { refresh: true }
},
FlagExpression_norefresh: (v) => {
return {refresh: false}
},
FlagExpression_explain: (v) => {
return { explain: true }
}
})
return s
}
export const parse = (query: string) => {
const grammar = ohm.grammar(SQLSealGrammar)
const match = grammar.match(query)
if (match.succeeded()) {
// Converting
const s = generateSemantic(grammar)(match)
return s.toObject()
} else {
throw new Error(match.message || 'Unknown parsing error')
}
}