Skip to content

Commit 244ec0e

Browse files
committed
support lexical scope by function
code4fukui#13
1 parent acb8d8a commit 244ec0e

File tree

3 files changed

+101
-47
lines changed

3 files changed

+101
-47
lines changed

DNCL3.js

+87-47
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,40 @@ class Return {
3434
class Break {
3535
}
3636

37+
class Scope {
38+
constructor(parent = null) {
39+
this.vars = {};
40+
this.parent = parent;
41+
}
42+
isDefined(name) {
43+
for (let scope = this; scope; scope = scope.parent) {
44+
if (scope.vars[name] !== undefined) {
45+
return true;
46+
}
47+
}
48+
return false;
49+
}
50+
getVar(name) {
51+
for (let scope = this; scope; scope = scope.parent) {
52+
if (scope.vars[name] !== undefined) {
53+
return scope.vars[name];
54+
}
55+
}
56+
throw new Error("定義されていない変数 " + name + " が使われました");
57+
}
58+
setVar(name, o, forcelocal = false) {
59+
if (!forcelocal) {
60+
for (let scope = this; scope; scope = scope.parent) {
61+
if (scope.vars[name] !== undefined) {
62+
scope.vars[name] = o;
63+
return;
64+
}
65+
}
66+
}
67+
this.vars[name] = o;
68+
}
69+
}
70+
3771
export class DNCL3 {
3872
constructor(s, callbackoutput) {
3973
this.s = s.replaceAll("\r", "");
@@ -811,55 +845,56 @@ export class DNCL3 {
811845
if (c.type == "eof") break;
812846
}
813847
}
814-
getArrayIndex(ast) {
815-
const prop = this.calcExpression(ast);
848+
getArrayIndex(ast, scope) {
849+
const prop = this.calcExpression(ast, scope);
816850
if (prop < 0 || typeof prop == "string" && parseInt(prop).toString() != prop) {
817851
throw new Error("配列には0または正の整数のみ指定可能です");
818852
}
819853
return prop;
820854
}
821-
runBlock(ast) {
855+
runBlock(ast, scope) {
822856
const body = ast.type == "BlockStatement" ||
823857
ast.type == "Program" ? ast.body :
824858
ast.type == "SequenceExpression" ? ast.expressions : [ast];
825859
for (const cmd of body) {
826860
//console.log(cmd)
827861
if (cmd.type == "ExpressionStatement") {
828-
this.runBlock(cmd.expression);
862+
this.runBlock(cmd.expression, scope);
829863
} else if (cmd.type == "AssignmentExpression") {
830864
const name = this.getVarName(cmd.left);
831-
if (this.vars[name] !== undefined && isConstantName(name)) {
865+
if (scope.isDefined(name) && isConstantName(name)) {
832866
throw new Error("定数には再代入できません");
833867
}
834868
if (cmd.left.type == "Identifier") {
835-
this.vars[name] = this.calcExpression(cmd.right);
869+
scope.setVar(name, this.calcExpression(cmd.right, scope));
836870
} else if (cmd.left.type == "MemberExpression") {
837-
if (this.vars[name] === undefined) {
838-
this.vars[name] = [];
871+
if (!scope.isDefined(name)) {
872+
scope.setVar(name, []);
839873
}
840-
const idx = this.getArrayIndex(cmd.left.property);
841-
this.vars[name][idx] = this.calcExpression(cmd.right);
874+
const idx = this.getArrayIndex(cmd.left.property, scope);
875+
scope.getVar(name)[idx] = this.calcExpression(cmd.right, scope);
842876
} else {
843877
throw new Error("非対応の type です " + cmd.left.type);
844878
}
845879
} else if (cmd.type == "CallExpression") {
846880
const name = cmd.callee.name;
847881
if (name == "print") {
848-
this.output(cmd.arguments.map(i => this.calcExpression(i)).join(" "));
882+
this.output(cmd.arguments.map(i => this.calcExpression(i, scope)).join(" "));
849883
} else {
850-
if (this.vars[name] === undefined) {
884+
if (!scope.isDefined(name)) {
851885
throw new Error("定義されていない関数 " + name + " が使われました");
852886
}
853-
const func = this.vars[name];
887+
const func = scope.getVar(name);
854888
if (ast.arguments.length != func.params.length) {
855889
throw new Error("引数の数が合っていません");
856890
}
891+
const scope2 = new Scope(scope);
857892
for (let i = 0; i < ast.arguments.length; i++) {
858893
const localvarname = func.params[i].name;
859-
this.vars[localvarname] = this.calcExpression(ast.arguments[i]);
894+
scope2.setVar(localvarname, this.calcExpression(ast.arguments[i], scope), true);
860895
}
861896
try {
862-
this.runBlock(func.body);
897+
this.runBlock(func.body, scope2);
863898
//throw new Error("関数が値を返しませんでした");
864899
} catch (e) {
865900
if (e instanceof Return) {
@@ -869,18 +904,18 @@ export class DNCL3 {
869904
}
870905
}
871906
} else if (cmd.type == "IfStatement") {
872-
const cond = this.calcExpression(cmd.test);
907+
const cond = this.calcExpression(cmd.test, scope);
873908
if (cond) {
874-
this.runBlock(cmd.consequent);
909+
this.runBlock(cmd.consequent, scope);
875910
} else if (cmd.alternate) {
876-
this.runBlock(cmd.alternate);
911+
this.runBlock(cmd.alternate, scope);
877912
}
878913
} else if (cmd.type == "WhileStatement") {
879914
try {
880915
for (let i = 0;; i++) {
881-
const cond = this.calcExpression(cmd.test);
916+
const cond = this.calcExpression(cmd.test, scope);
882917
if (!cond) break;
883-
this.runBlock(cmd.body);
918+
this.runBlock(cmd.body, scope);
884919
if (i >= MAX_LOOP) {
885920
throw new Error(MAX_LOOP + "回の繰り返し上限に達しました");
886921
}
@@ -893,8 +928,8 @@ export class DNCL3 {
893928
} else if (cmd.type == "DoWhileStatement") {
894929
try {
895930
for (let i = 0;; i++) {
896-
this.runBlock(cmd.body);
897-
const cond = this.calcExpression(cmd.test);
931+
this.runBlock(cmd.body, scope);
932+
const cond = this.calcExpression(cmd.test, scope);
898933
if (!cond) break;
899934
if (i >= MAX_LOOP) {
900935
throw new Error(MAX_LOOP + "回の繰り返し上限に達しました");
@@ -906,13 +941,13 @@ export class DNCL3 {
906941
}
907942
}
908943
} else if (cmd.type == "ForStatement") {
909-
this.runBlock(cmd.init);
944+
this.runBlock(cmd.init, scope);
910945
try {
911946
for (let i = 0;; i++) {
912-
const cond = this.calcExpression(cmd.test);
947+
const cond = this.calcExpression(cmd.test, scope);
913948
if (!cond) break;
914-
this.runBlock(cmd.body);
915-
this.runBlock(cmd.update);
949+
this.runBlock(cmd.body, scope);
950+
this.runBlock(cmd.update, scope);
916951
if (i >= MAX_LOOP) {
917952
throw new Error(MAX_LOOP + "回の繰り返し上限に達しました");
918953
}
@@ -924,12 +959,12 @@ export class DNCL3 {
924959
}
925960
} else if (cmd.type == "FunctionDeclaration") {
926961
const name = cmd.id.name;
927-
if (this.vars[name] !== undefined) {
962+
if (scope.isDefined(name)) {
928963
throw new Error("すでに宣言済みに名前では関数を定義できません");
929964
}
930-
this.vars[name] = cmd;
965+
scope.setVar(name, cmd);
931966
} else if (cmd.type == "ReturnStatement") {
932-
const val = this.calcExpression(cmd.argument);
967+
const val = this.calcExpression(cmd.argument, scope);
933968
throw new Return(val);
934969
} else if (cmd.type == "BreakStatement") {
935970
throw new Break();
@@ -939,7 +974,8 @@ export class DNCL3 {
939974
}
940975
}
941976
run() {
942-
this.runBlock(this.ast);
977+
this.scope = new Scope();
978+
this.runBlock(this.ast, this.scope);
943979
//console.log(this.vars);
944980
}
945981
getVarName(ast) {
@@ -949,30 +985,30 @@ export class DNCL3 {
949985
else throw new Error("非対応の type です " + ast.type);
950986
}
951987
}
952-
calcExpression(ast) {
988+
calcExpression(ast, scope) {
953989
if (ast.type == "Literal") {
954990
return ast.value;
955991
} else if (ast.type == "Identifier") {
956-
if (this.vars[ast.name] === undefined) {
992+
if (!scope.isDefined(ast.name)) {
957993
//console.log("var", this.vars)
958994
throw new Error("初期化されていない変数 " + ast.name + " が使われました");
959995
}
960-
return this.vars[ast.name];
996+
return scope.getVar(ast.name);
961997
} else if (ast.type == "MemberExpression") {
962998
const name = this.getVarName(ast);
963-
if (this.vars[name] === undefined) {
999+
if (!scope.isDefined(name)) {
9641000
throw new Error("初期化されていない配列 " + name + " が使われました");
9651001
}
966-
const idx = this.getArrayIndex(ast.property);
967-
const v = this.vars[name];
1002+
const idx = this.getArrayIndex(ast.property, scope);
1003+
const v = scope.getVar(name);
9681004
if (typeof v == "string") {
9691005
if (idx >= 0 && idx < v.length) return v[idx];
9701006
return "";
9711007
} else {
9721008
return v[idx];
9731009
}
9741010
} else if (ast.type == "UnaryExpression") {
975-
const n = this.calcExpression(ast.argument);
1011+
const n = this.calcExpression(ast.argument, scope);
9761012
if (ast.operator == "not") {
9771013
return !n;
9781014
} else if (ast.operator == "-") {
@@ -981,11 +1017,11 @@ export class DNCL3 {
9811017
throw new Error("対応していない演算子 " + ast.operator + " です");
9821018
}
9831019
} else if (ast.type == "ArrayExpression") {
984-
const ar = ast.elements.map(i => this.calcExpression(i));
1020+
const ar = ast.elements.map(i => this.calcExpression(i, scope));
9851021
return ar;
9861022
} else if (ast.type == "BinaryExpression" || ast.type == "LogicalExpression") {
987-
const n = this.calcExpression(ast.left);
988-
const m = this.calcExpression(ast.right);
1023+
const n = this.calcExpression(ast.left, scope);
1024+
const m = this.calcExpression(ast.right, scope);
9891025
const op = ast.operator;
9901026
if (typeof n == "string" || typeof m == "string") {
9911027
if (op != "+" && op != "==" && op != "!=") throw new Error("文字列では使用できない演算子です: " + op);
@@ -1027,26 +1063,27 @@ export class DNCL3 {
10271063
if (ast.arguments.length > 1) {
10281064
throw new Error("引数の数が合っていません");
10291065
}
1030-
const q = ast.arguments.length ? this.calcExpression(ast.arguments[0]) : "入力してください";
1066+
const q = ast.arguments.length ? this.calcExpression(ast.arguments[0], scope) : "入力してください";
10311067
const s = prompt(q);
10321068
if (s == null) return "";
10331069
const f = parseFloat(s);
10341070
if (!isNaN(f) && f.toString() == s) return f;
10351071
return s;
10361072
}
1037-
if (this.vars[name] === undefined) {
1073+
if (!scope.isDefined(name)) {
10381074
throw new Error("定義されていない関数 " + name + " が使われました");
10391075
}
1040-
const func = this.vars[name];
1076+
const func = scope.getVar(name);
10411077
if (ast.arguments.length != func.params.length) {
10421078
throw new Error("引数の数が合っていません");
10431079
}
1080+
const scope2 = new Scope(scope);
10441081
for (let i = 0; i < ast.arguments.length; i++) {
10451082
const localvarname = func.params[i].name;
1046-
this.vars[localvarname] = this.calcExpression(ast.arguments[i]);
1083+
scope2.setVar(localvarname, this.calcExpression(ast.arguments[i], scope), true);
10471084
}
10481085
try {
1049-
this.runBlock(func.body);
1086+
this.runBlock(func.body, scope2);
10501087
throw new Error("関数が値を返しませんでした");
10511088
} catch (e) {
10521089
if (e instanceof Return) {
@@ -1059,11 +1096,14 @@ export class DNCL3 {
10591096
}
10601097
}
10611098
getVars() {
1099+
const vars = this.scope.vars;
10621100
const res = {};
1063-
for (const name in this.vars) {
1064-
const o = this.vars[name];
1101+
for (const name in vars) {
1102+
const o = vars[name];
10651103
if (typeof o == "object" && o.type == "FunctionDeclaration") {
10661104
res[name] = "[function]";
1105+
} else if (typeof o == "function") {
1106+
res[name] = "[function in js]";
10671107
} else {
10681108
res[name] = o;
10691109
}

examples.csv

+1
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ func.dncl,16. 関数の定義と呼び出し function
1818
funcja.dncl,17. 日本語の関数の定義と呼び出し function
1919
printbin.dncl,18. 二進法で表示
2020
geo3x3.dncl,19. Geo3x3のエンコード、デコード
21+
scope.dncl,20. 変数のスコープ

examples/scope.dncl

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# 20. 変数のスコープ
2+
a = 3
3+
b = 100
4+
function func1(a, b) {
5+
a = a + 1
6+
function func2(a) {
7+
a = a * 2
8+
return a
9+
}
10+
print a + b + func2(1000)
11+
}
12+
func1(10, 3)
13+
print a, b

0 commit comments

Comments
 (0)