File: operator.bs

package info (click to toggle)
storm-lang 0.7.4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 52,004 kB
  • sloc: ansic: 261,462; cpp: 140,405; sh: 14,891; perl: 9,846; python: 2,525; lisp: 2,504; asm: 860; makefile: 678; pascal: 70; java: 52; xml: 37; awk: 12
file content (341 lines) | stat: -rw-r--r-- 8,438 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
use core:lang;
use lang:bs:macro;


/**
 * Operator that could be evaluated in Storm as well.
 */
class Operator extends SQLExpr {
	// Left hand side.
	SQLExpr lhs;

	// Right hand side.
	SQLExpr rhs;

	// Operator name in Storm (if available)
	Str stormOp;

	// Operator name in SQL.
	Str sqlOp;

	// Create.
	init(SQLExpr lhs, SQLExpr rhs, SStr op) {
		init(op.pos) {
			lhs = lhs;
			rhs = rhs;
			stormOp = op.v;
			sqlOp = op.v;
		}
	}

	// Create.
	init(SQLExpr lhs, SQLExpr rhs, SStr sql, Str storm) {
		init(sql.pos) {
			lhs = lhs;
			rhs = rhs;
			stormOp = storm;
			sqlOp = sql.v;
		}
	}

	SQLExpr resolve(ResolveContext context) : override {
		lhs = lhs.resolve(context);
		rhs = rhs.resolve(context);

		// Check if this is a comparison operator that is trivially true/false.
		checkOperands(lhs, rhs);

		// If this is a non-typed context, we don't know enough to really do anything. Calling
		// .result might cause an exception since we don't have type information for example.
		if (!context.typed)
			return this;

		// If maybe types are involved, we bail out in trying to simplify.
		if (isMaybe(lhs.result) | isMaybe(rhs.result))
			return this;

		if (lStorm = lhs.stormExpr) {
			if (rStorm = rhs.stormExpr) {
				// Simplify!
				if (fn = findFunction()) {
					lang:bs:Actuals params;
					params.add(lStorm);
					params.add(rStorm);
					lang:bs:FnCall call(pos, Scope(), fn, params);
					return StormValue(pos, call);
				}
			}
		}

		this;
	}

	// Compute the result.
	Value computeResult() : override {
		unless (fn = findFunction()) {
			Value l = unwrapMaybe(lhs.result);
			Value r = unwrapMaybe(rhs.result);
			throw SyntaxError(pos, "Invalid parameter types for the operator: ${l} ${sqlOp} ${r}");
		}

		if (isMaybe(lhs.result) | isMaybe(rhs.result))
			wrapMaybe(fn.result);
		else
			fn.result;
	}

	// Build the operator.
	void build(QueryBuilder to) : override {
		to.query.put("(");
		lhs.build(to);
		to.query.put(" ");
		to.query.put(sqlOp);
		to.query.put(" ");
		rhs.build(to);
		to.query.put(")");
	}

	// To string.
	void toS(StrBuf to) : override {
		to << lhs << " " << sqlOp << " " << rhs;
	}

	// Hook that allows operators to perform sanity checks after resolving operands.
	protected void checkOperands(SQLExpr lhs, SQLExpr rhs) {}

	// Figure out which function in Storm this operator would call.
	private Function? findFunction() {
		unless (lType = unwrapMaybe(lhs.result).type)
			throw SyntaxError(lhs.pos, "Unknown type of subexpression.");
		unless (rType = unwrapMaybe(rhs.result).type)
			throw SyntaxError(rhs.pos, "Unknown type of subexpression.");

		checkTypes(lType, rType);

		Value[] params = [Value(lType), Value(rType)];
		if (x = lType.find(stormOp, params, Scope()) as Function)
			return x;

		// Try one step up.
		if (parent = lType.parent) {
			if (x = parent.find(stormOp, params, Scope()) as Function)
				return x;
		}

		return null;
	}

	// Allow subclasses to override, in order to limit available types.
	protected void checkTypes(Type l, Type r) {}
}

// Variant for comparison operators.
class CompareOperator extends Operator {
	// Create.
	init(SQLExpr lhs, SQLExpr rhs, SStr op) {
		init(lhs, rhs, op) {}
	}

	// Create.
	init(SQLExpr lhs, SQLExpr rhs, SStr sql, Str storm) {
		init(lhs, rhs, sql, storm) {}
	}

	// Check if this operator is trivially true/false due to mistakes with binding variables (e.g.
	// WHERE x == x, where the programmer intended the second 'x' to refer to the surrounding
	// context).
	protected void checkOperands(SQLExpr lhs, SQLExpr rhs) : override {
		checkOperands(lhs, rhs, pos, sqlOp, sqlOp.find("=") != sqlOp.end);
	}

	// Helper called by 'checkOperands' so that we can use it from LikeOperator as well.
	void checkOperands(SQLExpr lhs, SQLExpr rhs, SrcPos pos, Str opName, Bool alwaysTrue) : static {
		unless (lhs as SQLColumn)
			return;
		unless (rhs as SQLColumn)
			return;

		if (lhs.table != rhs.table)
			return;
		if (lhs.column.name != rhs.column.name)
			return;

		var res = if (alwaysTrue) { "true"; } else { "false"; };
		throw SyntaxError(pos, "The result of this ${opName} operator is always ${res} since both operands refer to the same column.");
	}
}


// Create the equals operator, as we allow both = and ==.
Operator equalsOp(SQLExpr lhs, SQLExpr rhs, SStr op) on Compiler {
	CompareOperator(lhs, rhs, SStr("=", op.pos), "==");
}


class StrConcatOp extends Operator {
	init(SQLExpr lhs, SQLExpr rhs, SStr op) {
		init(lhs, rhs, op, "+") {}
	}

	protected void checkTypes(Type l, Type r) : override {
		if ((l !is named{Str}) | (r !is named{Str}))
			throw SyntaxError(pos, "Both operands must be strings. Not ${Value(l)} and ${Value(r)}.");
	}
}

// Just like Operator, but disallows strings.
class NumOperator extends Operator {
	init(SQLExpr lhs, SQLExpr rhs, SStr op) {
		init(lhs, rhs, op) {}
	}

	protected void checkTypes(Type l, Type r) : override {
		if ((l is named{Str}) | (r is named{Str}))
			throw SyntaxError(pos, "The operator ${sqlOp} is not applicable to strings in SQL.");
	}
}

// Just like Operator, but only allows boolean values.
class BoolOperator extends Operator {
	init(SQLExpr lhs, SQLExpr rhs, SStr op, Str storm) {
		init(lhs, rhs, op, storm) {}
	}

	protected void checkTypes(Type l, Type r) : override {
		if ((l !is named{Bool}) | (r !is named{Bool})) {
			throw SyntaxError(pos, "The operator ${sqlOp} requires both operands to be booleans.");
		}
	}
}

// Create logical operators.
BoolOperator andOp(SQLExpr lhs, SQLExpr rhs, SStr op) {
	BoolOperator(lhs, rhs, op, "&");
}
BoolOperator orOp(SQLExpr lhs, SQLExpr rhs, SStr op) {
	BoolOperator(lhs, rhs, op, "|");
}

// NOT operator.
class NotOperator extends SQLExpr {
	SQLExpr operand;

	init(SrcPos pos, SQLExpr op) {
		init(pos) {
			operand = op;
		}
	}

	SQLExpr resolve(ResolveContext context) : override {
		operand = operand.resolve(context);

		if (storm = operand.stormExpr) {
			// For typechecking.
			computeResult();

			unless (fn = named{Bool}.find("!", [Value(named{Bool})], Scope()) as Function)
				throw InternalError("Unable to find the unary negation function.");

			lang:bs:FnCall call(pos, Scope(), fn, lang:bs:Actuals(storm));
			return StormValue(pos, call);
		}

		this;
	}

	Value computeResult() : override {
		if (unwrapMaybe(operand.result).type !is named{Bool})
			throw SyntaxError(operand.pos, "The operand to NOT must be a boolean.");

		return named{Bool};
	}

	void build(QueryBuilder to) : override {
		to.query.put("!");
		operand.build(to);
	}
}

// LIKE operator. This is special since it does not exist in Storm.
class LikeOperator extends SQLExpr {
	// Left- and right hand sides.
	SQLExpr lhs;
	SQLExpr rhs;

	// Create.
	init(SrcPos pos, SQLExpr l, SQLExpr r) {
		init(pos) {
			lhs = l;
			rhs = r;
		}
	}

	SQLExpr resolve(ResolveContext context) : override {
		lhs = lhs.resolve(context);
		rhs = rhs.resolve(context);

		CompareOperator:checkOperands(lhs, rhs, pos, "LIKE", true);

		this;
	}

	Value computeResult() : override {
		if (unwrapMaybe(lhs.result).type !is named{Str})
			throw SyntaxError(lhs.pos, "The left-hand side of a LIKE operator must be a string.");
		if (unwrapMaybe(rhs.result).type !is named{Str})
			throw SyntaxError(lhs.pos, "The right-hand side of a LIKE operator must be a string.");

		if (isMaybe(lhs.result) | isMaybe(rhs.result))
			return wrapMaybe(named{Bool});
		else
			return named{Bool};
	}

	void build(QueryBuilder to) : override {
		to.query.put("(");
		lhs.build(to);
		to.query.put(" LIKE ");
		rhs.build(to);
		to.query.put(")");
	}
}

// IS NULL and IS NOT NULL operators. Distinct from how they work in Storm.
class NullCheckOperator extends SQLExpr {
	// Expression.
	SQLExpr expr;

	// Negate the null check?
	Bool negate;

	// Create.
	init(SrcPos pos, SQLExpr expr, Bool negate) {
		init(pos) {
			expr = expr;
		}
	}

	SQLExpr resolve(ResolveContext context) : override {
		expr = expr.resolve(context);
		this;
	}

	Value computeResult() : override {
		// TODO: This check would be nice, but we do not yet have enough context from left joins etc. for that.
		// if (!isMaybe(expr.result))
		// 	throw SyntaxError(expr.pos, "The operand to the null-check operator can not be NULL.");

		return named{Bool};
	}

	void build(QueryBuilder to) : override {
		to.query.put("(");
		expr.build(to);
		if (negate)
			to.query.put("IS NOT NULL");
		else
			to.query.put("IS NULL");
		to.query.put(")");
	}

}