aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ScriptEngine/YEngine/MMRScriptVarDict.cs
blob: 2561d0290bdc2a2e7bf6905d27df1795df044f42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
/*
 * Copyright (c) Contributors, http://opensimulator.org/
 * See CONTRIBUTORS.TXT for a full list of copyright holders.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSimulator Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

using System;
using System.Collections;
using System.Collections.Generic;

/**
 * @brief Collection of variable/function/method definitions
 */

namespace OpenSim.Region.ScriptEngine.Yengine
{
    public class VarDict: IEnumerable
    {
        public VarDict outerVarDict;            // next outer VarDict to search
        public TokenDeclSDTypeClass thisClass;  // this VarDict is for members of thisClass

        private struct ArgTypes
        {
            public TokenType[] argTypes;

            public bool CanBeCalledBy(TokenType[] calledBy)
            {
                if((argTypes == null) && (calledBy == null))
                    return true;
                if((argTypes == null) || (calledBy == null))
                    return false;
                if(argTypes.Length != calledBy.Length)
                    return false;
                for(int i = argTypes.Length; --i >= 0;)
                {
                    if(!TypeCast.IsAssignableFrom(argTypes[i], calledBy[i]))
                        return false;
                }
                return true;
            }

            public override bool Equals(Object that)
            {
                if(that == null)
                    return false;
                if(that.GetType() != typeof(ArgTypes))
                    return false;
                TokenType[] at = this.argTypes;
                TokenType[] bt = ((ArgTypes)that).argTypes;
                if((at == null) && (bt == null))
                    return true;
                if((at == null) || (bt == null))
                    return false;
                if(at.Length != bt.Length)
                    return false;
                for(int i = at.Length; --i >= 0;)
                {
                    if(at[i].ToString() != bt[i].ToString())
                        return false;
                }
                return true;
            }

            public override int GetHashCode()
            {
                TokenType[] at = this.argTypes;
                if(at == null)
                    return -1;
                int hc = 0;
                for(int i = at.Length; --i >= 0;)
                {
                    int c = (hc < 0) ? 1 : 0;
                    hc = hc * 2 + c;
                    hc ^= at[i].ToString().GetHashCode();
                }
                return hc;
            }
        }

        private struct TDVEntry
        {
            public int count;
            public TokenDeclVar var;
        }

        private bool isFrozen = false;
        private bool locals;
        private Dictionary<string, Dictionary<ArgTypes, TDVEntry>> master = new Dictionary<string, Dictionary<ArgTypes, TDVEntry>>();
        private int count = 0;
        private VarDict frozenLocals = null;

        /**
         * @brief Constructor.
         * @param locals = false: cannot be frozen, allows forward references
         *                  true: can be frozen, thus forbidding forward references
         */
        public VarDict(bool locals)
        {
            this.locals = locals;
        }

        /**
         * @brief Add new variable to the dictionary.
         */
        public bool AddEntry(TokenDeclVar var)
        {
            if(isFrozen)
            {
                throw new Exception("var dict is frozen");
            }

            /*
             * Make sure we have a sub-dictionary based on the bare name (ie, no signature)
             */
            Dictionary<ArgTypes, TDVEntry> typedic;
            if(!master.TryGetValue(var.name.val, out typedic))
            {
                typedic = new Dictionary<ArgTypes, TDVEntry>();
                master.Add(var.name.val, typedic);
            }

            /*
             * See if there is an entry in the sub-dictionary that matches the argument signature.
             * Note that fields have null argument lists.
             * Methods always have a non-null argument list, even if only 0 entries long.
             */
            ArgTypes types;
            types.argTypes = (var.argDecl == null) ? null : KeyTypesToStringTypes(var.argDecl.types);
            if(typedic.ContainsKey(types))
                return false;

            /*
             * It is unique, add to its name-specific sub-dictionary.
             */
            TDVEntry entry;
            entry.count = ++count;
            entry.var = var;
            typedic.Add(types, entry);
            return true;
        }

        public int Count
        {
            get
            {
                return count;
            }
        }

        /**
         * @brief If this is not a local variable frame, just return the frame as is.
         *        If this is a local variable frame, return a version that is frozen,
         *        ie, one that does not contain any future additions.
         */
        public VarDict FreezeLocals()
        {
            /*
             * If not local var frame, return original frame as is.
             * This will allow forward references as the future additions
             * will be seen by lookups done in this dictionary.
             */
            if(!locals)
                return this;

            /*
             * If local var frame, return a copy frozen at this point.
             * This disallows forward referenes as those future additions
             * will not be seen by lookups done in the frozen dictionary.
             */
            if((frozenLocals == null) || (frozenLocals.count != this.count))
            {

                /*
                 * Make a copy of the current var dictionary frame.
                 * We copy a reference to the dictionary, and though it may
                 * contain additions made after this point, those additions
                 * will have a count .gt. frozen count and will be ignored.
                 */
                frozenLocals = new VarDict(true);

                frozenLocals.outerVarDict = this.outerVarDict;
                frozenLocals.thisClass = this.thisClass;
                frozenLocals.master = this.master;
                frozenLocals.count = this.count;
                frozenLocals.frozenLocals = frozenLocals;

                /*
                 * Mark it as being frozen.
                 * - assert fail if any attempt is made to add to it
                 * - ignore any additions to the dictionary with greater count
                 */
                frozenLocals.isFrozen = true;
            }
            return frozenLocals;
        }

        /**
         * @brief Find all functions/variables that are callable
         * @param name = name of function/variable to look for
         * @param argTypes = the argument types the function is being called with
         *                   null to look for a variable
         * @returns null: no matching function/variable found
         *          else: list of matching functions/variables
         *                for variables, always of length 1
         */
        private List<TokenDeclVar> found = new List<TokenDeclVar>();
        public TokenDeclVar[] FindCallables(string name, TokenType[] argTypes)
        {
            argTypes = KeyTypesToStringTypes(argTypes);
            TokenDeclVar var = FindExact(name, argTypes);
            if(var != null)
                return new TokenDeclVar[] { var };

            Dictionary<ArgTypes, TDVEntry> typedic;
            if(!master.TryGetValue(name, out typedic))
                return null;

            found.Clear();
            foreach(KeyValuePair<ArgTypes, TDVEntry> kvp in typedic)
            {
                if((kvp.Value.count <= this.count) && kvp.Key.CanBeCalledBy(argTypes))
                {
                    found.Add(kvp.Value.var);
                }
            }
            return (found.Count > 0) ? found.ToArray() : null;
        }

        /**
         * @brief Find exact matching function/variable
         * @param name = name of function to look for
         * @param argTypes = argument types the function was declared with
         *                   null to look for a variable
         * @returns null: no matching function/variable found
         *          else: the matching function/variable
         */
        public TokenDeclVar FindExact(string name, TokenType[] argTypes)
        {
            /*
             * Look for list of stuff that matches the given name.
             */
            Dictionary<ArgTypes, TDVEntry> typedic;
            if(!master.TryGetValue(name, out typedic))
                return null;

            /*
             * Loop through all fields/methods declared by that name, regardless of arg signature.
             */
            foreach(TDVEntry entry in typedic.Values)
            {
                if(entry.count > this.count)
                    continue;
                TokenDeclVar var = entry.var;

                /*
                 * Get argument types of declaration.
                 *   fields are always null
                 *   methods are always non-null, though may be zero-length
                 */
                TokenType[] declArgs = (var.argDecl == null) ? null : var.argDecl.types;

                /*
                 * Convert any key args to string args.
                 */
                declArgs = KeyTypesToStringTypes(declArgs);

                /*
                 * If both are null, they are signature-less (ie, both are fields), and so match.
                 */
                if((declArgs == null) && (argTypes == null))
                    return var;

                /*
                 * If calling a delegate, it is a match, regardless of delegate arg types.
                 * If it turns out the arg types do not match, the compiler will give an error
                 * trying to cast the arguments to the delegate arg types.
                 * We don't allow overloading same field name with different delegate types.
                 */
                if((declArgs == null) && (argTypes != null))
                {
                    TokenType fieldType = var.type;
                    if(fieldType is TokenTypeSDTypeDelegate)
                        return var;
                }

                /*
                 * If not both null, no match, keep looking.
                 */
                if((declArgs == null) || (argTypes == null))
                    continue;

                /*
                 * Both not null, match argument types to make sure we have correct overload.
                 */
                int i = declArgs.Length;
                if(i != argTypes.Length)
                    continue;
                while(--i >= 0)
                {
                    string da = declArgs[i].ToString();
                    string ga = argTypes[i].ToString();
                    if(da == "key")
                        da = "string";
                    if(ga == "key")
                        ga = "string";
                    if(da != ga)
                        break;
                }
                if(i < 0)
                    return var;
            }

            /*
             * No match.
             */
            return null;
        }

        /**
         * @brief Replace any TokenTypeKey elements with TokenTypeStr so that
         *        it doesn't matter if functions are declared with key or string,
         *        they will accept either.
         * @param argTypes = argument types as declared in source code
         * @returns argTypes with any key replaced by string
         */
        private static TokenType[] KeyTypesToStringTypes(TokenType[] argTypes)
        {
            if(argTypes != null)
            {
                int i;
                int nats = argTypes.Length;
                for(i = nats; --i >= 0;)
                {
                    if(argTypes[i] is TokenTypeKey)
                        break;
                }
                if(i >= 0)
                {
                    TokenType[] at = new TokenType[nats];
                    for(i = nats; --i >= 0;)
                    {
                        at[i] = argTypes[i];
                        if(argTypes[i] is TokenTypeKey)
                        {
                            at[i] = new TokenTypeStr(argTypes[i]);
                        }
                    }
                    return at;
                }
            }
            return argTypes;
        }

        // foreach goes through all the TokenDeclVars that were added

        // IEnumerable
        public IEnumerator GetEnumerator()
        {
            return new VarDictEnumerator(this.master, this.count);
        }

        private class VarDictEnumerator: IEnumerator
        {
            private IEnumerator masterEnum;
            private IEnumerator typedicEnum;
            private int count;

            public VarDictEnumerator(Dictionary<string, Dictionary<ArgTypes, TDVEntry>> master, int count)
            {
                masterEnum = master.Values.GetEnumerator();
                this.count = count;
            }

            // IEnumerator
            public void Reset()
            {
                masterEnum.Reset();
                typedicEnum = null;
            }

            // IEnumerator
            public bool MoveNext()
            {
                while(true)
                {
                    if(typedicEnum != null)
                    {
                        while(typedicEnum.MoveNext())
                        {
                            if(((TDVEntry)typedicEnum.Current).count <= this.count)
                                return true;
                        }
                        typedicEnum = null;
                    }
                    if(!masterEnum.MoveNext())
                        return false;
                    Dictionary<ArgTypes, TDVEntry> ctd;
                    ctd = (Dictionary<ArgTypes, TDVEntry>)masterEnum.Current;
                    typedicEnum = ctd.Values.GetEnumerator();
                }
            }

            // IEnumerator
            public object Current
            {
                get
                {
                    return ((TDVEntry)typedicEnum.Current).var;
                }
            }
        }
    }
}