-
Notifications
You must be signed in to change notification settings - Fork 39
Expand file tree
/
Copy pathscope.ts
More file actions
160 lines (142 loc) · 4.4 KB
/
scope.ts
File metadata and controls
160 lines (142 loc) · 4.4 KB
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
import { autobind } from '../utils/mini-autobind.js';
import { AiScriptRuntimeError } from '../error.js';
import type { Value } from './value.js';
import type { Variable } from './variable.js';
import type { LogObject } from './index.js';
export type LayerdStates = [Map<string, Variable>, ...Map<string, Variable>[]]
export class Scope {
private parent?: Scope;
private layerdStates: LayerdStates;
public name: string;
public opts: {
log?(type: string, params: LogObject): void;
onUpdated?(name: string, value: Value): void;
} = {};
public nsName?: string;
constructor(layerdStates: Scope['layerdStates'] = [new Map()], parent?: Scope, name?: Scope['name'], nsName?: string) {
this.layerdStates = layerdStates;
this.parent = parent;
this.name = name || (layerdStates.length === 1 ? '<root>' : '<anonymous>');
this.nsName = nsName;
}
@autobind
private log(type: string, params: LogObject): void {
if (this.parent) {
this.parent.log(type, params);
} else {
if (this.opts.log) this.opts.log(type, params);
}
}
@autobind
private onUpdated(name: string, value: Value): void {
if (this.parent) {
this.parent.onUpdated(name, value);
} else {
if (this.opts.onUpdated) this.opts.onUpdated(name, value);
}
}
@autobind
public createChildScope(states: Map<string, Variable> = new Map(), name?: Scope['name']): Scope {
const layer: LayerdStates = [states, ...this.layerdStates];
return new Scope(layer, this, name);
}
@autobind
public createChildNamespaceScope(nsName: string, states: Map<string, Variable> = new Map(), name?: Scope['name']): Scope {
const layer: LayerdStates = [states, ...this.layerdStates];
return new Scope(layer, this, name, nsName);
}
/**
* 指定した名前の変数を取得します
* @param name - 変数名
*/
@autobind
public get(name: string): Value {
for (const layer of this.layerdStates) {
const value = layer.get(name);
if (value) {
const state = value.value;
this.log('read', { var: name, val: state });
return state;
}
}
throw new AiScriptRuntimeError(
`No such variable '${name}' in scope '${this.name}'`,
{ scope: this.layerdStates });
}
/**
* 名前空間名を取得します。
*/
@autobind
public getNsPrefix(): string {
if (this.parent == null || this.nsName == null) return '';
return this.parent.getNsPrefix() + this.nsName + ':';
}
/**
* 指定した名前の変数が存在するか判定します
* @param name - 変数名
*/
@autobind
public exists(name: string): boolean {
for (const layer of this.layerdStates) {
if (layer.has(name)) {
this.log('exists', { var: name });
return true;
}
}
this.log('not exists', { var: name });
return false;
}
/**
* 現在のスコープに存在する全ての変数を取得します
*/
@autobind
public getAll(): Map<string, Variable> {
const vars = this.layerdStates.reduce((arr, layer) => {
return [...arr, ...layer];
}, [] satisfies [string, Variable][]);
return new Map(vars);
}
/**
* 指定した名前の変数を現在のスコープに追加します。名前空間である場合は接頭辞を付して親のスコープにも追加します
* @param name - 変数名
* @param val - 初期値
*/
@autobind
public add(name: string, variable: Variable): void {
this.log('add', { var: name, val: variable });
const states = this.layerdStates[0];
if (states.has(name)) {
throw new AiScriptRuntimeError(
`Variable '${name}' already exists in scope '${this.name}'`,
{ scope: this.layerdStates });
}
states.set(name, variable);
if (this.parent == null) this.onUpdated(name, variable.value);
else if (this.nsName != null) this.parent.add(this.nsName + ':' + name, variable);
}
/**
* 指定した名前の変数に値を再代入します
* @param name - 変数名
* @param val - 値
*/
@autobind
public assign(name: string, val: Value): void {
let i = 1;
for (const layer of this.layerdStates) {
const variable = layer.get(name);
if (variable != null) {
if (!variable.isMutable) {
throw new AiScriptRuntimeError(`Cannot assign to an immutable variable ${name}.`);
}
variable.value = val;
this.log('assign', { var: name, val: val });
if (i === this.layerdStates.length) this.onUpdated(name, val);
return;
}
i++;
}
throw new AiScriptRuntimeError(
`No such variable '${name}' in scope '${this.name}'`,
{ scope: this.layerdStates });
}
}