Objective-C: steps 0-4, keywords, hash maps.
authorJoel Martin <github@martintribe.org>
Fri, 4 Mar 2016 05:08:47 +0000 (23:08 -0600)
committerJoel Martin <github@martintribe.org>
Sun, 6 Mar 2016 07:24:16 +0000 (01:24 -0600)
22 files changed:
.gitignore
objc/Dockerfile [new file with mode: 0644]
objc/Makefile [new file with mode: 0644]
objc/core.h [new file with mode: 0644]
objc/core.m [new file with mode: 0644]
objc/env.h [new file with mode: 0644]
objc/env.m [new file with mode: 0644]
objc/mal_readline.c [new file with mode: 0644]
objc/mal_readline.h [new file with mode: 0644]
objc/malfunc.h [new file with mode: 0644]
objc/malfunc.m [new file with mode: 0644]
objc/printer.h [new file with mode: 0644]
objc/printer.m [new file with mode: 0644]
objc/reader.h [new file with mode: 0644]
objc/reader.m [new file with mode: 0644]
objc/step0_repl.m [new file with mode: 0644]
objc/step1_read_print.m [new file with mode: 0644]
objc/step2_eval.m [new file with mode: 0644]
objc/step3_env.m [new file with mode: 0644]
objc/step4_if_fn_do.m [new file with mode: 0644]
objc/types.h [new file with mode: 0644]
objc/types.m [new file with mode: 0644]

index 0ce7187..6a21653 100644 (file)
@@ -75,6 +75,7 @@ make/mal.mk
 mal/mal.mal
 miniMAL/mal.json
 nim/nimcache*
+objc/*.d
 ocaml/*.cmi
 ocaml/*.cmo
 ocaml/*.swp
diff --git a/objc/Dockerfile b/objc/Dockerfile
new file mode 100644 (file)
index 0000000..fa7e3a4
--- /dev/null
@@ -0,0 +1,62 @@
+FROM ubuntu:vivid
+MAINTAINER Joel Martin <github@martintribe.org>
+
+##########################################################
+# General requirements for testing or common across many
+# implementations
+##########################################################
+
+RUN apt-get -y update
+
+# Required for running tests
+RUN apt-get -y install make python
+
+# Some typical implementation and test requirements
+RUN apt-get -y install curl libreadline-dev libedit-dev
+
+RUN mkdir -p /mal
+WORKDIR /mal
+
+##########################################################
+# Specific implementation requirements
+##########################################################
+
+# Based on:
+# https://blog.tlensing.org/2013/02/24/objective-c-on-linux-setting-up-gnustep-clang-llvm-objective-c-2-0-blocks-runtime-gcd-on-ubuntu-12-04/
+
+RUN apt-get -y install build-essential clang libblocksruntime-dev \
+    libkqueue-dev libpthread-workqueue-dev gobjc libxml2-dev \
+    libjpeg-dev libtiff-dev libpng12-dev libcups2-dev \
+    libfreetype6-dev libcairo2-dev libxt-dev libgl1-mesa-dev
+
+RUN mkdir -p /root/gnustep-dev
+RUN cd /root/gnustep-dev && \
+    curl http://download.gna.org/gnustep/libobjc2-1.7.tar.bz2 \
+    | tar xjf -
+RUN cd /root/gnustep-dev && \
+    curl ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-make-2.6.7.tar.gz \
+    | tar xzf -
+RUN cd /root/gnustep-dev && \
+    curl ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-base-1.24.8.tar.gz \
+    | tar xzf -
+RUN cd /root/gnustep-dev && \
+    curl ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-gui-0.24.1.tar.gz \
+    | tar xzf -
+RUN cd /root/gnustep-dev && \
+    curl ftp://ftp.gnustep.org/pub/gnustep/core/gnustep-back-0.24.1.tar.gz \
+    | tar xzf -
+
+
+# TODO move up
+RUN apt-get -y install gnutls-dev libxslt-dev libffi-dev openssl
+
+ENV CC clang
+RUN cd /root/gnustep-dev/libobjc2-1.7 && make && make install
+RUN cd /root/gnustep-dev/gnustep-make-2.6.7 && ./configure && make && make install
+RUN cd /root/gnustep-dev/gnustep-base-1.24.8 && ./configure && make && make install && ldconfig
+RUN cd /root/gnustep-dev/gnustep-gui-0.24.1 && ./configure && make && make install
+RUN cd /root/gnustep-dev/gnustep-back-0.24.1 && ./configure && make && make install
+
+RUN apt-get -y install libdispatch-dev
+
+ENV HOME /mal
diff --git a/objc/Makefile b/objc/Makefile
new file mode 100644 (file)
index 0000000..dc7e38d
--- /dev/null
@@ -0,0 +1,58 @@
+STEP0_DEPS = mal_readline.c mal_readline.h
+STEP1_DEPS = $(STEP0_DEPS) types.h types.m reader.h reader.m printer.h printer.m
+STEP2_DEPS = $(STEP1_DEPS)
+STEP3_DEPS = $(STEP2_DEPS) env.m
+STEP4_DEPS = $(STEP3_DEPS) malfunc.h malfunc.m core.h core.m
+
+SOURCES = $(STEP4_DEPS) stepA_mal.m
+SOURCES_LISP = env.h env.m core.h core.m stepA_mal.m
+
+STEPS = step0_repl step1_read_print step2_eval step3_env \
+       step4_if_fn_do step5_tco step6_file step7_quote \
+       step8_macros step9_try stepA_mal
+
+# From: https://blog.tlensing.org/2013/02/24/objective-c-on-linux-setting-up-gnustep-clang-llvm-objective-c-2-0-blocks-runtime-gcd-on-ubuntu-12-04/:
+#   clang `gnustep-config --objc-flags` -o main -x objective-c main.m -fconstant-string-class=NSConstantString -fobjc-nonfragile-abi -fblocks -lgnustep-base -lgnustep-gui -ldispatch -I/usr/local/include/GNUstep -L/usr/local/lib/GNUstep
+
+CC = clang
+LD = ld
+#OBJCC = clang -fblocks -fobjc-nonfragile-abi -fobjc-arc -x objective-c
+OBJCC = clang -fblocks -fobjc-nonfragile-abi -x objective-c
+## Bizzare gnustep-config/make interaction causes make to get run
+## during gnustep-config so we need to remove make output
+OBJC_FLAGS := $(shell gnustep-config --objc-flags | egrep -v "Entering|Leaving")
+OBJC_LIBS := $(filter-out -shared-libgcc,$(shell gnustep-config --base-libs | egrep -v "Entering|Leaving")) -ldispatch -lreadline
+
+all: $(STEPS)
+
+dist: mal
+
+mal: stepA_mal
+       cp $< $@
+
+step0_repl: $(STEP0_DEPS)
+step1_read_print: $(STEP1_DEPS)
+step2_eval: $(STEP2_DEPS)
+step3_env: $(STEP3_DEPS)
+step4_if_fn_do step5_tco step6_file step7_quote step8_macros step9_try stepA_mal: $(STEP4_DEPS)
+
+step%: step%.m
+       $(OBJCC) $(filter-out %.h mal_readline%,$+) -xc mal_readline.c -o $@ $(OBJC_FLAGS) $(OBJC_LIBS)
+
+%.o: %.c
+       $(CC) -c $< -o $@ $(CFLAGS)
+
+%.o: %.m
+       $(OBJCC) -c $< -o $@ $(OBJC_FLAGS)
+
+clean:
+       rm -f $(STEPS) *.o *.d mal
+
+.PHONY: stats tests
+
+stats: $(SOURCES)
+       @wc $^
+       @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"
+stats-lisp: $(SOURCES_LISP)
+       @wc $^
+       @printf "%5s %5s %5s %s\n" `grep -E "^[[:space:]]*//|^[[:space:]]*$$" $^ | wc` "[comments/blanks]"
diff --git a/objc/core.h b/objc/core.h
new file mode 100644 (file)
index 0000000..de078bd
--- /dev/null
@@ -0,0 +1,7 @@
+#import <Foundation/Foundation.h>
+
+@interface Core : NSObject
+
++ (NSDictionary *)ns;
+
+@end
diff --git a/objc/core.m b/objc/core.m
new file mode 100644 (file)
index 0000000..d830db1
--- /dev/null
@@ -0,0 +1,94 @@
+#import <Foundation/Foundation.h>
+
+#import "types.h"
+#import "printer.h"
+#import "core.h"
+
+NSObject * wrap_tf(BOOL val) {
+    return val ? [MalTrue alloc] : [MalFalse alloc];
+}
+
+@implementation Core
+
++ (NSDictionary *)ns {
+    return @{
+    @"=": ^(NSArray *args){
+        return wrap_tf(equal_Q(args[0], args[1]));
+    },
+
+    @"pr-str": ^(NSArray *args){
+        NSMutableArray * res = [NSMutableArray array];
+        for (id e in args) { [res addObject:_pr_str(e,true)]; }
+        return [res componentsJoinedByString:@" "];
+    },
+    @"str": ^(NSArray *args){
+        NSMutableArray * res = [NSMutableArray array];
+        for (id e in args) { [res addObject:_pr_str(e,false)]; }
+        return [res componentsJoinedByString:@""];
+    },
+    @"prn": ^(NSArray *args){
+        NSMutableArray * res = [NSMutableArray array];
+        for (id e in args) { [res addObject:_pr_str(e,true)]; }
+        printf("%s\n", [[res componentsJoinedByString:@" "] UTF8String]);
+        fflush(stdout);
+        return [NSNull alloc];
+    },
+    @"println": ^(NSArray *args){
+        NSMutableArray * res = [NSMutableArray array];
+        for (id e in args) { [res addObject:_pr_str(e,false)]; }
+        printf("%s\n", [[res componentsJoinedByString:@" "] UTF8String]);
+        fflush(stdout);
+        return [NSNull alloc];
+    },
+
+    @"<": ^(NSArray *args){
+        return wrap_tf([args[0] intValue] < [args[1] intValue]);
+    },
+    @"<=": ^(NSArray *args){
+        return wrap_tf([args[0] intValue] <= [args[1] intValue]);
+    },
+    @">": ^(NSArray *args){
+        return wrap_tf([args[0] intValue] > [args[1] intValue]);
+    },
+    @">=": ^(NSArray *args){
+        return wrap_tf([args[0] intValue] >= [args[1] intValue]);
+    },
+    @"+": ^(NSArray *args){
+        return [NSNumber numberWithInt:[args[0] intValue] + [args[1] intValue]];
+    },
+    @"-": ^(NSArray *args){
+        return [NSNumber numberWithInt:[args[0] intValue] - [args[1] intValue]];
+    },
+    @"*": ^(NSArray *args){
+        return [NSNumber numberWithInt:[args[0] intValue] * [args[1] intValue]];
+    },
+    @"/": ^(NSArray *args){
+        return [NSNumber numberWithInt:[args[0] intValue] / [args[1] intValue]];
+    },
+
+    @"list": ^(NSArray *args){
+        return args;
+    },
+    @"list?": ^(NSArray *args){
+        return wrap_tf(list_Q(args[0]));
+    },
+    
+    @"empty?": ^(NSArray *args){
+        if ([args[0] isKindOfClass:[NSNull class]]) {
+            return wrap_tf(true);
+        } else {
+            return wrap_tf([args[0] count] == 0);
+        }
+    },
+    @"count": ^(NSArray *args){
+        if ([args[0] isKindOfClass:[NSNull class]]) {
+            return @0;
+        } else {
+            return [NSNumber numberWithInt:[args[0] count]];
+        }
+    },
+
+    };
+}
+
+@end
diff --git a/objc/env.h b/objc/env.h
new file mode 100644 (file)
index 0000000..58437c5
--- /dev/null
@@ -0,0 +1,3 @@
+#import <Foundation/Foundation.h>
+
+// See types.h for Env interface definition
diff --git a/objc/env.m b/objc/env.m
new file mode 100644 (file)
index 0000000..3acf102
--- /dev/null
@@ -0,0 +1,75 @@
+#import <Foundation/Foundation.h>
+
+#import "types.h"
+//#import "env.h"
+
+@implementation Env
+
+@synthesize data = _data;
+@synthesize outer = _outer;
+
+- (id)initWithBindings:(Env *)outer binds:(NSArray *)binds exprs:(NSArray *)exprs {
+    self = [super init];
+    if (self) {
+        _outer = outer;
+        _data = [NSMutableDictionary dictionary];
+
+        for (int i=0; i < [binds count]; i++) {
+            if ([(NSString *)binds[i] isEqualTo:@"&"]) {
+                if ([exprs count] > i) {
+                    NSRange r = NSMakeRange(i, [exprs count] - i);
+                    _data[binds[i+1]] = [exprs subarrayWithRange:r];
+                } else {
+                    _data[binds[i+1]] = @[];
+                }
+                break;
+            } else {
+                _data[binds[i]] = exprs[i];
+            }
+        }
+    }
+    return self;
+}
+
+- (id)initWithOuter:(Env *)outer {
+    return [self initWithBindings:outer binds:@[] exprs:@[]];
+}
+
+- (id)init {
+    return [self initWithBindings:nil binds:@[] exprs:@[]];
+}
+
++ (id)fromOuter:(Env *)outer {
+    return [[Env alloc] initWithOuter:outer];
+}
+
++ (id)fromBindings:(Env *)outer binds:(NSArray *)binds exprs:(NSArray *)exprs {
+    return [[Env alloc] initWithBindings:outer binds:binds exprs:exprs];
+}
+
+- (NSObject *) set:(MalSymbol *)key val:(NSObject *)val {
+    _data[key] = val;
+    return val;
+}
+
+- (Env *) find:(MalSymbol *)key {
+    if (_data[key]) {
+        return self;
+    } else if (_outer) {
+        Env * e = _outer;
+        return [e find:key];
+    } else {
+        return nil;
+    }
+}
+
+- (NSObject *) get:(MalSymbol *)key {
+    Env * e = [self find:key];
+    if (e) {
+        return e.data[key];
+    } else {
+        @throw [NSString stringWithFormat:@"'%@' not found", key];
+    }
+}
+
+@end
diff --git a/objc/mal_readline.c b/objc/mal_readline.c
new file mode 100644 (file)
index 0000000..3594a1a
--- /dev/null
@@ -0,0 +1,75 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#if USE_READLINE
+  #include <readline/readline.h>
+  #include <readline/history.h>
+  #include <readline/tilde.h>
+#else
+  #include <editline/readline.h>
+#endif
+
+int history_loaded = 0;
+
+char HISTORY_FILE[] = "~/.mal-history";
+
+void load_history() {
+    if (history_loaded) { return; }
+    int ret;
+    char *hf = tilde_expand(HISTORY_FILE);
+    if (access(hf, F_OK) != -1) {
+        // TODO: check if file exists first, use non-static path
+#if USE_READLINE
+        ret = read_history(hf);
+#else
+        FILE *fp = fopen(hf, "r");
+        char *line = malloc(80); // getline reallocs as necessary
+        size_t sz = 80;
+        while ((ret = getline(&line, &sz, fp)) > 0) {
+            add_history(line); // Add line to in-memory history
+        }
+        free(line);
+        fclose(fp);
+#endif
+        history_loaded = 1;
+    }
+    free(hf);
+}
+
+void append_to_history() {
+    char *hf = tilde_expand(HISTORY_FILE);
+#ifdef USE_READLINE
+    append_history(1, hf);
+#else
+#if defined(RL_READLINE_VERSION)
+    HIST_ENTRY *he = history_get(history_base+history_length-1);
+#else
+    // libedit-2 segfaults if we add history_base
+    HIST_ENTRY *he = history_get(history_length-1);
+#endif
+    FILE *fp = fopen(hf, "a");
+    if (fp) {
+        fprintf(fp, "%s\n", he->line);
+        fclose(fp);
+    }
+#endif
+    free(hf);
+}
+
+
+// line must be freed by caller
+char *_readline (char prompt[]) {
+    char *line;
+
+    load_history();
+
+    line = readline(prompt);
+    if (!line) return NULL; // EOF
+    add_history(line); // Add input to in-memory history
+
+    append_to_history(); // Flush new line of history to disk
+
+    return line;
+}
+
diff --git a/objc/mal_readline.h b/objc/mal_readline.h
new file mode 100644 (file)
index 0000000..d524f4a
--- /dev/null
@@ -0,0 +1,6 @@
+#ifndef __MAL_READLINE__
+#define __MAL_READLINE__
+
+char *_readline (char prompt[]);
+
+#endif
diff --git a/objc/malfunc.h b/objc/malfunc.h
new file mode 100644 (file)
index 0000000..84d8ffc
--- /dev/null
@@ -0,0 +1,20 @@
+#import <Foundation/Foundation.h>
+
+/*
+// Forward declaration of Env (see env.h for full interface)
+@class Env;
+*/
+// Forward declaration of EVAL function
+NSObject *EVAL(id ast, id env);
+@interface MalFunc : NSObject
+
+@property (copy) NSArray * ast;
+@property (copy) Env * env;
+@property (copy) NSArray * params;
+
+- (id)init:(NSArray *)ast env:(Env *)env params:(NSArray *)params;
+
+- (id)apply:(NSArray *)args;
+
+@end
diff --git a/objc/malfunc.m b/objc/malfunc.m
new file mode 100644 (file)
index 0000000..e77a60a
--- /dev/null
@@ -0,0 +1,25 @@
+#import "types.h"
+
+#import "malfunc.h"
+
+@implementation MalFunc
+
+@synthesize ast = _ast;
+@synthesize env = _env;
+@synthesize params = _params;
+
+- (id)init:(NSArray *)ast env:(Env *)env params:(NSArray *)params {
+    self = [super init];
+    if (self) {
+        _ast = ast;
+        _env = env;
+        _params = params;
+    }
+    return self;
+}
+
+- (id)apply:(NSArray *)args {
+    return EVAL(_ast, [Env fromBindings:_env binds:_params exprs:args]);
+}
+
+@end
diff --git a/objc/printer.h b/objc/printer.h
new file mode 100644 (file)
index 0000000..19d785d
--- /dev/null
@@ -0,0 +1,3 @@
+#import <Foundation/Foundation.h>
+
+NSString * _pr_str(NSObject * obj, BOOL print_readably);
diff --git a/objc/printer.m b/objc/printer.m
new file mode 100644 (file)
index 0000000..6058c70
--- /dev/null
@@ -0,0 +1,55 @@
+#import <Foundation/Foundation.h>
+
+#import "types.h"
+
+NSString * _pr_str(NSObject * obj, BOOL print_readably) {
+    //NSLog(@"class: %@", [obj class]);
+    if ([obj isMemberOfClass:[NSNull class]]) {
+        return @"nil";
+    } else if ([obj isMemberOfClass:[MalTrue class]]) {
+        return @"true";
+    } else if ([obj isMemberOfClass:[MalFalse class]]) {
+        return @"false";
+    } else if ([obj isKindOfClass:[MalSymbol class]]) {
+        return (NSString *) obj;
+    } else if ([obj isKindOfClass:[NSString class]]) {
+        NSString * str = (NSString *)obj;
+        if ([str length] > 0 && ([str hasPrefix:@"\u029e"])) {
+            return [NSString stringWithFormat:@":%@",
+                      [str substringWithRange:NSMakeRange(1, [str length]-1)]];
+        } else if (print_readably) {
+            str = [[[(NSString *)obj
+                     stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]
+                    stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""]
+                   stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
+            return [NSString stringWithFormat:@"\"%@\"", str];
+        } else {
+            return str;
+        }
+    } else if ([obj isKindOfClass:[NSArray class]]) {
+        NSMutableArray * elems = [NSMutableArray array];
+        for (NSObject * elem in (NSArray *)obj) {
+            [elems addObject:_pr_str(elem, print_readably)];
+        }
+        if ([obj isKindOfClass:[MalVector class]]) {
+            return [NSString stringWithFormat:@"[%@]",
+                    [elems componentsJoinedByString:@" "]];
+        } else {
+            return [NSString stringWithFormat:@"(%@)",
+                    [elems componentsJoinedByString:@" "]];
+        }
+    } else if ([obj isKindOfClass:[NSDictionary class]]) {
+        NSDictionary * dict = (NSDictionary *)obj;
+        NSMutableArray * elems = [NSMutableArray array];
+        for (NSString * key in dict) {
+            [elems addObject:_pr_str(key, print_readably)];
+            [elems addObject:_pr_str(dict[key], print_readably)];
+        }
+        return [NSString stringWithFormat:@"{%@}",
+                [elems componentsJoinedByString:@" "]];
+    } else if (block_Q(obj)) {
+        return @"#<native function>";
+    } else {
+        return [obj description];
+    }
+}
diff --git a/objc/reader.h b/objc/reader.h
new file mode 100644 (file)
index 0000000..5e737b0
--- /dev/null
@@ -0,0 +1,2 @@
+NSArray * tokenize(NSString *str);
+NSObject * read_str(NSString *str);
diff --git a/objc/reader.m b/objc/reader.m
new file mode 100644 (file)
index 0000000..3128221
--- /dev/null
@@ -0,0 +1,185 @@
+#import <Foundation/Foundation.h>
+
+#import "types.h"
+
+// Only used here, so define interface locally
+@interface Reader : NSObject
+
+- (id)initWithTokens:(NSArray *)toks;
+- (id)init;
+
+- (NSString *) next;
+- (NSString *) peek;
+
+@end
+
+
+@implementation Reader
+
+NSArray  *_tokens;
+int _position;
+
+- (id)initWithTokens:(NSArray *)toks {
+    self = [super init];
+    if (self) {
+        _tokens = toks;
+        _position = 0;
+    }
+    return self;
+}
+
+- (id)init {
+    return [self initWithTokens:@[]];
+}
+
+- (NSString *)next {
+    _position++;
+    return _tokens[_position-1];
+}
+
+- (NSString *)peek {
+    if ([_tokens count] > _position) {
+        return _tokens[_position];
+    } else {
+        return nil;
+    }
+}
+
+@end
+
+
+NSArray * tokenize(NSString *str) {
+    NSRegularExpression *regex = [NSRegularExpression
+        regularExpressionWithPattern:@"[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}()'\"`@,;]+)"
+        options:0
+        error:NULL];
+
+    NSArray *matches = [regex
+        matchesInString:str
+        options:0
+        range:NSMakeRange(0, [str length])];
+
+    NSMutableArray * tokens = [NSMutableArray array];
+    for (NSTextCheckingResult *match in matches) {
+        [tokens addObject:[str substringWithRange:[match rangeAtIndex:1]]];
+    }
+    return tokens;
+}
+
+NSObject * read_atom(Reader * rdr) {
+    NSRegularExpression *regex = [NSRegularExpression
+        regularExpressionWithPattern:@"(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)"
+        options:0
+        error:NULL];
+    NSNumberFormatter *numf = [[NSNumberFormatter alloc] init];
+    numf.numberStyle = NSNumberFormatterDecimalStyle;
+
+    NSString *token = [rdr next];
+
+    NSArray *matches = [regex
+        matchesInString:token
+        options:0
+        range:NSMakeRange(0, [token length])];
+
+    if ([matches count] > 0) {
+        NSTextCheckingResult *match = matches[0];
+        if ([match rangeAtIndex:1].location != -1) {        // integer
+            return [numf numberFromString:token];
+        } else if ([match rangeAtIndex:2].location != -1) { // float
+            return [numf numberFromString:token];
+        } else if ([match rangeAtIndex:3].location != -1) { // nil
+            return [NSNull alloc];
+        } else if ([match rangeAtIndex:4].location != -1) { // true
+            return [MalTrue alloc]; // TODO: intern
+        } else if ([match rangeAtIndex:5].location != -1) { // false
+            return [MalFalse alloc]; // TODO: intern
+        } else if ([match rangeAtIndex:6].location != -1) { // string
+            NSString * str = [token substringWithRange:[match rangeAtIndex:6]];
+            return [[[str
+                      stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""]
+                     stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"]
+                    stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
+            return [token substringWithRange:[match rangeAtIndex:6]];
+        } else if ([match rangeAtIndex:7].location != -1) { // keyword
+            return [NSString stringWithFormat:@"\u029e%@",
+                    [token substringWithRange:[match rangeAtIndex:7]]];
+        } else if ([match rangeAtIndex:8].location != -1) { // symbol
+            return [MalSymbol stringWithString:token];
+        }
+    }
+
+    return @0;
+}
+
+// Only used locally, so declare here
+NSObject * read_form(Reader * rdr);
+
+NSArray * read_list(Reader * rdr, char start, char end) {
+    NSString * token = [rdr next];
+    NSMutableArray * ast = [NSMutableArray array];
+
+    if ([token characterAtIndex:0] != start) {
+        @throw [NSString stringWithFormat:@"expected '%c'", start];
+    }
+    while ((token = [rdr peek]) && ([token characterAtIndex:0] != end)) {
+        [ast addObject:read_form(rdr)];
+    }
+    if (!token) {
+        @throw [NSString stringWithFormat:@"expected '%c', got EOF", end];
+    }
+    [rdr next];
+    return ast;
+}
+
+NSObject * read_form(Reader * rdr) {
+    NSString *token = [rdr peek];
+    switch ([token characterAtIndex:0]) {
+    case '\'': [rdr next];
+              return @[[MalSymbol stringWithString:@"quote"],
+                       read_form(rdr)];
+    case '`': [rdr next];
+              return @[[MalSymbol stringWithString:@"quasiquote"],
+                       read_form(rdr)];
+    case '~': [rdr next];
+              if ([token isEqualToString:@"~@"]) {
+                  return @[[MalSymbol stringWithString:@"splice-unquote"],
+                           read_form(rdr)];
+              } else {
+                  return @[[MalSymbol stringWithString:@"unquote"],
+                           read_form(rdr)];
+              }
+    case '^': [rdr next];
+              NSObject * meta = read_form(rdr);
+              return @[[MalSymbol stringWithString:@"with-meta"],
+                       read_form(rdr),
+                       meta];
+    case '@': [rdr next];
+              return @[[MalSymbol stringWithString:@"deref"],
+                       read_form(rdr)];
+
+    // lists
+    case ')':
+        @throw @"unexpected ')'";
+    case '(':
+        return read_list(rdr, '(', ')');
+
+    // vectors
+    case ']':
+        @throw @"unexpected ']'";
+    case '[':
+        return [MalVector fromArray:read_list(rdr, '[', ']')];
+
+    // hash maps
+    case '}':
+        @throw @"unexpected '}'";
+    case '{':
+        return hash_map(read_list(rdr, '{', '}'));
+    default:
+        return read_atom(rdr);
+    }
+}
+
+NSObject * read_str(NSString *str) {
+    NSArray * tokens = tokenize(str);
+    return read_form([[Reader alloc] initWithTokens:tokens]);
+}
diff --git a/objc/step0_repl.m b/objc/step0_repl.m
new file mode 100644 (file)
index 0000000..d3ff873
--- /dev/null
@@ -0,0 +1,38 @@
+#import <Foundation/Foundation.h>
+
+#import "mal_readline.h"
+
+NSString *READ(NSString *str) {
+    return str;
+}
+
+NSString *EVAL(NSString *ast, NSString *env) {
+    return ast;
+}
+
+NSString *PRINT(NSString *exp) {
+    return exp;
+}
+
+NSString *REP(NSString *line) {
+    return PRINT(EVAL(READ(line), @""));
+}
+
+int main (int argc, const char * argv[]) {
+    // Create an autorelease pool to manage the memory into the program
+    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+    // If using automatic reference counting (ARC), use @autoreleasepool instead:
+//    @autoreleasepool {
+
+    while (true) {
+        char *rawline = _readline("user> ");
+        if (!rawline) { break; }
+        NSString *line = [NSString stringWithUTF8String:rawline];
+        if ([line length] == 0) { continue; }
+        printf("%s\n", [[REP(line) description] UTF8String]);
+    }
+
+    [pool drain];
+
+//    }
+}
diff --git a/objc/step1_read_print.m b/objc/step1_read_print.m
new file mode 100644 (file)
index 0000000..fb70c92
--- /dev/null
@@ -0,0 +1,44 @@
+#import <Foundation/Foundation.h>
+
+#import "mal_readline.h"
+#import "reader.h"
+#import "printer.h"
+
+NSObject *READ(NSString *str) {
+    return read_str(str);
+}
+
+NSObject *EVAL(NSObject *ast, NSString *env) {
+    return ast;
+}
+
+NSString *PRINT(NSObject *exp) {
+    return _pr_str(exp, true);
+}
+
+NSString *REP(NSString *line) {
+    return PRINT(EVAL(READ(line), @""));
+}
+
+int main (int argc, const char * argv[]) {
+    // Create an autorelease pool to manage the memory into the program
+    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+    // If using automatic reference counting (ARC), use @autoreleasepool instead:
+//    @autoreleasepool {
+
+    while (true) {
+        char *rawline = _readline("user> ");
+        if (!rawline) { break; }
+        NSString *line = [NSString stringWithUTF8String:rawline];
+        if ([line length] == 0) { continue; }
+        @try {
+            printf("%s\n", [[REP(line) description] UTF8String]);
+        } @catch(NSString *e) {
+            printf("Error: %s\n", [e UTF8String]);
+        }
+    }
+
+    [pool drain];
+
+//    }
+}
diff --git a/objc/step2_eval.m b/objc/step2_eval.m
new file mode 100644 (file)
index 0000000..6c97439
--- /dev/null
@@ -0,0 +1,104 @@
+#import <Foundation/Foundation.h>
+
+#import "mal_readline.h"
+#import "types.h"
+#import "reader.h"
+#import "printer.h"
+
+// read
+NSObject *READ(NSString *str) {
+    return read_str(str);
+}
+
+// eval
+
+// forward declaration
+NSObject *EVAL(NSObject *ast, NSDictionary *env);
+
+NSObject *eval_ast(NSObject *ast, NSDictionary *env) {
+    if ([ast isMemberOfClass:[MalSymbol class]]) {
+        if ([env objectForKey:ast]) {
+            return env[ast];
+        } else {
+            @throw [NSString stringWithFormat:@"'%@' not found", ast];
+        }
+    } else if ([ast isKindOfClass:[NSArray class]]) {
+        NSMutableArray *newLst = [NSMutableArray array];
+        for (NSObject * x in (NSArray *)ast) {
+            [newLst addObject:EVAL(x, env)];
+        }
+        if ([ast isKindOfClass:[MalVector class]]) {
+            return [MalVector fromArray:newLst];
+        } else {
+            return newLst;
+        }
+    } else if ([ast isKindOfClass:[NSDictionary class]]) {
+        NSMutableDictionary *newDict = [NSMutableDictionary dictionary];
+        for (NSString * k in (NSDictionary *)ast) {
+            newDict[k] = EVAL(((NSDictionary *)ast)[k], env);
+        }
+        return newDict;
+    } else {
+        return ast;
+    }
+}
+
+NSObject *EVAL(NSObject *ast, NSDictionary *env) {
+    //NSLog(@"EVAL: %@", ast);
+    if (!list_Q(ast)) {
+        return eval_ast(ast, env);
+    }
+
+    NSArray * el = (NSArray *) eval_ast(ast, env);
+    NSObject * (^ f)(NSArray *) = el[0];
+    NSArray * args = [el subarrayWithRange:NSMakeRange(1, [el count] - 1)];
+    return f(args);
+}
+
+// print
+NSString *PRINT(NSObject *exp) {
+    return _pr_str(exp, true);
+}
+
+// REPL
+NSString *REP(NSString *line, NSDictionary *env) {
+    return PRINT(EVAL(READ(line), env));
+}
+
+int main (int argc, const char * argv[]) {
+    // Create an autorelease pool to manage the memory into the program
+    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+    // If using automatic reference counting (ARC), use @autoreleasepool instead:
+//    @autoreleasepool {
+
+    NSDictionary * repl_env = @{
+        @"+": ^(NSArray *args){
+            return [NSNumber numberWithInt:[args[0] intValue] + [args[1] intValue]];
+        },
+        @"-": ^(NSArray *args){
+            return [NSNumber numberWithInt:[args[0] intValue] - [args[1] intValue]];
+        },
+        @"*": ^(NSArray *args){
+            return [NSNumber numberWithInt:[args[0] intValue] * [args[1] intValue]];
+        },
+        @"/": ^(NSArray *args){
+            return [NSNumber numberWithInt:[args[0] intValue] / [args[1] intValue]];
+        },
+        };
+
+    while (true) {
+        char *rawline = _readline("user> ");
+        if (!rawline) { break; }
+        NSString *line = [NSString stringWithUTF8String:rawline];
+        if ([line length] == 0) { continue; }
+        @try {
+            printf("%s\n", [[REP(line, repl_env) description] UTF8String]);
+        } @catch(NSString *e) {
+            printf("Error: %s\n", [e UTF8String]);
+        }
+    }
+
+    [pool drain];
+
+//    }
+}
diff --git a/objc/step3_env.m b/objc/step3_env.m
new file mode 100644 (file)
index 0000000..aa36523
--- /dev/null
@@ -0,0 +1,116 @@
+#import <Foundation/Foundation.h>
+
+#import "mal_readline.h"
+#import "types.h"
+#import "reader.h"
+#import "printer.h"
+#import "env.h"
+
+// read
+NSObject *READ(NSString *str) {
+    return read_str(str);
+}
+
+// eval
+
+// forward declaration
+NSObject *EVAL(NSObject *ast, Env *env);
+
+NSObject *eval_ast(NSObject *ast, Env *env) {
+    if ([ast isMemberOfClass:[MalSymbol class]]) {
+        return [env get:(MalSymbol *)ast];
+    } else if ([ast isKindOfClass:[NSArray class]]) {
+        NSMutableArray *newLst = [NSMutableArray array];
+        for (NSObject * x in (NSArray *)ast) {
+            [newLst addObject:EVAL(x, env)];
+        }
+        if ([ast isKindOfClass:[MalVector class]]) {
+            return [MalVector fromArray:newLst];
+        } else {
+            return newLst;
+        }
+    } else if ([ast isKindOfClass:[NSDictionary class]]) {
+        NSMutableDictionary *newDict = [NSMutableDictionary dictionary];
+        for (NSString * k in (NSDictionary *)ast) {
+            newDict[k] = EVAL(((NSDictionary *)ast)[k], env);
+        }
+        return newDict;
+    } else {
+        return ast;
+    }
+}
+
+NSObject *EVAL(NSObject *ast, Env *env) {
+    //NSLog(@"EVAL: %@", ast);
+    if (!list_Q(ast)) {
+        return eval_ast(ast, env);
+    }
+
+    NSArray * alst = (NSArray *)ast;
+    id a0 = alst[0];
+    if (![a0 isKindOfClass:[MalSymbol class]]) {
+        @throw @"attempt to apply on non-symbol";
+    }
+    if ([(NSString *)a0 isEqualTo:@"def!"]) {
+        return [env set:((MalSymbol *)alst[1]) val:EVAL(alst[2], env)];
+    } else if ([(NSString *)a0 isEqualTo:@"let*"]) {
+        Env *let_env = [Env fromOuter:env];
+        NSArray * binds = (NSArray *)alst[1];
+        for (int i=0; i < [binds count]; i+=2) {
+            [let_env set:binds[i] val:EVAL(binds[i+1], let_env)];
+        }
+        return EVAL(alst[2], let_env);
+    } else {
+        NSArray * el = (NSArray *) eval_ast(ast, env);
+        NSObject * (^ f)(NSArray *) = el[0];
+        NSArray * args = [el subarrayWithRange:NSMakeRange(1, [el count] - 1)];
+        return f(args);
+    }
+}
+
+// print
+NSString *PRINT(NSObject *exp) {
+    return _pr_str(exp, true);
+}
+
+// REPL
+NSString *REP(NSString *line, Env *env) {
+    return PRINT(EVAL(READ(line), env));
+}
+
+int main (int argc, const char * argv[]) {
+    // Create an autorelease pool to manage the memory into the program
+    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+    // If using automatic reference counting (ARC), use @autoreleasepool instead:
+//    @autoreleasepool {
+
+    Env * repl_env = [[Env alloc] init];
+    [repl_env set:(MalSymbol *)@"+" val:^(NSArray *args){
+        return [NSNumber numberWithInt:[args[0] intValue] + [args[1] intValue]];
+    }];
+    [repl_env set:(MalSymbol *)@"-" val:^(NSArray *args){
+        return [NSNumber numberWithInt:[args[0] intValue] - [args[1] intValue]];
+    }];
+    [repl_env set:(MalSymbol *)@"*" val:^(NSArray *args){
+        return [NSNumber numberWithInt:[args[0] intValue] * [args[1] intValue]];
+    }];
+    [repl_env set:(MalSymbol *)@"/" val:^(NSArray *args){
+        return [NSNumber numberWithInt:[args[0] intValue] / [args[1] intValue]];
+    }];
+
+    while (true) {
+        char *rawline = _readline("user> ");
+        if (!rawline) { break; }
+        NSString *line = [NSString stringWithUTF8String:rawline];
+        if ([line length] == 0) { continue; }
+        @try {
+            printf("%s\n", [[REP(line, repl_env) description] UTF8String]);
+        } @catch(NSString *e) {
+            printf("Error: %s\n", [e UTF8String]);
+        }
+    }
+
+    [pool drain];
+
+//    }
+}
diff --git a/objc/step4_if_fn_do.m b/objc/step4_if_fn_do.m
new file mode 100644 (file)
index 0000000..3ffacbc
--- /dev/null
@@ -0,0 +1,140 @@
+#import <Foundation/Foundation.h>
+
+#import "mal_readline.h"
+#import "types.h"
+#import "reader.h"
+#import "printer.h"
+#import "env.h"
+#import "malfunc.h"
+#import "core.h"
+
+// read
+NSObject *READ(NSString *str) {
+    return read_str(str);
+}
+
+// eval
+
+// forward declaration
+NSObject *EVAL(NSObject *ast, Env *env);
+
+NSObject *eval_ast(NSObject *ast, Env *env) {
+    if ([ast isMemberOfClass:[MalSymbol class]]) {
+        return [env get:(MalSymbol *)ast];
+    } else if ([ast isKindOfClass:[NSArray class]]) {
+        NSMutableArray *newLst = [NSMutableArray array];
+        for (NSObject * x in (NSArray *)ast) {
+            [newLst addObject:EVAL(x, env)];
+        }
+        if ([ast isKindOfClass:[MalVector class]]) {
+            return [MalVector fromArray:newLst];
+        } else {
+            return newLst;
+        }
+    } else if ([ast isKindOfClass:[NSDictionary class]]) {
+        NSMutableDictionary *newDict = [NSMutableDictionary dictionary];
+        for (NSString * k in (NSDictionary *)ast) {
+            newDict[k] = EVAL(((NSDictionary *)ast)[k], env);
+        }
+        return newDict;
+    } else {
+        return ast;
+    }
+}
+
+NSObject *EVAL(NSObject *ast, Env *env) {
+    //NSLog(@"EVAL: %@ (%@)", _pr_str(ast, true), env);
+    if (!list_Q(ast)) {
+        return eval_ast(ast, env);
+    }
+
+    NSArray * alst = (NSArray *)ast;
+    id a0 = alst[0];
+    NSString * a0sym = [a0 isKindOfClass:[MalSymbol class]] ? (NSString *)a0
+                                                            : @"__<*fn*>__";
+
+    if ([a0sym isEqualTo:@"def!"]) {
+        return [env set:((MalSymbol *)alst[1]) val:EVAL(alst[2], env)];
+    } else if ([(NSString *)a0 isEqualTo:@"let*"]) {
+        Env *let_env = [Env fromOuter:env];
+        NSArray * binds = (NSArray *)alst[1];
+        for (int i=0; i < [binds count]; i+=2) {
+            [let_env set:binds[i] val:EVAL(binds[i+1], let_env)];
+        }
+        return EVAL(alst[2], let_env);
+    } else if ([a0sym isEqualTo:@"do"]) {
+        NSRange r = NSMakeRange(1, [alst count] - 1);
+        NSArray * el = (NSArray *)eval_ast([alst subarrayWithRange:r], env);
+        return [el lastObject];
+    } else if ([a0sym isEqualTo:@"if"]) {
+        NSObject * cond = EVAL(alst[1], env);
+        if ([cond isKindOfClass:[NSNull class]] ||
+            [cond isKindOfClass:[MalFalse class]]) {
+            if ([alst count] > 3) {
+                return EVAL(alst[3], env);
+            } else {
+                return [NSNull alloc];
+            }
+        } else {
+            return EVAL(alst[2], env);
+        }
+    } else if ([a0sym isEqualTo:@"fn*"]) {
+        return [[MalFunc alloc] init:alst[2] env:env params:alst[1]];
+    } else {
+        NSArray * el = (NSArray *) eval_ast(ast, env);
+        NSArray * args = @[];
+        if ([el count] > 1) {
+            args = [el subarrayWithRange:NSMakeRange(1, [el count] - 1)];
+        }
+        if ([el[0] isKindOfClass:[MalFunc class]]) {
+            MalFunc * mf = el[0];
+            return [mf apply:args];
+        } else {
+            NSObject * (^ f)(NSArray *) = el[0];
+            return f(args);
+        }
+    }
+}
+
+// print
+NSString *PRINT(NSObject *exp) {
+    return _pr_str(exp, true);
+}
+
+// REPL
+NSString *REP(NSString *line, Env *env) {
+    return PRINT(EVAL(READ(line), env));
+}
+
+int main (int argc, const char * argv[]) {
+    // Create an autorelease pool to manage the memory into the program
+    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
+    // If using automatic reference counting (ARC), use @autoreleasepool instead:
+//    @autoreleasepool {
+
+    // core.m: defined using Objective-C
+    Env * repl_env = [[Env alloc] init];
+    NSDictionary * core_ns = [Core ns];
+    for (NSString* key in core_ns) {
+        [repl_env set:(MalSymbol *)key val:[core_ns objectForKey:key]];
+    }
+
+    // core.mal: defined using the language itself
+    REP(@"(def! not (fn* (a) (if a false true)))", repl_env);
+
+    while (true) {
+        char *rawline = _readline("user> ");
+        if (!rawline) { break; }
+        NSString *line = [NSString stringWithUTF8String:rawline];
+        if ([line length] == 0) { continue; }
+        @try {
+            printf("%s\n", [[REP(line, repl_env) description] UTF8String]);
+        } @catch(NSString *e) {
+            printf("Error: %s\n", [e UTF8String]);
+        }
+    }
+
+    [pool drain];
+
+//    }
+}
diff --git a/objc/types.h b/objc/types.h
new file mode 100644 (file)
index 0000000..f41a8c4
--- /dev/null
@@ -0,0 +1,77 @@
+#import <Foundation/Foundation.h>
+
+//
+// Env definition
+//
+
+@class MalSymbol;
+
+@interface Env : NSObject
+@property (copy) NSMutableDictionary * data;
+@property (copy) Env * outer;
+
+- (id)initWithBindings:(Env *)outer binds:(NSArray *)binds exprs:(NSArray *)exprs;
+- (id)initWithOuter:(Env *)outer;
+- (id)init;
+
++ (id)fromOuter:(Env *)outer;
++ (id)fromBindings:(Env *)outer binds:(NSArray *)binds exprs:(NSArray *)exprs;
+
+- (NSObject *) set:(MalSymbol *)key val:(NSObject *)val;
+- (Env *) find:(MalSymbol *)key;
+- (NSObject *) get:(MalSymbol *)key;
+
+@end
+
+//
+// Mal Types
+//
+
+
+@interface MalTrue : NSObject
+@end
+
+@interface MalFalse : NSObject
+@end
+
+@interface MalSymbol: NSString
+@end
+
+
+// Lists
+
+BOOL list_Q(id obj);
+
+
+// Vectors
+
+@interface MalVector : NSArray
+
+@property (copy) NSArray * array;
+@property(readonly) NSUInteger count;
+
+- (id)initWithArray:(NSArray *)arr;
+- (id)init;
+
++ (id)fromArray:(NSArray *)arr;
+
+- (id)objectAtIndex:(NSUInteger)index;
+
+@end
+
+
+// Hash Maps
+
+NSDictionary * hash_map(NSArray *kvs);
+
+
+// Mal Functions
+
+BOOL block_Q(id obj);
+
+
+
+// General functions
+
+BOOL equal_Q(NSObject * a, NSObject * b);
diff --git a/objc/types.m b/objc/types.m
new file mode 100644 (file)
index 0000000..78aee02
--- /dev/null
@@ -0,0 +1,142 @@
+#import "types.h"
+
+@implementation MalTrue
+@end
+
+@implementation MalFalse
+@end
+
+
+// NSString subclassing based on:
+// http://stackoverflow.com/a/21331422/471795
+
+// Symbols
+
+@interface MalSymbol ()
+@property (nonatomic, strong) NSString *stringHolder;
+@end
+
+@implementation MalSymbol
+
+- (instancetype)initWithCharactersNoCopy:(unichar *)characters length:(NSUInteger)length freeWhenDone:(BOOL)freeBuffer {
+    self = [super init];
+    if (self) {
+        self.stringHolder = [[NSString alloc] initWithCharactersNoCopy:characters length:length freeWhenDone:freeBuffer];
+    }
+    return self;
+}
+
+- (NSUInteger)length {
+    return self.stringHolder.length;
+}
+
+- (unichar)characterAtIndex:(NSUInteger)index {
+    return [self.stringHolder characterAtIndex:index];
+}
+
+@end
+
+
+// Lists
+
+//// Add map to NSArray
+//@implementation NSArray (CMMap)
+//- (NSArray *) map:(NSObject *(^)(NSObject * obj))block {
+//    NSMutableArray *res = [NSMutableArray array];
+//    for (NSObject * x in self) {
+//        [res addObject: block(x)];
+//    }
+//    return res;
+//}
+//@end
+//
+// E.g.:
+// return [(NSArray *)ast map:^(NSObject * x) { return EVAL(x, env); }];
+
+BOOL list_Q(id obj) {
+    return ([obj isKindOfClass:[NSArray class]] &&
+            ![obj isKindOfClass:[MalVector class]]);
+}
+
+// Vectors 
+
+@implementation MalVector
+
+@synthesize array = _array;
+@synthesize count = _count;
+
+- (id)initWithArray:(NSArray *)arr {
+    self = [super init];
+    if (self) {
+        _array = arr;
+        _count = [arr count];
+    }
+    return self;
+}
+        
+- (id)init {
+    return [self initWithArray:@[]];
+}
+
++ (id)fromArray:(NSArray *)arr {
+    return [[MalVector alloc] initWithArray:arr];
+}    
+
+- (id)objectAtIndex:(NSUInteger)index {
+    return _array[index];
+}
+
+@end
+
+
+// Hash Maps
+
+NSDictionary * assoc_BANG(NSMutableDictionary * d, NSArray * kvs) {
+    for (int i=0; i < [kvs count]; i+=2) {
+        d[kvs[i]] = kvs[i+1];
+    }
+    return d;
+}
+
+NSDictionary * hash_map(NSArray *kvs) {
+    return assoc_BANG([NSMutableDictionary dictionary], kvs);
+}
+
+
+// Mal Functions
+
+BOOL block_Q(id obj) {
+    id block = ^{};
+    Class blockClass = [block class];
+    while ([blockClass superclass] != [NSObject class]) {
+        blockClass = [blockClass superclass];
+    }
+    return [obj isKindOfClass:blockClass];
+}
+
+
+// General functions
+
+BOOL sequential_Q(NSObject * obj) {
+    return [obj isKindOfClass:[NSArray class]];
+}
+
+BOOL equal_Q(NSObject * a, NSObject * b) {
+    //NSLog(@"= %@ (%@), %@ (%@)", a, [a class], b, [b class]);
+    if (!(([a class] == [b class]) ||
+          ([a isKindOfClass:[NSArray class]] &&
+           [b isKindOfClass:[NSArray class]]) ||
+          ([a isKindOfClass:[NSNumber class]] &&
+           [b isKindOfClass:[NSNumber class]]))) {
+        return false;
+    }
+    if ([a isKindOfClass:[MalTrue class]]) {
+        return true;
+    } else if ([a isKindOfClass:[MalFalse class]]) {
+        return true;
+    } else if ([a isKindOfClass:[NSNumber class]]) {
+        return [(NSNumber *)a intValue] == [(NSNumber *)b intValue];
+    } else {
+        return [a isEqual:b];
+    }
+}