diff --git a/cst-visitor.js b/cst-visitor.js index f03a7fa..86c3ca8 100644 --- a/cst-visitor.js +++ b/cst-visitor.js @@ -46,18 +46,29 @@ class SQLToAstVisitor extends BaseSQLVisitor { fromClause: from, whereClause: where, orderByClause: orderBy, - limitClause: limit, + limitClause: limit } } selectClause(ctx) { // Each Terminal or Non-Terminal in a grammar rule are collected into // an array with the same name(key) in the ctx object. - const columns = ctx.Identifier.map(identToken => identToken.image) - - return { - type: 'SELECT_CLAUSE', - columns: columns, + if(ctx.Identifier) { + const columns = ctx.Identifier.map(identToken => identToken.image) + + return { + type: 'SELECT_CLAUSE', + columns: columns, + minMax: minmax + } + } else { + const column = ctx.minMaxExpression[0].children.Identifier[0].image + const minMax = this.visit(ctx.minMaxExpression) + return { + type: 'SELECT_CLAUSE', + columns: [column], // return an array for consistency + minMax: minMax + } } } @@ -104,6 +115,13 @@ class SQLToAstVisitor extends BaseSQLVisitor { return ctx.Asc[0].image } + minMaxExpression(ctx) { + if(ctx.Min) { + return ctx.Min[0].image + } + return ctx.Max[0].image + } + expression(ctx) { // Note the usage of the "rhs" and "lhs" labels defined in step 2 in the expression rule. const lhs = this.visit(ctx.lhs[0]) diff --git a/generated_diagrams.html b/generated_diagrams.html index a45514b..48fe5c1 100644 --- a/generated_diagrams.html +++ b/generated_diagrams.html @@ -73,7 +73,7 @@ { "type": "Rule", "name": "selectClause", - "orgText": "() => {\r\n $.CONSUME(Select)\r\n $.AT_LEAST_ONE_SEP({\r\n SEP: Comma,\r\n DEF: () => {\r\n $.CONSUME(Identifier)\r\n },\r\n })\r\n }", + "orgText": "() => {\r\n $.CONSUME(Select)\r\n $.OR([\r\n { ALT: () => {\r\n $.AT_LEAST_ONE_SEP({\r\n SEP: Comma,\r\n DEF: () => {\r\n $.CONSUME(Identifier)\r\n },\r\n })\r\n }\r\n },\r\n { ALT: () => {\r\n $.SUBRULE($.minMaxExpression)\r\n }\r\n }\r\n ])\r\n }", "definition": [ { "type": "Terminal", @@ -83,27 +83,106 @@ "pattern": "SELECT" }, { - "type": "RepetitionMandatoryWithSeparator", + "type": "Alternation", "idx": 0, - "separator": { - "type": "Terminal", - "name": "Comma", - "label": "Comma", - "idx": 1, - "pattern": "," - }, "definition": [ { - "type": "Terminal", - "name": "Identifier", - "label": "Identifier", - "idx": 0, - "pattern": "\\*|\\w+" + "type": "Flat", + "definition": [ + { + "type": "RepetitionMandatoryWithSeparator", + "idx": 0, + "separator": { + "type": "Terminal", + "name": "Comma", + "label": "Comma", + "idx": 1, + "pattern": "," + }, + "definition": [ + { + "type": "Terminal", + "name": "Identifier", + "label": "Identifier", + "idx": 0, + "pattern": "\\*|[a-zA-Z]\\w*" + } + ] + } + ] + }, + { + "type": "Flat", + "definition": [ + { + "type": "NonTerminal", + "name": "minMaxExpression", + "idx": 0 + } + ] } ] } ] }, + { + "type": "Rule", + "name": "minMaxExpression", + "orgText": "() => {\r\n $.OR([\r\n { ALT: () => $.CONSUME(Min) },\r\n { ALT: () => $.CONSUME(Max) }\r\n ])\r\n $.CONSUME(LParen)\r\n $.CONSUME(Identifier)\r\n $.CONSUME(RParen)\r\n }", + "definition": [ + { + "type": "Alternation", + "idx": 0, + "definition": [ + { + "type": "Flat", + "definition": [ + { + "type": "Terminal", + "name": "Min", + "label": "Min", + "idx": 0, + "pattern": "MIN" + } + ] + }, + { + "type": "Flat", + "definition": [ + { + "type": "Terminal", + "name": "Max", + "label": "Max", + "idx": 0, + "pattern": "MAX" + } + ] + } + ] + }, + { + "type": "Terminal", + "name": "LParen", + "label": "LParen", + "idx": 0, + "pattern": "\\(" + }, + { + "type": "Terminal", + "name": "Identifier", + "label": "Identifier", + "idx": 0, + "pattern": "\\*|[a-zA-Z]\\w*" + }, + { + "type": "Terminal", + "name": "RParen", + "label": "RParen", + "idx": 0, + "pattern": "\\)" + } + ] + }, { "type": "Rule", "name": "fromClause", @@ -121,7 +200,7 @@ "name": "Identifier", "label": "Identifier", "idx": 0, - "pattern": "\\*|\\w+" + "pattern": "\\*|[a-zA-Z]\\w*" } ] }, @@ -161,7 +240,7 @@ "name": "Identifier", "label": "Identifier", "idx": 0, - "pattern": "\\*|\\w+" + "pattern": "\\*|[a-zA-Z]\\w*" }, { "type": "Option", @@ -193,7 +272,7 @@ "name": "Integer", "label": "Integer", "idx": 0, - "pattern": "0|[1-9]\\d+" + "pattern": "0|[1-9]\\d*" } ] }, @@ -236,7 +315,7 @@ "name": "Integer", "label": "Integer", "idx": 0, - "pattern": "0|[1-9]\\d+" + "pattern": "0|[1-9]\\d*" } ] }, @@ -248,7 +327,7 @@ "name": "Identifier", "label": "Identifier", "idx": 0, - "pattern": "\\*|\\w+" + "pattern": "\\*|[a-zA-Z]\\w*" } ] } diff --git a/lexer.js b/lexer.js index 055efc9..a9f8df0 100644 --- a/lexer.js +++ b/lexer.js @@ -41,6 +41,18 @@ const Limit = createToken({ longer_alt: Identifier, }) +const Min = createToken({ + name: 'Min', + pattern: /MIN/i, + longer_alt: Identifier +}) + +const Max = createToken({ + name: 'Max', + pattern: /MAX/i, + longer_alt: Identifier +}) + const Comma = createToken({ name: 'Comma', pattern: /,/ }) const Integer = createToken({ name: 'Integer', pattern: /0|[1-9]\d*/ }) const GreaterThanEqual = createToken({ @@ -65,6 +77,16 @@ const Desc = createToken({ const Equal = createToken({ name: 'Equal', pattern: /=/ }) const NotEqual = createToken({ name: 'NotEqual', pattern: /<>/ }) +const LParen = createToken({ + name: 'LParen', + pattern: /\(/ +}) + +const RParen = createToken({ + name: 'RParen', + pattern: /\)/ +}) + const WhiteSpace = createToken({ name: 'WhiteSpace', pattern: /\s+/, @@ -82,6 +104,8 @@ let allTokens = [ Comma, Asc, Desc, + Min, + Max, // The Identifier must appear after the keywords because all keywords are valid identifiers. Identifier, Integer, @@ -91,6 +115,8 @@ let allTokens = [ GreaterThan, LessThanEqual, LessThan, + LParen, + RParen ] const SelectLexer = new Lexer(allTokens) diff --git a/parser.js b/parser.js index 1c7dec5..4feb15c 100644 --- a/parser.js +++ b/parser.js @@ -19,6 +19,8 @@ const OrderBy = tokenVocabulary.OrderBy const Asc = tokenVocabulary.Asc const Desc = tokenVocabulary.Desc const Limit = tokenVocabulary.Limit +const Min = tokenVocabulary.Min +const Max = tokenVocabulary.Max const Identifier = tokenVocabulary.Identifier const Integer = tokenVocabulary.Integer @@ -29,6 +31,8 @@ const LessThanEqual = tokenVocabulary.LessThanEqual const Comma = tokenVocabulary.Comma const Equal = tokenVocabulary.Equal const NotEqual = tokenVocabulary.NotEqual +const LParen = tokenVocabulary.LParen +const RParen = tokenVocabulary.RParen // ----------------- parser ----------------- class SelectParser extends Parser { @@ -56,12 +60,31 @@ class SelectParser extends Parser { $.RULE('selectClause', () => { $.CONSUME(Select) - $.AT_LEAST_ONE_SEP({ - SEP: Comma, - DEF: () => { - $.CONSUME(Identifier) + $.OR([ + { ALT: () => { + $.AT_LEAST_ONE_SEP({ + SEP: Comma, + DEF: () => { + $.CONSUME(Identifier) + }, + }) + } }, - }) + { ALT: () => { + $.SUBRULE($.minMaxExpression) + } + } + ]) + }) + + $.RULE('minMaxExpression', () => { + $.OR([ + { ALT: () => $.CONSUME(Min) }, + { ALT: () => $.CONSUME(Max) } + ]) + $.CONSUME(LParen) + $.CONSUME(Identifier) + $.CONSUME(RParen) }) $.RULE('fromClause', () => {