Repository: syuilo/aiscript
Pull request #213 (2022-09-04 17:00:37)

refactor errors
<!-- ℹ お読みください
PRありがとうございます！ PRを作成する前に、以下をご確認ください:
- 可能であればタイトルに、以下で示すようなPRの種類が分かるキーワードをプリフィクスしてください。
  - fix / refactor / feat / enhance / perf / chore
  - また、PRの粒度が適切であることを確認してください。ひとつのPRに複数の種類の変更や関心を含めることは避けてください。
- このPRによって解決されるIssueがある場合は、そのIssueへの参照を本文内に含めてください。
- CHANGELOG.mdに変更点を追記してください。リファクタリングなど、利用者に影響を与えない変更についてはこの限りではありません。
- この変更により新たに作成、もしくは更新すべきドキュメントがないか確認してください。
- 機能追加やバグ修正をした場合は、可能であればテストケースを追加してください。
- テスト、Lintが通っていることを予め確認してください。
  - `npm run test`、`npm run lint`でぞれぞれ実施可能です
- `npm run api`を実行してAPIレポートを更新し、差分がある場合はコミットしてください。
ご協力ありがとうございます🤗
-->
<!-- ℹ README
Thank you for your PR! Before creating a PR, please check the following:
- If possible, prefix the title with a keyword that identifies the type of this PR, as shown below.
  - fix / refactor / feat / enhance / perf / chore
  - Also, make sure that the granularity of this PR is appropriate. Please do not include more than one type of change or interest in a single PR.
- If there is an Issue which will be resolved by this PR, please include a reference to the Issue in the text.
- Please add the summary of the changes to CHANGELOG.md. However, this is not necessary for changes that do not affect the users, such as refactoring.
- Check if there are any documents that need to be created or updated due to this change.
- If you have added a feature or fixed a bug, please add a test case if possible.
- Please make sure that tests and Lint are passed in advance.
  - You can run it with `npm run test` and `npm run lint`.
- Run `npm run api` to update the API report and commit it if there are any diffs.
Thanks for your cooperation 🤗
-->

# What
<!-- このPRで何をしたのか？ どう変わるのか？ -->
<!-- What did you do with this PR? How will it change things? -->
発生するエラーをRuntimeErrorとSyntaxErrorの2つに整理。これらは抽象クラスAiScriptErrorを継承します。

# Why
<!-- なぜそうするのか？ どういう意図なのか？ 何が困っているのか？ -->
<!-- Why do you do it? What are your intentions? What is the problem? -->

# Additional info (optional)
<!-- テスト観点など -->
<!-- Test perspective, etc -->


codecov[bot]: # [Codecov](https://codecov.io/gh/syuilo/aiscript/pull/213?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo) Report
Base: **86.53**% // Head: **86.47**% // Decreases project coverage by **`-0.05%`** :warning:
> Coverage data is based on head [(`d077a3d`)](https://codecov.io/gh/syuilo/aiscript/pull/213?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo) compared to base [(`508407d`)](https://codecov.io/gh/syuilo/aiscript/commit/508407d8ae751e359b5253e44cf48b32f47389ca?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo).
> Patch coverage: 59.67% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##           master     #213      +/-   ##
==========================================
- Coverage   86.53%   86.47%   -0.06%     
==========================================
  Files          20       19       -1     
  Lines        2770     2758      -12     
  Branches      431      430       -1     
==========================================
- Hits         2397     2385      -12     
  Misses        366      366              
  Partials        7        7              
```


| [Impacted Files](https://codecov.io/gh/syuilo/aiscript/pull/213?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo) | Coverage Δ | |
|---|---|---|
| [src/parser/plugins/validate-type.ts](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo#diff-c3JjL3BhcnNlci9wbHVnaW5zL3ZhbGlkYXRlLXR5cGUudHM=) | `100.00% <ø> (ø)` | |
| [src/interpreter/util.ts](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo#diff-c3JjL2ludGVycHJldGVyL3V0aWwudHM=) | `46.71% <14.28%> (ø)` | |
| [src/interpreter/index.ts](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo#diff-c3JjL2ludGVycHJldGVyL2luZGV4LnRz) | `90.25% <28.57%> (ø)` | |
| [src/parser/plugins/set-attribute.ts](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo#diff-c3JjL3BhcnNlci9wbHVnaW5zL3NldC1hdHRyaWJ1dGUudHM=) | `81.25% <33.33%> (ø)` | |
| [src/interpreter/scope.ts](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo#diff-c3JjL2ludGVycHJldGVyL3Njb3BlLnRz) | `90.51% <50.00%> (ø)` | |
| [src/parser/plugins/infix-to-fncall.ts](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo#diff-c3JjL3BhcnNlci9wbHVnaW5zL2luZml4LXRvLWZuY2FsbC50cw==) | `98.34% <50.00%> (ø)` | |
| [src/type.ts](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo#diff-c3JjL3R5cGUudHM=) | `57.22% <50.00%> (ø)` | |
| [src/interpreter/lib/std.ts](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo#diff-c3JjL2ludGVycHJldGVyL2xpYi9zdGQudHM=) | `69.08% <66.66%> (ø)` | |
| [src/parser/index.ts](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo#diff-c3JjL3BhcnNlci9pbmRleC50cw==) | `82.95% <66.66%> (ø)` | |
| [src/error.ts](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo#diff-c3JjL2Vycm9yLnRz) | `100.00% <100.00%> (ø)` | |
| ... and [2 more](https://codecov.io/gh/syuilo/aiscript/pull/213/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/syuilo/aiscript/pull/213?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=syuilo).


Patch:
diff --git a/etc/aiscript.api.md b/etc/aiscript.api.md
index db5de04..6b6e728 100644
--- a/etc/aiscript.api.md
+++ b/etc/aiscript.api.md
@@ -23,7 +23,7 @@ type AddAssign_2 = NodeBase_2 & {
 };
 
 // @public (undocumented)
-export class AiScriptError extends Error {
+abstract class AiScriptError extends Error {
     constructor(message: string, info?: any);
     // (undocumented)
     info?: any;
@@ -233,6 +233,15 @@ type Each_2 = NodeBase_2 & {
 // @public (undocumented)
 function eq(a: Value, b: Value): boolean;
 
+declare namespace errors {
+    export {
+        AiScriptError,
+        SyntaxError_2 as SyntaxError,
+        RuntimeError
+    }
+}
+export { errors }
+
 // @public (undocumented)
 function expectAny(val: Value | null | undefined): asserts val is Value;
 
@@ -658,6 +667,11 @@ type Return_2 = NodeBase_2 & {
     expr: Expression_2;
 };
 
+// @public (undocumented)
+class RuntimeError extends AiScriptError {
+    constructor(message: string, info?: any);
+}
+
 // @public (undocumented)
 export class Scope {
     constructor(layerdStates?: Scope['layerdStates'], parent?: Scope, name?: Scope['name']);
@@ -676,11 +690,6 @@ export class Scope {
     };
 }
 
-// @public (undocumented)
-export class SemanticError extends Error {
-    constructor(details?: string);
-}
-
 // @public (undocumented)
 type Statement = Definition | Return | Each | For | Loop | Break | Continue | Assign | AddAssign | SubAssign;
 
@@ -721,10 +730,9 @@ type SubAssign_2 = NodeBase_2 & {
 };
 
 // @public (undocumented)
-class SyntaxError_2 extends Error {
-    constructor(details?: string);
+class SyntaxError_2 extends AiScriptError {
+    constructor(message: string, info?: any);
 }
-export { SyntaxError_2 as SyntaxError }
 
 // @public (undocumented)
 type Tmpl = NodeBase & {
diff --git a/src/error.ts b/src/error.ts
index a56094b..97e93cc 100644
--- a/src/error.ts
+++ b/src/error.ts
@@ -1,19 +1,26 @@
-export class SyntaxError extends Error {
-	constructor(details?: string) {
-		let message = 'Syntax error.';
-		if (details) {
-			message += ` ${details}`;
-		}
+export abstract class AiScriptError extends Error {
+	public info?: any;
+
+	constructor(message: string, info?: any) {
 		super(message);
+
+		this.info = info;
+
+		// Maintains proper stack trace for where our error was thrown (only available on V8)
+		if (Error.captureStackTrace) {
+			Error.captureStackTrace(this, AiScriptError);
+		}
 	}
 }
 
-export class SemanticError extends Error {
-	constructor(details?: string) {
-		let message = 'Semantic error.';
-		if (details) {
-			message += ` ${details}`;
-		}
-		super(message);
+export class SyntaxError extends AiScriptError {
+	constructor(message: string, info?: any) {
+		super(message, info);
+	}
+}
+
+export class RuntimeError extends AiScriptError {
+	constructor(message: string, info?: any) {
+		super(message, info);
 	}
 }
diff --git a/src/index.ts b/src/index.ts
index 2dc4195..292cc54 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -6,18 +6,15 @@ import { Interpreter } from './interpreter/index';
 import { Scope } from './interpreter/scope';
 import * as utils from './interpreter/util';
 import * as values from './interpreter/value';
-import { AiScriptError } from './interpreter/error';
 import { Parser, ParserPlugin, PluginType } from './parser';
-import { SyntaxError, SemanticError } from './error';
+import * as errors from './error';
 import * as N from './node';
 export { Interpreter };
 export { Scope };
 export { utils };
 export { values };
-export { AiScriptError };
 export { Parser };
 export { ParserPlugin };
 export { PluginType };
-export { SyntaxError };
-export { SemanticError };
+export { errors };
 export { N };
diff --git a/src/interpreter/error.ts b/src/interpreter/error.ts
deleted file mode 100644
index f110baa..0000000
--- a/src/interpreter/error.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-export class AiScriptError extends Error {
-	public info?: any;
-
-	constructor(message: string, info?: any) {
-		super(message);
-
-		this.info = info;
-
-		// Maintains proper stack trace for where our error was thrown (only available on V8)
-		if (Error.captureStackTrace) {
-			Error.captureStackTrace(this, AiScriptError);
-		}
-	}
-}
diff --git a/src/interpreter/index.ts b/src/interpreter/index.ts
index 9b10d4c..46cd526 100644
--- a/src/interpreter/index.ts
+++ b/src/interpreter/index.ts
@@ -5,7 +5,7 @@
 import autobind from 'autobind-decorator';
 import * as N from '../node';
 import { Scope } from './scope';
-import { AiScriptError } from './error';
+import { RuntimeError } from '../error';
 import { std } from './lib/std';
 import { assertNumber, assertString, assertFunction, assertBoolean, assertObject, assertArray, eq, isObject, isArray, isString, expectAny, isNumber } from './util';
 import { Value, NULL, RETURN, unWrapRet, FN_NATIVE, BOOL, NUM, STR, ARR, OBJ, FN, VFn, BREAK, CONTINUE } from './value';
@@ -180,7 +180,7 @@ export class Interpreter {
 		this.log('node', { node: node });
 		this.stepCount++;
 		if (this.opts.maxStep && this.stepCount > this.opts.maxStep) {
-			throw new AiScriptError('max step exceeded');
+			throw new RuntimeError('max step exceeded');
 		}
 
 		switch (node.type) {
@@ -322,7 +322,7 @@ export class Interpreter {
 					assignee.value.set(node.dest.name, v);
 					return NULL;
 				} else {
-					throw new AiScriptError('The left-hand side of an assignment expression must be a variable or a property/index access.');
+					throw new RuntimeError('The left-hand side of an assignment expression must be a variable or a property/index access.');
 				}
 			}
 
@@ -374,22 +374,22 @@ export class Interpreter {
 					if (Object.hasOwn(PRIMITIVE_PROPS.num, node.name)) {
 						return PRIMITIVE_PROPS.num[node.name as keyof typeof PRIMITIVE_PROPS['num']](target);
 					} else {
-						throw new AiScriptError(`No such prop (${node.name}) in ${target.type}.`);
+						throw new RuntimeError(`No such prop (${node.name}) in ${target.type}.`);
 					}
 				} else if (isString(target)) {
 					if (Object.hasOwn(PRIMITIVE_PROPS.str, node.name)) {
 						return PRIMITIVE_PROPS.str[node.name as keyof typeof PRIMITIVE_PROPS['str']](target);
 					} else {
-						throw new AiScriptError(`No such prop (${node.name}) in ${target.type}.`);
+						throw new RuntimeError(`No such prop (${node.name}) in ${target.type}.`);
 					}
 				} else if (isArray(target)) {
 					if (Object.hasOwn(PRIMITIVE_PROPS.arr, node.name)) {
 						return PRIMITIVE_PROPS.arr[node.name as keyof typeof PRIMITIVE_PROPS['arr']](target);
 					} else {
-						throw new AiScriptError(`No such prop (${node.name}) in ${target.type}.`);
+						throw new RuntimeError(`No such prop (${node.name}) in ${target.type}.`);
 					}
 				} else {
-					throw new AiScriptError(`Cannot read prop (${node.name}) of ${target.type}.`);
+					throw new RuntimeError(`Cannot read prop (${node.name}) of ${target.type}.`);
 				}
 			}
 
diff --git a/src/interpreter/lib/std.ts b/src/interpreter/lib/std.ts
index 959f55d..41be9dc 100644
--- a/src/interpreter/lib/std.ts
+++ b/src/interpreter/lib/std.ts
@@ -4,7 +4,7 @@ import { substring, length, indexOf, toArray } from 'stringz';
 import seedrandom from 'seedrandom';
 import { Value, NUM, STR, FN_NATIVE, FALSE, TRUE, VArr, ARR, NULL, BOOL, OBJ } from '../value';
 import { assertNumber, assertString, assertArray, assertBoolean, valToJs, jsToVal, assertFunction, assertObject, eq, expectAny } from '../util';
-import { AiScriptError } from '../error';
+import { RuntimeError } from '../../error';
 
 export const std: Record<string, Value> = {
 	'help': STR('SEE: https://github.com/syuilo/aiscript/blob/master/docs/get-started.md'),
@@ -65,7 +65,7 @@ export const std: Record<string, Value> = {
 		assertNumber(a);
 		assertNumber(b);
 		const res = a.value / b.value;
-		if (isNaN(res)) throw new AiScriptError('Invalid operation.');
+		if (isNaN(res)) throw new RuntimeError('Invalid operation.');
 		return NUM(res);
 	}),
 
@@ -198,7 +198,7 @@ export const std: Record<string, Value> = {
 	'Math:sqrt': FN_NATIVE(([v]) => {
 		assertNumber(v);
 		const res = Math.sqrt(v.value);
-		if (isNaN(res)) throw new AiScriptError('Invalid operation.');
+		if (isNaN(res)) throw new RuntimeError('Invalid operation.');
 		return NUM(res);
 	}),
 
diff --git a/src/interpreter/scope.ts b/src/interpreter/scope.ts
index df452d5..4426308 100644
--- a/src/interpreter/scope.ts
+++ b/src/interpreter/scope.ts
@@ -1,5 +1,5 @@
 import autobind from 'autobind-decorator';
-import { AiScriptError } from './error';
+import { RuntimeError } from '../error';
 import { Value } from './value';
 
 export class Scope {
@@ -55,7 +55,7 @@ export class Scope {
 			}
 		}
 
-		throw new AiScriptError(
+		throw new RuntimeError(
 			`No such variable '${name}' in scope '${this.name}'`, {
 				scope: this.layerdStates,
 			});
@@ -81,7 +81,7 @@ export class Scope {
 	public add(name: string, val: Value) {
 		this.log('add', { var: name, val: val });
 		if (this.layerdStates[0].has(name)) {
-			throw new AiScriptError(
+			throw new RuntimeError(
 				`Variable '${name}' is alerady exists in scope '${this.name}'`, {
 					scope: this.layerdStates,
 				});
@@ -108,7 +108,7 @@ export class Scope {
 			i++;
 		}
 
-		throw new AiScriptError(
+		throw new RuntimeError(
 			`No such variable '${name}' in scope '${this.name}'`, {
 				scope: this.layerdStates,
 			});
diff --git a/src/interpreter/util.ts b/src/interpreter/util.ts
index 4281ff1..ca26165 100644
--- a/src/interpreter/util.ts
+++ b/src/interpreter/util.ts
@@ -1,63 +1,63 @@
 import { Value, VStr, VNum, VBool, VFn, VObj, VArr, STR, NUM, ARR, OBJ, NULL, BOOL } from './value';
-import { AiScriptError } from './error';
+import { RuntimeError } from '../error';
 
 export function expectAny(val: Value | null | undefined): asserts val is Value {
 	if (val == null) {
-		throw new AiScriptError('Expect anything, but got nothing.');
+		throw new RuntimeError('Expect anything, but got nothing.');
 	}
 }
 
 export function assertBoolean(val: Value | null | undefined): asserts val is VBool {
 	if (val == null) {
-		throw new AiScriptError('Expect boolean, but got nothing.');
+		throw new RuntimeError('Expect boolean, but got nothing.');
 	}
 	if (val.type !== 'bool') {
-		throw new AiScriptError(`Expect boolean, but got ${val.type}.`);
+		throw new RuntimeError(`Expect boolean, but got ${val.type}.`);
 	}
 }
 
 export function assertFunction(val: Value | null | undefined): asserts val is VFn {
 	if (val == null) {
-		throw new AiScriptError('Expect function, but got nothing.');
+		throw new RuntimeError('Expect function, but got nothing.');
 	}
 	if (val.type !== 'fn') {
-		throw new AiScriptError(`Expect function, but got ${val.type}.`);
+		throw new RuntimeError(`Expect function, but got ${val.type}.`);
 	}
 }
 
 export function assertString(val: Value | null | undefined): asserts val is VStr {
 	if (val == null) {
-		throw new AiScriptError('Expect string, but got nothing.');
+		throw new RuntimeError('Expect string, but got nothing.');
 	}
 	if (val.type !== 'str') {
-		throw new AiScriptError(`Expect string, but got ${val.type}.`);
+		throw new RuntimeError(`Expect string, but got ${val.type}.`);
 	}
 }
 
 export function assertNumber(val: Value | null | undefined): asserts val is VNum {
 	if (val == null) {
-		throw new AiScriptError('Expect number, but got nothing.');
+		throw new RuntimeError('Expect number, but got nothing.');
 	}
 	if (val.type !== 'num') {
-		throw new AiScriptError(`Expect number, but got ${val.type}.`);
+		throw new RuntimeError(`Expect number, but got ${val.type}.`);
 	}
 }
 
 export function assertObject(val: Value | null | undefined): asserts val is VObj {
 	if (val == null) {
-		throw new AiScriptError('Expect object, but got nothing.');
+		throw new RuntimeError('Expect object, but got nothing.');
 	}
 	if (val.type !== 'obj') {
-		throw new AiScriptError(`Expect object, but got ${val.type}.`);
+		throw new RuntimeError(`Expect object, but got ${val.type}.`);
 	}
 }
 
 export function assertArray(val: Value | null | undefined): asserts val is VArr {
 	if (val == null) {
-		throw new AiScriptError('Expect array, but got nothing.');
+		throw new RuntimeError('Expect array, but got nothing.');
 	}
 	if (val.type !== 'arr') {
-		throw new AiScriptError(`Expect array, but got ${val.type}.`);
+		throw new RuntimeError(`Expect array, but got ${val.type}.`);
 	}
 }
 
diff --git a/src/parser/index.ts b/src/parser/index.ts
index d4c0bdd..580da65 100644
--- a/src/parser/index.ts
+++ b/src/parser/index.ts
@@ -1,6 +1,6 @@
-import * as aiscript from '..';
 import * as N from '../node';
 import * as Ast from './node';
+import { SyntaxError } from '../error';
 import * as parser from './parser.js';
 
 import { validateKeyword } from './plugins/validate-keyword';
@@ -65,9 +65,9 @@ export class Parser {
 		} catch (e) {
 			if (e.location) {
 				if (e.expected) {
-					throw new aiscript.SyntaxError(`Line ${e.location.start.line}:${e.location.start.column}`);
+					throw new SyntaxError(`Parsing error. (Line ${e.location.start.line}:${e.location.start.column})`);
 				} else {
-					throw new aiscript.SyntaxError(`${e.message} Line ${e.location.start.line}:${e.location.start.column}`);
+					throw new SyntaxError(`${e.message} (Line ${e.location.start.line}:${e.location.start.column})`);
 				}
 			}
 			throw e;
diff --git a/src/parser/plugins/infix-to-fncall.ts b/src/parser/plugins/infix-to-fncall.ts
index 587c038..4ea672b 100644
--- a/src/parser/plugins/infix-to-fncall.ts
+++ b/src/parser/plugins/infix-to-fncall.ts
@@ -1,6 +1,6 @@
 import * as Ast from '../node';
 import { visitNode } from '../visit';
-import { SemanticError } from '../../error';
+import { SyntaxError } from '../../error';
 
 /**
  * 中置演算子式を表す木
@@ -97,7 +97,7 @@ function transform(node: Ast.Infix): Ast.Node {
 	const infos = node.operators.map(op => {
 		const info = infoTable[op];
 		if (info == null) {
-			throw new SemanticError(`No such operator: ${op}.`);
+			throw new SyntaxError(`No such operator: ${op}.`);
 		}
 		return info;
 	});
diff --git a/src/parser/plugins/set-attribute.ts b/src/parser/plugins/set-attribute.ts
index 30905a4..4680a6d 100644
--- a/src/parser/plugins/set-attribute.ts
+++ b/src/parser/plugins/set-attribute.ts
@@ -1,4 +1,4 @@
-import * as aiscript from '../..';
+import { SyntaxError } from '../../error';
 import * as Ast from '../node';
 
 export function setAttribute(node: Ast.Expression[]): Ast.Expression[]
@@ -25,7 +25,7 @@ export function setAttribute(nodes: Ast.Node[]): Ast.Node[] {
 			result.push(node);
 		} else {
 			if (stockedAttrs.length > 0) {
-				throw new aiscript.SemanticError('invalid attribute.');
+				throw new SyntaxError('invalid attribute.');
 			}
 			switch (node.type) {
 				case 'fn': {
@@ -41,7 +41,7 @@ export function setAttribute(nodes: Ast.Node[]): Ast.Node[] {
 		}
 	}
 	if (stockedAttrs.length > 0) {
-		throw new aiscript.SemanticError('invalid attribute.');
+		throw new SyntaxError('invalid attribute.');
 	}
 
 	return result;
diff --git a/src/parser/plugins/validate-keyword.ts b/src/parser/plugins/validate-keyword.ts
index b67e4a2..cb412ea 100644
--- a/src/parser/plugins/validate-keyword.ts
+++ b/src/parser/plugins/validate-keyword.ts
@@ -1,4 +1,4 @@
-import * as aiscript from '../..';
+import { SyntaxError } from '../../error';
 import * as Ast from '../node';
 import { visitNode } from '../visit';
 
@@ -42,7 +42,7 @@ const reservedWord = [
 ];
 
 function throwReservedWordError(name: string) {
-	throw new aiscript.SemanticError(`Reserved word "${name}" cannot be used as variable name.`);
+	throw new SyntaxError(`Reserved word "${name}" cannot be used as variable name.`);
 }
 
 function validateNode(node: Ast.Node): Ast.Node {
diff --git a/src/parser/plugins/validate-type.ts b/src/parser/plugins/validate-type.ts
index a8d609b..99a9845 100644
--- a/src/parser/plugins/validate-type.ts
+++ b/src/parser/plugins/validate-type.ts
@@ -1,5 +1,3 @@
-import * as aiscript from '../..';
-import { SemanticError } from '../../error';
 import * as Ast from '../node';
 import { getTypeBySource } from '../../type';
 import { visitNode } from '../visit';
diff --git a/src/type.ts b/src/type.ts
index 99a7b57..42ce343 100644
--- a/src/type.ts
+++ b/src/type.ts
@@ -1,4 +1,4 @@
-import { SemanticError } from './error';
+import { SyntaxError } from './error';
 import { Loc } from './node';
 
 // Type source (AST)
@@ -165,7 +165,7 @@ export function getTypeBySource(typeSource: TypeSource): Type {
 				return T_GENERIC(typeSource.name, [innerType]);
 			}
 		}
-		throw new SemanticError(`Unknown type: '${ getTypeNameBySource(typeSource) }'`);
+		throw new SyntaxError(`Unknown type: '${ getTypeNameBySource(typeSource) }'`);
 	} else {
 		const argTypes = typeSource.args.map(arg => getTypeBySource(arg));
 		return T_FN(argTypes, getTypeBySource(typeSource.result));


Test patch:
diff --git a/test/index.ts b/test/index.ts
index ee78807..c9f02da 100644
--- a/test/index.ts
+++ b/test/index.ts
@@ -4,11 +4,8 @@
  */
 
 import * as assert from 'assert';
-import { Parser, utils } from '../src';
-import { Interpreter } from '../src/interpreter';
-import { AiScriptError } from '../src/interpreter/error';
+import { Parser, Interpreter, utils, errors, N } from '../src';
 import { NUM, STR, NULL, ARR, OBJ, BOOL, TRUE, FALSE } from '../src/interpreter/value';
-import * as N from '../src/node';
 
 const exe = (program: string): Promise<any> => new Promise((ok, err) => {
 	const aiscript = new Interpreter({}, {
@@ -958,7 +955,7 @@ describe('Function call', () => {
 			<: Core:eq(1)
 			`);
 		} catch (e) {
-			assert.equal(e instanceof AiScriptError, true);
+			assert.equal(e instanceof errors.RuntimeError, true);
 			return;
 		}
 		assert.fail();
@@ -2241,7 +2238,7 @@ describe('Security', () => {
 			`);
 			assert.fail();
 		} catch (e) {
-			assert.equal(e instanceof AiScriptError, true);
+			assert.equal(e instanceof errors.RuntimeError, true);
 		}
 
 		try {
@@ -2250,7 +2247,7 @@ describe('Security', () => {
 			`);
 			assert.fail();
 		} catch (e) {
-			assert.equal(e instanceof AiScriptError, true);
+			assert.equal(e instanceof errors.RuntimeError, true);
 		}
 
 		try {
@@ -2259,7 +2256,7 @@ describe('Security', () => {
 			`);
 			assert.fail();
 		} catch (e) {
-			assert.equal(e instanceof AiScriptError, true);
+			assert.equal(e instanceof errors.RuntimeError, true);
 		}
 	});
 
@@ -2293,7 +2290,7 @@ describe('Security', () => {
 			`);
 			assert.fail();
 		} catch (e) {
-			assert.equal(e instanceof AiScriptError, true);
+			assert.equal(e instanceof errors.RuntimeError, true);
 		}
 
 		try {
@@ -2302,7 +2299,7 @@ describe('Security', () => {
 			`);
 			assert.fail();
 		} catch (e) {
-			assert.equal(e instanceof AiScriptError, true);
+			assert.equal(e instanceof errors.RuntimeError, true);
 		}
 
 		try {
@@ -2311,7 +2308,7 @@ describe('Security', () => {
 			`);
 			assert.fail();
 		} catch (e) {
-			assert.equal(e instanceof AiScriptError, true);
+			assert.equal(e instanceof errors.RuntimeError, true);
 		}
 	});
 });