hogql-parser 1.2.12__tar.gz → 1.3.2__tar.gz

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.
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hogql_parser
3
- Version: 1.2.12
3
+ Version: 1.3.2
4
4
  Summary: HogQL parser for internal PostHog use
5
5
  Home-page: https://github.com/PostHog/posthog/tree/master/common/hogql_parser
6
6
  Author: PostHog Inc.
7
7
  Author-email: hey@posthog.com
8
8
  Maintainer: PostHog Inc.
9
9
  Maintainer-email: hey@posthog.com
10
+ License: MIT
10
11
  Classifier: Development Status :: 5 - Production/Stable
11
- Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: MacOS
13
13
  Classifier: Operating System :: POSIX :: Linux
14
14
  Classifier: Programming Language :: Python
@@ -23,6 +23,7 @@ Dynamic: classifier
23
23
  Dynamic: description
24
24
  Dynamic: description-content-type
25
25
  Dynamic: home-page
26
+ Dynamic: license
26
27
  Dynamic: maintainer
27
28
  Dynamic: maintainer-email
28
29
  Dynamic: requires-python
@@ -42,3 +42,38 @@ def parse_program(source: str, /, *, is_internal: bool = False) -> Program:
42
42
  If the expr `is_internal`, spans and notices won't be included in the AST.
43
43
  """
44
44
  ...
45
+
46
+ def parse_expr_json(expr: str, /, *, is_internal: bool = False) -> str:
47
+ """Parse the HogQL expression string into a JSON AST.
48
+
49
+ If the expr `is_internal`, spans and notices won't be included in the AST.
50
+ """
51
+ ...
52
+
53
+ def parse_order_expr_json(expr: str, /, *, is_internal: bool = False) -> str:
54
+ """Parse the ORDER BY clause string into a JSON AST.
55
+
56
+ If the expr `is_internal`, spans and notices won't be included in the AST.
57
+ """
58
+ ...
59
+
60
+ def parse_select_json(expr: str, /, *, is_internal: bool = False) -> str:
61
+ """Parse the HogQL SELECT statement string into a JSON AST.
62
+
63
+ If the expr `is_internal`, spans and notices won't be included in the AST.
64
+ """
65
+ ...
66
+
67
+ def parse_full_template_string_json(expr: str, /, *, is_internal: bool = False) -> str:
68
+ """Parse a Hog template string into a JSON AST.
69
+
70
+ If the expr `is_internal`, spans and notices won't be included in the AST.
71
+ """
72
+ ...
73
+
74
+ def parse_program_json(source: str, /, *, is_internal: bool = False) -> str:
75
+ """Parse a Hog program into a JSON AST.
76
+
77
+ If the expr `is_internal`, spans and notices won't be included in the AST.
78
+ """
79
+ ...
@@ -1,14 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hogql_parser
3
- Version: 1.2.12
3
+ Version: 1.3.2
4
4
  Summary: HogQL parser for internal PostHog use
5
5
  Home-page: https://github.com/PostHog/posthog/tree/master/common/hogql_parser
6
6
  Author: PostHog Inc.
7
7
  Author-email: hey@posthog.com
8
8
  Maintainer: PostHog Inc.
9
9
  Maintainer-email: hey@posthog.com
10
+ License: MIT
10
11
  Classifier: Development Status :: 5 - Production/Stable
11
- Classifier: License :: OSI Approved :: MIT License
12
12
  Classifier: Operating System :: MacOS
13
13
  Classifier: Operating System :: POSIX :: Linux
14
14
  Classifier: Programming Language :: Python
@@ -23,6 +23,7 @@ Dynamic: classifier
23
23
  Dynamic: description
24
24
  Dynamic: description-content-type
25
25
  Dynamic: home-page
26
+ Dynamic: license
26
27
  Dynamic: maintainer
27
28
  Dynamic: maintainer-email
28
29
  Dynamic: requires-python
@@ -4,7 +4,8 @@ HogQLParserBaseVisitor.cpp
4
4
  HogQLParserVisitor.cpp
5
5
  README.md
6
6
  error.cpp
7
- parser.cpp
7
+ json.cpp
8
+ parser_python.cpp
8
9
  pyproject.toml
9
10
  setup.py
10
11
  string.cpp
@@ -0,0 +1,291 @@
1
+ #include "json.h"
2
+
3
+ #include <iomanip>
4
+ #include <sstream>
5
+ #include <stdexcept>
6
+
7
+ // Static empty instances for safe returns
8
+ static const Json::String emptyString;
9
+ static const Json::Array emptyArray;
10
+ static const Json::Object emptyObject;
11
+
12
+ // Initializer list constructor - creates array
13
+ Json::Json(std::initializer_list<Json> init) : value(Array(init)) {}
14
+
15
+ // Object subscript operator
16
+ Json& Json::operator[](const std::string& key) {
17
+ if (isNull()) {
18
+ value = Object{};
19
+ }
20
+ if (!isObject()) {
21
+ throw std::runtime_error("Json::operator[](string): not an object");
22
+ }
23
+ return std::get<Object>(value)[key];
24
+ }
25
+
26
+ // Array subscript operator
27
+ Json& Json::operator[](size_t index) {
28
+ if (isNull()) {
29
+ value = Array{};
30
+ }
31
+ if (!isArray()) {
32
+ throw std::runtime_error("Json::operator[](size_t): not an array");
33
+ }
34
+ auto& arr = std::get<Array>(value);
35
+ if (index >= arr.size()) {
36
+ arr.resize(index + 1);
37
+ }
38
+ return arr[index];
39
+ }
40
+
41
+ // Value getters
42
+ Json::Bool Json::getBool(Bool defaultVal) const {
43
+ if (isBool()) {
44
+ return std::get<Bool>(value);
45
+ }
46
+ return defaultVal;
47
+ }
48
+
49
+ Json::Int Json::getInt(Int defaultVal) const {
50
+ if (isInt()) {
51
+ return std::get<Int>(value);
52
+ }
53
+ if (isFloat()) {
54
+ return static_cast<Int>(std::get<Float>(value));
55
+ }
56
+ return defaultVal;
57
+ }
58
+
59
+ Json::Float Json::getFloat(Float defaultVal) const {
60
+ if (isFloat()) {
61
+ return std::get<Float>(value);
62
+ }
63
+ if (isInt()) {
64
+ return static_cast<Float>(std::get<Int>(value));
65
+ }
66
+ return defaultVal;
67
+ }
68
+
69
+ const Json::String& Json::getString() const {
70
+ if (isString()) {
71
+ return std::get<String>(value);
72
+ }
73
+ return emptyString;
74
+ }
75
+
76
+ const Json::Array& Json::getArray() const {
77
+ if (isArray()) {
78
+ return std::get<Array>(value);
79
+ }
80
+ return emptyArray;
81
+ }
82
+
83
+ const Json::Object& Json::getObject() const {
84
+ if (isObject()) {
85
+ return std::get<Object>(value);
86
+ }
87
+ return emptyObject;
88
+ }
89
+
90
+ Json::Array& Json::getArrayMut() {
91
+ if (isNull()) {
92
+ value = Array{};
93
+ }
94
+ if (!isArray()) {
95
+ throw std::runtime_error("Json::getArrayMut(): not an array");
96
+ }
97
+ return std::get<Array>(value);
98
+ }
99
+
100
+ Json::Object& Json::getObjectMut() {
101
+ if (isNull()) {
102
+ value = Object{};
103
+ }
104
+ if (!isObject()) {
105
+ throw std::runtime_error("Json::getObjectMut(): not an object");
106
+ }
107
+ return std::get<Object>(value);
108
+ }
109
+
110
+ // Array operations
111
+ void Json::pushBack(const Json& val) {
112
+ if (isNull()) {
113
+ value = Array{};
114
+ }
115
+ if (!isArray()) {
116
+ throw std::runtime_error("Json::pushBack(): not an array");
117
+ }
118
+ std::get<Array>(value).push_back(val);
119
+ }
120
+
121
+ void Json::pushBack(Json&& val) {
122
+ if (isNull()) {
123
+ value = Array{};
124
+ }
125
+ if (!isArray()) {
126
+ throw std::runtime_error("Json::pushBack(): not an array");
127
+ }
128
+ std::get<Array>(value).push_back(std::move(val));
129
+ }
130
+
131
+ size_t Json::size() const {
132
+ if (isArray()) {
133
+ return std::get<Array>(value).size();
134
+ }
135
+ if (isObject()) {
136
+ return std::get<Object>(value).size();
137
+ }
138
+ if (isString()) {
139
+ return std::get<String>(value).size();
140
+ }
141
+ return 0;
142
+ }
143
+
144
+ bool Json::empty() const {
145
+ if (isArray()) {
146
+ return std::get<Array>(value).empty();
147
+ }
148
+ if (isObject()) {
149
+ return std::get<Object>(value).empty();
150
+ }
151
+ if (isString()) {
152
+ return std::get<String>(value).empty();
153
+ }
154
+ return isNull();
155
+ }
156
+
157
+ // String escaping for JSON output
158
+ std::string Json::escapeString(const std::string& s) {
159
+ std::ostringstream escaped;
160
+ escaped << '"';
161
+ for (char c : s) {
162
+ switch (c) {
163
+ case '"':
164
+ escaped << "\\\"";
165
+ break;
166
+ case '\\':
167
+ escaped << "\\\\";
168
+ break;
169
+ case '\b':
170
+ escaped << "\\b";
171
+ break;
172
+ case '\f':
173
+ escaped << "\\f";
174
+ break;
175
+ case '\n':
176
+ escaped << "\\n";
177
+ break;
178
+ case '\r':
179
+ escaped << "\\r";
180
+ break;
181
+ case '\t':
182
+ escaped << "\\t";
183
+ break;
184
+ default:
185
+ if (static_cast<unsigned char>(c) < 0x20) {
186
+ escaped << "\\u" << std::hex << std::setw(4) << std::setfill('0')
187
+ << static_cast<int>(static_cast<unsigned char>(c));
188
+ } else {
189
+ escaped << c;
190
+ }
191
+ break;
192
+ }
193
+ }
194
+ escaped << '"';
195
+ return escaped.str();
196
+ }
197
+
198
+ std::string Json::indentString(int spaces) {
199
+ if (spaces <= 0) {
200
+ return "";
201
+ }
202
+ return std::string(spaces, ' ');
203
+ }
204
+
205
+ std::string Json::dump(int indent) const {
206
+ return dumpImpl(indent, 0);
207
+ }
208
+
209
+ std::string Json::dumpImpl(int indent, int currentIndent) const {
210
+ return std::visit(
211
+ [this, indent, currentIndent](const auto& v) -> std::string {
212
+ using T = std::decay_t<decltype(v)>;
213
+
214
+ if constexpr (std::is_same_v<T, Null>) {
215
+ return "null";
216
+ } else if constexpr (std::is_same_v<T, Bool>) {
217
+ return v ? "true" : "false";
218
+ } else if constexpr (std::is_same_v<T, Int>) {
219
+ return std::to_string(v);
220
+ } else if constexpr (std::is_same_v<T, Float>) {
221
+ std::ostringstream oss;
222
+ oss << std::setprecision(17) << v;
223
+ std::string result = oss.str();
224
+ // Ensure it looks like a float (has decimal point or exponent)
225
+ if (result.find('.') == std::string::npos && result.find('e') == std::string::npos &&
226
+ result.find('E') == std::string::npos) {
227
+ result += ".0";
228
+ }
229
+ return result;
230
+ } else if constexpr (std::is_same_v<T, String>) {
231
+ return escapeString(v);
232
+ } else if constexpr (std::is_same_v<T, Raw>) {
233
+ // Raw JSON is already serialized, output as-is
234
+ return v.json;
235
+ } else if constexpr (std::is_same_v<T, Array>) {
236
+ if (v.empty()) {
237
+ return "[]";
238
+ }
239
+ std::ostringstream oss;
240
+ oss << '[';
241
+ bool pretty = indent >= 0;
242
+ int nextIndent = currentIndent + (pretty ? indent : 0);
243
+
244
+ for (size_t i = 0; i < v.size(); ++i) {
245
+ if (pretty) {
246
+ oss << '\n' << indentString(nextIndent);
247
+ }
248
+ oss << v[i].dumpImpl(indent, nextIndent);
249
+ if (i < v.size() - 1) {
250
+ oss << ',';
251
+ }
252
+ }
253
+ if (pretty) {
254
+ oss << '\n' << indentString(currentIndent);
255
+ }
256
+ oss << ']';
257
+ return oss.str();
258
+ } else if constexpr (std::is_same_v<T, Object>) {
259
+ if (v.empty()) {
260
+ return "{}";
261
+ }
262
+ std::ostringstream oss;
263
+ oss << '{';
264
+ bool pretty = indent >= 0;
265
+ int nextIndent = currentIndent + (pretty ? indent : 0);
266
+
267
+ size_t i = 0;
268
+ for (const auto& [key, val] : v) {
269
+ if (pretty) {
270
+ oss << '\n' << indentString(nextIndent);
271
+ }
272
+ oss << escapeString(key) << ':';
273
+ if (pretty) {
274
+ oss << ' ';
275
+ }
276
+ oss << val.dumpImpl(indent, nextIndent);
277
+ if (i < v.size() - 1) {
278
+ oss << ',';
279
+ }
280
+ ++i;
281
+ }
282
+ if (pretty) {
283
+ oss << '\n' << indentString(currentIndent);
284
+ }
285
+ oss << '}';
286
+ return oss.str();
287
+ }
288
+ },
289
+ value
290
+ );
291
+ }
@@ -8,16 +8,16 @@
8
8
  #include "HogQLParserBaseVisitor.h"
9
9
 
10
10
  #include "error.h"
11
- #include "parser.h"
11
+ #include "parser_python.h"
12
12
  #include "string.h"
13
13
 
14
14
  #define VISIT(RULE) any visit##RULE(HogQLParser::RULE##Context* ctx) override
15
- #define VISIT_UNSUPPORTED(RULE) \
16
- VISIT(RULE) { \
15
+ #define VISIT_UNSUPPORTED(RULE) \
16
+ VISIT(RULE) { \
17
17
  throw NotImplementedError("Unsupported rule: " #RULE); \
18
18
  }
19
19
 
20
- #define HANDLE_HOGQL_ERROR(TYPE, OTHER_CLEANUP) \
20
+ #define HANDLE_HOGQL_ERROR(TYPE, OTHER_CLEANUP) \
21
21
  (const TYPE& e) { \
22
22
  string err_what = e.what(); \
23
23
  PyObject *error_type = NULL, *py_err_args = NULL, *py_err = NULL, *py_start = NULL, *py_end = NULL; \
@@ -50,7 +50,7 @@
50
50
  PyObject* ret = build_ast_node(TYPE_NAME, KWARGS_FORMAT, __VA_ARGS__); \
51
51
  /* Fortunately we don't need to care about decrementing Py_BuildValue/Py_VaBuildValue args, */ \
52
52
  /* so just throw is enough: https://github.com/python/cpython/blob/a254120f/Python/modsupport.c#L147-L148*/ \
53
- if (!ret) throw PyInternalError(); \
53
+ if (!ret) throw PyInternalError(); \
54
54
  return ret
55
55
 
56
56
  using namespace std;
@@ -221,9 +221,9 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
221
221
  PyObject* visitAsPyObjectFinal(antlr4::tree::ParseTree* tree) {
222
222
  try {
223
223
  return visitAsPyObject(tree);
224
- } catch HANDLE_HOGQL_ERROR(SyntaxError, ) catch HANDLE_HOGQL_ERROR(
225
- NotImplementedError,
226
- ) catch HANDLE_HOGQL_ERROR(ParsingError, ) catch (const PyInternalError& e) {
224
+ } catch HANDLE_HOGQL_ERROR(SyntaxError, ) catch HANDLE_HOGQL_ERROR(NotImplementedError, ) catch HANDLE_HOGQL_ERROR(
225
+ ParsingError,
226
+ ) catch (const PyInternalError& e) {
227
227
  return NULL;
228
228
  } catch (const bad_any_cast& e) {
229
229
  PyObject* error_type = PyObject_GetAttrString(state->errors_module, "ParsingError");
@@ -334,9 +334,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
334
334
  throw ParsingError("Declaration must be either a varDecl or a statement");
335
335
  }
336
336
 
337
- VISIT(Expression) {
338
- return visit(ctx->columnExpr());
339
- }
337
+ VISIT(Expression) { return visit(ctx->columnExpr()); }
340
338
 
341
339
  VISIT(VarDecl) {
342
340
  string name = visitAsString(ctx->identifier());
@@ -428,8 +426,11 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
428
426
  return visit(empty_stmt_ctx);
429
427
  }
430
428
 
431
- throw ParsingError("Statement must be one of returnStmt, throwStmt, tryCatchStmt, ifStmt, whileStmt, forStmt, forInStmt, funcStmt, "
432
- "varAssignment, block, exprStmt, or emptyStmt");
429
+ throw ParsingError(
430
+ "Statement must be one of returnStmt, throwStmt, tryCatchStmt, ifStmt, whileStmt, forStmt, forInStmt, "
431
+ "funcStmt, "
432
+ "varAssignment, block, exprStmt, or emptyStmt"
433
+ );
433
434
  }
434
435
 
435
436
  VISIT(ExprStmt) {
@@ -707,21 +708,16 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
707
708
  Py_DECREF(expr);
708
709
  throw;
709
710
  }
710
- PyObject* ret = second_identifier.empty()
711
- ? build_ast_node(
712
- "ForInStatement", "{s:O,s:s#,s:N,s:N}",
713
- "keyVar", Py_None,
714
- "valueVar", first_identifier.data(), first_identifier.size(),
715
- "expr", expr,
716
- "body", body
717
- )
718
- : build_ast_node(
719
- "ForInStatement", "{s:s#,s:s#,s:N,s:N}",
720
- "keyVar", first_identifier.data(), first_identifier.size(),
721
- "valueVar", second_identifier.data(), second_identifier.size(),
722
- "expr", expr,
723
- "body", body
724
- );
711
+ PyObject* ret =
712
+ second_identifier.empty()
713
+ ? build_ast_node(
714
+ "ForInStatement", "{s:O,s:s#,s:N,s:N}", "keyVar", Py_None, "valueVar", first_identifier.data(),
715
+ first_identifier.size(), "expr", expr, "body", body
716
+ )
717
+ : build_ast_node(
718
+ "ForInStatement", "{s:s#,s:s#,s:N,s:N}", "keyVar", first_identifier.data(), first_identifier.size(),
719
+ "valueVar", second_identifier.data(), second_identifier.size(), "expr", expr, "body", body
720
+ );
725
721
  if (!ret) {
726
722
  Py_DECREF(expr);
727
723
  Py_DECREF(body);
@@ -743,7 +739,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
743
739
  }
744
740
 
745
741
  if (!params) {
746
- throw PyInternalError();
742
+ throw PyInternalError();
747
743
  }
748
744
 
749
745
  PyObject* body;
@@ -754,7 +750,8 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
754
750
  throw;
755
751
  }
756
752
 
757
- PyObject* ret = build_ast_node("Function", "{s:s#,s:N,s:N}", "name", name.data(), name.size(), "params", params, "body", body);
753
+ PyObject* ret =
754
+ build_ast_node("Function", "{s:s#,s:N,s:N}", "name", name.data(), name.size(), "params", params, "body", body);
758
755
  if (!ret) {
759
756
  Py_DECREF(params);
760
757
  Py_DECREF(body);
@@ -763,9 +760,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
763
760
  return ret;
764
761
  }
765
762
 
766
- VISIT(KvPairList) {
767
- return visitPyListOfObjects(ctx->kvPair());
768
- }
763
+ VISIT(KvPairList) { return visitPyListOfObjects(ctx->kvPair()); }
769
764
 
770
765
  VISIT(KvPair) {
771
766
  PyObject* k = visitAsPyObject(ctx->expression(0));
@@ -785,13 +780,9 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
785
780
  return ret;
786
781
  }
787
782
 
788
- VISIT(IdentifierList) {
789
- return visitAsVectorOfStrings(ctx->identifier());
790
- }
783
+ VISIT(IdentifierList) { return visitAsVectorOfStrings(ctx->identifier()); }
791
784
 
792
- VISIT(EmptyStmt) {
793
- RETURN_NEW_AST_NODE("ExprStatement", "{s:O}", "expr", Py_None);
794
- }
785
+ VISIT(EmptyStmt) { RETURN_NEW_AST_NODE("ExprStatement", "{s:O}", "expr", Py_None); }
795
786
 
796
787
  VISIT(Block) {
797
788
  PyObject* declarations = PyList_New(0);
@@ -866,29 +857,34 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
866
857
  for (auto subsequent : ctx->subsequentSelectSetClause()) {
867
858
  char* set_operator;
868
859
  if (subsequent->UNION() && subsequent->ALL()) {
869
- set_operator = "UNION ALL";
860
+ set_operator = "UNION ALL";
870
861
  } else if (subsequent->UNION() && subsequent->DISTINCT()) {
871
- set_operator = "UNION DISTINCT";
862
+ set_operator = "UNION DISTINCT";
872
863
  } else if (subsequent->INTERSECT() && subsequent->DISTINCT()) {
873
- set_operator = "INTERSECT DISTINCT";
864
+ set_operator = "INTERSECT DISTINCT";
874
865
  } else if (subsequent->INTERSECT()) {
875
- set_operator = "INTERSECT";
866
+ set_operator = "INTERSECT";
876
867
  } else if (subsequent->EXCEPT()) {
877
- set_operator = "EXCEPT";
868
+ set_operator = "EXCEPT";
878
869
  } else {
879
- throw SyntaxError("Set operator must be one of UNION ALL, UNION DISTINCT, INTERSECT, INTERSECT DISTINCT, and EXCEPT");
870
+ throw SyntaxError(
871
+ "Set operator must be one of UNION ALL, UNION DISTINCT, INTERSECT, INTERSECT DISTINCT, and EXCEPT"
872
+ );
880
873
  }
881
874
  select_query = visitAsPyObject(subsequent->selectStmtWithParens());
882
- PyObject* query = build_ast_node("SelectSetNode", "{s:N,s:N}", "select_query", select_query, "set_operator", PyUnicode_FromString(set_operator));
875
+ PyObject* query = build_ast_node(
876
+ "SelectSetNode", "{s:N,s:N}", "select_query", select_query, "set_operator",
877
+ PyUnicode_FromString(set_operator)
878
+ );
883
879
  if (!query) {
884
880
  throw PyInternalError();
885
881
  }
886
882
  PyList_Append(select_queries, query);
887
883
  }
888
884
  } catch (...) {
889
- Py_DECREF(select_queries);
890
- Py_DECREF(initial_query);
891
- throw;
885
+ Py_DECREF(select_queries);
886
+ Py_DECREF(initial_query);
887
+ throw;
892
888
  }
893
889
 
894
890
  if (PyList_Size(select_queries) == 0) {
@@ -896,7 +892,10 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
896
892
  return initial_query;
897
893
  }
898
894
 
899
- RETURN_NEW_AST_NODE("SelectSetQuery", "{s:N, s:N}", "initial_select_query", initial_query, "subsequent_select_queries", select_queries);
895
+ RETURN_NEW_AST_NODE(
896
+ "SelectSetQuery", "{s:N, s:N}", "initial_select_query", initial_query, "subsequent_select_queries",
897
+ select_queries
898
+ );
900
899
  }
901
900
 
902
901
  VISIT(SelectStmt) {
@@ -1182,10 +1181,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
1182
1181
  }
1183
1182
 
1184
1183
  // Create the LimitByExpr node (transfers ownership of n, offset_value, and exprs)
1185
- RETURN_NEW_AST_NODE("LimitByExpr", "{s:N,s:N,s:N}",
1186
- "n", n,
1187
- "offset_value", offset_value,
1188
- "exprs", exprs);
1184
+ RETURN_NEW_AST_NODE("LimitByExpr", "{s:N,s:N,s:N}", "n", n, "offset_value", offset_value, "exprs", exprs);
1189
1185
  }
1190
1186
 
1191
1187
  VISIT(LimitExpr) {
@@ -1196,7 +1192,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
1196
1192
 
1197
1193
  // If no second expression, just return the first
1198
1194
  if (!ctx->columnExpr(1)) {
1199
- return first; // Return first as is, ownership transferred
1195
+ return first; // Return first as is, ownership transferred
1200
1196
  }
1201
1197
 
1202
1198
  // We have both limit and offset
@@ -1227,13 +1223,11 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
1227
1223
  return result;
1228
1224
  }
1229
1225
 
1230
- VISIT(OffsetOnlyClause) {
1231
- return visitAsPyObject(ctx->columnExpr());
1232
- }
1226
+ VISIT(OffsetOnlyClause) { return visitAsPyObject(ctx->columnExpr()); }
1233
1227
 
1234
1228
  VISIT_UNSUPPORTED(ProjectionOrderByClause)
1235
1229
 
1236
- VISIT_UNSUPPORTED(LimitAndOffsetClause) // We handle this directly in the SelectStmt visitor
1230
+ VISIT_UNSUPPORTED(LimitAndOffsetClause) // We handle this directly in the SelectStmt visitor
1237
1231
 
1238
1232
  VISIT_UNSUPPORTED(SettingsClause)
1239
1233
 
@@ -1246,20 +1240,20 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
1246
1240
  if (!next_join) { \
1247
1241
  Py_DECREF(join1); \
1248
1242
  Py_DECREF(join2); \
1249
- throw PyInternalError(); \
1243
+ throw PyInternalError(); \
1250
1244
  } \
1251
1245
  int reached_end_of_chain = Py_IsNone(next_join); \
1252
1246
  if (reached_end_of_chain == -1) { \
1253
1247
  Py_DECREF(join1); \
1254
1248
  Py_DECREF(join2); \
1255
- throw PyInternalError(); \
1249
+ throw PyInternalError(); \
1256
1250
  } \
1257
1251
  if (reached_end_of_chain) { \
1258
1252
  int err_indicator = PyObject_SetAttrString(last_join, "next_join", join2); \
1259
1253
  if (err_indicator == -1) { \
1260
1254
  Py_DECREF(join1); \
1261
1255
  Py_DECREF(join2); \
1262
- throw PyInternalError(); \
1256
+ throw PyInternalError(); \
1263
1257
  } \
1264
1258
  Py_DECREF(join2); \
1265
1259
  return join1; \
@@ -1781,7 +1775,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
1781
1775
  if (is_##VAR##_a_call == -1) { \
1782
1776
  Py_DECREF(left); \
1783
1777
  Py_DECREF(right); \
1784
- throw PyInternalError(); \
1778
+ throw PyInternalError(); \
1785
1779
  } \
1786
1780
  if (is_##VAR##_a_call) { \
1787
1781
  PyObject* VAR##_name = PyObject_GetAttrString(VAR, "name"); \
@@ -1789,7 +1783,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
1789
1783
  Py_DECREF(left); \
1790
1784
  Py_DECREF(right); \
1791
1785
  Py_DECREF(concat_as_str); \
1792
- throw PyInternalError(); \
1786
+ throw PyInternalError(); \
1793
1787
  } \
1794
1788
  PyObject* VAR##_name_lower = PyObject_CallMethod(VAR##_name, "lower", NULL); \
1795
1789
  Py_DECREF(VAR##_name); \
@@ -1797,7 +1791,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
1797
1791
  Py_DECREF(left); \
1798
1792
  Py_DECREF(right); \
1799
1793
  Py_DECREF(concat_as_str); \
1800
- throw PyInternalError(); \
1794
+ throw PyInternalError(); \
1801
1795
  } \
1802
1796
  is_##VAR##_a_concat_call = PyObject_RichCompareBool(VAR##_name_lower, concat_as_str, Py_EQ); \
1803
1797
  Py_DECREF(VAR##_name_lower); \
@@ -1805,7 +1799,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
1805
1799
  Py_DECREF(left); \
1806
1800
  Py_DECREF(right); \
1807
1801
  Py_DECREF(concat_as_str); \
1808
- throw PyInternalError(); \
1802
+ throw PyInternalError(); \
1809
1803
  } \
1810
1804
  }
1811
1805
 
@@ -2014,10 +2008,9 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
2014
2008
  }
2015
2009
 
2016
2010
  PyObject* ret = build_ast_node(
2017
- "Call",
2018
- "{s:s,s:[O]}", // s:[O] means "args" => [the single PyObject]
2019
- "name", name.c_str(),
2020
- "args", constant_count
2011
+ "Call",
2012
+ "{s:s,s:[O]}", // s:[O] means "args" => [the single PyObject]
2013
+ "name", name.c_str(), "args", constant_count
2021
2014
  );
2022
2015
 
2023
2016
  if (!ret) {
@@ -2142,12 +2135,8 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
2142
2135
  throw;
2143
2136
  }
2144
2137
  RETURN_NEW_AST_NODE(
2145
- "BetweenExpr",
2146
- "{s:N,s:N,s:N,s:O}",
2147
- "expr", expr,
2148
- "low", low,
2149
- "high", high,
2150
- "negated", ctx->NOT() ? Py_True : Py_False
2138
+ "BetweenExpr", "{s:N,s:N,s:N,s:O}", "expr", expr, "low", low, "high", high, "negated",
2139
+ ctx->NOT() ? Py_True : Py_False
2151
2140
  );
2152
2141
  }
2153
2142
 
@@ -2353,8 +2342,8 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
2353
2342
  throw;
2354
2343
  }
2355
2344
  RETURN_NEW_AST_NODE(
2356
- "WindowFunction", "{s:s#,s:N,s:N,s:N}", "name", identifier.data(), identifier.size(), "exprs", exprs,
2357
- "args", args, "over_expr", over_expr
2345
+ "WindowFunction", "{s:s#,s:N,s:N,s:N}", "name", identifier.data(), identifier.size(), "exprs", exprs, "args",
2346
+ args, "over_expr", over_expr
2358
2347
  );
2359
2348
  }
2360
2349
 
@@ -2687,101 +2676,116 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
2687
2676
  }
2688
2677
 
2689
2678
  auto attribute_ctxs = ctx->hogqlxTagAttribute();
2690
- PyObject *attributes = PyList_New(attribute_ctxs.size());
2679
+ PyObject* attributes = PyList_New(attribute_ctxs.size());
2691
2680
  if (!attributes) {
2692
2681
  throw PyInternalError();
2693
2682
  }
2694
2683
  for (size_t i = 0; i < attribute_ctxs.size(); i++) {
2695
- PyObject *attr_obj;
2684
+ PyObject* attr_obj;
2696
2685
  try {
2697
2686
  attr_obj = visitAsPyObject(attribute_ctxs[i]);
2698
2687
  } catch (...) {
2699
2688
  Py_DECREF(attributes);
2700
2689
  throw;
2701
2690
  }
2702
- PyList_SET_ITEM(attributes, i, attr_obj); // Steals reference
2691
+ PyList_SET_ITEM(attributes, i, attr_obj); // Steals reference
2703
2692
  }
2704
2693
 
2705
2694
  /* ── children ───────────────────────────────────────────── */
2706
2695
  std::vector<PyObject*> kept_children;
2707
2696
  for (auto childCtx : ctx->hogqlxChildElement()) {
2708
- PyObject *child_ast;
2709
- try { child_ast = visitAsPyObject(childCtx); }
2710
- catch (...) { X_Py_DECREF_ALL(kept_children); Py_DECREF(attributes); throw; }
2711
-
2712
- /* drop Constant nodes that are only-whitespace *and* contain a line-break */
2713
- int is_const = is_ast_node_instance(child_ast, "Constant");
2714
- if (is_const == 1) {
2715
- PyObject *valueObj = PyObject_GetAttrString(child_ast, "value");
2716
- if (valueObj && PyUnicode_Check(valueObj)) {
2717
- Py_ssize_t n = 0; const char *s = PyUnicode_AsUTF8AndSize(valueObj, &n);
2718
- if (s) {
2719
- std::string v(s, n);
2720
- bool only_ws = std::all_of(v.begin(), v.end(),
2721
- [](unsigned char c){ return std::isspace(c); });
2722
- bool has_newline = v.find('\n') != std::string::npos ||
2723
- v.find('\r') != std::string::npos;
2724
- if (only_ws && has_newline) { // skip it
2725
- Py_DECREF(valueObj);
2726
- Py_DECREF(child_ast);
2727
- continue;
2728
- }
2729
- }
2697
+ PyObject* child_ast;
2698
+ try {
2699
+ child_ast = visitAsPyObject(childCtx);
2700
+ } catch (...) {
2701
+ X_Py_DECREF_ALL(kept_children);
2702
+ Py_DECREF(attributes);
2703
+ throw;
2704
+ }
2705
+
2706
+ /* drop Constant nodes that are only-whitespace *and* contain a line-break */
2707
+ int is_const = is_ast_node_instance(child_ast, "Constant");
2708
+ if (is_const == 1) {
2709
+ PyObject* valueObj = PyObject_GetAttrString(child_ast, "value");
2710
+ if (valueObj && PyUnicode_Check(valueObj)) {
2711
+ Py_ssize_t n = 0;
2712
+ const char* s = PyUnicode_AsUTF8AndSize(valueObj, &n);
2713
+ if (s) {
2714
+ std::string v(s, n);
2715
+ bool only_ws = std::all_of(v.begin(), v.end(), [](unsigned char c) { return std::isspace(c); });
2716
+ bool has_newline = v.find('\n') != std::string::npos || v.find('\r') != std::string::npos;
2717
+ if (only_ws && has_newline) { // skip it
2718
+ Py_DECREF(valueObj);
2719
+ Py_DECREF(child_ast);
2720
+ continue;
2730
2721
  }
2731
- Py_XDECREF(valueObj);
2722
+ }
2732
2723
  }
2724
+ Py_XDECREF(valueObj);
2725
+ }
2733
2726
 
2734
- kept_children.push_back(child_ast); // keep
2727
+ kept_children.push_back(child_ast); // keep
2735
2728
  }
2736
2729
 
2737
2730
  /* if we have child nodes, validate + attach them as attribute "children" */
2738
2731
  if (!kept_children.empty()) {
2739
- for (Py_ssize_t i = 0; i < PyList_Size(attributes); ++i) {
2740
- PyObject *attr = PyList_GetItem(attributes, i); // borrowed
2741
- PyObject *name_obj = PyObject_GetAttrString(attr, "name");
2742
- PyObject *children_str = PyUnicode_FromString("children");
2743
- int is_children = PyObject_RichCompareBool(name_obj, children_str, Py_EQ);
2744
- Py_DECREF(children_str); Py_DECREF(name_obj);
2745
- if (is_children == 1) {
2746
- X_Py_DECREF_ALL(kept_children); Py_DECREF(attributes);
2747
- throw SyntaxError("Can't have a HogQLX tag with both children and a 'children' attribute");
2748
- }
2749
- if (is_children == -1) { X_Py_DECREF_ALL(kept_children); Py_DECREF(attributes); throw PyInternalError();}
2732
+ for (Py_ssize_t i = 0; i < PyList_Size(attributes); ++i) {
2733
+ PyObject* attr = PyList_GetItem(attributes, i); // borrowed
2734
+ PyObject* name_obj = PyObject_GetAttrString(attr, "name");
2735
+ PyObject* children_str = PyUnicode_FromString("children");
2736
+ int is_children = PyObject_RichCompareBool(name_obj, children_str, Py_EQ);
2737
+ Py_DECREF(children_str);
2738
+ Py_DECREF(name_obj);
2739
+ if (is_children == 1) {
2740
+ X_Py_DECREF_ALL(kept_children);
2741
+ Py_DECREF(attributes);
2742
+ throw SyntaxError("Can't have a HogQLX tag with both children and a 'children' attribute");
2743
+ }
2744
+ if (is_children == -1) {
2745
+ X_Py_DECREF_ALL(kept_children);
2746
+ Py_DECREF(attributes);
2747
+ throw PyInternalError();
2750
2748
  }
2749
+ }
2751
2750
 
2752
- /* build children list */
2753
- PyObject *children_list = PyList_New(kept_children.size());
2754
- if (!children_list) { X_Py_DECREF_ALL(kept_children); Py_DECREF(attributes); throw PyInternalError(); }
2751
+ /* build children list */
2752
+ PyObject* children_list = PyList_New(kept_children.size());
2753
+ if (!children_list) {
2754
+ X_Py_DECREF_ALL(kept_children);
2755
+ Py_DECREF(attributes);
2756
+ throw PyInternalError();
2757
+ }
2755
2758
 
2756
- for (size_t i = 0; i < kept_children.size(); ++i)
2757
- PyList_SET_ITEM(children_list, i, kept_children[i]); // steals refs
2759
+ for (size_t i = 0; i < kept_children.size(); ++i)
2760
+ PyList_SET_ITEM(children_list, i, kept_children[i]); // steals refs
2758
2761
 
2759
- PyObject *children_attr = build_ast_node(
2760
- "HogQLXAttribute", "{s:s#,s:O}", "name", "children", (Py_ssize_t)8, "value", children_list);
2761
- if (!children_attr) { Py_DECREF(children_list); Py_DECREF(attributes); throw PyInternalError(); }
2762
+ PyObject* children_attr =
2763
+ build_ast_node("HogQLXAttribute", "{s:s#,s:O}", "name", "children", (Py_ssize_t)8, "value", children_list);
2764
+ if (!children_attr) {
2765
+ Py_DECREF(children_list);
2766
+ Py_DECREF(attributes);
2767
+ throw PyInternalError();
2768
+ }
2762
2769
 
2763
- if (PyList_Append(attributes, children_attr) == -1) {
2764
- Py_DECREF(children_attr); Py_DECREF(attributes); throw PyInternalError();
2765
- }
2766
- Py_DECREF(children_attr); // list now owns it
2770
+ if (PyList_Append(attributes, children_attr) == -1) {
2771
+ Py_DECREF(children_attr);
2772
+ Py_DECREF(attributes);
2773
+ throw PyInternalError();
2774
+ }
2775
+ Py_DECREF(children_attr); // list now owns it
2767
2776
  }
2768
2777
 
2769
- PyObject *ret = build_ast_node(
2770
- "HogQLXTag",
2771
- "{s:s#,s:N}",
2772
- "kind", opening.data(), (Py_ssize_t)opening.size(),
2773
- "attributes", attributes
2778
+ PyObject* ret = build_ast_node(
2779
+ "HogQLXTag", "{s:s#,s:N}", "kind", opening.data(), (Py_ssize_t)opening.size(), "attributes", attributes
2774
2780
  );
2775
2781
  if (!ret) {
2776
- Py_DECREF(attributes);
2777
- throw PyInternalError();
2782
+ Py_DECREF(attributes);
2783
+ throw PyInternalError();
2778
2784
  }
2779
2785
  return ret;
2780
2786
  }
2781
2787
 
2782
- VISIT(Placeholder) {
2783
- RETURN_NEW_AST_NODE("Placeholder", "{s:N}", "expr", visitAsPyObject(ctx->columnExpr()));
2784
- }
2788
+ VISIT(Placeholder) { RETURN_NEW_AST_NODE("Placeholder", "{s:N}", "expr", visitAsPyObject(ctx->columnExpr())); }
2785
2789
 
2786
2790
  VISIT_UNSUPPORTED(EnumValue)
2787
2791
 
@@ -2828,8 +2832,8 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
2828
2832
  // If it's a Field, get `chain` and see if there's exactly one item in it.
2829
2833
  PyObject* chain = PyObject_GetAttrString(expr, "chain");
2830
2834
  if (!chain) {
2831
- Py_DECREF(expr);
2832
- throw PyInternalError();
2835
+ Py_DECREF(expr);
2836
+ throw PyInternalError();
2833
2837
  }
2834
2838
 
2835
2839
  if (PyList_Check(chain) && PyList_Size(chain) == 1) {
@@ -2867,8 +2871,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
2867
2871
  }
2868
2872
 
2869
2873
  // Build a Call node: ast.Call(name=<func_name>, args=[<select_node>])
2870
- PyObject* call_node =
2871
- build_ast_node("Call", "{s:s#,s:N}", "name", func_name, size, "args", args_list);
2874
+ PyObject* call_node = build_ast_node("Call", "{s:s#,s:N}", "name", func_name, size, "args", args_list);
2872
2875
  Py_DECREF(chain);
2873
2876
  Py_DECREF(expr);
2874
2877
 
@@ -2904,8 +2907,7 @@ class HogQLParseTreeConverter : public HogQLParserBaseVisitor {
2904
2907
  PyList_SET_ITEM(args_list, 0, select_node);
2905
2908
 
2906
2909
  // Return ast.ExprCall(expr=expr, args=args_list).
2907
- PyObject* expr_call_node =
2908
- build_ast_node("ExprCall", "{s:N,s:N}", "expr", expr, "args", args_list);
2910
+ PyObject* expr_call_node = build_ast_node("ExprCall", "{s:N,s:N}", "expr", expr, "args", args_list);
2909
2911
  if (!expr_call_node) {
2910
2912
  Py_DECREF(expr);
2911
2913
  Py_DECREF(select_node);
@@ -3030,52 +3032,63 @@ parser_state* get_module_state(PyObject* module) {
3030
3032
  return static_cast<parser_state*>(PyModule_GetState(module));
3031
3033
  }
3032
3034
 
3035
+ // Include JSON parser methods (depends on get_module_state and HogQLErrorListener)
3036
+ #include "parser_json_python.cpp"
3037
+
3033
3038
  // MODULE METHODS
3034
3039
 
3035
- #define METHOD_PARSE_NODE(PASCAL_CASE, CAMEL_CASE, SNAKE_CASE) \
3036
- static PyObject* method_parse_##SNAKE_CASE(PyObject* self, PyObject* args, PyObject* kwargs) { \
3037
- parser_state* state = get_module_state(self); \
3038
- const char* str; \
3039
- int internal = 0; \
3040
- static const char* kwlist[] = {"input", "is_internal", NULL}; \
3041
- if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|p", (char**)kwlist, &str, &internal)) { \
3042
- return NULL; \
3043
- } \
3044
- auto input_stream = new antlr4::ANTLRInputStream(str, strnlen(str, 65536)); \
3045
- auto lexer = new HogQLLexer(input_stream); \
3046
- auto stream = new antlr4::CommonTokenStream(lexer); \
3047
- auto parser = new HogQLParser(stream); \
3048
- parser->removeErrorListeners(); \
3049
- auto error_listener = new HogQLErrorListener(str); \
3050
- parser->addErrorListener(error_listener); \
3051
- HogQLParser::PASCAL_CASE##Context* parse_tree; \
3052
- try { \
3053
- parse_tree = parser->CAMEL_CASE(); \
3054
- } catch HANDLE_HOGQL_ERROR(SyntaxError, delete error_listener; delete parser; delete stream; delete lexer; \
3055
- delete input_stream;) \
3056
- catch (const antlr4::EmptyStackException &e) { \
3057
- delete error_listener; delete parser; delete stream; delete lexer; delete input_stream; \
3058
- PyObject* error_type = PyObject_GetAttrString(state->errors_module, "SyntaxError"); \
3059
- if (error_type) { \
3060
- PyErr_SetString(error_type, "Unmatched curly bracket"); \
3061
- } \
3062
- return NULL; \
3063
- } catch (...) { \
3064
- delete error_listener; delete parser; delete stream; delete lexer; delete input_stream; \
3065
- PyObject* error_type = PyObject_GetAttrString(state->errors_module, "ParsingError"); \
3066
- if (error_type) { \
3067
- PyErr_SetString(error_type, "Unexpected Antlr exception in C++ parser"); \
3068
- } \
3069
- return NULL; \
3070
- }; \
3071
- HogQLParseTreeConverter converter = HogQLParseTreeConverter(state, internal == 1); \
3072
- PyObject* result_node = converter.visitAsPyObjectFinal(parse_tree); \
3073
- delete error_listener; \
3074
- delete parser; \
3075
- delete stream; \
3076
- delete lexer; \
3077
- delete input_stream; \
3078
- return result_node; \
3040
+ #define METHOD_PARSE_NODE(PASCAL_CASE, CAMEL_CASE, SNAKE_CASE) \
3041
+ static PyObject* method_parse_##SNAKE_CASE(PyObject* self, PyObject* args, PyObject* kwargs) { \
3042
+ parser_state* state = get_module_state(self); \
3043
+ const char* str; \
3044
+ int internal = 0; \
3045
+ static const char* kwlist[] = {"input", "is_internal", NULL}; \
3046
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|p", (char**)kwlist, &str, &internal)) { \
3047
+ return NULL; \
3048
+ } \
3049
+ auto input_stream = new antlr4::ANTLRInputStream(str, strnlen(str, 65536)); \
3050
+ auto lexer = new HogQLLexer(input_stream); \
3051
+ auto stream = new antlr4::CommonTokenStream(lexer); \
3052
+ auto parser = new HogQLParser(stream); \
3053
+ parser->removeErrorListeners(); \
3054
+ auto error_listener = new HogQLErrorListener(str); \
3055
+ parser->addErrorListener(error_listener); \
3056
+ HogQLParser::PASCAL_CASE##Context* parse_tree; \
3057
+ try { \
3058
+ parse_tree = parser->CAMEL_CASE(); \
3059
+ } catch HANDLE_HOGQL_ERROR( \
3060
+ SyntaxError, delete error_listener; delete parser; delete stream; delete lexer; delete input_stream; \
3061
+ ) catch (const antlr4::EmptyStackException& e) { \
3062
+ delete error_listener; \
3063
+ delete parser; \
3064
+ delete stream; \
3065
+ delete lexer; \
3066
+ delete input_stream; \
3067
+ PyObject* error_type = PyObject_GetAttrString(state->errors_module, "SyntaxError"); \
3068
+ if (error_type) { \
3069
+ PyErr_SetString(error_type, "Unmatched curly bracket"); \
3070
+ } \
3071
+ return NULL; \
3072
+ } catch (...) { \
3073
+ delete error_listener; \
3074
+ delete parser; \
3075
+ delete stream; \
3076
+ delete lexer; \
3077
+ delete input_stream; \
3078
+ PyObject* error_type = PyObject_GetAttrString(state->errors_module, "ParsingError"); \
3079
+ if (error_type) { \
3080
+ PyErr_SetString(error_type, "Unexpected Antlr exception in C++ parser"); \
3081
+ } \
3082
+ return NULL; \
3083
+ }; \
3084
+ HogQLParseTreeConverter converter = HogQLParseTreeConverter(state, internal == 1); \
3085
+ PyObject* result_node = converter.visitAsPyObjectFinal(parse_tree); \
3086
+ delete error_listener; \
3087
+ delete parser; \
3088
+ delete stream; \
3089
+ delete lexer; \
3090
+ delete input_stream; \
3091
+ return result_node; \
3079
3092
  }
3080
3093
 
3081
3094
  METHOD_PARSE_NODE(Expr, expr, expr)
@@ -3126,6 +3139,29 @@ static PyMethodDef parser_methods[] = {
3126
3139
  .ml_meth = method_parse_string_literal_text,
3127
3140
  .ml_flags = METH_VARARGS,
3128
3141
  .ml_doc = "Unquote the string (an identifier or a string literal))"},
3142
+
3143
+ // JSON output methods
3144
+ {.ml_name = "parse_expr_json",
3145
+ .ml_meth = (PyCFunction)method_parse_expr_json,
3146
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
3147
+ .ml_doc = "Parse the HogQL expression string into a JSON AST"},
3148
+ {.ml_name = "parse_order_expr_json",
3149
+ .ml_meth = (PyCFunction)method_parse_order_expr_json,
3150
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
3151
+ .ml_doc = "Parse the ORDER BY clause string into a JSON AST"},
3152
+ {.ml_name = "parse_select_json",
3153
+ .ml_meth = (PyCFunction)method_parse_select_json,
3154
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
3155
+ .ml_doc = "Parse the HogQL SELECT statement string into a JSON AST"},
3156
+ {.ml_name = "parse_full_template_string_json",
3157
+ .ml_meth = (PyCFunction)method_parse_full_template_string_json,
3158
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
3159
+ .ml_doc = "Parse a Hog template string into a JSON AST"},
3160
+ {.ml_name = "parse_program_json",
3161
+ .ml_meth = (PyCFunction)method_parse_program_json,
3162
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
3163
+ .ml_doc = "Parse a Hog program into a JSON AST"},
3164
+
3129
3165
  {NULL, NULL, 0, NULL}
3130
3166
  };
3131
3167
 
@@ -3181,4 +3217,4 @@ static struct PyModuleDef parser = {
3181
3217
 
3182
3218
  PyMODINIT_FUNC PyInit_hogql_parser(void) {
3183
3219
  return PyModuleDef_Init(&parser);
3184
- }
3220
+ }
@@ -18,14 +18,17 @@ module = Extension(
18
18
  "HogQLParserVisitor.cpp",
19
19
  "error.cpp",
20
20
  "string.cpp",
21
- "parser.cpp",
21
+ "json.cpp",
22
+ "parser_python.cpp",
22
23
  ],
23
- include_dirs=[
24
- f"{homebrew_location}/include/",
25
- f"{homebrew_location}/include/antlr4-runtime/",
26
- ]
27
- if is_macos
28
- else ["/usr/include/", "/usr/include/antlr4-runtime/"],
24
+ include_dirs=(
25
+ [
26
+ f"{homebrew_location}/include/",
27
+ f"{homebrew_location}/include/antlr4-runtime/",
28
+ ]
29
+ if is_macos
30
+ else ["/usr/include/", "/usr/include/antlr4-runtime/"]
31
+ ),
29
32
  library_dirs=[f"{homebrew_location}/lib/"] if is_macos else ["/usr/lib/", "/usr/lib64/"],
30
33
  libraries=["antlr4-runtime"],
31
34
  extra_compile_args=["-std=c++20"],
@@ -33,7 +36,7 @@ module = Extension(
33
36
 
34
37
  setup(
35
38
  name="hogql_parser",
36
- version="1.2.12",
39
+ version="1.3.2",
37
40
  url="https://github.com/PostHog/posthog/tree/master/common/hogql_parser",
38
41
  description="HogQL parser for internal PostHog use",
39
42
  author="PostHog Inc.",
@@ -46,9 +49,9 @@ setup(
46
49
  include_package_data=True,
47
50
  ext_modules=[module],
48
51
  python_requires=">=3.10",
52
+ license="MIT",
49
53
  classifiers=[
50
54
  "Development Status :: 5 - Production/Stable",
51
- "License :: OSI Approved :: MIT License",
52
55
  "Operating System :: MacOS",
53
56
  "Operating System :: POSIX :: Linux",
54
57
  "Programming Language :: Python",
File without changes
File without changes
File without changes
File without changes