Defined   A
last analyzed

Complexity

Total Complexity 3

Size/Duplication

Total Lines 15
Duplicated Lines 0 %
Metric Value
dl 0
loc 15
rs 10
wmc 3

1 Method

Rating   Name   Duplication   Size   Complexity  
A call() 0 14 3
1
from trifle_types import (Function, FunctionWithEnv, Lambda, Macro, Special,
2
                          Integer, Float, Fraction, RBigInt,
3
                          List, Hashmap, Keyword,
4
                          FileHandle, Bytestring, Character,
5
                          TRUE, FALSE, NULL, Symbol, String,
6
                          TrifleExceptionInstance, TrifleExceptionType,
7
                          is_equal)
8
from errors import (
9
    ArityError, changing_closed_handle, division_by_zero,
10
    wrong_type, file_not_found, value_error, missing_key,
11
)
12
from almost_python import deepcopy, copy, raw_input, list
13
from parameters import validate_parameters
14
from lexer import lex
15
from trifle_parser import parse
16
from arguments import check_args
17
from hashable import check_hashable
18
19
20
class SetSymbol(FunctionWithEnv):
21
    def call(self, args, env, stack):
22
        check_args(u"set-symbol!", args, 2, 2)
23
24
        variable_name = args[0]
25
        variable_value = args[1]
26
27
        if not isinstance(variable_name, Symbol):
28
            return TrifleExceptionInstance(
29
                wrong_type,
30
                u"The first argument to set-symbol! must be a symbol, but got: %s"
31
                % variable_name.repr())
32
33
        env.set(variable_name.symbol_name, variable_value)
34
35
        return NULL
36
37
38
class Let(Special):
39
    def call(self, args, env, stack):
40
        # TODO: we should take at least two arguments.
41
        check_args(u'let', args, 1)
42
43
        bindings = args[0]
44
        body = args[1:]
45
46
        if not isinstance(bindings, List):
47
            return TrifleExceptionInstance(
48
                wrong_type,
49
                u"let requires a list as its first argument, but got: %s"
50
                % bindings.repr()
51
            )
52
53
        for index, expression in enumerate(bindings.values):
54
            if index % 2 == 0:
55
                if not isinstance(expression, Symbol):
56
                    return TrifleExceptionInstance(
57
                        wrong_type,
58
                        u"Expected a symbol for a let-bound variable, but got: %s"
59
                        % expression.repr()
60
                    )
61
62
        if len(bindings.values) % 2 == 1:
63
            raise ArityError(
64
                u"no value given for let-bound variable: %s"
65
                % bindings.values[-1].repr())
66
67
        # Fix circular import by importing here.
68
        from environment import LetScope
69
        from evaluator import Frame
70
        
71
        frame = stack.peek()
72
73
        if frame.expression_index == 0:
74
            # We don't evaluate the let symbol.
75
            frame.expression_index += 1
76
            return None
77
78
        elif frame.expression_index == 1:
79
            # We evaluate each of the variable bindings and assign
80
            # them, allowing bindings to access previous bindings in
81
            # the same let block.
82
            if frame.let_assignment_index == 0:
83
                let_scope = LetScope({})
84
                let_env = env.with_nested_scope(let_scope)
85
                frame.let_environment = let_env
86
            else:
87
                let_env = frame.let_environment
88
89
            if frame.let_assignment_index * 2 >= len(bindings.values):
90
                # We've finished setting up the bindings. Assign the
91
                # last result into the environment.
92
93
                # Unless we had no bindings at all, assign the final binding.
94
                if frame.let_assignment_index > 0:
95
                    previous_sym = bindings.values[2 * (frame.let_assignment_index - 1)]
96
                    previous_value = frame.evalled.pop()
97
98
                    let_scope = let_env.scopes[-1]
99
                    let_scope.set(previous_sym.symbol_name, previous_value)
100
                
101
                frame.expression_index += 1
102
                return None
103
104
            if frame.let_assignment_index == 0:
105
                # Evaluate the first assignment.
106
                stack.push(Frame(bindings.values[1], let_env))
107
                frame.let_assignment_index += 1
108
                return None
109
            else:
110
                # Assign the previous result in the environment, and
111
                # evaluate the next.
112
                
113
                previous_sym = bindings.values[2 * (frame.let_assignment_index - 1)]
114
                previous_value = frame.evalled.pop()
115
                
116
                let_scope = let_env.scopes[-1]
117
                let_scope.set(previous_sym.symbol_name, previous_value)
118
                
119
                stack.push(Frame(bindings.values[2 * frame.let_assignment_index + 1], let_env))
120
                frame.let_assignment_index += 1
121
                return None
122
                
123
        elif frame.expression_index == 2:
124
            # Evaluate the body now we have all the assignments.
125
            stack.push(Frame(List(body), frame.let_environment, as_block=True))
126
            
127
            frame.expression_index += 1
128
            return None
129
130
        else:
131
            # Evalled body, just return the result
132
            return frame.evalled[-1]
133
134
135
class LambdaFactory(Special):
136
    """Return a fresh Lambda object every time it's called."""
137
138
    def call(self, args, env, stack):
139
        check_args(u'lambda', args, 1)
140
141
        parameters = args[0]
142
143
        error = validate_parameters(parameters)
144
        if error:
145
            return error
146
147
        lambda_body = List(args[1:])
148
        return Lambda(parameters, lambda_body, env)
149
150
151
# todo: support docstrings
152
class DefineMacro(Special):
153
    """Create a new macro object and bind it to the variable name given,
154
    in the global scope.
155
156
    """
157
158
    def call(self, args, env, stack):
159
        check_args(u'macro', args, 3)
160
161
        macro_name = args[0]
162
        parameters = args[1]
163
        
164
        if not isinstance(macro_name, Symbol):
165
            return TrifleExceptionInstance(
166
                wrong_type,
167
                u"macro name should be a symbol, but got: %s" %
168
                macro_name.repr())
169
170
        parameters = args[1]
171
        error = validate_parameters(parameters)
172
173
        if error:
174
            return error
175
176
        macro_body = List(args[2:])
177
        env.set_global(macro_name.symbol_name,
178
                       Macro(macro_name.symbol_name, parameters, macro_body))
179
180
        return NULL
181
182
183
# TODO: unit test
184
# TODOC
185
# TODO: add an expand-all-macros special too.
186
# TODO: Could we make this a function?
187
class ExpandMacro(Special):
188
    """Given an expression that is a macro call, expand it one step and
189
    return the resulting (unevaluated) expression.
190
191
    """
192
    def call(self, args, env, stack):
193
        check_args(u'expand-macro', args, 1)
194
195
        expr = args[0]
196
197
        if not isinstance(expr, List):
198
            return TrifleExceptionInstance(
199
                wrong_type,
200
                u"The first argument to expand-macro must be a list, but got: %s" % args[0].repr())
201
202
        if not expr.values:
203
            return TrifleExceptionInstance(
204
                wrong_type,
205
                u"The first argument to expand-macro must be a non-empty list.")
206
207
        macro_name = expr.values[0]
208
209
        from evaluator import evaluate, expand_macro
210
        macro = evaluate(macro_name, env)
211
212
        if not isinstance(macro, Macro):
213
            return TrifleExceptionInstance(
214
                wrong_type,
215
                u"Expected a macro, but got: %s" % macro.repr())
216
217
        macro_args = expr.values[1:]
218
        return expand_macro(macro, macro_args, env)
219
220
221
# todo: it would be nice to define this as a trifle macro using a 'literal' primitive
222
# (e.g. elisp defines backquote in terms of quote)
223
class Quote(Special):
224
    def is_unquote(self, expression):
225
        """Is this expression of the form (unquote expression)?"""
226
        if not isinstance(expression, List):
227
            return False
228
229
        if not expression.values:
230
            return False
231
232
        list_head = expression.values[0]
233
        if not isinstance(list_head, Symbol):
234
            return False
235
236
        if not list_head.symbol_name == u'unquote':
237
            return False
238
239
        return True
240
241
    def is_unquote_star(self, expression):
242
        """Is this expression of the form (unquote* expression)?"""
243
        if not isinstance(expression, List):
244
            return False
245
246
        if not expression.values:
247
            return False
248
249
        list_head = expression.values[0]
250
        if not isinstance(list_head, Symbol):
251
            return False
252
253
        if not list_head.symbol_name == u'unquote*':
254
            return False
255
256
        return True
257
    
258
    # todo: fix the potential stack overflow
259
    def evaluate_unquote_calls(self, expression, env, stack):
260
        from evaluator import evaluate
261
        if isinstance(expression, List):
262
            for index, item in enumerate(copy(expression).values):
263
                if self.is_unquote(item):
264
                    # TODO: this is calling repr but including the function call itself
265
                    if len(item.values) != 2:
266
                        raise ArityError(
267
                            u"unquote takes 1 argument, but got: %s" % item.repr())
268
            
269
                    unquote_argument = item.values[1]
270
                    expression.values[index] = evaluate(unquote_argument, env)
271
                    
272
                elif self.is_unquote_star(item):
273
                    if len(item.values) != 2:
274
                        raise ArityError(
275
                            u"unquote* takes 1 argument, but got: %s" % item.repr())
276
            
277
                    unquote_argument = item.values[1]
278
                    values_list = evaluate(unquote_argument, env)
279
280
                    if not isinstance(values_list, List):
281
                        return TrifleExceptionInstance(
282
                            wrong_type,
283
                            u"unquote* must be used with a list, but got a %s" % values_list.repr())
284
285
                    # Splice in the result of evaluating the unquote* argument
286
                    expression.values = expression.values[:index] + values_list.values + expression.values[index+1:]
287
288
                elif isinstance(item, List):
289
                    # recurse the nested list
290
                    result = self.evaluate_unquote_calls(item, env, stack)
291
292
                    if isinstance(result, TrifleExceptionInstance):
293
                        return result
294
295
        return expression
296
    
297
    def call(self, args, env, stack):
298
        check_args(u'quote', args, 1, 1)
299
300
        if isinstance(args[0], List) and args[0].values:
301
            list_head = args[0].values[0]
302
303
            if isinstance(list_head, Symbol):
304
                if list_head.symbol_name == u"unquote*":
305
                    return TrifleExceptionInstance(
306
                        value_error,
307
                        u"Can't call unquote* at top level of quote expression, you need to be inside a list.")
308
309
        result = self.evaluate_unquote_calls(List([deepcopy(args[0])]), env, stack)
310
311
        if isinstance(result, TrifleExceptionInstance):
312
            return result
313
        elif isinstance(result, List):
314
            return result.values[0]
315
316
317
class If(Special):
318
    def call(self, args, environment, stack):
319
        check_args(u'if', args, 3, 3)
320
321
        condition = args[0]
322
        then = args[1]
323
        otherwise = args[2]
324
325
        frame = stack.peek()
326
327
        # TODO: Move Frame to separate module to fix the cyclic import.
328
        from evaluator import Frame
329
        
330 View Code Duplication
        if frame.expression_index == 0:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
331
            # We don't evaluate the if symbol.
332
            frame.expression_index += 1
333
            return None
334
335
        elif frame.expression_index == 1:
336
            # Evaluate the condition.
337
            stack.push(Frame(condition, environment))
338
339
            frame.expression_index += 1
340
            return None
341
342
        elif frame.expression_index == 2:
343
            # We've evaluated the condition, so either evaluate 'then'
344
            # or 'otherwise' depending on the return value.
345
            evalled_condition = frame.evalled[-1]
346
            
347
            if evalled_condition == TRUE:
348
                stack.push(Frame(then, environment))
349
                
350
                frame.expression_index += 1
351
                return None
352
353
            elif evalled_condition == FALSE:
354
                stack.push(Frame(otherwise, environment))
355
                
356
                frame.expression_index += 2
357
                return None
358
359
            else:
360
                return TrifleExceptionInstance(
361
                    wrong_type,
362
                    u"The first argument to if must be a boolean, but got: %s" %
363
                    evalled_condition.repr())
364
                
365
        else:
366
            # We've evaluated the condition and either 'then' or
367
            # 'otherwise', so pop this frame and return.
368
            return frame.evalled[-1]
369
370
371
class While(Special):
372
    def call(self, args, env, stack):
373
        check_args(u'while', args, 1)
374
375
        condition = args[0]
376
        body = List(args[1:])
377
378
        frame = stack.peek()
379
380
        from evaluator import Frame
381
382 View Code Duplication
        if frame.expression_index == 0:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
383
            # We don't evaluate the while symbol.
384
            frame.expression_index += 1
385
            return None
386
387
        elif frame.expression_index == 1:
388
            # Evaluate the condition.
389
            stack.push(Frame(condition, env))
390
391
            frame.expression_index += 1
392
            return None
393
394
        elif frame.expression_index == 2:
395
            # We've evaluated the condition, so either evaluate the body, or return.
396
            evalled_condition = frame.evalled[-1]
397
            
398
            if evalled_condition == TRUE:
399
                stack.push(Frame(body, env, as_block=True))
400
401
                # Once we've evaluated the body, we should evaluate
402
                # the condition again.
403
                frame.expression_index = 1
404
405
                return None
406
407
            elif evalled_condition == FALSE:
408
                # while loops always return #null when done.
409
                return NULL
410
411
            else:
412
                return TrifleExceptionInstance(
413
                    wrong_type,
414
                    u"The first argument to while must be a boolean, but got: %s" %
415
                    evalled_condition.repr())
416
        
417
418
# todo: implement in prelude in terms of stdin and stdout
419
class Input(Function):
420
    def call(self, args):
421
        check_args(u'input', args, 1, 1)
422
        prefix = args[0]
423
424
        if not isinstance(prefix, String):
425
            return TrifleExceptionInstance(
426
                wrong_type,
427
                u"The first argument to input must be a string, but got: %s"
428
                % prefix.repr())
429
430
        user_input = raw_input(prefix.as_unicode())
431
        return String([char for char in user_input])
432
433
434
class Same(Function):
435
    def call(self, args):
436
        check_args(u'same?', args, 2, 2)
437
438
        # Sadly, we can't access .__class__ in RPython.
439
        # TODO: proper symbol interning.
440
        if isinstance(args[0], Symbol):
441
            if isinstance(args[1], Symbol):
442
                if args[0].symbol_name == args[1].symbol_name:
443
                    return TRUE
444
445
            return FALSE
446
447
        if args[0] is args[1]:
448
            return TRUE
449
        else:
450
            return FALSE
451
452
453
# TODO: Is there a better place in the docs for this, rather than under booleans?
454
# We're inconsistent between grouping by input type or output type.
455
class Equal(Function):
456
    def call(self, args):
457
        check_args(u'equal?', args, 2, 2)
458
459
        if is_equal(args[0], args[1]):
460
            return TRUE
461
        else:
462
            return FALSE
463
464
465
class FreshSymbol(Function):
466
    def __init__(self):
467
        self.count = 1
468
469
    def call(self, args):
470
        check_args(u'fresh-symbol', args, 0, 0)
471
472
        symbol_name = u"%d-unnamed" % self.count
473
        self.count += 1
474
475
        return Symbol(symbol_name)
476
477
478
def coerce_numbers(nums):
479
    """Given a list of Trifle numbers, coerce them all to be the same
480
    type. Convert to the lower common denominator if necessary.
481
482
    Assumes all elements are numbers.
483
484
    >>> coerce_numbers([Integer.fromint(1)])
485
    [Integer.fromint(1)]
486
    >>> coerce_numbers([Fraction(RBigInt.fromint(1), RBigInt.fromint(2)), Float(1.0)])
487
    [Float(0.5), Float(1.0)]
488
489
    """
490
    contains_floats = False
491
    contains_fractions = False
492
    
493
    for num in nums:
494
        if isinstance(num, Float):
495
            contains_floats = True
496
            break
497
        elif isinstance(num, Fraction):
498
            contains_fractions = True
499
500
    result = []
501
    if contains_floats:
502
503
        for num in nums:
504
            if isinstance(num, Integer):
505
                # TODO: This could overflow if the integer is outside
506
                # the range of acceptable floats.
507
                result.append(Float(num.bigint_value.tofloat()))
508
            elif isinstance(num, Fraction):
509
                # TODO: Carefully document and unit test the corner cases
510
                # here.
511
                result.append(Float(num.numerator.tofloat() / num.denominator.toint()))
512
            elif isinstance(num, Float):
513
                result.append(num)
514
515
        return result
516
        
517
    elif contains_fractions:
518
        for num in nums:
519
            if isinstance(num, Integer):
520
                # TODO: we need abitrary sized fractions too.
521
                result.append(Fraction(num.bigint_value, RBigInt.fromint(1)))
522
            elif isinstance(num, Fraction):
523
                result.append(num)
524
525
        return result
526
527
    else:
528
        return nums
529
530
531 View Code Duplication
class Add(Function):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
532
    def call(self, args):
533
        float_args = False
534
        fraction_args = False
535
        
536
        for arg in args:
537
            if isinstance(arg, Integer):
538
                pass
539
            elif isinstance(arg, Fraction):
540
                fraction_args = True
541
            elif isinstance(arg, Float):
542
                float_args = True
543
            else:
544
                return TrifleExceptionInstance(
545
                    wrong_type,
546
                    u"+ requires numbers, but got: %s." % arg.repr())
547
548
        args = coerce_numbers(args)
549
550
        if float_args:
551
            total = 0.0
552
            for arg in args:
553
                total += arg.float_value
554
555
            return Float(total)
556
557
        elif fraction_args:
558
            total = Fraction(RBigInt.fromint(0), RBigInt.fromint(1))
559
560
            for arg in args:
561
                # a/b + c/d == (ad + bc) / bd
562
                total = Fraction(
563
                    total.numerator.mul(arg.denominator).add(arg.numerator.mul(total.denominator)),
564
                    arg.denominator.mul(total.denominator)
565
                )
566
567
            if total.denominator.eq(RBigInt.fromint(1)):
568
                return Integer(total.numerator)
569
570
            return total
571
572
        else:
573
            # Just integers.
574
            total = RBigInt.fromint(0)
575
            for arg in args:
576
                total = total.add(arg.bigint_value)
577
            return Integer(total)
578
579
580
class Subtract(Function):
581
    def call(self, args):
582
        float_args = False
583
        fraction_args = False
584
        
585
        for arg in args:
586
            if isinstance(arg, Integer):
587
                pass
588
            elif isinstance(arg, Fraction):
589
                fraction_args = True
590
            elif isinstance(arg, Float):
591
                float_args = True
592
            else:
593
                return TrifleExceptionInstance(
594
                    wrong_type,
595
                    u"- requires numbers, but got: %s." % arg.repr())
596
597
        if not args:
598
            return Integer.fromint(0)
599
600
        if len(args) == 1:
601
            if isinstance(args[0], Integer):
602
                return Integer(args[0].bigint_value.neg())
603
            elif isinstance(args[0], Fraction):
604
                return Fraction(args[0].numerator.neg(), args[0].denominator)
605
            else:
606
                return Float(-args[0].float_value)
607
608
        args = coerce_numbers(args)
609
610
        if float_args:
611
            total = args[0].float_value
612
                
613
            for arg in args[1:]:
614
                total -= arg.float_value
615
616
            return Float(total)
617
618
        elif fraction_args:
619
            total = args[0]
620
                
621
            for arg in args[1:]:
622
                # a/b - c/d == (ad - bc) / bd
623
                total = Fraction(
624
                    total.numerator.mul(arg.denominator).sub(arg.numerator.mul(total.denominator)),
625
                    arg.denominator.mul(total.denominator)
626
                )
627
628
            if total.denominator.eq(RBigInt.fromint(1)):
629
                return Integer(total.numerator)
630
631
            return total
632
633
        else:
634
            total = args[0].bigint_value
635
            for arg in args[1:]:
636
                total = total.sub(arg.bigint_value)
637
            return Integer(total)
638
639
640 View Code Duplication
class Multiply(Function):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
641
    def call(self, args):
642
        float_args = False
643
        fraction_args = False
644
        
645
        for arg in args:
646
            if isinstance(arg, Integer):
647
                pass
648
            elif isinstance(arg, Fraction):
649
                fraction_args = True
650
            elif isinstance(arg, Float):
651
                float_args = True
652
            else:
653
                return TrifleExceptionInstance(
654
                    wrong_type,
655
                    u"* requires numbers, but got: %s." % arg.repr())
656
657
        args = coerce_numbers(args)
658
659
        if float_args:
660
            product = 1.0
661
            for arg in args:
662
                product *= arg.float_value
663
664
            return Float(product)
665
666
        elif fraction_args:
667
            product = Fraction(RBigInt.fromint(1), RBigInt.fromint(1))
668
669
            for arg in args:
670
                product = Fraction(
671
                    product.numerator.mul(arg.numerator),
672
                    product.denominator.mul(arg.denominator),
673
                )
674
675
            # TODO: It would be convenient to have RBIGINT_ZERO and RBIGINT_ONE
676
            # even if we don't cache small numbers the way Python does.
677
            if product.denominator.eq(RBigInt.fromint(1)):
678
                return Integer(product.numerator)
679
680
            return product
681
682
        else:
683
            product = RBigInt.fromint(1)
684
            for arg in args:
685
                product = product.mul(arg.bigint_value)
686
            return Integer(product)
687
688
689
class Divide(Function):
690
    def call(self, args):
691
        check_args(u'/', args, 2)
692
693
        float_args = False
694
        
695
        for arg in args:
696
            if isinstance(arg, Integer):
697
                pass
698
            elif isinstance(arg, Fraction):
699
                pass
700
            elif isinstance(arg, Float):
701
                float_args = True
702
            else:
703
                return TrifleExceptionInstance(
704
                    wrong_type,
705
                    u"/ requires numbers, but got: %s." % arg.repr())
706
707
        args = coerce_numbers(args)
708
709
        if float_args:
710
            quotient = args[0].float_value
711
712
            for arg in args[1:]:
713
                try:
714
                    quotient /= arg.float_value
715
                except ZeroDivisionError:
716
                    return TrifleExceptionInstance(
717
                        division_by_zero,
718
                        u"Divided %f by %s" % (quotient, arg.repr()))
719
720
            return Float(quotient)
721
722
        else:
723
            if isinstance(args[0], Integer):
724
                quotient = Fraction(args[0].bigint_value, RBigInt.fromint(1))
725
            elif isinstance(args[0], Fraction):
726
                quotient = args[0]
727
            else:
728
                # Never happens, but to keep RPython happy.
729
                quotient = Fraction(RBigInt.fromint(1), RBigInt.fromint(1))
730
                
731
            for arg in args[1:]:
732
                if isinstance(arg, Integer):
733
                    if arg.bigint_value.eq(RBigInt.fromint(0)):
734
                        return TrifleExceptionInstance(
735
                            division_by_zero,
736
                            u"Divided %s by %s" % (quotient.repr(), arg.repr()))
737
                    
738
                    quotient = Fraction(
739
                        quotient.numerator, quotient.denominator.mul(arg.bigint_value)
740
                    )
741
742
                elif isinstance(arg, Fraction):
743
                    # Since fractions are always non-zero, we can't get a
744
                    # zero division error here.
745
746
                    # a/b / b/c == ac/bd
747
                    quotient = Fraction(
748
                        quotient.numerator.mul(arg.denominator),
749
                        quotient.denominator.mul(arg.numerator),
750
                    )
751
752
            if quotient.denominator.eq(RBigInt.fromint(1)):
753
                return Integer(quotient.numerator)
754
755
            return quotient
756
            
757
758
# TODO: it would be nice to support floats too
759 View Code Duplication
class Mod(Function):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
760
    def call(self, args):
761
        check_args(u'mod', args, 2, 2)
762
763
        for arg in args:
764
            if not isinstance(arg, Integer):
765
                return TrifleExceptionInstance(
766
                    wrong_type,
767
                    u"mod requires integers, but got: %s." % arg.repr())
768
769
        if args[1].bigint_value.eq(RBigInt.fromint(0)):
770
            return TrifleExceptionInstance(
771
                division_by_zero,
772
                u"Divided by zero: %s" % args[1].repr())
773
774
        return Integer(args[0].bigint_value.mod(args[1].bigint_value))
775
776
777 View Code Duplication
class Div(Function):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
778
    """Integer division. Note this differs from Python's //, which is
779
    floor division. In Python:
780
781
    >>> 4.5 // 1.5
782
    3.0
783
784
    """
785
    def call(self, args):
786
        check_args(u'div', args, 2, 2)
787
788
        for arg in args:
789
            if not isinstance(arg, Integer):
790
                return TrifleExceptionInstance(
791
                    wrong_type,
792
                    u"div requires integers, but got: %s." % arg.repr())
793
794
        if args[1].bigint_value.eq(RBigInt.fromint(0)):
795
            return TrifleExceptionInstance(
796
                division_by_zero,
797
                u"Divided by zero: %s" % args[1].repr())
798
799
        return Integer(args[0].bigint_value.floordiv(args[1].bigint_value))
800
            
801
802
class LessThan(Function):
803
    def call(self, args):
804
        check_args(u'<', args, 2)
805
806
        float_args = False
807
        fraction_args = False
808
809
        for arg in args:
810
            if isinstance(arg, Integer):
811
                pass
812
            elif isinstance(arg, Fraction):
813
                fraction_args = True
814
            elif isinstance(arg, Float):
815
                float_args = True
816
            else:
817
                return TrifleExceptionInstance(
818
                    wrong_type,
819
                    u"< requires numbers, but got: %s." % arg.repr())
820
821
        args = coerce_numbers(args)
822
                
823
        previous_number = args[0]
824
        if float_args:
825
826
            for arg in args[1:]:
827
                if not previous_number.float_value < arg.float_value:
828
                    return FALSE
829
830
                previous_number = arg
831
                    
832
            return TRUE
833
834
        elif fraction_args:
835
            
836
            for arg in args[1:]:
837
                # To find out if a/b is less than c/d, we compare whether
838
                # ad < bc. This is safe because b and d are always positive.
839
                ad = previous_number.numerator.mul(arg.denominator)
840
                bc = arg.numerator.mul(previous_number.denominator)
841
842
                if not ad.lt(bc):
843
                    return FALSE
844
845
                previous_number = arg
846
                    
847
            return TRUE
848
849
        else:
850
            # Only integers.
851
            for arg in args[1:]:
852
                if not previous_number.bigint_value.lt(arg.bigint_value):
853
                    return FALSE
854
855
                previous_number = arg
856
857
            return TRUE
858
859
860
class SymbolPredicate(Function):
861
    def call(self, args):
862
        check_args(u'symbol?', args, 1, 1)
863
        value = args[0]
864
865
        if isinstance(value, Symbol):
866
            return TRUE
867
        else:
868
            return FALSE
869
870
871
# TODO: Just write a generic function that returns the type of its argument.
872
class ListPredicate(Function):
873
    def call(self, args):
874
        check_args(u'list?', args, 1, 1)
875
        value = args[0]
876
877
        if isinstance(value, List):
878
            return TRUE
879
        else:
880
            return FALSE
881
882
883
class HashmapPredicate(Function):
884
    def call(self, args):
885
        check_args(u'hashmap?', args, 1, 1)
886
        value = args[0]
887
888
        if isinstance(value, Hashmap):
889
            return TRUE
890
        else:
891
            return FALSE
892
893
894
class StringPredicate(Function):
895
    def call(self, args):
896
        check_args(u'string?', args, 1, 1)
897
        value = args[0]
898
899
        if isinstance(value, String):
900
            return TRUE
901
        else:
902
            return FALSE
903
904
905
class BytestringPredicate(Function):
906
    def call(self, args):
907
        check_args(u'bytestring?', args, 1, 1)
908
        value = args[0]
909
910
        if isinstance(value, Bytestring):
911
            return TRUE
912
        else:
913
            return FALSE
914
915
916
class CharacterPredicate(Function):
917
    def call(self, args):
918
        check_args(u'character?', args, 1, 1)
919
        value = args[0]
920
921
        if isinstance(value, Character):
922
            return TRUE
923
        else:
924
            return FALSE
925
926
927
class GetIndex(Function):
928
    def call(self, args):
929
        check_args(u'get-index', args, 2, 2)
930
        sequence = args[0]
931
        index = args[1]
932
933
        if isinstance(sequence, List):
934
            sequence_length = RBigInt.fromint(len(sequence.values))
935
        elif isinstance(sequence, Bytestring):
936
            sequence_length = RBigInt.fromint(len(sequence.byte_value))
937
        elif isinstance(sequence, String):
938
            sequence_length = RBigInt.fromint(len(sequence.string))
939
        else:
940
            return TrifleExceptionInstance(
941
                wrong_type,
942
                u"the first argument to get-index must be a sequence, but got: %s"
943
                % sequence.repr())
944
945
        if not isinstance(index, Integer):
946
            return TrifleExceptionInstance(
947
                wrong_type,
948
                u"the second argument to get-index must be an integer, but got: %s"
949
                % index.repr())
950
951
        if sequence_length.eq(RBigInt.fromint(0)):
952
            return TrifleExceptionInstance(
953
                value_error,
954
                u"can't call get-item on an empty sequence")
955
956
        # todo: use a separate error class for index errors
957
        if index.bigint_value.ge(sequence_length):
958
            return TrifleExceptionInstance(
959
                value_error,
960
                u"the sequence has %s items, but you asked for index %s"
961
                % (unicode(sequence_length.str()), index.repr()))
962
963
        if index.bigint_value.lt(sequence_length.neg()):
964
            return TrifleExceptionInstance(
965
                value_error,
966
                u"Can't get index %s of a %s element sequence (must be -%s or higher)"
967
                % (index.repr(), unicode(sequence_length.str()), unicode(sequence_length.str())))
968
969
        if isinstance(sequence, List):
970
            return sequence.values[index.bigint_value.toint()]
971
        elif isinstance(sequence, Bytestring):
972
            return Integer.fromint(sequence.byte_value[index.bigint_value.toint()])
973
        elif isinstance(sequence, String):
974
            return Character(sequence.string[index.bigint_value.toint()])
975
976
977
class GetKey(Function):
978
    def call(self, args):
979
        check_args(u'get-key', args, 2, 2)
980
        hashmap = args[0]
981
        key = args[1]
982
983
        if not isinstance(hashmap, Hashmap):
984
            return TrifleExceptionInstance(
985
                wrong_type,
986
                u"the first argument to get-key must be a hashmap, but got: %s"
987
                % hashmap.repr())
988
989
        value = hashmap.dict.get(key, None)
990
991
        if value is None:
992
            return TrifleExceptionInstance(
993
                missing_key,
994
                u"Key %s not found in hashmap" % key.repr()
995
            )
996
997
        return value
998
999
1000
class SetKey(Function):
1001
    def call(self, args):
1002
        check_args(u'set-key!', args, 3, 3)
1003
        hashmap = args[0]
1004
        key = args[1]
1005
        value = args[2]
1006
1007
        if not isinstance(hashmap, Hashmap):
1008
            return TrifleExceptionInstance(
1009
                wrong_type,
1010
                u"the first argument to set-key! must be a hashmap, but got: %s"
1011
                % hashmap.repr())
1012
1013
        is_hashable_error = check_hashable([key])
1014
        if isinstance(is_hashable_error, TrifleExceptionInstance):
1015
            return is_hashable_error
1016
1017
        hashmap.dict[key] = value
1018
        return NULL
1019
1020
1021
class GetItems(Function):
1022
    def call(self, args):
1023
        check_args(u'get-items', args, 1, 1)
1024
        hashmap = args[0]
1025
1026
        if not isinstance(hashmap, Hashmap):
1027
            return TrifleExceptionInstance(
1028
                wrong_type,
1029
                u"the first argument to get-items must be a hashmap, but got: %s"
1030
                % hashmap.repr())
1031
1032
        items = List()
1033
        for key, value in hashmap.dict.iteritems():
1034
            items.append(List([key, value]))
1035
1036
        return items
1037
1038
1039
class Length(Function):
1040
    def call(self, args):
1041
        check_args(u'length', args, 1, 1)
1042
        sequence = args[0]
1043
1044
        if isinstance(sequence, List):
1045
            return Integer.fromint(len(sequence.values))
1046
        elif isinstance(sequence, Bytestring):
1047
            return Integer.fromint(len(sequence.byte_value))
1048
        elif isinstance(sequence, String):
1049
            return Integer.fromint(len(sequence.string))
1050
1051
        return TrifleExceptionInstance(
1052
            wrong_type,
1053
            u"the first argument to length must be a sequence, but got: %s"
1054
            % sequence.repr())
1055
1056
1057
class SetIndex(Function):
1058
    def call(self, args):
1059
        check_args(u'set-index!', args, 3, 3)
1060
        sequence = args[0]
1061
        index = args[1]
1062
        value = args[2]
1063
1064
        if isinstance(sequence, List):
1065
            sequence_length = RBigInt.fromint(len(sequence.values))
1066
        elif isinstance(sequence, Bytestring):
1067
            sequence_length = RBigInt.fromint(len(sequence.byte_value))
1068
        elif isinstance(sequence, String):
1069
            sequence_length = RBigInt.fromint(len(sequence.string))
1070
        else:
1071
            return TrifleExceptionInstance(
1072
                wrong_type,
1073
                u"the first argument to set-index! must be a sequence, but got: %s"
1074
                % sequence.repr())
1075
1076
        if not isinstance(index, Integer):
1077
            return TrifleExceptionInstance(
1078
                wrong_type,
1079
                u"the second argument to set-index! must be an integer, but got: %s"
1080
                % index.repr())
1081
1082
        if sequence_length.eq(RBigInt.fromint(0)):
1083
            return TrifleExceptionInstance(
1084
                value_error,
1085
                u"can't call set-index! on an empty sequence")
1086
1087
        # TODO: use a separate error class for index error
1088
        if index.bigint_value.ge(sequence_length):
1089
            return TrifleExceptionInstance(
1090
                value_error,
1091
                # TODO: pluralisation (to avoid '1 items')
1092
                u"the sequence has %s items, but you asked to set index %s"
1093
                % (unicode(sequence_length.str()), index.repr()))
1094
1095
        if index.bigint_value.lt(sequence_length.neg()):
1096
            return TrifleExceptionInstance(
1097
                value_error,
1098
                u"Can't set index %s of a %s element sequence (must be -%s or higher)"
1099
                % (index.repr(), unicode(sequence_length.str()), unicode(sequence_length.str())))
1100
1101
        if isinstance(sequence, List):
1102
            sequence.values[index.bigint_value.toint()] = value
1103
        elif isinstance(sequence, Bytestring):
1104
            if not isinstance(value, Integer):
1105
                return TrifleExceptionInstance(
1106
                    wrong_type,
1107
                    u"Permitted values inside bytestrings are only integers between 0 and 255, but got: %s"
1108
                    % value.repr())
1109
1110
            if value.bigint_value.lt(RBigInt.fromint(0)) or value.bigint_value.gt(RBigInt.fromint(255)):
1111
                return TrifleExceptionInstance(
1112
                    value_error,
1113
                    u"Permitted values inside bytestrings are only integers between 0 and 255, but got: %s"
1114
                    % value.repr())
1115
1116
            sequence.byte_value[index.bigint_value.toint()] = value.bigint_value.toint()
1117
        elif isinstance(sequence, String):
1118
            if not isinstance(value, Character):
1119
                return TrifleExceptionInstance(
1120
                    wrong_type,
1121
                    u"Permitted values inside strings are only characters, but got: %s"
1122
                    % value.repr())
1123
1124
            # TODO: what if the list contains more than 2 ** 32 items?
1125
            # We should remove all uses of .toint, it's risky.
1126
            sequence.string[index.bigint_value.toint()] = value.character
1127
1128
        return NULL
1129
1130
1131
class Insert(Function):
1132
    def call(self, args):
1133
        check_args(u'insert!', args, 3, 3)
1134
        sequence = args[0]
1135
        index = args[1]
1136
        value = args[2]
1137
1138
        if isinstance(sequence, List):
1139
            # TODO: what if the sequence has more than 2**32 items?
1140
            sequence_length = RBigInt.fromint(len(sequence.values))
1141
        elif isinstance(sequence, Bytestring):
1142
            sequence_length = RBigInt.fromint(len(sequence.byte_value))
1143
        elif isinstance(sequence, String):
1144
            sequence_length = RBigInt.fromint(len(sequence.string))
1145
        else:
1146
            return TrifleExceptionInstance(
1147
                wrong_type,
1148
                u"the first argument to insert! must be a sequence, but got: %s"
1149
                % sequence.repr())
1150
1151
        if not isinstance(index, Integer):
1152
            return TrifleExceptionInstance(
1153
                wrong_type,
1154
                u"the second argument to insert! must be an integer, but got: %s"
1155
                % index.repr())
1156
1157
        # todo: use a separate error class for index error
1158
        if index.bigint_value.gt(sequence_length):
1159
            return TrifleExceptionInstance(
1160
                value_error,
1161
                u"the sequence has %s items, but you asked to insert at index %s"
1162
                % (unicode(sequence_length.str()), index.repr()))
1163
1164
        if index.bigint_value.lt(sequence_length.neg()):
1165
            return TrifleExceptionInstance(
1166
                value_error,
1167
                u"Can't set index %s of a %s element sequence (must be -%s or higher)"
1168
                % (index.repr(), unicode(sequence_length.str()), unicode(sequence_length.str())))
1169
1170
        target_index = index.bigint_value
1171
        if target_index.lt(RBigInt.fromint(0)):
1172
            target_index = target_index.mod(sequence_length)
1173
1174
        target_index_int = target_index.toint()
1175
        # We know that this is always non-negative, but RPython
1176
        # cannot prove it. This if statement is just to keep
1177
        # RPython happy.
1178
        if target_index_int < 0:
1179
            target_index_int = 0
1180
1181
        if isinstance(sequence, List):
1182
            sequence.values.insert(target_index_int, value)
1183
        elif isinstance(sequence, Bytestring):
1184
            if not isinstance(value, Integer):
1185
                return TrifleExceptionInstance(
1186
                    wrong_type,
1187
                    u"Permitted values inside bytestrings are only integers between 0 and 255, but got: %s"
1188
                    % value.repr())
1189
1190
            if value.bigint_value.lt(RBigInt.fromint(0)) or value.bigint_value.gt(RBigInt.fromint(255)):
1191
                return TrifleExceptionInstance(
1192
                    value_error,
1193
                    u"Permitted values inside bytestrings are only integers between 0 and 255, but got: %s"
1194
                    % value.repr())
1195
1196
            sequence.byte_value.insert(target_index_int, value.bigint_value.toint())
1197
        elif isinstance(sequence, String):
1198
            if not isinstance(value, Character):
1199
                return TrifleExceptionInstance(
1200
                    wrong_type,
1201
                    u"Permitted values inside strings are only characters, but got: %s"
1202
                    % value.repr())
1203
1204
            sequence.string.insert(target_index_int, value.character)
1205
1206
        return NULL
1207
1208
1209
class Parse(Function):
1210
    def call(self, args):
1211
        check_args(u'parse', args, 1, 1)
1212
        program_string = args[0]
1213
1214
        if not isinstance(program_string, String):
1215
            return TrifleExceptionInstance(
1216
                wrong_type,
1217
                u"the first argument to parse must be a string, but got: %s"
1218
                % program_string.repr())
1219
1220
        tokens = lex(program_string.as_unicode())
1221
1222
        if isinstance(tokens, TrifleExceptionInstance):
1223
            return tokens
1224
        
1225
        return parse(tokens)
1226
1227
1228
# todo: consider allowing the user to pass in an environment for sandboxing
1229
class Eval(FunctionWithEnv):
1230
    def call(self, args, env, stack):
1231
        check_args(u'eval', args, 1, 1)
1232
1233
        frame = stack.peek()
1234
1235
        # Note that the expression index will already be 2, since
1236
        # evaluate_function_call will have iterated over our
1237
        # arguments.
1238
        from evaluator import Frame
1239
1240
        # Evaluate our argument.
1241
        stack.push(Frame(args[0], env))
1242
1243
        # Incrementing the expression_index will result in evaluate()
1244
        # just returning frame.evalled, so Eval.call will not be called again.
1245
        frame.expression_index += 1
1246
        return None
1247
1248
class Call(FunctionWithEnv):
1249
    def call(self, args, env, stack):
1250
        check_args(u'call', args, 2, 2)
1251
        function = args[0]
1252
        arguments = args[1]
1253
1254
        # Sadly, RPython doesn't support isinstance(x, (A, B)).
1255
        if not (isinstance(function, Function) or isinstance(function, FunctionWithEnv)
1256
                or isinstance(function, Lambda)):
1257
            return TrifleExceptionInstance(
1258
                wrong_type,
1259
                u"the first argument to call must be a function, but got: %s"
1260
                % function.repr())
1261
1262
        if not isinstance(arguments, List):
1263
            return TrifleExceptionInstance(
1264
                wrong_type,
1265
                u"the second argument to call must be a list, but got: %s"
1266
                % arguments.repr())
1267
1268
        frame = stack.peek()
1269
1270
        # Note that the expression index will already be 3, since
1271
        # evaluate_function_call will have iterated over our
1272
        # arguments. We just increment from there.
1273
        if frame.expression_index == 3:
1274
            # Build an equivalent expression
1275
            expression = List([function] + arguments.values)
1276
1277
            from evaluator import Frame
1278
            new_frame = Frame(expression, env)
1279
1280
            # Ensure that we don't evaluate the arguments to the function
1281
            # a second time.
1282
            new_frame.expression_index = len(arguments.values) + 1
1283
            new_frame.evalled = [function] + arguments.values
1284
1285
            # Call the function.
1286
            stack.push(new_frame)
1287
1288
            # Increment expression_index to show there's nothing left to do here.
1289
            frame.expression_index += 1
1290
            return None
1291
1292
1293
# todo: rename to DefinedPredicate
1294
class Defined(FunctionWithEnv):
1295
    def call(self, args, env, stack):
1296
        check_args(u'defined?', args, 1, 1)
1297
        symbol = args[0]
1298
1299
        if not isinstance(symbol, Symbol):
1300
            return TrifleExceptionInstance(
1301
                wrong_type,
1302
                u"the first argument to defined? must be a symbol, but got: %s"
1303
                % symbol.repr())
1304
1305
        if env.contains(symbol.symbol_name):
1306
            return TRUE
1307
        else:
1308
            return FALSE
1309
1310
1311
# TODO: error on a file we can't write to
1312
# TODO: error when we run out of file handles
1313
# TODO: other errors the file system can throw at us
1314
class Open(Function):
1315
    def call(self, args):
1316
        check_args(u'open', args, 2, 2)
1317
        path = args[0]
1318
1319
        if not isinstance(path, String):
1320
            return TrifleExceptionInstance(
1321
                wrong_type,
1322
                u"the first argument to open must be a string, but got: %s"
1323
                % path.repr())
1324
1325
        flag = args[1]
1326
1327
        if not isinstance(flag, Keyword):
1328
            return TrifleExceptionInstance(
1329
                wrong_type,
1330
                u"the second argument to open must be a keyword, but got: %s"
1331
                % flag.repr())
1332
1333
        if flag.symbol_name == u'write':
1334
            handle = open(path.as_unicode().encode('utf-8'), 'w')
1335
        elif flag.symbol_name == u'read':
1336
            try:
1337
                handle = open(path.as_unicode().encode('utf-8'), 'r')
1338
            except IOError as e:
1339
                # TODO: Fix RPython error that stops us inspecting .errno.
1340
                # This will throw on other IOErrors, such as permission problems.
1341
                return TrifleExceptionInstance(
1342
                    file_not_found,
1343
                    u"No file found: %s" % path.as_unicode())
1344
                # if e.errno == 2:
1345
                #     raise FileNotFound(u"No file found: %s" % path.as_unicode())
1346
                # else:
1347
                #     raise
1348
        else:
1349
            return TrifleExceptionInstance(
1350
                value_error,
1351
                u"Invalid flag for open: :%s" % flag.symbol_name)
1352
1353
        return FileHandle(path.as_unicode().encode('utf-8'), handle, flag)
1354
1355
1356
class Close(Function):
1357
    def call(self, args):
1358
        check_args(u'close!', args, 1, 1)
1359
        handle = args[0]
1360
1361
        if not isinstance(handle, FileHandle):
1362
            return TrifleExceptionInstance(
1363
                wrong_type,
1364
                u"the first argument to close! must be a file handle, but got: %s"
1365
                % handle.repr())
1366
1367
        if handle.is_closed:
1368
            return TrifleExceptionInstance(
1369
                changing_closed_handle,
1370
                # TODO: This assumes the file name is always UTF-8, which may not be
1371
                # true if the user has chosen a different file system encoding.
1372
                u"File handle for %s is already closed." % handle.file_name.decode('utf-8'))
1373
        else:
1374
            handle.close()
1375
1376
        return NULL
1377
1378
1379
# TODO: specify a limit for how much to read.
1380
class Read(Function):
1381
    def call(self, args):
1382
        check_args(u'read', args, 1, 1)
1383
        handle = args[0]
1384
1385
        if not isinstance(handle, FileHandle):
1386
            return TrifleExceptionInstance(
1387
                wrong_type,
1388
                u"the first argument to read must be a file handle, but got: %s"
1389
                % handle.repr())
1390
1391
        return Bytestring([ord(c) for c in handle.file_handle.read()])
1392
1393
1394
class Write(Function):
1395
    def call(self, args):
1396
        check_args(u'write!', args, 2, 2)
1397
        handle = args[0]
1398
1399
        if not isinstance(handle, FileHandle):
1400
            return TrifleExceptionInstance(
1401
                wrong_type,
1402
                u"the first argument to write! must be a file handle, but got: %s"
1403
                % handle.repr())
1404
1405
        if handle.mode.symbol_name != u"write":
1406
            return TrifleExceptionInstance(
1407
                value_error,
1408
                u"%s is a read-only file handle, you can't write to it."
1409
                % handle.repr())
1410
1411
        if handle.is_closed:
1412
            return TrifleExceptionInstance(
1413
                changing_closed_handle,
1414
                u"File handle for %s is already closed." % handle.file_name.decode('utf-8'))
1415
1416
        to_write = args[1]
1417
1418
        if not isinstance(to_write, Bytestring):
1419
            return TrifleExceptionInstance(
1420
                wrong_type,
1421
                u"the second argument to write! must be a bytes, but got: %s"
1422
                % to_write.repr())
1423
1424
        handle.write("".join([chr(c) for c in to_write.byte_value]))
1425
1426
        return NULL
1427
1428
1429
class Flush(Function):
1430
    def call(self, args):
1431
        check_args(u'flush', args, 1, 1)
1432
        handle = args[0]
1433
1434
        if not isinstance(handle, FileHandle):
1435
            return TrifleExceptionInstance(
1436
                wrong_type,
1437
                u"the first argument to flush must be a file handle, but got: %s"
1438
                % handle.repr())
1439
1440
        if handle.is_closed:
1441
            return TrifleExceptionInstance(
1442
                changing_closed_handle,
1443
                u"File handle for %s is already closed." % handle.file_name.decode('utf-8'))
1444
1445
        handle.flush()
1446
1447
        return NULL
1448
1449
1450
# TODO: take a second argument that specifies the encoding.
1451
class Encode(Function):
1452
    def call(self, args):
1453
        check_args(u'encode', args, 1, 1)
1454
        string = args[0]
1455
1456
        if not isinstance(string, String):
1457
            return TrifleExceptionInstance(
1458
                wrong_type,
1459
                u"the first argument to encode must be a string, but got: %s"
1460
                % string.repr())
1461
1462
        string_as_bytestring = string.as_unicode().encode('utf-8')
1463
        return Bytestring([ord(c) for c in string_as_bytestring])
1464
1465
1466
# TODO: take a second argument that specifies the encoding.
1467
# TODO: throw an exception on bytes that aren't valid UTF-8.
1468
class Decode(Function):
1469
    def call(self, args):
1470
        check_args(u'decode', args, 1, 1)
1471
        bytestring = args[0]
1472
1473
        if not isinstance(bytestring, Bytestring):
1474
            return TrifleExceptionInstance(
1475
                wrong_type,
1476
                u"the first argument to decode must be bytes, but got: %s"
1477
                % bytestring.repr())
1478
1479
        bytestring_chars = [chr(c) for c in bytestring.byte_value]
1480
        py_unicode = b"".join(bytestring_chars).decode('utf-8')
1481
        return String([char for char in py_unicode])
1482
1483
1484
# TODO: take an extra argument for return codes (and check against the maximum legal value).
1485
# TODOC
1486
class Exit(Function):
1487
    def call(self, args):
1488
        check_args(u'exit!', args, 0, 0)
1489
        raise SystemExit()
1490
1491
1492
class Try(Special):
1493
    def call(self, args, env, stack):
1494
        # TODO: multiple catch blocks, finally, resuming.
1495
        check_args(u'try', args, 5, 5)
1496
1497
        body = args[0]
1498
        catch_keyword = args[1]
1499
        raw_exception_type = args[2]
1500
        exception_binding = args[3]
1501
1502
        if not isinstance(catch_keyword, Keyword) or catch_keyword.symbol_name != u"catch":
1503
            return TrifleExceptionInstance(
1504
                wrong_type,
1505
                u"The second argument to try must be :catch, but got: %s"
1506
                % catch_keyword.repr())
1507
1508
        if not isinstance(exception_binding, Symbol):
1509
            return TrifleExceptionInstance(
1510
                wrong_type,
1511
                u"The fourth argument to try must be a symbol, but got: %s"
1512
                % exception_binding.repr())
1513
1514
        frame = stack.peek()
1515
        from evaluator import Frame
1516
1517
        # Note that we increment the expression index even though
1518
        # we evaluate the expected exception type first.
1519
        if frame.expression_index == 0:
1520
            # First, we evaluate the exception type.
1521
            stack.push(Frame(raw_exception_type, env))
1522
1523
            frame.expression_index = 1
1524
            return None
1525
1526
        elif frame.expression_index == 1:
1527
            exception_type = frame.evalled[-1]
1528
1529
            if not isinstance(exception_type, TrifleExceptionType):
1530
                return TrifleExceptionInstance(
1531
                    wrong_type,
1532
                    u"Expected a trifle exception type for :catch, but got: %s"
1533
                    % exception_type.repr())
1534
1535
            # Mark the current frame as something we can come back to
1536
            # if we encounter an error.
1537
            frame.catch_error = exception_type
1538
1539
            # Evaluate the body.
1540
            stack.push(Frame(body, env))
1541
1542
            frame.expression_index = 2
1543
            return None
1544
1545
        else:
1546
            # We've evaluated the body without any errors, just return
1547
            # the result.
1548
            return frame.evalled[-1]
1549
1550
1551
# TODO: rethrow, to throw an exception but with the stacktrace from
1552
# the original call site.
1553
class Throw(Function):
1554
    def call(self, args):
1555
        check_args(u'throw', args, 2, 2)
1556
1557
        exception_type = args[0]
1558
        exception_message = args[1]
1559
1560
        if not isinstance(exception_type, TrifleExceptionType):
1561
            return TrifleExceptionInstance(
1562
                wrong_type,
1563
                u"The first argument to throw must be an exception type, but got: %s"
1564
                % exception_type.repr())
1565
1566
        if not isinstance(exception_message, String):
1567
            return TrifleExceptionInstance(
1568
                wrong_type,
1569
                u"The second argument to throw must be a string, but got: %s"
1570
                % exception_message.repr())
1571
1572
        return TrifleExceptionInstance(
1573
            exception_type,
1574
            exception_message.as_unicode(),
1575
        )
1576
1577
1578
class Message(Function):
1579
    def call(self, args):
1580
        check_args(u'message', args, 1, 1)
1581
1582
        exception = args[0]
1583
        
1584
        if not isinstance(exception, TrifleExceptionInstance):
1585
            return TrifleExceptionInstance(
1586
                wrong_type,
1587
                u"The first argument to message must be an exception, but got: %s"
1588
                % exception.repr())
1589
1590
        return String(list(exception.message))
1591
1592
1593
class ExceptionType(Function):
1594
    def call(self, args):
1595
        check_args(u'exception-type', args, 1, 1)
1596
1597
        exception = args[0]
1598
        
1599
        if not isinstance(exception, TrifleExceptionInstance):
1600
            return TrifleExceptionInstance(
1601
                wrong_type,
1602
                u"The first argument to function `exception-type` must be an exception, but got: %s"
1603
                % exception.repr())
1604
1605
        return exception.exception_type
1606
1607
1608
class Printable(Function):
1609
    def call(self, args):
1610
        check_args(u'printable', args, 1, 1)
1611
        return String(list(args[0].repr()))
1612