From 92bf05308edc98d482aaa1a584fec2e760c375dc Mon Sep 17 00:00:00 2001 From: vvakame Date: Sat, 25 Feb 2017 13:28:38 +0900 Subject: [PATCH] add isSeq function to types --- ts/core.ts | 22 +++++++++++----------- ts/step4_if_fn_do.ts | 10 +++++----- ts/step5_tco.ts | 8 ++++---- ts/step6_file.ts | 8 ++++---- ts/step7_quote.ts | 14 +++++++------- ts/step8_macros.ts | 18 +++++++++--------- ts/step9_try.ts | 20 ++++++++++---------- ts/stepA_mal.ts | 20 ++++++++++---------- ts/types.ts | 16 ++++++---------- 9 files changed, 66 insertions(+), 70 deletions(-) diff --git a/ts/core.ts b/ts/core.ts index 353b35a0..5d8ed93f 100644 --- a/ts/core.ts +++ b/ts/core.ts @@ -2,7 +2,7 @@ import * as fs from "fs"; import { readline } from "./node_readline"; -import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals } from "./types"; +import { MalType, MalSymbol, MalFunction, MalNull, MalList, MalVector, MalBoolean, MalNumber, MalString, MalKeyword, MalHashMap, MalAtom, equals, isSeq } from "./types"; import { readStr } from "./reader"; import { prStr } from "./printer"; @@ -244,10 +244,10 @@ export const ns: Map = (() => { }, "sequential?"(v: MalType) { - return new MalBoolean(MalList.is(v) || MalVector.is(v)); + return new MalBoolean(isSeq(v)); }, cons(a: MalType, b: MalType) { - if (!MalList.is(b) && !MalVector.is(b)) { + if (!isSeq(b)) { throw new Error(`unexpected symbol: ${b.type}, expected: list or vector`); } @@ -256,7 +256,7 @@ export const ns: Map = (() => { concat(...args: MalType[]) { const list = args .map(arg => { - if (!MalList.is(arg) && !MalVector.is(arg)) { + if (!isSeq(arg)) { throw new Error(`unexpected symbol: ${arg.type}, expected: list or vector`); } return arg; @@ -266,7 +266,7 @@ export const ns: Map = (() => { return new MalList(list); }, nth(list: MalType, idx: MalType) { - if (!MalList.is(list) && !MalVector.is(list)) { + if (!isSeq(list)) { throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); } if (!MalNumber.is(idx)) { @@ -284,7 +284,7 @@ export const ns: Map = (() => { if (MalNull.is(v)) { return MalNull.instance; } - if (!MalList.is(v) && !MalVector.is(v)) { + if (!isSeq(v)) { throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); } @@ -294,20 +294,20 @@ export const ns: Map = (() => { if (MalNull.is(v)) { return new MalList([]); } - if (!MalList.is(v) && !MalVector.is(v)) { + if (!isSeq(v)) { throw new Error(`unexpected symbol: ${v.type}, expected: list or vector`); } return new MalList(v.list.slice(1)); }, "empty?"(v: MalType): MalBoolean { - if (!MalList.is(v) && !MalVector.is(v)) { + if (!isSeq(v)) { return new MalBoolean(false); } return new MalBoolean(v.list.length === 0); }, count(v: MalType): MalNumber { - if (MalList.is(v) || MalVector.is(v)) { + if (isSeq(v)) { return new MalNumber(v.list.length); } if (MalNull.is(v)) { @@ -321,7 +321,7 @@ export const ns: Map = (() => { } const tail = list[list.length - 1]; - if (!MalList.is(tail) && !MalVector.is(tail)) { + if (!isSeq(tail)) { throw new Error(`unexpected symbol: ${tail.type}, expected: list or vector`); } const args = list.slice(0, -1).concat(tail.list); @@ -331,7 +331,7 @@ export const ns: Map = (() => { if (!MalFunction.is(f)) { throw new Error(`unexpected symbol: ${f.type}, expected: function`); } - if (!MalList.is(list) && !MalVector.is(list)) { + if (!isSeq(list)) { throw new Error(`unexpected symbol: ${list.type}, expected: list or vector`); } diff --git a/ts/step4_if_fn_do.ts b/ts/step4_if_fn_do.ts index 6ec0f9f5..65d81928 100644 --- a/ts/step4_if_fn_do.ts +++ b/ts/step4_if_fn_do.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -60,7 +60,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { let letEnv = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -80,7 +80,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "do": { const [, ...list] = ast.list; const ret = evalAST(new MalList(list), env); - if (!MalList.is(ret) && !MalVector.is(ret)) { + if (!isSeq(ret)) { throw new Error(`unexpected return type: ${ret.type}, expected: list or vector`); } return ret.list[ret.list.length - 1]; @@ -104,7 +104,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, args, binds] = ast.list; - if (!MalList.is(args) && !MalVector.is(args)) { + if (!isSeq(args)) { throw new Error(`unexpected return type: ${args.type}, expected: list or vector`); } const symbols = args.list.map(arg => { @@ -120,7 +120,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/step5_tco.ts b/ts/step5_tco.ts index 4626444a..714c406b 100644 --- a/ts/step5_tco.ts +++ b/ts/step5_tco.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { MalType, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -61,7 +61,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -105,7 +105,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -119,7 +119,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/step6_file.ts b/ts/step6_file.ts index 39f2e9ad..c11fd5e9 100644 --- a/ts/step6_file.ts +++ b/ts/step6_file.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -61,7 +61,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -105,7 +105,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -119,7 +119,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/step7_quote.ts b/ts/step7_quote.ts index e069936a..dfac5e8b 100644 --- a/ts/step7_quote.ts +++ b/ts/step7_quote.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -15,7 +15,7 @@ function quasiquote(ast: MalType): MalType { if (!isPair(ast)) { return new MalList([MalSymbol.get("quote"), ast]); } - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; @@ -23,7 +23,7 @@ function quasiquote(ast: MalType): MalType { return arg2; } if (isPair(arg1)) { - if (!MalList.is(arg1) && !MalVector.is(arg1)) { + if (!isSeq(arg1)) { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; @@ -43,7 +43,7 @@ function quasiquote(ast: MalType): MalType { ]); function isPair(ast: MalType) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } @@ -101,7 +101,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -152,7 +152,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -166,7 +166,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/step8_macros.ts b/ts/step8_macros.ts index 432b1fb0..e599dfe8 100644 --- a/ts/step8_macros.ts +++ b/ts/step8_macros.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction } from "./types"; +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -15,7 +15,7 @@ function quasiquote(ast: MalType): MalType { if (!isPair(ast)) { return new MalList([MalSymbol.get("quote"), ast]); } - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; @@ -23,7 +23,7 @@ function quasiquote(ast: MalType): MalType { return arg2; } if (isPair(arg1)) { - if (!MalList.is(arg1) && !MalVector.is(arg1)) { + if (!isSeq(arg1)) { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; @@ -43,7 +43,7 @@ function quasiquote(ast: MalType): MalType { ]); function isPair(ast: MalType) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } @@ -52,7 +52,7 @@ function quasiquote(ast: MalType): MalType { } function isMacro(ast: MalType, env: Env): boolean { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } const s = ast.list[0]; @@ -74,7 +74,7 @@ function isMacro(ast: MalType, env: Env): boolean { function macroexpand(ast: MalType, env: Env): MalType { while (isMacro(ast, env)) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const s = ast.list[0]; @@ -147,7 +147,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -216,7 +216,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -230,7 +230,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/step9_try.ts b/ts/step9_try.ts index 32dfc162..0f281877 100644 --- a/ts/step9_try.ts +++ b/ts/step9_try.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST } from "./types"; +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -15,7 +15,7 @@ function quasiquote(ast: MalType): MalType { if (!isPair(ast)) { return new MalList([MalSymbol.get("quote"), ast]); } - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; @@ -23,7 +23,7 @@ function quasiquote(ast: MalType): MalType { return arg2; } if (isPair(arg1)) { - if (!MalList.is(arg1) && !MalVector.is(arg1)) { + if (!isSeq(arg1)) { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; @@ -43,7 +43,7 @@ function quasiquote(ast: MalType): MalType { ]); function isPair(ast: MalType) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } @@ -52,7 +52,7 @@ function quasiquote(ast: MalType): MalType { } function isMacro(ast: MalType, env: Env): boolean { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } const s = ast.list[0]; @@ -74,7 +74,7 @@ function isMacro(ast: MalType, env: Env): boolean { function macroexpand(ast: MalType, env: Env): MalType { while (isMacro(ast, env)) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const s = ast.list[0]; @@ -147,7 +147,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -195,7 +195,7 @@ function evalMal(ast: MalType, env: Env): MalType { return evalMal(ast.list[1], env); } catch (e) { const catchBody = ast.list[2]; - if (!MalList.is(catchBody) && !MalVector.is(catchBody)) { + if (!isSeq(catchBody)) { throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`); } const catchSymbol = catchBody.list[0]; @@ -238,7 +238,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -252,7 +252,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/stepA_mal.ts b/ts/stepA_mal.ts index 13b55f43..3f311a3e 100644 --- a/ts/stepA_mal.ts +++ b/ts/stepA_mal.ts @@ -1,6 +1,6 @@ import { readline } from "./node_readline"; -import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST } from "./types"; +import { MalType, MalString, MalBoolean, MalNull, MalList, MalVector, MalHashMap, MalSymbol, MalFunction, isAST, isSeq } from "./types"; import { Env } from "./env"; import * as core from "./core"; import { readStr } from "./reader"; @@ -15,7 +15,7 @@ function quasiquote(ast: MalType): MalType { if (!isPair(ast)) { return new MalList([MalSymbol.get("quote"), ast]); } - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const [arg1, arg2] = ast.list; @@ -23,7 +23,7 @@ function quasiquote(ast: MalType): MalType { return arg2; } if (isPair(arg1)) { - if (!MalList.is(arg1) && !MalVector.is(arg1)) { + if (!isSeq(arg1)) { throw new Error(`unexpected token type: ${arg1.type}, expected: list or vector`); } const [arg11, arg12] = arg1.list; @@ -43,7 +43,7 @@ function quasiquote(ast: MalType): MalType { ]); function isPair(ast: MalType) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } @@ -52,7 +52,7 @@ function quasiquote(ast: MalType): MalType { } function isMacro(ast: MalType, env: Env): boolean { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { return false; } const s = ast.list[0]; @@ -74,7 +74,7 @@ function isMacro(ast: MalType, env: Env): boolean { function macroexpand(ast: MalType, env: Env): MalType { while (isMacro(ast, env)) { - if (!MalList.is(ast) && !MalVector.is(ast)) { + if (!isSeq(ast)) { throw new Error(`unexpected token type: ${ast.type}, expected: list or vector`); } const s = ast.list[0]; @@ -147,7 +147,7 @@ function evalMal(ast: MalType, env: Env): MalType { case "let*": { env = new Env(env); const pairs = ast.list[1]; - if (!MalList.is(pairs) && !MalVector.is(pairs)) { + if (!isSeq(pairs)) { throw new Error(`unexpected token type: ${pairs.type}, expected: list or vector`); } for (let i = 0; i < pairs.list.length; i += 2) { @@ -195,7 +195,7 @@ function evalMal(ast: MalType, env: Env): MalType { return evalMal(ast.list[1], env); } catch (e) { const catchBody = ast.list[2]; - if (!MalList.is(catchBody) && !MalVector.is(catchBody)) { + if (!isSeq(catchBody)) { throw new Error(`unexpected return type: ${catchBody.type}, expected: list or vector`); } const catchSymbol = catchBody.list[0]; @@ -238,7 +238,7 @@ function evalMal(ast: MalType, env: Env): MalType { } case "fn*": { const [, params, bodyAst] = ast.list; - if (!MalList.is(params) && !MalVector.is(params)) { + if (!isSeq(params)) { throw new Error(`unexpected return type: ${params.type}, expected: list or vector`); } const symbols = params.list.map(param => { @@ -252,7 +252,7 @@ function evalMal(ast: MalType, env: Env): MalType { } } const result = evalAST(ast, env); - if (!MalList.is(result) && !MalVector.is(result)) { + if (!isSeq(result)) { throw new Error(`unexpected return type: ${result.type}, expected: list or vector`); } const [f, ...args] = result.list; diff --git a/ts/types.ts b/ts/types.ts index db9bb945..6b23b3d8 100644 --- a/ts/types.ts +++ b/ts/types.ts @@ -3,22 +3,14 @@ import { Env } from "./env"; export type MalType = MalList | MalNumber | MalString | MalNull | MalBoolean | MalSymbol | MalKeyword | MalVector | MalHashMap | MalFunction | MalAtom; export function equals(a: MalType, b: MalType, strict?: boolean): boolean { - if (strict && a.constructor !== b.constructor) { + if (strict && a.type !== b.type) { return false; - } else if ( - (MalList.is(a) || MalVector.is(a)) - && (MalList.is(b) || MalVector.is(b)) - ) { - return listEquals(a.list, b.list); } if (MalNull.is(a) && MalNull.is(b)) { return true; } - if ( - (MalList.is(a) && MalList.is(b)) - || (MalVector.is(a) && MalVector.is(b)) - ) { + if (isSeq(a) && isSeq(b)) { return listEquals(a.list, b.list); } if (MalHashMap.is(a) && MalHashMap.is(b)) { @@ -68,6 +60,10 @@ export function equals(a: MalType, b: MalType, strict?: boolean): boolean { } } +export function isSeq(ast: MalType): ast is MalList | MalVector { + return MalList.is(ast) || MalVector.is(ast); +} + export function isAST(v: MalType): v is MalType { return !!v.type; } -- 2.20.1