API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPPredicate.j
Go to the documentation of this file.
1 /*
2  * CPPredicate.j
3  *
4  * CPPredicate parsing based on NSPredicate.m in GNUStep Base Library (http://www.gnustep.org/)
5  * Copyright (c) 2005 Free Software Foundation.
6  *
7  * Created by cacaodev.
8  * Copyright 2010.
9  *
10  * This library is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public
12  * License as published by the Free Software Foundation; either
13  * version 2.1 of the License, or (at your option) any later version.
14  *
15  * This library is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with this library; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23  */
24 
25 
52 @implementation CPPredicate : CPObject
53 {
54  id __doxygen__;
55 }
56 
63 + (CPPredicate)predicateWithFormat:(CPString)format, ...
64 {
65  if (!format)
66  [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"];
67 
68  var args = Array.prototype.slice.call(arguments, 3);
69  return [self predicateWithFormat:arguments[2] argumentArray:args];
70 }
71 
78 + (CPPredicate)predicateWithFormat:(CPString)format argumentArray:(CPArray)args
79 {
80  if (!format)
81  [CPException raise:CPInvalidArgumentException reason:_cmd + " the format can't be 'nil'"];
82 
83  var s = [[CPPredicateScanner alloc] initWithString:format args:args],
84  p = [s parse];
85 
86  return p;
87 }
88 
95 + (CPPredicate)predicateWithFormat:(CPString)format arguments:(va_list)argList
96 {
97  // UNIMPLEMENTED
98  return nil;
99 }
100 
106 - (CPPredicate)predicateWithSubstitutionVariables:(CPDictionary)variables
107 {
108  // IMPLEMENTED BY SUBCLASSES
109 }
110 
116 + (CPPredicate)predicateWithValue:(BOOL)value
117 {
118  return [[CPPredicate_BOOL alloc] initWithBool:value];
119 }
120 
121 // Evaluating a Predicate
127 - (BOOL)evaluateWithObject:(id)object
128 {
129  // IMPLEMENTED BY SUBCLASSES
130 }
131 
138 - (BOOL)evaluateWithObject:(id)object substitutionVariables:(CPDictionary)variables
139 {
140  // IMPLEMENTED BY SUBCLASSES
141 }
142 
143 // Getting Format Information
148 - (CPString)predicateFormat
149 {
150  // IMPLEMENTED BY SUBCLASSES
151 }
152 
153 - (CPString)description
154 {
155  return [self predicateFormat];
156 }
157 
158 @end
159 
160 @implementation CPPredicate_BOOL : CPPredicate
161 {
162  BOOL _value;
163 }
164 
165 - (id)initWithBool:(BOOL)value
166 {
167  _value = value;
168  return self;
169 }
170 
171 - (BOOL)isEqual:(id)anObject
172 {
173  if (self === anObject)
174  return YES;
175 
176  if (self.isa !== anObject.isa || _value !== [anObject evaluateObject:nil])
177  return NO;
178 
179  return YES;
180 }
181 
182 - (BOOL)evaluateObject:(id)object
183 {
184  return _value;
185 }
186 
187 - (CPString)predicateFormat
188 {
189  return (_value) ? @"TRUEPREDICATE" : @"FALSEPREDICATE";
190 }
191 
192 @end
193 
194 
195 @implementation CPArray (CPPredicate)
196 
197 - (CPArray)filteredArrayUsingPredicate:(CPPredicate)predicate
198 {
199  var count = [self count],
200  result = [CPArray array],
201  i = 0;
202 
203  for (; i < count; i++)
204  {
205  var object = [self objectAtIndex:i];
206  if ([predicate evaluateWithObject:object])
207  result.push(object);
208  }
209 
210  return result;
211 }
212 
213 - (void)filterUsingPredicate:(CPPredicate)predicate
214 {
215  var count = [self count];
216 
217  while (count--)
218  {
219  if (![predicate evaluateWithObject:[self objectAtIndex:count]])
220  splice(count, 1);
221  }
222 }
223 
224 @end
225 
226 @implementation CPSet (CPPredicate)
227 
228 - (CPSet)filteredSetUsingPredicate:(CPPredicate)predicate
229 {
230  var count = [self count],
231  result = [CPSet set],
232  i = 0;
233 
234  for (; i < count; i++)
235  {
236  var object = [self objectAtIndex:i];
237 
238  if ([predicate evaluateWithObject:object])
239  [result addObject:object];
240  }
241 
242  return result;
243 }
244 
245 - (void)filterUsingPredicate:(CPPredicate)predicate
246 {
247  var count = [self count];
248 
249  while (--count >= 0)
250  {
251  var object = [self objectAtIndex:count];
252 
253  if (![predicate evaluateWithObject:object])
254  [self removeObjectAtIndex:count];
255  }
256 }
257 
258 @end
259 
260 #define REFERENCE(variable) \
261 function(newValue)\
262 {\
263  var oldValue = variable;\
264  if (typeof newValue != 'undefined')\
265  variable = newValue;\
266  return oldValue;\
267 }
268 
269 @implementation CPPredicateScanner : CPScanner
270 {
271  CPEnumerator _args;
272  unsigned _retrieved;
273 }
274 
275 - (id)initWithString:(CPString)format args:(CPArray)args
276 {
277  self = [super initWithString:format]
278 
279  if (self)
280  {
281  _args = [args objectEnumerator];
282  }
283  return self;
284 }
285 
286 - (id)nextArg
287 {
288  return [_args nextObject];
289 }
290 
291 - (BOOL)scanPredicateKeyword:(CPString)key
292 {
293  var loc = [self scanLocation];
294 
295  [self setCaseSensitive:NO];
296  if (![self scanString:key intoString:NULL])
297  return NO;
298 
299  if ([self isAtEnd])
300  return YES;
301 
302  var c = [[self string] characterAtIndex:[self scanLocation]];
303  if (![[CPCharacterSet alphanumericCharacterSet] characterIsMember:c])
304  return YES;
305 
306  [self setScanLocation:loc];
307 
308  return NO;
309 }
310 
311 - (CPPredicate)parse
312 {
313  var r = nil;
314 
315  try
316  {
317  [self setCharactersToBeSkipped:[CPCharacterSet whitespaceCharacterSet]];
318  r = [self parsePredicate];
319  }
320  catch(error)
321  {
322  CPLogConsole(@"Unable to parse predicate '" + [self string] + "' with " + error);
323  }
324  finally
325  {
326  if (![self isAtEnd])
327  {
328  var pstr = [self string],
329  loc = [self scanLocation];
330  CPLogConsole(@"Format string contains extra characters: '" + [pstr substringToIndex:loc] + "**" + [pstr substringFromIndex:loc] + "**'");
331  }
332  }
333 
334  return r;
335 }
336 
337 - (CPPredicate)parsePredicate
338 {
339  return [self parseAnd];
340 }
341 
342 - (CPPredicate)parseAnd
343 {
344  var l = [self parseOr];
345 
346  while ([self scanPredicateKeyword:@"AND"] || [self scanPredicateKeyword:@"&&"])
347  {
348  var r = [self parseOr];
349 
350  if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPAndPredicateType)
351  {
352  if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType)
353  {
354  [[l subpredicates] addObjectsFromArray:[r subpredicates]];
355  }
356  else
357  {
358  [[r subpredicates] insertObject:l atIndex:0];
359  l = r;
360  }
361  }
362  else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPAndPredicateType)
363  {
364  [[l subpredicates] addObject:r];
365  }
366  else
367  {
369  }
370  }
371  return l;
372 }
373 
374 - (CPPredicate)parseNot
375 {
376  if ([self scanString:@"(" intoString:NULL])
377  {
378  var r = [self parsePredicate];
379 
380  if (![self scanString:@")" intoString:NULL])
381  CPRaiseParseError(self, @"predicate");
382 
383  return r;
384  }
385 
386  if ([self scanPredicateKeyword:@"NOT"] || [self scanPredicateKeyword:@"!"])
387  {
388  return [CPCompoundPredicate notPredicateWithSubpredicate:[self parseNot]];
389  }
390  if ([self scanPredicateKeyword:@"TRUEPREDICATE"])
391  {
392  return [CPPredicate predicateWithValue:YES];
393  }
394  if ([self scanPredicateKeyword:@"FALSEPREDICATE"])
395  {
396  return [CPPredicate predicateWithValue:NO];
397  }
398 
399  return [self parseComparison];
400 }
401 
402 - (CPPredicate)parseOr
403 {
404  var l = [self parseNot];
405  while ([self scanPredicateKeyword:@"OR"] || [self scanPredicateKeyword:@"||"])
406  {
407  var r = [self parseNot];
408 
409  if ([r isKindOfClass:[CPCompoundPredicate class]] && [r compoundPredicateType] == CPOrPredicateType)
410  {
411  if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType)
412  {
413  [[l subpredicates] addObjectsFromArray:[r subpredicates]];
414  }
415  else
416  {
417  [[r subpredicates] insertObject:l atIndex:0];
418  l = r;
419  }
420  }
421  else if ([l isKindOfClass:[CPCompoundPredicate class]] && [l compoundPredicateType] == CPOrPredicateType)
422  {
423  [[l subpredicates] addObject:r];
424  }
425  else
426  {
428  }
429  }
430  return l;
431 }
432 
433 - (CPPredicate)parseComparison
434 {
435  var modifier = CPDirectPredicateModifier,
436  type = 0,
437  opts = 0,
438  left,
439  right,
440  p,
441  negate = NO;
442 
443  if ([self scanPredicateKeyword:@"ANY"])
444  {
445  modifier = CPAnyPredicateModifier;
446  }
447  else if ([self scanPredicateKeyword:@"ALL"])
448  {
449  modifier = CPAllPredicateModifier;
450  }
451  else if ([self scanPredicateKeyword:@"NONE"])
452  {
453  modifier = CPAnyPredicateModifier;
454  negate = YES;
455  }
456  else if ([self scanPredicateKeyword:@"SOME"])
457  {
458  modifier = CPAllPredicateModifier;
459  negate = YES;
460  }
461 
462  left = [self parseExpression];
463  if ([self scanString:@"!=" intoString:NULL] || [self scanString:@"<>" intoString:NULL])
464  {
466  }
467  else if ([self scanString:@"<=" intoString:NULL] || [self scanString:@"=<" intoString:NULL])
468  {
470  }
471  else if ([self scanString:@">=" intoString:NULL] || [self scanString:@"=>" intoString:NULL])
472  {
474  }
475  else if ([self scanString:@"<" intoString:NULL])
476  {
478  }
479  else if ([self scanString:@">" intoString:NULL])
480  {
482  }
483  else if ([self scanString:@"==" intoString:NULL] || [self scanString:@"=" intoString:NULL])
484  {
486  }
487  else if ([self scanPredicateKeyword:@"MATCHES"])
488  {
490  }
491  else if ([self scanPredicateKeyword:@"LIKE"])
492  {
494  }
495  else if ([self scanPredicateKeyword:@"BEGINSWITH"])
496  {
498  }
499  else if ([self scanPredicateKeyword:@"ENDSWITH"])
500  {
502  }
503  else if ([self scanPredicateKeyword:@"IN"])
504  {
506  }
507  else if ([self scanPredicateKeyword:@"CONTAINS"])
508  {
510  }
511  else if ([self scanPredicateKeyword:@"BETWEEN"])
512  {
514  }
515  else
516  CPRaiseParseError(self, @"comparison predicate");
517 
518  if ([self scanString:@"[cd]" intoString:NULL])
519  {
521  }
522  else if ([self scanString:@"[c]" intoString:NULL])
523  {
525  }
526  else if ([self scanString:@"[d]" intoString:NULL])
527  {
529  }
530 
531  right = [self parseExpression];
532 
534  rightExpression:right
535  modifier:modifier
536  type:type
537  options:opts];
538 
540 }
541 
542 - (CPExpression)parseExpression
543 {
544  return [self parseBinaryExpression];
545 }
546 
547 - (CPExpression)parseSimpleExpression
548 {
549  var identifier,
550  location,
551  ident,
552  dbl;
553 
554  if ([self scanDouble:REFERENCE(dbl)])
556 
557  // FIXME: handle integer, hex constants, 0x 0o 0b
558  if ([self scanString:@"-" intoString:NULL])
559  return [CPExpression expressionForFunction:@"chs:" arguments:[CPArray arrayWithObject:[self parseExpression]]];
560 
561  if ([self scanString:@"(" intoString:NULL])
562  {
563  var arg = [self parseExpression];
564 
565  if (![self scanString:@")" intoString:NULL])
566  CPRaiseParseError(self, @"expression");
567 
568  return arg;
569  }
570 
571  if ([self scanString:@"{" intoString:NULL])
572  {
573  if ([self scanString:@"}" intoString:NULL])
575 
576  var a = [];
577 
578  [a addObject:[self parseExpression]];
579 
580  while ([self scanString:@"," intoString:NULL])
581  [a addObject:[self parseExpression]];
582 
583  if (![self scanString:@"}" intoString:NULL])
584  CPRaiseParseError(self, @"expression");
585 
587  }
588 
589  if ([self scanPredicateKeyword:@"NULL"] || [self scanPredicateKeyword:@"NIL"])
590  {
592  }
593  if ([self scanPredicateKeyword:@"TRUE"] || [self scanPredicateKeyword:@"YES"])
594  {
596  }
597  if ([self scanPredicateKeyword:@"FALSE"] || [self scanPredicateKeyword:@"NO"])
598  {
600  }
601  if ([self scanPredicateKeyword:@"SELF"])
602  {
604  }
605 
606  if ([self scanString:@"$" intoString:NULL])
607  {
608  var variable = [self parseSimpleExpression];
609 
610  if (![variable keyPath])
611  CPRaiseParseError(self, @"expression");
612 
613  return [CPExpression expressionForVariable:variable];
614  }
615 
616  location = [self scanLocation];
617 
618  if ([self scanString:@"%" intoString:NULL])
619  {
620  if ([self isAtEnd] == NO)
621  {
622  var c = [[self string] characterAtIndex:[self scanLocation]];
623 
624  switch (c)
625  {
626  case '%':// '%%' is treated as '%'
627  location = [self scanLocation];
628  break;
629  case 'K':
630  [self setScanLocation:[self scanLocation] + 1];
631  return [CPExpression expressionForKeyPath:[self nextArg]];
632  case '@':
633  case 'c':
634  case 'C':
635  case 'd':
636  case 'D':
637  case 'i':
638  case 'o':
639  case 'O':
640  case 'u':
641  case 'U':
642  case 'x':
643  case 'X':
644  case 'e':
645  case 'E':
646  case 'f':
647  case 'g':
648  case 'G':
649  [self setScanLocation:[self scanLocation] + 1];
650  return [CPExpression expressionForConstantValue:[self nextArg]];
651  case 'h':
652  [self scanString:@"h" intoString:NULL];
653  if ([self isAtEnd] == NO)
654  {
655  c = [[self string] characterAtIndex:[self scanLocation]];
656  if (c == 'i' || c == 'u')
657  {
658  [self setScanLocation:[self scanLocation] + 1];
659  return [CPExpression expressionForConstantValue:[self nextArg]];
660  }
661  }
662  break;
663  case 'q':
664  [self scanString:@"q" intoString:NULL];
665  if ([self isAtEnd] == NO)
666  {
667  c = [[self string] characterAtIndex:[self scanLocation]];
668  if (c == 'i' || c == 'u' || c == 'x' || c == 'X')
669  {
670  [self setScanLocation:[self scanLocation] + 1];
671  return [CPExpression expressionForConstantValue:[self nextArg]];
672  }
673  }
674  break;
675  }
676  }
677 
678  [self setScanLocation:location];
679  }
680 
681  if ([self scanString:@"\"" intoString:NULL])
682  {
683  var skip = [self charactersToBeSkipped],
684  str = @"";
685 
686  [self setCharactersToBeSkipped:nil];
687  [self scanUpToString:@"\"" intoString:REFERENCE(str)];
688 
689  if ([self scanString:@"\"" intoString:NULL] == NO)
690  CPRaiseParseError(self, @"expression");
691 
692  [self setCharactersToBeSkipped:skip];
693 
694  return [CPExpression expressionForConstantValue:str];
695  }
696 
697  if ([self scanString:@"'" intoString:NULL])
698  {
699  var skip = [self charactersToBeSkipped],
700  str = @"";
701 
702  [self setCharactersToBeSkipped:nil];
703  [self scanUpToString:@"'" intoString:REFERENCE(str)];
704 
705  if ([self scanString:@"'" intoString:NULL] == NO)
706  CPRaiseParseError(self, @"expression");
707 
708  [self setCharactersToBeSkipped:skip];
709 
710  return [CPExpression expressionForConstantValue:str];
711  }
712 
713  if ([self scanString:@"@" intoString:NULL])
714  {
715  var e = [self parseExpression];
716 
717  if (![e keyPath])
718  CPRaiseParseError(self, @"expression");
719 
720  return [CPExpression expressionForKeyPath:[e keyPath] + "@"];
721  }
722 
723  if ([self scanString:@"SUBQUERY" intoString:NULL])
724  {
725  if (![self scanString:@"(" intoString:NULL])
726  CPRaiseParseError(self, @"expression");
727 
728  var collection = [self parseExpression],
729  variableExpression,
730  subpredicate;
731 
732  if (![self scanString:@"," intoString:NULL])
733  CPRaiseParseError(self, @"expression");
734  variableExpression = [self parseExpression];
735 
736  if (![self scanString:@"," intoString:NULL])
737  CPRaiseParseError(self, @"expression");
738  subpredicate = [self parsePredicate];
739 
740  if (![self scanString:@")" intoString:NULL])
741  CPRaiseParseError(self, @"expression");
742 
743  return [[_CPSubqueryExpression alloc] initWithExpression:collection usingIteratorExpression:variableExpression predicate:subpredicate];
744  }
745 
746  if ([self scanString:@"FUNCTION" intoString:NULL])
747  {
748  if (![self scanString:@"(" intoString:NULL])
749  CPRaiseParseError(self, @"expression");
750 
751  var args = [CPArray arrayWithObject:[self parseExpression]];
752  while ([self scanString:@"," intoString:NULL])
753  [args addObject:[self parseExpression]];
754 
755  if (![self scanString:@")" intoString:NULL] || [args count] < 2 || [args[1] expressionType] != CPConstantValueExpressionType)
756  CPRaiseParseError(self, @"expression");
757 
758  return [CPExpression expressionForFunction:args[0] selectorName:[args[1] constantValue] arguments:args.slice(2)];
759  }
760 
761  [self scanString:@"#" intoString:NULL];
762  if (!identifier)
763  identifier = [CPCharacterSet characterSetWithCharactersInString:@"_$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"];
764 
765  if (![self scanCharactersFromSet:identifier intoString:REFERENCE(ident)])
766  CPRaiseParseError(self, @"expression");
767 
768  return [CPExpression expressionForKeyPath:ident];
769 }
770 
771 - (CPExpression)parseFunctionalExpression
772 {
773  var left = [self parseSimpleExpression];
774 
775  while (YES)
776  {
777  if ([self scanString:@"." intoString:NULL])
778  {
779  var right = [self parseSimpleExpression],
780  expressionType = [right expressionType];
781 
782  if (expressionType == CPKeyPathExpressionType)
783  left = [[_CPKeyPathExpression alloc] initWithOperand:left andKeyPath:[right keyPath]];
784  else if (expressionType == CPVariableExpressionType)
785  left = [CPExpression expressionForFunction:left selectorName:@"valueForKey:" arguments:[right]];
786  else
787  CPRaiseParseError(self, @"expression");
788  }
789  else if ([self scanString:@"[" intoString:NULL])
790  {
791  // index expression
792  if ([self scanPredicateKeyword:@"FIRST"])
793  {
794  left = [CPExpression expressionForFunction:@"first:" arguments:[CPArray arrayWithObject:left]];
795  }
796  else if ([self scanPredicateKeyword:@"LAST"])
797  {
798  left = [CPExpression expressionForFunction:@"last:" arguments:[CPArray arrayWithObject:left]];
799  }
800  else if ([self scanPredicateKeyword:@"SIZE"])
801  {
802  left = [CPExpression expressionForFunction:@"count:" arguments:[CPArray arrayWithObject:left]];
803  }
804  else
805  {
806  var index = [self parseExpression];
807  left = [CPExpression expressionForFunction:@"fromObject:index:" arguments:[CPArray arrayWithObjects:left, index]];
808  }
809 
810  if (![self scanString:@"]" intoString:NULL])
811  CPRaiseParseError(self, @"expression");
812  }
813  else if ([self scanString:@":" intoString:NULL])
814  {
815  // function - this parser allows for (max)(a, b, c) to be properly
816  // recognized and even (%K)(a, b, c) if %K evaluates to "max"
817 
818  if (![left keyPath])
819  CPRaiseParseError(self, @"expression");
820 
821  var selector = [left keyPath] + @":",
822  args = [];
823 
824  if (![self scanString:@"(" intoString:NULL])
825  {
826  var str;
827  [self scanCharactersFromSet:[CPCharacterSet lowercaseLetterCharacterSet] intoString:REFERENCE(str)];
828 
829  if (![self scanString:@":(" intoString:NULL])
830  CPRaiseParseError(self, @"expression");
831 
832  selector += str + @":";
833  }
834 
835  if (![self scanString:@")" intoString:NULL])
836  {
837  [args addObject:[self parseExpression]];
838  while ([self scanString:@"," intoString:NULL])
839  [args addObject:[self parseExpression]];
840 
841  if (![self scanString:@")" intoString:NULL])
842  CPRaiseParseError(self, @"expression");
843  }
844 
845  left = [CPExpression expressionForFunction:selector arguments:args];
846  }
847  else if ([self scanString:@"UNION" intoString:NULL])
848  {
849  left = [CPExpression expressionForUnionSet:left with:[self parseExpression]];
850  }
851  else if ([self scanString:@"INTERSECT" intoString:NULL])
852  {
853  left = [CPExpression expressionForIntersectSet:left with:[self parseExpression]];
854  }
855  else if ([self scanString:@"MINUS" intoString:NULL])
856  {
857  left = [CPExpression expressionForMinusSet:left with:[self parseExpression]];
858  }
859  else
860  {
861  // done with suffixes
862  return left;
863  }
864  }
865 }
866 
867 - (CPExpression)parsePowerExpression
868 {
869  var left = [self parseFunctionalExpression];
870 
871  while (YES)
872  {
873  var right;
874 
875  if ([self scanString:@"**" intoString:NULL])
876  {
877  right = [self parseFunctionalExpression];
878  left = [CPExpression expressionForFunction:@"raise:to:" arguments:[CPArray arrayWithObjects:left, right]];
879  }
880  else
881  {
882  return left;
883  }
884  }
885 }
886 
887 - (CPExpression)parseMultiplicationExpression
888 {
889  var left = [self parsePowerExpression];
890 
891  while (YES)
892  {
893  var right;
894 
895  if ([self scanString:@"*" intoString:NULL])
896  {
897  right = [self parsePowerExpression];
898  left = [CPExpression expressionForFunction:@"multiply:by:" arguments:[CPArray arrayWithObjects:left, right]];
899  }
900  else if ([self scanString:@"/" intoString:NULL])
901  {
902  right = [self parsePowerExpression];
903  left = [CPExpression expressionForFunction:@"divide:by:" arguments:[CPArray arrayWithObjects:left, right]];
904  }
905  else
906  {
907  return left;
908  }
909  }
910 }
911 
912 - (CPExpression)parseAdditionExpression
913 {
914  var left = [self parseMultiplicationExpression];
915 
916  while (YES)
917  {
918  var right;
919 
920  if ([self scanString:@"+" intoString:NULL])
921  {
922  right = [self parseMultiplicationExpression];
923  left = [CPExpression expressionForFunction:@"add:to:" arguments:[CPArray arrayWithObjects:left, right]];
924  }
925  else if ([self scanString:@"-" intoString:NULL])
926  {
927  right = [self parseMultiplicationExpression];
928  left = [CPExpression expressionForFunction:@"from:substract:" arguments:[CPArray arrayWithObjects:left, right]];
929  }
930  else
931  {
932  return left;
933  }
934  }
935 }
936 
937 - (CPExpression)parseBinaryExpression
938 {
939  var left = [self parseAdditionExpression];
940 
941  while (YES)
942  {
943  var right;
944 
945  if ([self scanString:@":=" intoString:NULL]) // assignment
946  {
947  // check left to be a variable?
948  right = [self parseAdditionExpression];
949  // FIXME
950  }
951  else
952  {
953  return left;
954  }
955  }
956 }
957 
958 @end
959 
960 var CPRaiseParseError = function CPRaiseParseError(aScanner, target)
961 {
962  [CPException raise:CPInvalidArgumentException reason:@"unable to parse " + target + " at index " + [aScanner scanLocation]];
963 };
964