Objective-C: steps 0-4, keywords, hash maps.
[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) {
64 [tokens addObject:[str substringWithRange:[match rangeAtIndex:1]]];
65 }
66 return tokens;
67}
68
69NSObject * read_atom(Reader * rdr) {
70 NSRegularExpression *regex = [NSRegularExpression
71 regularExpressionWithPattern:@"(^-?[0-9]+$)|(^-?[0-9][0-9.]*$)|(^nil$)|(^true$)|(^false$)|^\"(.*)\"$|:(.*)|(^[^\"]*$)"
72 options:0
73 error:NULL];
74 NSNumberFormatter *numf = [[NSNumberFormatter alloc] init];
75 numf.numberStyle = NSNumberFormatterDecimalStyle;
76
77 NSString *token = [rdr next];
78
79 NSArray *matches = [regex
80 matchesInString:token
81 options:0
82 range:NSMakeRange(0, [token length])];
83
84 if ([matches count] > 0) {
85 NSTextCheckingResult *match = matches[0];
86 if ([match rangeAtIndex:1].location != -1) { // integer
87 return [numf numberFromString:token];
88 } else if ([match rangeAtIndex:2].location != -1) { // float
89 return [numf numberFromString:token];
90 } else if ([match rangeAtIndex:3].location != -1) { // nil
91 return [NSNull alloc];
92 } else if ([match rangeAtIndex:4].location != -1) { // true
93 return [MalTrue alloc]; // TODO: intern
94 } else if ([match rangeAtIndex:5].location != -1) { // false
95 return [MalFalse alloc]; // TODO: intern
96 } else if ([match rangeAtIndex:6].location != -1) { // string
97 NSString * str = [token substringWithRange:[match rangeAtIndex:6]];
98 return [[[str
99 stringByReplacingOccurrencesOfString:@"\\\"" withString:@"\""]
100 stringByReplacingOccurrencesOfString:@"\\n" withString:@"\n"]
101 stringByReplacingOccurrencesOfString:@"\\\\" withString:@"\\"];
102 return [token substringWithRange:[match rangeAtIndex:6]];
103 } else if ([match rangeAtIndex:7].location != -1) { // keyword
104 return [NSString stringWithFormat:@"\u029e%@",
105 [token substringWithRange:[match rangeAtIndex:7]]];
106 } else if ([match rangeAtIndex:8].location != -1) { // symbol
107 return [MalSymbol stringWithString:token];
108 }
109 }
110
111 return @0;
112}
113
114// Only used locally, so declare here
115NSObject * read_form(Reader * rdr);
116
117NSArray * read_list(Reader * rdr, char start, char end) {
118 NSString * token = [rdr next];
119 NSMutableArray * ast = [NSMutableArray array];
120
121 if ([token characterAtIndex:0] != start) {
122 @throw [NSString stringWithFormat:@"expected '%c'", start];
123 }
124 while ((token = [rdr peek]) && ([token characterAtIndex:0] != end)) {
125 [ast addObject:read_form(rdr)];
126 }
127 if (!token) {
128 @throw [NSString stringWithFormat:@"expected '%c', got EOF", end];
129 }
130 [rdr next];
131 return ast;
132}
133
134NSObject * read_form(Reader * rdr) {
135 NSString *token = [rdr peek];
136 switch ([token characterAtIndex:0]) {
137 case '\'': [rdr next];
138 return @[[MalSymbol stringWithString:@"quote"],
139 read_form(rdr)];
140 case '`': [rdr next];
141 return @[[MalSymbol stringWithString:@"quasiquote"],
142 read_form(rdr)];
143 case '~': [rdr next];
144 if ([token isEqualToString:@"~@"]) {
145 return @[[MalSymbol stringWithString:@"splice-unquote"],
146 read_form(rdr)];
147 } else {
148 return @[[MalSymbol stringWithString:@"unquote"],
149 read_form(rdr)];
150 }
151 case '^': [rdr next];
152 NSObject * meta = read_form(rdr);
153 return @[[MalSymbol stringWithString:@"with-meta"],
154 read_form(rdr),
155 meta];
156 case '@': [rdr next];
157 return @[[MalSymbol stringWithString:@"deref"],
158 read_form(rdr)];
159
160 // lists
161 case ')':
162 @throw @"unexpected ')'";
163 case '(':
164 return read_list(rdr, '(', ')');
165
166 // vectors
167 case ']':
168 @throw @"unexpected ']'";
169 case '[':
170 return [MalVector fromArray:read_list(rdr, '[', ']')];
171
172 // hash maps
173 case '}':
174 @throw @"unexpected '}'";
175 case '{':
176 return hash_map(read_list(rdr, '{', '}'));
177 default:
178 return read_atom(rdr);
179 }
180}
181
182NSObject * read_str(NSString *str) {
183 NSArray * tokens = tokenize(str);
184 return read_form([[Reader alloc] initWithTokens:tokens]);
185}