Implement step 7
[jackhill/mal.git] / objc / reader.m
CommitLineData
57350ed7
JM
1#import <Foundation/Foundation.h>
2
3#import "types.h"
4
5// Only used here, so define interface locally
6@interface Reader : NSObject
7
8- (id)initWithTokens:(NSArray *)toks;
9- (id)init;
10
11- (NSString *) next;
12- (NSString *) peek;
13
14@end
15
16
17@implementation Reader
18
19NSArray *_tokens;
20int _position;
21
22- (id)initWithTokens:(NSArray *)toks {
23 self = [super init];
24 if (self) {
25 _tokens = toks;
26 _position = 0;
27 }
28 return self;
29}
30
31- (id)init {
32 return [self initWithTokens:@[]];
33}
34
35- (NSString *)next {
36 _position++;
37 return _tokens[_position-1];
38}
39
40- (NSString *)peek {
41 if ([_tokens count] > _position) {
42 return _tokens[_position];
43 } else {
44 return nil;
45 }
46}
47
48@end
49
50
51NSArray * tokenize(NSString *str) {
52 NSRegularExpression *regex = [NSRegularExpression
53 regularExpressionWithPattern:@"[\\s,]*(~@|[\\[\\]{}()'`~^@]|\"(?:[\\\\].|[^\\\\\"])*\"?|;.*|[^\\s\\[\\]{}()'\"`@,;]+)"
54 options:0
55 error:NULL];
56
57 NSArray *matches = [regex
58 matchesInString:str
59 options:0
60 range:NSMakeRange(0, [str length])];
61
62 NSMutableArray * tokens = [NSMutableArray array];
63 for (NSTextCheckingResult *match in matches) {
7cae6e6f
JM
64 NSString * mstr = [str substringWithRange:[match rangeAtIndex:1]];
65 if ([mstr characterAtIndex:0] == ';') { continue; }
66 [tokens addObject:mstr];
57350ed7
JM
67 }
68 return tokens;
69}
70
71NSObject * read_atom(Reader * rdr) {
72 NSRegularExpression *regex = [NSRegularExpression
73 regularExpressionWithPattern:@"(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)"
74 options:0
75 error:NULL];
76 NSNumberFormatter *numf = [[NSNumberFormatter alloc] init];
77 numf.numberStyle = NSNumberFormatterDecimalStyle;
78
79 NSString *token = [rdr next];
80
81 NSArray *matches = [regex
82 matchesInString:token
83 options:0
84 range:NSMakeRange(0, [token length])];
85
86 if ([matches count] > 0) {
2faae94c 87
57350ed7 88 NSTextCheckingResult *match = matches[0];
2faae94c 89 if ([match rangeAtIndex:1].location < -1ULL/2) { // integer
57350ed7 90 return [numf numberFromString:token];
2faae94c 91 } else if ([match rangeAtIndex:2].location < -1ULL/2) { // float
57350ed7 92 return [numf numberFromString:token];
2faae94c 93 } else if ([match rangeAtIndex:3].location < -1ULL/2) { // nil
57350ed7 94 return [NSNull alloc];
2faae94c 95 } else if ([match rangeAtIndex:4].location < -1ULL/2) { // true
57350ed7 96 return [MalTrue alloc]; // TODO: intern
2faae94c 97 } else if ([match rangeAtIndex:5].location < -1ULL/2) { // false
57350ed7 98 return [MalFalse alloc]; // TODO: intern
2faae94c 99 } else if ([match rangeAtIndex:6].location < -1ULL/2) { // string
57350ed7
JM
100 NSString * str = [token substringWithRange:[match rangeAtIndex:6]];
101 return [[[str
102 stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""]
103 stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"]
104 stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
2faae94c 105 } else if ([match rangeAtIndex:7].location < -1ULL/2) { // keyword
57350ed7
JM
106 return [NSString stringWithFormat:@"\u029e%@",
107 [token substringWithRange:[match rangeAtIndex:7]]];
2faae94c 108 } else if ([match rangeAtIndex:8].location < -1ULL/2) { // symbol
57350ed7
JM
109 return [MalSymbol stringWithString:token];
110 }
111 }
112
2faae94c 113 @throw @"read_atom: invalid token";
57350ed7
JM
114}
115
116// Only used locally, so declare here
117NSObject * read_form(Reader * rdr);
118
119NSArray * read_list(Reader * rdr, char start, char end) {
120 NSString * token = [rdr next];
121 NSMutableArray * ast = [NSMutableArray array];
122
123 if ([token characterAtIndex:0] != start) {
124 @throw [NSString stringWithFormat:@"expected '%c'", start];
125 }
126 while ((token = [rdr peek]) && ([token characterAtIndex:0] != end)) {
127 [ast addObject:read_form(rdr)];
128 }
129 if (!token) {
130 @throw [NSString stringWithFormat:@"expected '%c', got EOF", end];
131 }
132 [rdr next];
133 return ast;
134}
135
136NSObject * read_form(Reader * rdr) {
137 NSString *token = [rdr peek];
138 switch ([token characterAtIndex:0]) {
139 case '\'': [rdr next];
140 return @[[MalSymbol stringWithString:@"quote"],
141 read_form(rdr)];
142 case '`': [rdr next];
143 return @[[MalSymbol stringWithString:@"quasiquote"],
144 read_form(rdr)];
145 case '~': [rdr next];
146 if ([token isEqualToString:@"~@"]) {
147 return @[[MalSymbol stringWithString:@"splice-unquote"],
148 read_form(rdr)];
149 } else {
150 return @[[MalSymbol stringWithString:@"unquote"],
151 read_form(rdr)];
152 }
153 case '^': [rdr next];
154 NSObject * meta = read_form(rdr);
155 return @[[MalSymbol stringWithString:@"with-meta"],
156 read_form(rdr),
157 meta];
158 case '@': [rdr next];
159 return @[[MalSymbol stringWithString:@"deref"],
160 read_form(rdr)];
161
162 // lists
163 case ')':
164 @throw @"unexpected ')'";
165 case '(':
166 return read_list(rdr, '(', ')');
167
168 // vectors
169 case ']':
170 @throw @"unexpected ']'";
171 case '[':
172 return [MalVector fromArray:read_list(rdr, '[', ']')];
173
174 // hash maps
175 case '}':
176 @throw @"unexpected '}'";
177 case '{':
178 return hash_map(read_list(rdr, '{', '}'));
179 default:
180 return read_atom(rdr);
181 }
182}
183
184NSObject * read_str(NSString *str) {
185 NSArray * tokens = tokenize(str);
7cae6e6f
JM
186 if ([tokens count] == 0) { @throw [NSException exceptionWithName:@"ReaderContinue"
187 reason:@"empty token"
188 userInfo:nil]; }
189 //if ([tokens count] == 0) { @throw [[MalContinue alloc] init]; }
57350ed7
JM
190 return read_form([[Reader alloc] initWithTokens:tokens]);
191}