1 /**
2 * Authors: k.inaba
3 * License: NYSL 0.9982 http://www.kmonos.net/nysl/
4 *
5 * Runtime data structures for Polemy programming language.
6 */
7 module polemy.value;
8 import polemy._common;
9 import polemy.failure;
10 import polemy.ast;
11 import polemy.layer;
12
13 /// Runtime values of Polemy
14
15 abstract class Value
16 {
17 override bool opEquals(Object rhs) { return 0==opCmp(rhs); }
18 }
19
20 ///
21 class IntValue : Value
22 {
23 BigInt data;
24
25 this(bool n) { this.data = n?1:0; }
26 this(int n) { this.data = n; }
27 this(long n) { this.data = n; }
28 this(BigInt n) { this.data = n; }
29 this(string n) { this.data = BigInt(n); }
30 override string toString() const { return toDecimalString(cast(BigInt)data); }
31 override int opCmp(Object rhs) {
32 if(auto r = cast(IntValue)rhs) return data.opCmp(r.data);
33 if(auto r = cast(Value)rhs) return typeid(this).opCmp(typeid(r));
34 throw genex!RuntimeException("comparison with value and somithing other");
35 }
36 mixin SimpleToHash;
37 }
38
39 ///
40 class StrValue : Value
41 {
42 string data;
43
44 mixin SimpleConstructor;
45 override string toString() const { return data; }
46 override int opCmp(Object rhs) {
47 if(auto r = cast(StrValue)rhs) return typeid(string).compare(&data, &r.data);
48 if(auto r = cast(Value)rhs) return typeid(this).opCmp(typeid(r));
49 throw genex!RuntimeException("comparison with value and somithing other");
50 }
51 mixin SimpleToHash;
52 }
53
54 ///
55 class UndefinedValue : Value
56 {
57 mixin SimpleConstructor;
58 override string toString() const { return "<undefined>"; }
59 override int opCmp(Object rhs) {
60 if(auto r = cast(StrValue)rhs) return 0;
61 if(auto r = cast(Value)rhs) return typeid(this).opCmp(typeid(r));
62 throw genex!RuntimeException("comparison with value and somithing other");
63 }
64 mixin SimpleToHash;
65 }
66
67 ///
68 abstract class FunValue : Value
69 {
70 const(Parameter[]) params();
71 Table definitionContext();
72 Value invoke(Layer lay, Table ctx, LexPosition pos);
73 }
74
75 /// Context (variable environment)
76 /// Simlar to prototype chain of ECMAScript etc.
77 /// But extended with the notion of "Layer"
78
79 class Table : Value
80 {
81 enum Kind {PropagateSet, NotPropagateSet};
82
83 this( Table proto=null, Kind k = Kind.PropagateSet )
84 { this.prototype = proto; this.kind = k; }
85
86 /// Set the value v to the index i of layer lay
87 void set(string i, Layer lay, Value v)
88 {
89 if( setIfExist(i, lay, v) )
90 return;
91 data[i][lay] = v;
92 }
93
94 /// True if index i has value in layer lay
95 bool has(string i, Layer lay) const
96 {
97 if( i in data )
98 return !!(lay in data[i]);
99 if( prototype is null )
100 return false;
101 return prototype.has(i, lay);
102 }
103
104 /// Return the value of index i at layer lay. Throws if it is not set
105 Value get(string i, Layer lay, LexPosition pos=null)
106 {
107 if( i in data ) {
108 // [TODO] consider forwarding to proto also in this case
109 if( lay !in data[i] )
110 throw genex!RuntimeException(pos, sprintf!"'%s' is not set in %s layer"(i,lay));
111 return data[i][lay];
112 }
113 if( prototype is null )
114 throw genex!RuntimeException(pos, sprintf!"'%s' not found in %s layer"(i,lay));
115 return prototype.get(i, lay, pos);
116 }
117
118 /// t.access!T(lay,a,b,...) returns t.get(a,lay).get(b,lay).... if exists
119 /// and has type T. Returns null otherwise
120 T access(T,S...)( Layer lay, string path, S rest )
121 {
122 static if( rest.length == 0 )
123 {
124 if( this.has(path, lay) )
125 return cast(T) this.get(path, lay);
126 }
127 else
128 {
129 if(auto next = this.access!Table(lay,path))
130 return next.access!T(lay,rest);
131 }
132 return null;
133 }
134
135 /// Is this an empty table?
136 bool empty()
137 {
138 return data.length==0 && (prototype is null || prototype.empty);
139 }
140
141 /// Can be seen as a cons-list?
142 bool isList()
143 {
144 Table t = this;
145 while(t.has("car", ValueLayer) && t.has("cdr", ValueLayer))
146 if(auto tt = cast(Table)t.get("cdr", ValueLayer))
147 t = tt;
148 else
149 return false;
150 return t.empty;
151 }
152
153 /// Regard table as a cons-list and convert to an array
154 Value[] toList()
155 {
156 Value[] result;
157 Table t = this;
158 while(t.has("car", ValueLayer) && t.has("cdr", ValueLayer))
159 {
160 result ~= t.get("car", ValueLayer);
161 if(auto tt = cast(Table)t.get("cdr", ValueLayer))
162 t = tt;
163 else
164 throw genex!RuntimeException("this table is not a cons-list");
165 }
166 if( t.empty )
167 return result;
168 throw genex!RuntimeException("this table is not a cons-list");
169 }
170
171 /// Get the list of direct entries ignoring prototypes in sorted order
172 Tuple!(string,Layer,Value)[] direct_entries()
173 {
174 Tuple!(string,Layer,Value)[] arr;
175 foreach(k, l2d; data)
176 foreach(l,d; l2d)
177 arr ~= tuple(k,l,d);
178 arr.sort();
179 return arr;
180 }
181
182 /// Get the whole list of observable entries in unspecified order
183 Tuple!(string,Layer,Value)[] entries()
184 {
185 bool[string] hidden;
186 Tuple!(string,Layer,Value)[] arr;
187 enumerateEntries(hidden, arr);
188 return arr;
189 }
190
191 private void enumerateEntries( ref bool[string] hidden, ref Tuple!(string,Layer,Value)[] arr )
192 {
193 foreach(k, l2d; data)
194 if( k !in hidden )
195 {
196 foreach(l,d; l2d)
197 arr ~= tuple(k,l,d);
198 hidden[k] = true;
199 }
200 if(prototype !is null)
201 prototype.enumerateEntries(hidden, arr);
202 }
203
204 override string toString()
205 {
206 if( isList() )
207 return text(toList());
208 return "{" ~ toStringWithoutParen() ~ "}";
209 }
210
211 override int opCmp(Object rhs)
212 {
213 if(auto r = cast(Table)rhs) {
214 Tuple!(string,Layer,Value)[] ls = this.entries();
215 Tuple!(string,Layer,Value)[] rs = r.entries();
216 if( ls.length != rs.length )
217 return (ls.length < rs.length ? -1 : +1);
218 ls.sort();
219 rs.sort();
220 foreach(i,_; ls)
221 if(auto c = ls[i].opCmp(rs[i]))
222 return c;
223 return 0;
224 }
225 if(auto r = cast(Value)rhs) return typeid(this).opCmp(typeid(r));
226 throw genex!RuntimeException("comparison with value and somithing other");
227 }
228
229 override hash_t toHash()
230 {
231 Tuple!(string,Layer,Value)[] ls = this.entries();
232 ls.sort();
233 hash_t h;
234 foreach(e; ls)
235 h += structuralHash(e[0])+structuralHash(e[1])+structuralHash(e[2]);
236 return h;
237 }
238
239 private:
240 Table prototype;
241 Kind kind;
242 Value[Layer][string] data;
243
244 string toStringWithoutParen() const
245 {
246 string result;
247 bool first = true;
248 foreach(k, l2d; data)
249 foreach(l,d; l2d)
250 {
251 if(first) first=false; else result~=", ";
252 result ~= k;
253 if( l.empty )
254 result ~= "(emptylayer)";
255 else if( l != ValueLayer )
256 result ~= l;
257 result ~= ":";
258 result ~= text(cast(Value)d);
259 }
260 if( prototype !is null )
261 {
262 result ~= " / ";
263 result ~= prototype.toStringWithoutParen();
264 }
265 return result;
266 }
267
268 bool setIfExist(string i, Layer lay, Value v)
269 {
270 if( i in data )
271 {
272 data[i][lay] = v;
273 return true;
274 }
275 if( kind==Kind.PropagateSet && prototype !is null )
276 return prototype.setIfExist(i, lay, v);
277 return false;
278 }
279 }
280
281 unittest
282 {
283 Table c0 = new Table;
284 Table c01 = new Table(c0, Table.Kind.NotPropagateSet);
285 Table c012 = new Table(c01, Table.Kind.PropagateSet);
286 Table c013 = new Table(c01, Table.Kind.PropagateSet);
287
288 assert_nothrow( c012.set("x", ValueLayer, new IntValue(12)) );
289 assert_throw!RuntimeException( c013.get("x", ValueLayer) );
290 assert_nothrow( c013.set("x", ValueLayer, new IntValue(13)) );
291 assert_eq( c013.get("x", ValueLayer), new IntValue(13) );
292 assert_eq( c012.get("x", ValueLayer), new IntValue(12) );
293 assert_throw!RuntimeException( c01.get("x", ValueLayer) );
294
295 assert_nothrow( c01.set("y", ValueLayer, new IntValue(1)) );
296 assert_eq( c013.get("y", ValueLayer), new IntValue(1) );
297 assert_eq( c012.get("y", ValueLayer), new IntValue(1) );
298 assert_eq( c01.get("y", ValueLayer), new IntValue(1) );
299
300 assert_nothrow( c0.set("z", ValueLayer, new IntValue(0)) );
301 assert_eq( c013.get("z", ValueLayer), new IntValue(0) );
302 assert_eq( c012.get("z", ValueLayer), new IntValue(0) );
303 assert_eq( c01.get("z", ValueLayer), new IntValue(0) );
304 assert_eq( c0.get("z", ValueLayer), new IntValue(0) );
305
306 assert_nothrow( c012.set("y", ValueLayer, new IntValue(444)) );
307 assert_eq( c013.get("y", ValueLayer), new IntValue(444) );
308 assert_eq( c012.get("y", ValueLayer), new IntValue(444) );
309 assert_eq( c01.get("y", ValueLayer), new IntValue(444) );
310
311 assert_nothrow( c012.set("z", ValueLayer, new IntValue(555)) );
312 assert_eq( c013.get("z", ValueLayer), new IntValue(0) );
313 assert_eq( c012.get("z", ValueLayer), new IntValue(555) );
314 assert_eq( c01.get("z", ValueLayer), new IntValue(0) );
315 assert_eq( c0.get("z", ValueLayer), new IntValue(0) );
316
317 // [TODO] define the semantics and test @layers
318 }