|
1 | 1 | package com.annimon.ownlang.lib;
|
2 | 2 |
|
3 | 3 | import java.util.Map;
|
4 |
| -import java.util.Stack; |
5 | 4 | import java.util.concurrent.ConcurrentHashMap;
|
6 | 5 |
|
7 | 6 | /**
|
|
10 | 9 | */
|
11 | 10 | public final class Variables {
|
12 | 11 |
|
13 |
| - private static final Stack<Map<String, Value>> stack; |
14 |
| - private static Map<String, Value> variables; |
15 |
| - |
| 12 | + private static final Object lock = new Object(); |
| 13 | + |
| 14 | + private static class Scope { |
| 15 | + public final Scope parent; |
| 16 | + public final Map<String, Value> variables; |
| 17 | + |
| 18 | + public Scope() { |
| 19 | + this(null); |
| 20 | + } |
| 21 | + |
| 22 | + public Scope(Scope parent) { |
| 23 | + this.parent = parent; |
| 24 | + variables = new ConcurrentHashMap<>(); |
| 25 | + } |
| 26 | + } |
| 27 | + |
| 28 | + private static class ScopeFindData { |
| 29 | + public boolean isFound; |
| 30 | + public Scope scope; |
| 31 | + } |
| 32 | + |
| 33 | + private static volatile Scope scope; |
16 | 34 | static {
|
17 |
| - stack = new Stack<>(); |
18 |
| - variables = new ConcurrentHashMap<>(); |
| 35 | + scope = new Scope(); |
19 | 36 | Variables.clear();
|
20 | 37 | }
|
21 | 38 |
|
22 | 39 | public static void clear() {
|
23 |
| - stack.clear(); |
24 |
| - variables.clear(); |
25 |
| - variables.put("true", NumberValue.ONE); |
26 |
| - variables.put("false", NumberValue.ZERO); |
| 40 | + scope.variables.clear(); |
| 41 | + scope.variables.put("true", NumberValue.ONE); |
| 42 | + scope.variables.put("false", NumberValue.ZERO); |
27 | 43 | }
|
28 | 44 |
|
29 | 45 | public static void push() {
|
30 |
| - stack.push(new ConcurrentHashMap<>(variables)); |
| 46 | + synchronized (lock) { |
| 47 | + final Scope newScope = new Scope(scope); |
| 48 | + scope = newScope; |
| 49 | + } |
31 | 50 | }
|
32 | 51 |
|
33 | 52 | public static void pop() {
|
34 |
| - variables = stack.pop(); |
| 53 | + synchronized (lock) { |
| 54 | + if (scope.parent != null) { |
| 55 | + scope = scope.parent; |
| 56 | + } |
| 57 | + } |
35 | 58 | }
|
36 | 59 |
|
37 | 60 | public static boolean isExists(String key) {
|
38 |
| - return variables.containsKey(key); |
| 61 | + synchronized (lock) { |
| 62 | + return findScope(key).isFound; |
| 63 | + } |
39 | 64 | }
|
40 | 65 |
|
41 | 66 | public static Value get(String key) {
|
42 |
| - if (!isExists(key)) return NumberValue.ZERO; |
43 |
| - return variables.get(key); |
| 67 | + synchronized (lock) { |
| 68 | + final ScopeFindData scopeData = findScope(key); |
| 69 | + if (scopeData.isFound) { |
| 70 | + return scopeData.scope.variables.get(key); |
| 71 | + } |
| 72 | + } |
| 73 | + return NumberValue.ZERO; |
44 | 74 | }
|
45 | 75 |
|
46 | 76 | public static void set(String key, Value value) {
|
47 |
| - variables.put(key, value); |
| 77 | + synchronized (lock) { |
| 78 | + findScope(key).scope.variables.put(key, value); |
| 79 | + } |
48 | 80 | }
|
49 | 81 |
|
| 82 | + public static void define(String key, Value value) { |
| 83 | + synchronized (lock) { |
| 84 | + scope.variables.put(key, value); |
| 85 | + } |
| 86 | + } |
| 87 | + |
50 | 88 | public static void remove(String key) {
|
51 |
| - variables.remove(key); |
| 89 | + synchronized (lock) { |
| 90 | + findScope(key).scope.variables.remove(key); |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + /* |
| 95 | + * Find scope where variable exists. |
| 96 | + */ |
| 97 | + private static ScopeFindData findScope(String variable) { |
| 98 | + final ScopeFindData result = new ScopeFindData(); |
| 99 | + |
| 100 | + Scope current = scope; |
| 101 | + do { |
| 102 | + if (current.variables.containsKey(variable)) { |
| 103 | + result.isFound = true; |
| 104 | + result.scope = current; |
| 105 | + return result; |
| 106 | + } |
| 107 | + } while ((current = current.parent) != null); |
| 108 | + |
| 109 | + result.isFound = false; |
| 110 | + result.scope = scope; |
| 111 | + return result; |
52 | 112 | }
|
53 | 113 | }
|
0 commit comments