libidav/davql.c

changeset 135
664aeaec8d25
parent 134
4bccc18820e8
child 136
59058927b885
equal deleted inserted replaced
134:4bccc18820e8 135:664aeaec8d25
1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 2015 Olaf Wintermann. All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32
33 #include "davql.h"
34 #include "methods.h"
35 #include "utils.h"
36
37 DavQuery dav_ql_parse(char *query, va_list ap) {
38 DavQuery davquery;
39 davquery.command = DAV_QUERY_ERROR;
40 davquery.command_data = NULL;
41 sstr_t q = sstr(query);
42 q = sstrtrim(q);
43
44 // get query command
45 sstr_t cmd;
46 cmd.ptr = NULL;
47 int i;
48 for(i=0;i<q.length;i++) {
49 if(q.ptr[i] == ' ') {
50 cmd = sstrsubsl(q, 0, i);
51 break;
52 }
53 }
54 if(!cmd.ptr) {
55 fprintf(stderr, "DQL syntax error\n");
56 return davquery;
57 }
58
59 cmd = sstrtrim(cmd);
60 q = sstrtrim(sstrsubs(q, i));
61 if(!sstrcmp(cmd, S("get"))) {
62 davquery.command = DAV_QUERY_GET;
63 davquery.command_data = dav_ql_parse_get(q, ap);
64 }
65
66 return davquery;
67 }
68
69 DavGetQuery* dav_ql_parse_get(sstr_t q, va_list ap) {
70 sstr_t property_query = q;
71 q = util_getsubstr_until_token(q, S("from"), &property_query);
72
73 sstr_t from_query = q;
74 sstr_t cond = util_getsubstr_until_token(q, S("where"), &from_query);
75 sstr_t with = util_getsubstr_until_token(cond, S("with"), &cond);
76 int depth = 1;
77
78 // insert variable values
79 UcxBuffer *fbuf = ucx_buffer_new(NULL, 1024, UCX_BUFFER_AUTOEXTEND);
80 int var = 0;
81 for(int i=0;i<from_query.length;i++) {
82 char c = from_query.ptr[i];
83 if(c == '%') {
84 if(var) {
85 ucx_buffer_putc(fbuf, '%'); // previous '%'
86 } else {
87 var = 1;
88 }
89 } else if(var) {
90 switch(c) {
91 case 's': {
92 char *arg = va_arg(ap, char*);
93 ucx_buffer_puts(fbuf, arg);
94 break;
95 }
96 default: {
97 ucx_buffer_putc(fbuf, '%');
98 ucx_buffer_putc(fbuf, c);
99 }
100 }
101 var = 0;
102 } else {
103 ucx_buffer_putc(fbuf, c);
104 }
105 }
106
107 // condition
108 DavQOp *condition = NULL;
109 size_t oplen = 0;
110 if(cond.ptr) {
111 //printf("cond: {%.*s}\n", cond.length, cond.ptr);
112 UcxList *ops = NULL;
113 if(dav_parse_condition(&ops, cond, ap)) {
114 // TODO: error
115 printf("parse error\n");
116 return NULL;
117 }
118 oplen = ucx_list_size(ops);
119 condition = calloc(sizeof(DavQOp), oplen);
120 int l = 0;
121 UCX_FOREACH(elm, ops) {
122 condition[l] = *(DavQOp*)elm->data;
123 free(elm->data);
124 l++;
125 }
126 ucx_list_free(ops);
127 }
128
129 // with
130 if(with.ptr) {
131 if(dav_parse_with(with, &depth, ap)) {
132 // TODO: error
133 printf("parse error\n");
134 return NULL;
135 }
136 }
137
138 DavGetQuery *getquery = malloc(sizeof(DavGetQuery));
139 getquery->properties = sstrdup(property_query);
140 getquery->from = sstrdup(sstrn(fbuf->space, fbuf->pos));
141 getquery->depth = depth;
142 if(condition) {
143 getquery->condition = condition;
144 getquery->condlen = oplen;
145 } else {
146 getquery->condition = NULL;
147 getquery->condlen = 0;
148 }
149
150 ucx_buffer_free(fbuf);
151 return getquery;
152 }
153
154 void free_get_query(DavGetQuery *q) {
155 free(q->from.ptr);
156 free(q->properties.ptr);
157 if(q->condition) {
158 free(q->condition);
159 }
160 free(q);
161 }
162
163 int parse_path_query(sstr_t query, char **path, int *depth) {
164 if(query.length == 1) {
165 if(query.ptr[0] == '/') {
166 *path = sstrdup(query).ptr;
167 *depth = 1;
168 return 0;
169 } else {
170 *path = NULL;
171 return 1;
172 }
173 }
174
175 if(query.ptr[query.length-1] == '*') {
176 *depth = -1;
177 *path = sstrdup(sstrsubsl(query, 0, query.length-1)).ptr;
178 } else {
179 *path = sstrdup(query).ptr;
180 *depth = 1;
181 }
182
183 return 0;
184 }
185
186 static int dav_str2depth(sstr_t str, int *depth) {
187 if(!sstrcmp(str, S("infinity"))) {
188 *depth = -1;
189 } else {
190 sstr_t cp = sstrdup(str); // terminate
191 *depth = atoi(cp.ptr);
192 free(cp.ptr);
193 }
194 return 0;
195 }
196
197 int dav_parse_with(sstr_t with, int *depth, va_list ap) {
198 int i;
199 for(i=0;i<with.length;i++) {
200 if(with.ptr[i] == ' ') {
201 break;
202 }
203 }
204
205 sstr_t name = sstrsubsl(with, 0, i);
206 sstr_t value = sstrtrim(sstrsubs(with, i));
207 //printf("with {%.*s} {%.*s}\n", name.length, name.ptr, value.length, value.ptr);
208
209 if(!sstrcmp(name, S("depth"))) {
210 if(value.length == 0) {
211 return 1;
212 } else if(value.ptr[0] == '%') {
213 switch(value.ptr[1]) {
214 default: return 1;
215 case 's': {
216 sstr_t v = sstr(va_arg(ap, char*));
217 if(dav_str2depth(value, depth)) {
218 return 1;
219 }
220 break;
221 }
222 case 'd': {
223 *depth = va_arg(ap, int);
224 break;
225 }
226 }
227 } else {
228 if(dav_str2depth(value, depth)) {
229 return 1;
230 }
231 }
232 }
233
234 return 0;
235 }
236
237 int dav_parse_condition(UcxList **ops, sstr_t cond, va_list ap) {
238 sstr_t token;
239 DavQOp *op1 = NULL; // level 1 operator
240 DavQOp *op2 = NULL; // level 2 operator
241 DavQOp *op3 = NULL; // level 3 operator
242 while((token = condition_parser_next_token(&cond)).length > 0) {
243 //printf("token: {%.*s}[%d]\n", token.length, token.ptr, token.length);
244 int64_t type = 0;
245 int tkop = condition_operator_type(token, &type);
246 DavQOp *operation = malloc(sizeof(DavQOp));
247 if(tkop > 0) {
248 // operator token
249 operation->type = DAVQOP_OPERATOR;
250 operation->val = NULL;
251 operation->intval = type;
252 switch(tkop) {
253 case 1: {
254 // operators: + - / * not
255 // add operation after next non operator token
256 op1 = operation;
257 break;
258 }
259 case 2: {
260 // operators: < > == != <= >=
261 if(op2) {
262 *ops = ucx_list_append(*ops, op2);
263 }
264 op2 = operation;
265 break;
266 }
267 case 3: {
268 // operators: and or xor
269 if(op2) {
270 *ops = ucx_list_append(*ops, op2);
271 op2 = NULL;
272 }
273 if(op3) {
274 *ops = ucx_list_append(*ops, op3);
275 }
276 op3 = operation;
277 break;
278 }
279 }
280 } else {
281 if(token.ptr[0] == '"' || token.ptr[0] == '\'') {
282 operation->type = DAVQOP_STRING;
283 operation->val = token.ptr+1;
284 operation->intval = token.length-2;
285 } else if(!sstrcmp(token, S("true")) ||
286 !sstrcmp(token, S("false")))
287 {
288 operation->type = DAVQOP_INTEGER;
289 operation->val = NULL;
290 operation->intval = util_getboolean(token.ptr);
291 } else if(token.length == 2 && token.ptr[0] == '%') {
292 switch(token.ptr[1]) {
293 case 's': {
294 char *arg = va_arg(ap, char*);
295 operation->type = DAVQOP_STRING;
296 operation->val = arg;
297 operation->intval = strlen(arg);
298 break;
299 }
300 case 'd': {
301 operation->type = DAVQOP_INTEGER;
302 operation->val = NULL;
303 operation->intval = va_arg(ap, int);
304 break;
305 }
306 case 't': {
307 operation->type = DAVQOP_INTEGER;
308 operation->val = NULL;
309 operation->intval = va_arg(ap, time_t);
310 break;
311 }
312 default: {
313 operation->type = DAVQOP_STRING;
314 operation->val = token.ptr;
315 operation->intval = token.length;
316 }
317 }
318 } else {
319 sstr_t d = sstrdup(token);
320 int64_t val = 0;
321 int intval = util_strtoint(d.ptr, &val);
322 free(d.ptr);
323 if(intval) {
324 operation->type = DAVQOP_INTEGER;
325 operation->val = NULL;
326 operation->intval = val;
327 } else {
328 if(!sstrcmp(token, S("contentlength"))) {
329 operation->type = DAVQOP_RESPROP;
330 } else if(!sstrcmp(token, S("lastmodified"))) {
331 operation->type = DAVQOP_RESPROP;
332 } else if(!sstrcmp(token, S("creationdate"))) {
333 operation->type = DAVQOP_RESPROP;
334 } else if(!sstrcmp(token, S("name"))) {
335 operation->type = DAVQOP_RESPROP;
336 } else if(!sstrcmp(token, S("path"))) {
337 operation->type = DAVQOP_RESPROP;
338 } else if(!sstrcmp(token, S("iscollection"))) {
339 operation->type = DAVQOP_RESPROP;
340 } else {
341 operation->type = DAVQOP_PROPERTY;
342 }
343 operation->val = token.ptr;
344 operation->intval = token.length;
345 }
346 }
347
348 // add operation
349 *ops = ucx_list_append(*ops, operation);
350 if(op1) {
351 // add level 1 operator
352 *ops = ucx_list_append(*ops, op1);
353 op1 = NULL;
354 }
355 }
356 }
357 if(op1) {
358 *ops = ucx_list_append(*ops, op1);
359 }
360 if(op2) {
361 *ops = ucx_list_append(*ops, op2);
362 }
363 if(op3) {
364 *ops = ucx_list_append(*ops, op3);
365 }
366 return 0;
367 }
368
369 sstr_t condition_parser_next_token(sstr_t *str) {
370 sstr_t s = *str;
371 sstr_t t;
372 t.ptr = NULL;
373 t.length = 0;
374 // remove leading space
375 int i;
376 for(i=0;i<s.length;i++) {
377 if(s.ptr[i] > 32) {
378 break;
379 }
380 }
381 s.length -= i;
382 s.ptr += i;
383
384 if(s.length == 0) {
385 *str = s;
386 return t;
387 }
388
389 // check for single char operators
390 switch(s.ptr[0]) {
391 case '<':
392 case '>':
393 case '+':
394 case '-':
395 case '*':
396 case '/': {
397 t.ptr = s.ptr;
398 t.length = 1;
399 str->ptr = s.ptr + 1;
400 str->length = s.length - 1;
401 return t;
402 }
403 }
404
405 if(s.length > 1) {
406 // check for double char operators
407 int16_t op = *(int16_t*)s.ptr;
408 if(op == '==' || op == '!=' || op == '>=' || op == '=<') {
409 t.ptr = s.ptr;
410 t.length = 2;
411 str->ptr = s.ptr + 2;
412 str->length = s.length - 2;
413 return t;
414 }
415 } else {
416 t.ptr = s.ptr;
417 t.length = 1;
418 str->ptr = s.ptr + 1;
419 str->length = s.length - 1;
420 return t;
421 }
422
423 // TODO: brackets
424
425 // check for string literal
426 if(s.ptr[0] == '\'' || s.ptr[0] == '"') {
427 for(i=1;i<s.length;i++) {
428 if(s.ptr[0] == s.ptr[i]) {
429 i++;
430 break;
431 }
432 }
433 t.ptr = s.ptr;
434 t.length = i;
435 str->ptr = s.ptr + i;
436 str->length = s.length - i;
437 return t;
438 }
439
440 for(i=0;i<s.length;i++) {
441 char c = s.ptr[i];
442 if((c < 33) || (c > 41 && c < 48) || (c > 59 && c < 63)) {
443 break;
444 }
445 }
446 t.ptr = s.ptr;
447 t.length = i;
448 str->ptr = s.ptr + i;
449 str->length = s.length - i;
450 return t;
451 }
452
453 int condition_operator_type(sstr_t token, int64_t *type) {
454 // returns the operator level and sets the type
455
456 if(token.ptr[0] == '"' || token.ptr[0] == '\'' || token.ptr[0] == '(') {
457 return 0;
458 }
459
460 if(token.length == 1) {
461 switch(token.ptr[0]) {
462 case '+': *type = 1; return 1;
463 case '-': *type = 2; return 1;
464 case '*': *type = 3; return 1;
465 case '/': *type = 4; return 1;
466 case '<': *type = 5; return 2;
467 case '>': *type = 6; return 2;
468 }
469 }
470 if(!sstrcmp(token, S("not"))) {
471 *type = 0;
472 return 1;
473 }
474
475 if(!sstrcmp(token, S("=="))) {
476 *type = 7;
477 return 2;
478 }
479 if(!sstrcmp(token, S("!="))) {
480 *type = 8;
481 return 2;
482 }
483 if(!sstrcmp(token, S("<="))) {
484 *type = 9;
485 return 2;
486 }
487 if(!sstrcmp(token, S(">="))) {
488 *type = 10;
489 return 2;
490 }
491
492 if(!sstrcmp(token, S("and"))) {
493 *type = 11;
494 return 3;
495 }
496 if(!sstrcmp(token, S("or"))) {
497 *type = 12;
498 return 3;
499 }
500 if(!sstrcmp(token, S("xor"))) {
501 *type = 13;
502 return 3;
503 }
504
505 return 0;
506 }
507
508 int condition_eval(DavResource *res, DavQOp *cond, size_t len) {
509 DavQOp stack[128];
510 int stackpos = 0;
511 for(int i=0;i<len;i++) {
512 DavQOp op = cond[i];
513 switch(op.type) {
514 case DAVQOP_OPERATOR: {
515 if(op.intval == 0) {
516 // not operator
517 if(stackpos < 1) {
518 // error
519 printf("no data on stack\n");
520 return 0;
521 }
522 int pos = stackpos-1;
523 if(stack[pos].type == DAVQOP_INTEGER) {
524 //printf("not %" PRId64 "\n", stack[pos].intval);
525 stack[pos].intval = !stack[pos].intval;
526 } else {
527 // error
528 printf("wrong value for 'not' operator\n");
529 return 0;
530 }
531 } else {
532 DavQOp val1 = stack[stackpos-2];
533 DavQOp val2 = stack[stackpos-1];
534 DavQOp result;
535 if(val1.type == DAVQOP_INTEGER) {
536 if(val2.type == DAVQOP_INTEGER) {
537 result = compare_intint(
538 op.intval,
539 val1.intval,
540 val2.intval);
541 } else {
542 result = compare_intstr(op.intval, val1, val2);
543 }
544 } else {
545 if(val2.type == DAVQOP_INTEGER) {
546 result = compare_strint(op.intval, val1, val2);
547 } else {
548 result = compare_strstr(op.intval, val1, val2);
549 }
550 }
551 stack[stackpos-2] = result;
552 stackpos--;
553 }
554 break;
555 }
556 case DAVQOP_STRING:
557 case DAVQOP_INTEGER:
558 case DAVQOP_TIME: {
559 if(op.type == DAVQOP_STRING) {
560 //printf("put on stack: '%s'\n", op.val);
561 } else {
562 //printf("put on stack[%d]: %" PRId64 "\n", stackpos, op.intval);
563 }
564 stack[stackpos++] = op;
565 break;
566 }
567 case DAVQOP_PROPERTY: {
568 sstr_t pname = sstrn(op.val, op.intval);
569 pname = sstrdup(pname);
570 char *property_value = dav_get_property(res, pname.ptr);
571 free(pname.ptr);
572 DavQOp value;
573 value.type = DAVQOP_STRING;
574 if(property_value) {
575 //printf("put on stack: \"%s\"\n", property_value);
576 value.val = property_value;
577 value.intval = strlen(property_value);
578 } else {
579 //printf("put on stack: null string\n");
580 value.val = NULL;
581 value.intval = 0;
582 }
583 stack[stackpos++] = value;
584 break;
585 }
586 case DAVQOP_RESPROP: {
587 sstr_t name = sstrn(op.val, op.intval);
588 DavQOp value;
589 value.type = DAVQOP_INTEGER;
590 value.val = NULL;
591 if(!sstrcmp(name, S("contentlength"))) {
592 //printf("put contentlength\n");
593 value.intval = res->contentlength;
594 } else if(!sstrcmp(name, S("lastmodified"))) {
595 //printf("put getlastmodified\n");
596 value.intval = res->lastmodified;
597 } else if(!sstrcmp(name, S("creationdate"))) {
598 value.intval = res->creationdate;
599 } else if(!sstrcmp(name, S("name"))) {
600 value.type = DAVQOP_STRING;
601 value.val = res->name;
602 value.intval = strlen(res->name);
603 } else if(!sstrcmp(name, S("path"))) {
604 value.type = DAVQOP_STRING;
605 value.val = res->path;
606 value.intval = strlen(res->path);
607 } else if(!sstrcmp(name, S("iscollection"))) {
608 value.type = DAVQOP_INTEGER;
609 value.val = NULL;
610 value.intval = res->iscollection;
611 }
612 stack[stackpos++] = value;
613 break;
614 }
615 }
616 }
617 if(stackpos != 1) {
618 return 0;
619 }
620 DavQOp result = stack[0];
621 //printf("result: %" PRId64 "\n", result.intval);
622 return (int)result.intval;
623 }
624
625 DavQOp compare_intint(int op, int64_t v1, int64_t v2) {
626 DavQOp res;
627 res.type = DAVQOP_INTEGER;
628 res.val = NULL;
629 res.intval = 0;
630 switch(op) {
631 case 5: {
632 // <
633 //printf("compare: %" PRId64 " < %" PRId64 "\n", v1, v2);
634 res.intval = v1 < v2;
635 break;
636 }
637 case 6: {
638 // >
639 //printf("compare: %" PRId64 " > %" PRId64 "\n", v1, v2);
640 res.intval = v1 > v2;
641 break;
642 }
643 case 7: {
644 // ==
645 //printf("compare: %" PRId64 " == %" PRId64 "\n", v1, v2);
646 res.intval = v1 == v2;
647 break;
648 }
649 case 8: {
650 // !=
651 //printf("compare: %" PRId64 " != %" PRId64 "\n", v1, v2);
652 res.intval = v1 != v2;
653 break;
654 }
655 case 9: {
656 // <=
657 //printf("compare: %" PRId64 " <= %" PRId64 "\n", v1, v2);
658 res.intval = v1 <= v2;
659 break;
660 }
661 case 10: {
662 // >=
663 //printf("compare: %" PRId64 " >= %" PRId64 "\n", v1, v2);
664 res.intval = v1 >= v2;
665 break;
666 }
667 case 11: {
668 // and
669 //printf("compare: %" PRId64 " and %" PRId64 "\n", v1, v2);
670 res.intval = v1 && v2;
671 break;
672 }
673 case 12: {
674 // or
675 //printf("compare: %" PRId64 " or %" PRId64 "\n", v1, v2);
676 res.intval = v1 || v2;
677 break;
678 }
679 case 13: {
680 // xor
681 //printf("compare: %" PRId64 " xor %" PRId64 "\n", v1, v2);
682 res.intval = v1 ^ v2;
683 break;
684 }
685 }
686 return res;
687 }
688
689 DavQOp compare_strint(int op, DavQOp v1, DavQOp v2) {
690 int64_t v1int;
691 sstr_t s1 = sstrn(v1.val, v1.intval);
692 s1 = sstrdup(s1);
693 if(util_strtoint(s1.ptr, &v1int)) {
694 free(s1.ptr);
695 return compare_intint(op, v1int, v2.intval);
696 } else {
697 free(s1.ptr);
698 // TODO
699 }
700 }
701
702 DavQOp compare_intstr(int op, DavQOp v1, DavQOp v2) {
703 // TODO
704 }
705
706 DavQOp compare_strstr(int op, DavQOp v1, DavQOp v2) {
707 DavQOp res;
708 res.type = DAVQOP_INTEGER;
709 res.val = NULL;
710 res.intval = 0;
711 sstr_t s1 = sstrn(v1.val, v1.intval);
712 sstr_t s2 = sstrn(v2.val, v2.intval);
713 switch(op) {
714 case 5: {
715 // <
716 //printf("str compare: %.*s < %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
717 res.intval = s1.length < s2.length;
718 break;
719 }
720 case 6: {
721 // >
722 //printf("str compare: %.*s > %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
723 res.intval = s1.length > s2.length;
724 break;
725 }
726 case 7: {
727 // ==
728 //printf("str compare: %.*s == %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
729 res.intval = sstrcmp(s1, s2) == 0;
730 break;
731 }
732 case 8: {
733 // !=
734 //printf("str compare: %.*s != %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
735 res.intval = sstrcmp(s1, s2) != 0;
736 break;
737 }
738 case 9: {
739 // <=
740 //printf("str compare: %.*s <= %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
741 res.intval = s1.length <= s2.length;
742 break;
743 }
744 case 10: {
745 // >=
746 //printf("str compare: %.*s >= %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
747 res.intval = s1.length >= s2.length;
748 break;
749 }
750 case 11: {
751 // and
752 //printf("str compare: %.*s and %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
753 res.intval = s1.ptr && s2.ptr;
754 break;
755 }
756 case 12: {
757 // or
758 //printf("str compare: %.*s or %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
759 res.intval = s1.ptr || s2.ptr;
760 break;
761 }
762 case 13: {
763 // xor
764 //printf("str compare: %.*s xor %.*s\n", s1.length, s1.ptr, s2.length, s2.ptr);
765 res.intval = (intptr_t)s1.ptr ^ (intptr_t)s2.ptr;
766 break;
767 }
768 }
769 return res;
770 }

mercurial