diff --git a/core/deno.json b/core/deno.json index f8a4cdf..ca36a81 100644 --- a/core/deno.json +++ b/core/deno.json @@ -8,5 +8,5 @@ }, "name": "@dalbit-yaksok/core", "exports": "./mod.ts", - "version": "0.2.0-alpha.10+20241217.nightly" + "version": "0.2.0-alpha.13+20250104.nightly" } \ No newline at end of file diff --git a/core/mod.ts b/core/mod.ts index 0601027..88f13b8 100644 --- a/core/mod.ts +++ b/core/mod.ts @@ -3,6 +3,7 @@ export { ValueType, ObjectValue, PrimitiveValue } from './value/base.ts' export { ListValue } from './value/list.ts' export { CodeFile } from './type/code-file.ts' +export type { Position } from './type/position.ts' export { yaksok, Runtime } from './runtime/index.ts' export { Scope } from './executer/scope.ts' @@ -11,6 +12,8 @@ export * from './node/index.ts' export { tokenize } from './prepare/tokenize/index.ts' export * from './prepare/tokenize/token.ts' +export { parse } from './prepare/parse/index.ts' + export type { RuntimeConfig } from './runtime/runtime-config.ts' export type { FunctionInvokingParams } from './constant/type.ts' export type { FEATURE_FLAG } from './constant/feature-flags.ts' diff --git a/core/node/IfStatement.ts b/core/node/IfStatement.ts index 4473cf0..a97dece 100644 --- a/core/node/IfStatement.ts +++ b/core/node/IfStatement.ts @@ -4,6 +4,7 @@ import type { Block } from './block.ts' import { isTruthy } from '../executer/internal/isTruthy.ts' import { CallFrame } from '../executer/callFrame.ts' import type { Scope } from '../executer/scope.ts' +import type { Token } from '../prepare/tokenize/token.ts' interface Case { condition?: Evaluable @@ -13,7 +14,7 @@ interface Case { export class IfStatement extends Executable { static override friendlyName = '조건문(만약)' - constructor(public cases: Case[]) { + constructor(public cases: Case[], public override tokens: Token[]) { super() } @@ -46,7 +47,7 @@ export class IfStatement extends Executable { export class ElseStatement extends Executable { static override friendlyName = '조건문(아니면)' - constructor(public body: Block) { + constructor(public body: Block, public override tokens: Token[]) { super() } } @@ -54,7 +55,7 @@ export class ElseStatement extends Executable { export class ElseIfStatement extends Executable { static override friendlyName = '조건문(아니면 만약)' - constructor(public elseIfCase: Case) { + constructor(public elseIfCase: Case, public override tokens: Token[]) { super() } } diff --git a/core/node/base.ts b/core/node/base.ts index 0faa7ef..4605518 100644 --- a/core/node/base.ts +++ b/core/node/base.ts @@ -1,12 +1,13 @@ import type { CallFrame } from '../executer/callFrame.ts' import type { Scope } from '../executer/scope.ts' import { NotDefinedIdentifierError } from '../error/variable.ts' -import { Position } from '../type/position.ts' import { ValueType } from '../value/base.ts' +import { Token } from '../prepare/tokenize/token.ts' export class Node { [key: string]: unknown - position?: Position + tokens?: Token[] + static friendlyName = '노드' toJSON(): object { @@ -44,7 +45,7 @@ export class Evaluable extends Executable { export class Identifier extends Evaluable { static override friendlyName = '식별자' - constructor(public value: string, override position?: Position) { + constructor(public value: string, public override tokens: Token[]) { super() } @@ -57,7 +58,7 @@ export class Identifier extends Evaluable { return Promise.resolve(scope.getVariable(this.value)) } catch (e) { if (e instanceof NotDefinedIdentifierError) { - e.position = this.position + e.position = this.tokens?.[0].position } throw e @@ -65,15 +66,15 @@ export class Identifier extends Evaluable { } } -export class Operator extends Node { +export class Operator extends Node implements OperatorNode { static override friendlyName = '연산자' - constructor(public value?: string, public override position?: Position) { + constructor(public value: string | null, public override tokens: Token[]) { super() } override toPrint(): string { - return this.value ?? 'unknown' + return 'unknown' } call(..._operands: ValueType[]): ValueType { @@ -81,10 +82,18 @@ export class Operator extends Node { } } +export interface OperatorNode { + call(...operands: ValueType[]): ValueType +} + +export type OperatorClass ={ + new (...args: any[]): OperatorNode +} + export class Expression extends Node { static override friendlyName = '표현식' - constructor(public value: string, public override position?: Position) { + constructor(public value: string, public override tokens: Token[]) { super() } diff --git a/core/node/block.ts b/core/node/block.ts index 4860006..9644587 100644 --- a/core/node/block.ts +++ b/core/node/block.ts @@ -1,15 +1,17 @@ import { CallFrame } from '../executer/callFrame.ts' import { CannotParseError } from '../error/index.ts' -import type { Scope } from '../executer/scope.ts' -import { EOL } from './misc.ts' import { Executable, type Node } from './base.ts' +import { EOL } from './misc.ts' + +import type { Token } from '../prepare/tokenize/token.ts' +import type { Scope } from '../executer/scope.ts' export class Block extends Executable { static override friendlyName = '코드 덩어리' children: Node[] - constructor(content: Node[]) { + constructor(content: Node[], public override tokens: Token[]) { super() this.children = content } @@ -24,7 +26,7 @@ export class Block extends Executable { continue } else { throw new CannotParseError({ - position: child.position, + position: child.tokens?.[0].position, resource: { part: child, }, diff --git a/core/node/calculation.ts b/core/node/calculation.ts index 53c6313..20db376 100644 --- a/core/node/calculation.ts +++ b/core/node/calculation.ts @@ -15,12 +15,13 @@ import { OrOperator, PlusOperator, PowerOperator, + RangeOperator, } from './operator.ts' -import { Evaluable, Operator } from './base.ts' -import { RangeOperator } from './list.ts' +import { Evaluable, Operator, OperatorClass } from './base.ts' import { ValueType } from '../value/base.ts' +import type { Token } from '../prepare/tokenize/token.ts' -const OPERATOR_PRECEDENCES: Array<(typeof Operator)[]> = [ +const OPERATOR_PRECEDENCES: OperatorClass[][] = [ [AndOperator, OrOperator], [ EqualOperator, @@ -38,7 +39,7 @@ const OPERATOR_PRECEDENCES: Array<(typeof Operator)[]> = [ export class ValueWithParenthesis extends Evaluable { static override friendlyName = '괄호로 묶인 값' - constructor(public value: Evaluable) { + constructor(public value: Evaluable, public override tokens: Token[]) { super() } @@ -55,7 +56,10 @@ export class ValueWithParenthesis extends Evaluable { export class Formula extends Evaluable { static override friendlyName = '계산식' - constructor(public terms: (Evaluable | Operator)[]) { + constructor( + public terms: (Evaluable | Operator)[], + public override tokens: Token[], + ) { super() } @@ -95,7 +99,7 @@ export class Formula extends Evaluable { const isOperator = term instanceof Operator const isCurrentPrecedence = currentOperators.includes( - term.constructor as typeof Operator, + term.constructor as OperatorClass, ) if (!isOperator || !isCurrentPrecedence) continue diff --git a/core/node/ffi.ts b/core/node/ffi.ts index e9abc85..d86bd7d 100644 --- a/core/node/ffi.ts +++ b/core/node/ffi.ts @@ -3,11 +3,12 @@ import { Executable, Node } from './base.ts' import type { Scope } from '../executer/scope.ts' import type { Position } from '../type/position.ts' +import type { Token } from '../prepare/tokenize/token.ts' export class FFIBody extends Node { static override friendlyName = '번역할 내용' - constructor(public code: string, public override position?: Position) { + constructor(public code: string, public override tokens: Token[]) { super() } } @@ -19,12 +20,15 @@ export class DeclareFFI extends Executable { public body: string public runtime: string - constructor(props: { - name: string - body: string - runtime: string - position?: Position - }) { + constructor( + props: { + name: string + body: string + runtime: string + position?: Position + }, + public override tokens: Token[], + ) { super() this.name = props.name this.body = props.body diff --git a/core/node/function.ts b/core/node/function.ts index cbea0fb..d8fa547 100644 --- a/core/node/function.ts +++ b/core/node/function.ts @@ -7,6 +7,7 @@ import type { Block } from './block.ts' import { FunctionObject } from '../value/function.ts' import { FFIResultTypeIsNotForYaksokError } from '../error/ffi.ts' import { ValueType } from '../value/base.ts' +import type { Token } from '../prepare/tokenize/token.ts' export class DeclareFunction extends Executable { static override friendlyName = '새 약속 만들기' @@ -14,7 +15,10 @@ export class DeclareFunction extends Executable { name: string body: Block - constructor(props: { body: Block; name: string }) { + constructor( + props: { body: Block; name: string }, + public override tokens: Token[], + ) { super() this.name = props.name @@ -35,7 +39,10 @@ export class FunctionInvoke extends Evaluable { public name: string public params: Record - constructor(props: { name: string; params: Record }) { + constructor( + props: { name: string; params: Record }, + public override tokens: Token[], + ) { super() this.name = props.name! @@ -85,6 +92,6 @@ function assertValidReturnValue(node: FunctionInvoke, returnValue: ValueType) { throw new FFIResultTypeIsNotForYaksokError({ ffiName: node.name, value: returnValue, - position: node.position, + position: node.tokens?.[0].position, }) } diff --git a/core/node/index.ts b/core/node/index.ts index 3f1b2a6..5802975 100644 --- a/core/node/index.ts +++ b/core/node/index.ts @@ -12,3 +12,4 @@ export * from './variable.ts' export * from './return.ts' export * from './mention.ts' export * from './ffi.ts' +export * from './listLoop.ts' diff --git a/core/node/list.ts b/core/node/list.ts index 2ca1dc7..2a90360 100644 --- a/core/node/list.ts +++ b/core/node/list.ts @@ -1,22 +1,19 @@ import { TargetIsNotIndexedValueError } from '../error/indexed.ts' -import { - ListIndexTypeError, - RangeEndMustBeNumberError, - RangeStartMustBeLessThanEndError, - RangeStartMustBeNumberError, -} from '../error/indexed.ts' +import { ListIndexTypeError } from '../error/indexed.ts' import { CallFrame } from '../executer/callFrame.ts' import { Scope } from '../executer/scope.ts' import { ValueType } from '../value/base.ts' import { IndexedValue } from '../value/indexed.ts' import { ListValue } from '../value/list.ts' import { NumberValue, StringValue } from '../value/primitive.ts' -import { Evaluable, Executable, Node, Operator } from './base.ts' +import { Evaluable, Executable, Node } from './base.ts' + +import type { Token } from '../prepare/tokenize/token.ts' export class Sequence extends Node { static override friendlyName = '나열된 값' - constructor(public items: Evaluable[]) { + constructor(public items: Evaluable[], public override tokens: Token[]) { super() } } @@ -24,7 +21,7 @@ export class Sequence extends Node { export class ListLiteral extends Evaluable { static override friendlyName = '목록' - constructor(public items: Evaluable[]) { + constructor(public items: Evaluable[], public override tokens: Token[]) { super() } @@ -47,6 +44,7 @@ export class IndexFetch extends Evaluable { constructor( public list: Evaluable, public index: Evaluable, + public override tokens: Token[], ) { super() } @@ -60,7 +58,7 @@ export class IndexFetch extends Evaluable { if (!(list instanceof IndexedValue)) { throw new TargetIsNotIndexedValueError({ - position: this.position, + position: this.tokens[0].position, resource: { target: list, }, @@ -86,7 +84,7 @@ export class IndexFetch extends Evaluable { if (!(list instanceof IndexedValue)) { throw new TargetIsNotIndexedValueError({ - position: this.position, + position: this.tokens[0].position, resource: { target: list, }, @@ -98,7 +96,7 @@ export class IndexFetch extends Evaluable { !(index instanceof StringValue) ) { throw new ListIndexTypeError({ - position: this.position, + position: this.tokens[0].position, resource: { index: index.toPrint(), }, @@ -112,7 +110,11 @@ export class IndexFetch extends Evaluable { export class SetToIndex extends Executable { static override friendlyName = '목록에 값 넣기' - constructor(private target: IndexFetch, private value: Evaluable) { + constructor( + public target: IndexFetch, + public value: Evaluable, + public override tokens: Token[], + ) { super() this.position = target.position @@ -123,68 +125,3 @@ export class SetToIndex extends Executable { await this.target.setValue(scope, callFrame, value) } } - -export class RangeOperator extends Operator { - static override friendlyName = '범위에서 목록 만들기(~)' - - override toPrint(): string { - return '~' - } - - override call(...operands: ValueType[]): ListValue { - this.assertProperOperands(operands) - - const [start, end] = operands - const items = new Array(end.value - start.value + 1) - .fill(null) - .map((_, index) => new NumberValue(start.value + index)) - - return new ListValue(items) - } - - private assertProperOperands( - operands: ValueType[], - ): asserts operands is [NumberValue, NumberValue] { - const [start, end] = operands - this.assertProperStartType(start) - this.assertProperEndType(end) - - this.assertRangeStartLessThanEnd(start.value, end.value) - } - - private assertProperStartType( - start: ValueType, - ): asserts start is NumberValue { - if (start instanceof NumberValue) return - - throw new RangeStartMustBeNumberError({ - position: this.position, - resource: { - start, - }, - }) - } - - private assertProperEndType(end: ValueType): asserts end is NumberValue { - if (end instanceof NumberValue) return - - throw new RangeEndMustBeNumberError({ - position: this.position, - resource: { - end, - }, - }) - } - - private assertRangeStartLessThanEnd(start: number, end: number) { - if (start <= end) return - - throw new RangeStartMustBeLessThanEndError({ - position: this.position, - resource: { - start, - end, - }, - }) - } -} diff --git a/core/node/listLoop.ts b/core/node/listLoop.ts index 7e97d4b..80e9904 100644 --- a/core/node/listLoop.ts +++ b/core/node/listLoop.ts @@ -7,6 +7,7 @@ import { Scope } from '../executer/scope.ts' import type { ValueType } from '../value/base.ts' import type { Block } from './block.ts' +import type { Token } from '../prepare/tokenize/token.ts' export class ListLoop extends Executable { static override friendlyName = '목록 반복' @@ -15,6 +16,7 @@ export class ListLoop extends Executable { public list: Evaluable, public variableName: string, public body: Block, + public override tokens: Token[], ) { super() } @@ -49,7 +51,7 @@ export class ListLoop extends Executable { resource: { value: target, }, - position: this.position, + position: this.tokens[0].position, }) } } diff --git a/core/node/loop.ts b/core/node/loop.ts index ac849fd..da432ac 100644 --- a/core/node/loop.ts +++ b/core/node/loop.ts @@ -1,13 +1,15 @@ import { CallFrame } from '../executer/callFrame.ts' -import type { Scope } from '../executer/scope.ts' -import type { Block } from './block.ts' import { BreakSignal } from '../executer/signals.ts' import { Executable } from './base.ts' +import type { Token } from '../prepare/tokenize/token.ts' +import type { Scope } from '../executer/scope.ts' +import type { Block } from './block.ts' + export class Loop extends Executable { static override friendlyName = '반복' - constructor(public body: Block) { + constructor(public body: Block, public override tokens: Token[]) { super() } @@ -29,7 +31,11 @@ export class Loop extends Executable { export class Break extends Executable { static override friendlyName = '그만' + constructor(public override tokens: Token[]) { + super() + } + override execute(_scope: Scope, _callFrame: CallFrame): Promise { - throw new BreakSignal(this.position) + throw new BreakSignal(this.tokens[0].position) } } diff --git a/core/node/mention.ts b/core/node/mention.ts index 9f442b1..6dcf173 100644 --- a/core/node/mention.ts +++ b/core/node/mention.ts @@ -1,17 +1,18 @@ import { Evaluable, Executable, Identifier } from './base.ts' -import type { CallFrame } from '../executer/callFrame.ts' -import type { Scope } from '../executer/scope.ts' import { ErrorInModuleError } from '../error/index.ts' -import type { Position } from '../type/position.ts' import { YaksokError } from '../error/common.ts' import { FunctionInvoke } from './function.ts' import { evaluateParams } from './function.ts' import { ValueType } from '../value/base.ts' +import type { Token } from '../prepare/tokenize/token.ts' +import type { CallFrame } from '../executer/callFrame.ts' +import type { Scope } from '../executer/scope.ts' + export class Mention extends Executable { static override friendlyName = '불러올 파일 이름' - constructor(public value: string, public override position?: Position) { + constructor(public value: string, public override tokens: Token[]) { super() } @@ -26,6 +27,7 @@ export class MentionScope extends Evaluable { constructor( public fileName: string, public child: FunctionInvoke | Identifier, + public override tokens: Token[], ) { super() } @@ -34,8 +36,6 @@ export class MentionScope extends Evaluable { scope: Scope, callFrame: CallFrame, ): Promise { - this.setChildPosition() - try { const moduleCodeFile = scope.codeFile!.runtime!.getCodeFile( this.fileName, @@ -65,7 +65,7 @@ export class MentionScope extends Evaluable { resource: { fileName: this.fileName, }, - position: this.position, + position: this.tokens[0].position, child: error, }) } @@ -77,13 +77,4 @@ export class MentionScope extends Evaluable { override toPrint(): string { return '@' + this.fileName + ' ' + this.child.toPrint() } - - private setChildPosition() { - if (!this.position) return - - this.child.position = { - line: this.position.line, - column: this.position.column + 1 + this.fileName.length, - } - } } diff --git a/core/node/misc.ts b/core/node/misc.ts index 2acc49a..2cd5fac 100644 --- a/core/node/misc.ts +++ b/core/node/misc.ts @@ -1,13 +1,13 @@ +import { Node, Executable, type Evaluable } from './base.ts' + import type { CallFrame } from '../executer/callFrame.ts' import type { Scope } from '../executer/scope.ts' -import { Position } from '../type/position.ts' - -import { Node, Executable, type Evaluable } from './base.ts' +import type { Token } from '../prepare/tokenize/token.ts' export class EOL extends Node { static override friendlyName = '줄바꿈' - constructor(public override position?: Position) { + constructor(public override tokens: Token[]) { super() } } @@ -15,7 +15,7 @@ export class EOL extends Node { export class Indent extends Node { static override friendlyName = '들여쓰기' - constructor(public size: number, public override position?: Position) { + constructor(public size: number, public override tokens: Token[]) { super() } } @@ -23,7 +23,7 @@ export class Indent extends Node { export class Print extends Executable { static override friendlyName = '보여주기' - constructor(public value: Evaluable, public override position?: Position) { + constructor(public value: Evaluable, public override tokens: Token[]) { super() } diff --git a/core/node/operator.ts b/core/node/operator.ts index 539b53b..bc29f47 100644 --- a/core/node/operator.ts +++ b/core/node/operator.ts @@ -1,12 +1,24 @@ import { InvalidTypeForCompareError } from '../error/calculation.ts' -import { InvalidTypeForOperatorError } from '../error/index.ts' +import { + InvalidTypeForOperatorError, + RangeEndMustBeNumberError, + RangeStartMustBeLessThanEndError, + RangeStartMustBeNumberError, +} from '../error/index.ts' +import { Token } from '../prepare/tokenize/token.ts' + import { PrimitiveValue, ValueType } from '../value/base.ts' +import { ListValue } from '../value/list.ts' import { BooleanValue, NumberValue, StringValue } from '../value/primitive.ts' import { Operator } from './base.ts' export class PlusOperator extends Operator { static override friendlyName = '더하기(+)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '+' } @@ -31,7 +43,7 @@ export class PlusOperator extends Operator { } throw new InvalidTypeForOperatorError({ - position: this.position, + position: this.tokens?.[0].position, resource: { operator: this, operands, @@ -43,6 +55,10 @@ export class PlusOperator extends Operator { export class MinusOperator extends Operator { static override friendlyName = '빼기(-)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '-' } @@ -54,7 +70,7 @@ export class MinusOperator extends Operator { } throw new InvalidTypeForOperatorError({ - position: this.position, + position: this.tokens?.[0].position, resource: { operator: this, operands, @@ -66,6 +82,10 @@ export class MinusOperator extends Operator { export class MultiplyOperator extends Operator { static override friendlyName = '곱하기(*)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '*' } @@ -85,7 +105,7 @@ export class MultiplyOperator extends Operator { } throw new InvalidTypeForOperatorError({ - position: this.position, + position: this.tokens?.[0].position, resource: { operator: this, operands, @@ -97,6 +117,10 @@ export class MultiplyOperator extends Operator { export class DivideOperator extends Operator { static override friendlyName = '나누기(/)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '/' } @@ -109,7 +133,7 @@ export class DivideOperator extends Operator { } throw new InvalidTypeForOperatorError({ - position: this.position, + position: this.tokens?.[0].position, resource: { operator: this, operands, @@ -121,6 +145,10 @@ export class DivideOperator extends Operator { export class ModularOperator extends Operator { static override friendlyName = '나머지(%)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '%' } @@ -133,7 +161,7 @@ export class ModularOperator extends Operator { } throw new InvalidTypeForOperatorError({ - position: this.position, + position: this.tokens?.[0].position, resource: { operator: this, operands, @@ -144,6 +172,10 @@ export class ModularOperator extends Operator { export class PowerOperator extends Operator { static override friendlyName = '제곱(**)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '**' } @@ -156,7 +188,7 @@ export class PowerOperator extends Operator { } throw new InvalidTypeForOperatorError({ - position: this.position, + position: this.tokens?.[0].position, resource: { operator: this, operands, @@ -167,6 +199,10 @@ export class PowerOperator extends Operator { export class IntegerDivideOperator extends Operator { static override friendlyName = '정수 나누기(//)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '//' } @@ -179,7 +215,7 @@ export class IntegerDivideOperator extends Operator { } throw new InvalidTypeForOperatorError({ - position: this.position, + position: this.tokens?.[0].position, resource: { operator: this, operands, @@ -191,6 +227,10 @@ export class IntegerDivideOperator extends Operator { export class EqualOperator extends Operator { static override friendlyName = '같다(=)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '=' } @@ -208,7 +248,7 @@ export class EqualOperator extends Operator { left, right, }, - position: this.position, + position: this.tokens?.[0].position, }) } @@ -219,6 +259,10 @@ export class EqualOperator extends Operator { export class AndOperator extends Operator { static override friendlyName = '이고(그리고)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '이고(그리고)' } @@ -231,7 +275,7 @@ export class AndOperator extends Operator { !(right instanceof BooleanValue) ) { throw new InvalidTypeForOperatorError({ - position: this.position, + position: this.tokens?.[0].position, resource: { operator: this, operands, @@ -246,6 +290,10 @@ export class AndOperator extends Operator { export class OrOperator extends Operator { static override friendlyName = '이거나(거나)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '이거나(거나)' } @@ -258,7 +306,7 @@ export class OrOperator extends Operator { !(right instanceof BooleanValue) ) { throw new InvalidTypeForOperatorError({ - position: this.position, + position: this.tokens?.[0].position, resource: { operator: this, operands, @@ -273,6 +321,10 @@ export class OrOperator extends Operator { export class GreaterThanOperator extends Operator { static override friendlyName = '크다(>)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '>' } @@ -285,7 +337,7 @@ export class GreaterThanOperator extends Operator { } throw new InvalidTypeForCompareError({ - position: this.position, + position: this.tokens?.[0].position, resource: { left, right, @@ -297,6 +349,10 @@ export class GreaterThanOperator extends Operator { export class LessThanOperator extends Operator { static override friendlyName = '작다(<)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '<' } @@ -309,7 +365,7 @@ export class LessThanOperator extends Operator { } throw new InvalidTypeForCompareError({ - position: this.position, + position: this.tokens?.[0].position, resource: { left, right, @@ -321,6 +377,10 @@ export class LessThanOperator extends Operator { export class GreaterThanOrEqualOperator extends Operator { static override friendlyName = '크거나 같다(>=)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '>=' } @@ -333,7 +393,7 @@ export class GreaterThanOrEqualOperator extends Operator { } throw new InvalidTypeForCompareError({ - position: this.position, + position: this.tokens?.[0].position, resource: { left, right, @@ -345,6 +405,10 @@ export class GreaterThanOrEqualOperator extends Operator { export class LessThanOrEqualOperator extends Operator { static override friendlyName = '작거나 같다(<=)' + constructor(public override tokens: Token[]) { + super(null, tokens) + } + override toPrint(): string { return '<=' } @@ -357,7 +421,7 @@ export class LessThanOrEqualOperator extends Operator { } throw new InvalidTypeForCompareError({ - position: this.position, + position: this.tokens?.[0].position, resource: { left, right, @@ -365,3 +429,72 @@ export class LessThanOrEqualOperator extends Operator { }) } } + +export class RangeOperator extends Operator { + static override friendlyName = '범위에서 목록 만들기(~)' + + constructor(public override tokens: Token[]) { + super(null, tokens) + } + + override toPrint(): string { + return '~' + } + + override call(...operands: ValueType[]): ListValue { + this.assertProperOperands(operands) + + const [start, end] = operands + const items = new Array(end.value - start.value + 1) + .fill(null) + .map((_, index) => new NumberValue(start.value + index)) + + return new ListValue(items) + } + + private assertProperOperands( + operands: ValueType[], + ): asserts operands is [NumberValue, NumberValue] { + const [start, end] = operands + this.assertProperStartType(start) + this.assertProperEndType(end) + + this.assertRangeStartLessThanEnd(start.value, end.value) + } + + private assertProperStartType( + start: ValueType, + ): asserts start is NumberValue { + if (start instanceof NumberValue) return + + throw new RangeStartMustBeNumberError({ + position: this.tokens[0].position, + resource: { + start, + }, + }) + } + + private assertProperEndType(end: ValueType): asserts end is NumberValue { + if (end instanceof NumberValue) return + + throw new RangeEndMustBeNumberError({ + position: this.tokens[0].position, + resource: { + end, + }, + }) + } + + private assertRangeStartLessThanEnd(start: number, end: number) { + if (start <= end) return + + throw new RangeStartMustBeLessThanEndError({ + position: this.tokens[0].position, + resource: { + start, + end, + }, + }) + } +} diff --git a/core/node/primitive-literal.ts b/core/node/primitive-literal.ts index 9ff8671..50a3927 100644 --- a/core/node/primitive-literal.ts +++ b/core/node/primitive-literal.ts @@ -1,14 +1,14 @@ import { BooleanValue, NumberValue, StringValue } from '../value/primitive.ts' import { Evaluable } from './base.ts' +import type { Token } from '../prepare/tokenize/token.ts' import type { CallFrame } from '../executer/callFrame.ts' -import type { Position } from '../type/position.ts' import type { Scope } from '../executer/scope.ts' export class NumberLiteral extends Evaluable { static override friendlyName = '숫자' - constructor(private content: number, public override position?: Position) { + constructor(private content: number, public override tokens: Token[]) { super() } @@ -27,7 +27,7 @@ export class NumberLiteral extends Evaluable { export class StringLiteral extends Evaluable { static override friendlyName = '문자' - constructor(private content: string, public override position?: Position) { + constructor(private content: string, public override tokens: Token[]) { super() } @@ -46,7 +46,7 @@ export class StringLiteral extends Evaluable { export class BooleanLiteral extends Evaluable { static override friendlyName = '참거짓' - constructor(private content: boolean, public override position?: Position) { + constructor(private content: boolean, public override tokens: Token[]) { super() } diff --git a/core/node/return.ts b/core/node/return.ts index 2856147..304d524 100644 --- a/core/node/return.ts +++ b/core/node/return.ts @@ -1,11 +1,17 @@ -import type { Scope } from '../executer/scope.ts' import { ReturnSignal } from '../executer/signals.ts' import { Executable } from './base.ts' +import type { Token } from '../prepare/tokenize/token.ts' +import type { Scope } from '../executer/scope.ts' + export class Return extends Executable { static override friendlyName = '결과' + constructor(public override tokens: Token[]) { + super() + } + override execute(_scope: Scope): Promise { - throw new ReturnSignal(this.position) + throw new ReturnSignal(this.tokens[0].position) } } diff --git a/core/node/variable.ts b/core/node/variable.ts index 2592edc..b3dd927 100644 --- a/core/node/variable.ts +++ b/core/node/variable.ts @@ -4,6 +4,7 @@ import { Evaluable } from './base.ts' import type { ValueType } from '../value/base.ts' import type { Scope } from '../executer/scope.ts' +import type { Token } from '../prepare/tokenize/token.ts' export const RESERVED_WORDS = [ '약속', @@ -24,7 +25,11 @@ export const RESERVED_WORDS = [ export class SetVariable extends Evaluable { static override friendlyName = '변수 정하기' - constructor(public name: string, public value: Evaluable) { + constructor( + public name: string, + public value: Evaluable, + public override tokens: Token[], + ) { super() this.assertValidName() } @@ -46,7 +51,7 @@ export class SetVariable extends Evaluable { if (!RESERVED_WORDS.includes(this.name)) return throw new CannotUseReservedWordForIdentifierNameError({ - position: this.position, + position: this.tokens[0].position, resource: { name: this.name, }, diff --git a/core/prepare/lex/convert-tokens-to-nodes.ts b/core/prepare/lex/convert-tokens-to-nodes.ts index 656bda2..febca62 100644 --- a/core/prepare/lex/convert-tokens-to-nodes.ts +++ b/core/prepare/lex/convert-tokens-to-nodes.ts @@ -12,39 +12,32 @@ export function convertTokensToNodes(tokens: Token[]): Node[] { function mapTokenToNode(token: Token) { switch (token.type) { - case TOKEN_TYPE.NUMBER: - return new NumberLiteral(parseFloat(token.value), token.position) - case TOKEN_TYPE.STRING: - return new StringLiteral(token.value.slice(1, -1), token.position) - case TOKEN_TYPE.OPERATOR: - return new Operator(token.value, token.position) case TOKEN_TYPE.SPACE: + case TOKEN_TYPE.LINE_COMMENT: + case TOKEN_TYPE.UNKNOWN: return null - case TOKEN_TYPE.INDENT: - return new Indent(token.value.length, token.position) - case TOKEN_TYPE.IDENTIFIER: - return new Identifier(token.value, token.position) case TOKEN_TYPE.COMMA: case TOKEN_TYPE.OPENING_PARENTHESIS: case TOKEN_TYPE.CLOSING_PARENTHESIS: case TOKEN_TYPE.OPENING_BRACKET: case TOKEN_TYPE.CLOSING_BRACKET: case TOKEN_TYPE.COLON: - return new Expression(token.value, token.position) + return new Expression(token.value, [token]) + case TOKEN_TYPE.NUMBER: + return new NumberLiteral(parseFloat(token.value), [token]) + case TOKEN_TYPE.STRING: + return new StringLiteral(token.value.slice(1, -1), [token]) + case TOKEN_TYPE.OPERATOR: + return new Operator(token.value, [token]) + case TOKEN_TYPE.INDENT: + return new Indent(token.value.length, [token]) + case TOKEN_TYPE.IDENTIFIER: + return new Identifier(token.value, [token]) case TOKEN_TYPE.FFI_BODY: - return new FFIBody(token.value.slice(3, -3), token.position) + return new FFIBody(token.value.slice(3, -3), [token]) case TOKEN_TYPE.NEW_LINE: - return new EOL(token.position) - case TOKEN_TYPE.LINE_COMMENT: - return null + return new EOL([token]) case TOKEN_TYPE.MENTION: - return new Mention(token.value.slice(1), token.position) - case TOKEN_TYPE.UNKNOWN: - throw new UnexpectedCharError({ - resource: { - parts: '코드', - char: token.value, - }, - }) + return new Mention(token.value.slice(1), [token]) } } diff --git a/core/prepare/parse/dynamicRule/functions/invoke-rule.ts b/core/prepare/parse/dynamicRule/functions/invoke-rule.ts index dbb02ec..508a854 100644 --- a/core/prepare/parse/dynamicRule/functions/invoke-rule.ts +++ b/core/prepare/parse/dynamicRule/functions/invoke-rule.ts @@ -85,16 +85,19 @@ function createRuleFromFunctionTemplate( return { pattern, - factory(matchedNodes) { + factory(matchedNodes, tokens) { const params = parseParameterFromTemplate( functionTemplate, matchedNodes, ) - return new FunctionInvoke({ - name: functionTemplate.name, - params, - }) + return new FunctionInvoke( + { + name: functionTemplate.name, + params, + }, + tokens, + ) }, config: { exported: true, diff --git a/core/prepare/parse/dynamicRule/mention/create-mentioning-rules.ts b/core/prepare/parse/dynamicRule/mention/create-mentioning-rules.ts index 688e249..02367ee 100644 --- a/core/prepare/parse/dynamicRule/mention/create-mentioning-rules.ts +++ b/core/prepare/parse/dynamicRule/mention/create-mentioning-rules.ts @@ -2,6 +2,8 @@ import { Mention, MentionScope } from '../../../../node/mention.ts' import { FunctionInvoke } from '../../../../node/function.ts' import { Identifier, Node } from '../../../../node/base.ts' import { Rule } from '../../rule.ts' +import { Token } from '../../../tokenize/token.ts' +import { getTokensFromNodes } from '../../../../util/merge-tokens.ts' export function createMentioningRule( fileName: string, @@ -23,12 +25,15 @@ export function createMentioningRule( } function createFactory(fileName: string, rule: Rule) { - return (nodes: Node[]) => { - const child = rule.factory(nodes.slice(1)) as + return (nodes: Node[], tokens: Token[]) => { + const childNodes = nodes.slice(1) + const childTokens = getTokensFromNodes(childNodes) + + const child = rule.factory(nodes.slice(1), childTokens) as | Identifier | FunctionInvoke child.position = nodes[1].position - return new MentionScope(fileName, child) + return new MentionScope(fileName, child, tokens) } } diff --git a/core/prepare/parse/index.ts b/core/prepare/parse/index.ts index 0e541e3..1a0e1c6 100644 --- a/core/prepare/parse/index.ts +++ b/core/prepare/parse/index.ts @@ -8,6 +8,7 @@ import { Block } from '../../node/block.ts' import type { CodeFile } from '../../type/code-file.ts' import type { Rule } from './rule.ts' +import { getTokensFromNodes } from '../../util/merge-tokens.ts' interface ParseResult { ast: Block @@ -18,7 +19,10 @@ export function parse(codeFile: CodeFile): ParseResult { const dynamicRules = createDynamicRule(codeFile) const indentedNodes = parseIndent(convertTokensToNodes(codeFile.tokens)) - const ast = new Block(callParseRecursively(indentedNodes, dynamicRules)) + const childNodes = callParseRecursively(indentedNodes, dynamicRules) + const childTokens = getTokensFromNodes(childNodes) + + const ast = new Block(childNodes, childTokens) const exportedVariables = getExportedVariablesRules(ast) const exportedRules = [...dynamicRules.flat(), ...exportedVariables] @@ -40,7 +44,8 @@ function getExportedVariablesRules(ast: Block): Rule[] { value: variableName, }, ], - factory: () => new Identifier(variableName), + factory: (_nodes, tokens) => + new Identifier(variableName, tokens), config: { exported: true, }, diff --git a/core/prepare/parse/parse-indent.ts b/core/prepare/parse/parse-indent.ts index 6dccbbf..e3bbf01 100644 --- a/core/prepare/parse/parse-indent.ts +++ b/core/prepare/parse/parse-indent.ts @@ -1,4 +1,5 @@ import { type Node, Indent, EOL, Block } from '../../node/index.ts' +import { getTokensFromNodes } from '../../util/merge-tokens.ts' export function parseIndent(_tokens: Node[], indent = 0) { const groups: Node[] = [] @@ -42,7 +43,9 @@ export function parseIndent(_tokens: Node[], indent = 0) { } const child = parseIndent(blockTokens, indent + 1) - groups.push(new Block(child)) + const childTokens = getTokensFromNodes(child) + + groups.push(new Block(child, childTokens)) } else { groups.push(token) } diff --git a/core/prepare/parse/rule.ts b/core/prepare/parse/rule.ts index 3a4fd49..119ab42 100644 --- a/core/prepare/parse/rule.ts +++ b/core/prepare/parse/rule.ts @@ -41,6 +41,8 @@ import { ListLoop } from '../../node/listLoop.ts' import { IndexedValue } from '../../value/indexed.ts' import { NumberValue, StringValue } from '../../value/primitive.ts' +import type { Token } from '../tokenize/token.ts' + export interface PatternUnit { type: { new (...args: any[]): Node @@ -51,7 +53,7 @@ export interface PatternUnit { export type Rule = { pattern: PatternUnit[] - factory: (nodes: Node[]) => Node + factory: (nodes: Node[], tokens: Token[]) => Node config?: Record } @@ -74,11 +76,11 @@ export const BASIC_RULES: Rule[][] = [ value: ']', }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const target = nodes[0] as Evaluable const index = nodes[2] as Evaluable - return new IndexFetch(target, index) + return new IndexFetch(target, index, tokens) }, }, ], @@ -97,8 +99,11 @@ export const BASIC_RULES: Rule[][] = [ value: ')', }, ], - factory: (nodes) => { - const newNode = new ValueWithParenthesis(nodes[1] as Evaluable) + factory: (nodes, tokens) => { + const newNode = new ValueWithParenthesis( + nodes[1] as Evaluable, + tokens, + ) newNode.position = nodes[0].position return newNode @@ -118,9 +123,9 @@ export const BASIC_RULES: Rule[][] = [ value: ']', }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const sequence = nodes[1] as Sequence - return new ListLiteral(sequence.items) + return new ListLiteral(sequence.items, tokens) }, }, { @@ -135,16 +140,16 @@ export const BASIC_RULES: Rule[][] = [ type: Evaluable, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const left = nodes[0] as Evaluable const operator = nodes[1] as Operator const right = nodes[2] as Evaluable if (left instanceof Formula) { - return new Formula([...left.terms, operator, right]) + return new Formula([...left.terms, operator, right], tokens) } - return new Formula([left, operator, right]) + return new Formula([left, operator, right], tokens) }, }, { @@ -154,7 +159,7 @@ export const BASIC_RULES: Rule[][] = [ value: '=', }, ], - factory: () => new EqualOperator(), + factory: (_nodes, tokens) => new EqualOperator(tokens), }, { pattern: [ @@ -163,7 +168,7 @@ export const BASIC_RULES: Rule[][] = [ value: '>', }, ], - factory: () => new GreaterThanOperator(), + factory: (_nodes, tokens) => new GreaterThanOperator(tokens), }, { pattern: [ @@ -172,7 +177,7 @@ export const BASIC_RULES: Rule[][] = [ value: '<', }, ], - factory: () => new LessThanOperator(), + factory: (_nodes, tokens) => new LessThanOperator(tokens), }, { pattern: [ @@ -181,7 +186,7 @@ export const BASIC_RULES: Rule[][] = [ value: '>=', }, ], - factory: () => new GreaterThanOrEqualOperator(), + factory: (_nodes, tokens) => new GreaterThanOrEqualOperator(tokens), }, { pattern: [ @@ -190,7 +195,7 @@ export const BASIC_RULES: Rule[][] = [ value: '<=', }, ], - factory: () => new LessThanOrEqualOperator(), + factory: (_nodes, tokens) => new LessThanOrEqualOperator(tokens), }, { pattern: [ @@ -199,7 +204,7 @@ export const BASIC_RULES: Rule[][] = [ value: '//', }, ], - factory: () => new IntegerDivideOperator(), + factory: (_nodes, tokens) => new IntegerDivideOperator(tokens), }, { pattern: [ @@ -208,7 +213,7 @@ export const BASIC_RULES: Rule[][] = [ value: '%', }, ], - factory: () => new ModularOperator(), + factory: (_nodes, tokens) => new ModularOperator(tokens), }, { pattern: [ @@ -217,7 +222,7 @@ export const BASIC_RULES: Rule[][] = [ value: '**', }, ], - factory: () => new PowerOperator(), + factory: (_nodes, tokens) => new PowerOperator(tokens), }, { pattern: [ @@ -226,7 +231,7 @@ export const BASIC_RULES: Rule[][] = [ value: '/', }, ], - factory: () => new DivideOperator(), + factory: (_nodes, tokens) => new DivideOperator(tokens), }, { pattern: [ @@ -235,7 +240,7 @@ export const BASIC_RULES: Rule[][] = [ value: '*', }, ], - factory: () => new MultiplyOperator(), + factory: (_nodes, tokens) => new MultiplyOperator(tokens), }, { pattern: [ @@ -244,7 +249,7 @@ export const BASIC_RULES: Rule[][] = [ value: '+', }, ], - factory: () => new PlusOperator(), + factory: (_nodes, tokens) => new PlusOperator(tokens), }, { pattern: [ @@ -253,7 +258,7 @@ export const BASIC_RULES: Rule[][] = [ value: '-', }, ], - factory: () => new MinusOperator(), + factory: (_nodes, tokens) => new MinusOperator(tokens), }, { pattern: [ @@ -262,7 +267,7 @@ export const BASIC_RULES: Rule[][] = [ value: '이고', }, ], - factory: () => new AndOperator(), + factory: (_nodes, tokens) => new AndOperator(tokens), }, { pattern: [ @@ -271,7 +276,7 @@ export const BASIC_RULES: Rule[][] = [ value: '고', }, ], - factory: () => new AndOperator(), + factory: (_nodes, tokens) => new AndOperator(tokens), }, { pattern: [ @@ -280,7 +285,7 @@ export const BASIC_RULES: Rule[][] = [ value: '이거나', }, ], - factory: () => new OrOperator(), + factory: (_nodes, tokens) => new OrOperator(tokens), }, { pattern: [ @@ -289,7 +294,7 @@ export const BASIC_RULES: Rule[][] = [ value: '거나', }, ], - factory: () => new OrOperator(), + factory: (_nodes, tokens) => new OrOperator(tokens), }, { pattern: [ @@ -298,7 +303,7 @@ export const BASIC_RULES: Rule[][] = [ value: '~', }, ], - factory: () => new RangeOperator(), + factory: (_nodes, tokens) => new RangeOperator(tokens), }, { pattern: [ @@ -320,7 +325,7 @@ export const BASIC_RULES: Rule[][] = [ type: Block, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const [_, __, functionInvoke, ___, body] = nodes as [ unknown, unknown, @@ -331,10 +336,13 @@ export const BASIC_RULES: Rule[][] = [ const functionName = functionInvoke.name - return new DeclareFunction({ - body, - name: functionName, - }) + return new DeclareFunction( + { + body, + name: functionName, + }, + tokens, + ) }, }, { @@ -360,7 +368,7 @@ export const BASIC_RULES: Rule[][] = [ type: FFIBody, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const [_, runtimeNode, __, functionInvoke, ___, body] = nodes as [ unknown, @@ -374,11 +382,14 @@ export const BASIC_RULES: Rule[][] = [ const functionName = functionInvoke.name const runtime = (runtimeNode.value as Identifier).value - return new DeclareFFI({ - body: body.code, - name: functionName, - runtime, - }) + return new DeclareFFI( + { + body: body.code, + name: functionName, + runtime, + }, + tokens, + ) }, }, ], @@ -398,11 +409,11 @@ export const ADVANCED_RULES: Rule[] = [ type: Evaluable, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const a = nodes[0] as Evaluable const b = nodes[2] as Evaluable - return new Sequence([a, b]) + return new Sequence([a, b], tokens) }, }, { @@ -418,11 +429,11 @@ export const ADVANCED_RULES: Rule[] = [ type: Evaluable, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const a = nodes[0] as Sequence const b = nodes[2] as Evaluable - return new Sequence([...a.items, b]) + return new Sequence([...a.items, b], tokens) }, }, { @@ -436,7 +447,7 @@ export const ADVANCED_RULES: Rule[] = [ value: ']', }, ], - factory: () => new ListLiteral([]), + factory: (_nodes, tokens) => new ListLiteral([], tokens), }, { pattern: [ @@ -451,11 +462,11 @@ export const ADVANCED_RULES: Rule[] = [ type: Evaluable, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const target = nodes[0] as IndexFetch const value = nodes[2] as Evaluable - return new SetToIndex(target, value) + return new SetToIndex(target, value, tokens) }, }, { @@ -474,11 +485,11 @@ export const ADVANCED_RULES: Rule[] = [ type: EOL, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const name = (nodes[0] as Identifier).value const value = nodes[2] as Evaluable - return new SetVariable(name, value) + return new SetVariable(name, value, tokens) }, }, { @@ -490,7 +501,7 @@ export const ADVANCED_RULES: Rule[] = [ type: ElseIfStatement, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const [ifStatement, elseIfStatement] = nodes as [ IfStatement, ElseIfStatement, @@ -499,6 +510,8 @@ export const ADVANCED_RULES: Rule[] = [ const elseIfCase = elseIfStatement.elseIfCase ifStatement.cases.push(elseIfCase) + ifStatement.tokens = tokens + return ifStatement }, }, @@ -511,7 +524,7 @@ export const ADVANCED_RULES: Rule[] = [ type: ElseStatement, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const [ifStatement, elseStatement] = nodes as [ IfStatement, ElseStatement, @@ -520,7 +533,9 @@ export const ADVANCED_RULES: Rule[] = [ const elseCase = { body: elseStatement.body, } + ifStatement.cases.push(elseCase) + ifStatement.tokens = tokens return ifStatement }, @@ -549,11 +564,11 @@ export const ADVANCED_RULES: Rule[] = [ type: Block, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const condition = nodes[2] as Evaluable const body = nodes[5] as Block - return new ElseIfStatement({ condition, body }) + return new ElseIfStatement({ condition, body }, tokens) }, }, { @@ -569,10 +584,10 @@ export const ADVANCED_RULES: Rule[] = [ type: Block, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const body = nodes[2] as Block - return new ElseStatement(body) + return new ElseStatement(body, tokens) }, }, { @@ -595,11 +610,11 @@ export const ADVANCED_RULES: Rule[] = [ type: Block, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const condition = nodes[1] as Evaluable const body = nodes[4] as Block - return new IfStatement([{ condition, body }]) + return new IfStatement([{ condition, body }], tokens) }, }, { @@ -612,9 +627,9 @@ export const ADVANCED_RULES: Rule[] = [ value: '보여주기', }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const value = nodes[0] as Evaluable - return new Print(value) + return new Print(value, tokens) }, }, { @@ -630,7 +645,7 @@ export const ADVANCED_RULES: Rule[] = [ type: Block, }, ], - factory: (nodes) => new Loop(nodes[2] as Block), + factory: (nodes, tokens) => new Loop(nodes[2] as Block, tokens), }, { pattern: [ @@ -643,7 +658,7 @@ export const ADVANCED_RULES: Rule[] = [ value: '그만', }, ], - factory: () => new Break(), + factory: (_nodes, tokens) => new Break(tokens), }, { pattern: [ @@ -656,7 +671,7 @@ export const ADVANCED_RULES: Rule[] = [ value: '그만', }, ], - factory: () => new Return(), + factory: (_nodes, tokens) => new Return(tokens), }, { pattern: [ @@ -685,12 +700,12 @@ export const ADVANCED_RULES: Rule[] = [ type: Block, }, ], - factory: (nodes) => { + factory: (nodes, tokens) => { const list = nodes[1] as Evaluable const name = (nodes[3] as Identifier).value const body = nodes[6] as Block - return new ListLoop(list, name, body) + return new ListLoop(list, name, body, tokens) }, }, ] diff --git a/core/prepare/parse/srParse.ts b/core/prepare/parse/srParse.ts index a2d4461..260c80d 100644 --- a/core/prepare/parse/srParse.ts +++ b/core/prepare/parse/srParse.ts @@ -2,8 +2,11 @@ import { type Rule, ADVANCED_RULES, BASIC_RULES } from './rule.ts' import { satisfiesPattern } from './satisfiesPattern.ts' import { Block } from '../../node/block.ts' +import { TOKEN_TYPE } from '../tokenize/token.ts' import { EOL } from '../../node/misc.ts' + import type { Node } from '../../node/base.ts' +import { getTokensFromNodes } from '../../util/merge-tokens.ts' export function SRParse(_tokens: Node[], rules: Rule[]) { const tokens = [..._tokens] @@ -37,9 +40,11 @@ export function SRParse(_tokens: Node[], rules: Rule[]) { } } -export function reduce(tokens: Node[], rule: Rule) { - const reduced = rule.factory(tokens) - reduced.position = tokens[0].position +export function reduce(nodes: Node[], rule: Rule) { + const tokens = getTokensFromNodes(nodes) + + const reduced = rule.factory(nodes, tokens) + reduced.position = nodes[0].position return reduced } @@ -53,11 +58,23 @@ export function callParseRecursively( for (let i = 0; i < parsedTokens.length; i++) { const token = parsedTokens[i] - if (!(token instanceof Block)) continue - - token.children = callParseRecursively(token.children, externalPatterns) + if (token instanceof Block) { + token.children = callParseRecursively( + token.children, + externalPatterns, + ) + } } - parsedTokens.push(new EOL()) + + parsedTokens.push( + new EOL([ + { + position: { column: 0, line: 0 }, + value: '\n', + type: TOKEN_TYPE.NEW_LINE, + }, + ]), + ) const patternsByLevel = [...BASIC_RULES, externalPatterns, ADVANCED_RULES] diff --git a/core/util/merge-tokens.ts b/core/util/merge-tokens.ts new file mode 100644 index 0000000..d4d9bfc --- /dev/null +++ b/core/util/merge-tokens.ts @@ -0,0 +1,5 @@ +import type { Node, Token } from '../mod.ts' + +export function getTokensFromNodes(nodes: Node[]): Token[] { + return nodes.flatMap((node) => node.tokens || []) +} diff --git a/deno.json b/deno.json index 016819f..5f53eb1 100644 --- a/deno.json +++ b/deno.json @@ -19,9 +19,10 @@ "quickjs", "docs", "core", - "test" + "test", + "monaco-language-provider" ], - "version": "0.2.0-alpha.11+20241217.nightly", + "version": "0.2.0-alpha.13+20250104.nightly", "tasks": { "apply-version": "deno run --allow-read --allow-write apply-version.ts", "publish": "deno task --recursive test && deno publish --allow-dirty" diff --git a/deno.lock b/deno.lock index a4351d9..e0d5597 100644 --- a/deno.lock +++ b/deno.lock @@ -1,28 +1,28 @@ { "version": "4", "specifiers": { - "jsr:@std/assert@^1.0.7": "1.0.8", - "jsr:@std/assert@^1.0.8": "1.0.8", + "jsr:@std/assert@^1.0.7": "1.0.10", + "jsr:@std/assert@^1.0.8": "1.0.10", "jsr:@std/internal@^1.0.5": "1.0.5", - "npm:@deno/vite-plugin@^1.0.2": "1.0.2_vite@6.0.3", + "npm:@deno/vite-plugin@^1.0.2": "1.0.2_vite@6.0.5", "npm:@vue/runtime-dom@^3.5.12": "3.5.13", "npm:@vueuse/core@^11.2.0": "11.3.0_vue@3.5.13", "npm:ansi-to-html@~0.7.2": "0.7.2", "npm:madge@*": "8.0.0", - "npm:monaco-editor@0.52": "0.52.2", + "npm:monaco-editor@~0.52.2": "0.52.2", "npm:quickjs-emscripten-core@0.31": "0.31.0", "npm:quickjs-emscripten@0.31": "0.31.0", - "npm:typedoc-plugin-markdown@^4.2.10": "4.3.2_typedoc@0.27.4__typescript@5.6.3", - "npm:typedoc-vitepress-theme@^1.1.1": "1.1.1_typedoc-plugin-markdown@4.3.2__typedoc@0.27.4___typescript@5.6.3_typedoc@0.27.4__typescript@5.6.3", - "npm:typedoc@0.27.4": "0.27.4_typescript@5.6.3", + "npm:typedoc-plugin-markdown@^4.2.10": "4.3.3_typedoc@0.27.4__typescript@5.7.2", + "npm:typedoc-vitepress-theme@^1.1.1": "1.1.1_typedoc-plugin-markdown@4.3.3__typedoc@0.27.4___typescript@5.7.2_typedoc@0.27.4__typescript@5.7.2", + "npm:typedoc@0.27.4": "0.27.4_typescript@5.7.2", "npm:vitepress-sidebar@^1.29.0": "1.30.2", "npm:vitepress@*": "1.5.0_vite@5.4.11_vue@3.5.13_focus-trap@7.6.2", "npm:vitepress@^1.5.0": "1.5.0_vite@5.4.11_vue@3.5.13_focus-trap@7.6.2", "npm:vue@^3.5.12": "3.5.13" }, "jsr": { - "@std/assert@1.0.8": { - "integrity": "ebe0bd7eb488ee39686f77003992f389a06c3da1bbd8022184804852b2fa641b", + "@std/assert@1.0.10": { + "integrity": "59b5cbac5bd55459a19045d95cc7c2ff787b4f8527c0dd195078ff6f9481fbb3", "dependencies": [ "jsr:@std/internal" ] @@ -32,21 +32,21 @@ } }, "npm": { - "@algolia/autocomplete-core@1.17.7_algoliasearch@5.17.0": { + "@algolia/autocomplete-core@1.17.7_algoliasearch@5.18.0": { "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", "dependencies": [ "@algolia/autocomplete-plugin-algolia-insights", "@algolia/autocomplete-shared" ] }, - "@algolia/autocomplete-plugin-algolia-insights@1.17.7_search-insights@2.17.3_algoliasearch@5.17.0": { + "@algolia/autocomplete-plugin-algolia-insights@1.17.7_search-insights@2.17.3_algoliasearch@5.18.0": { "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", "dependencies": [ "@algolia/autocomplete-shared", "search-insights" ] }, - "@algolia/autocomplete-preset-algolia@1.17.7_@algolia+client-search@5.17.0_algoliasearch@5.17.0": { + "@algolia/autocomplete-preset-algolia@1.17.7_@algolia+client-search@5.18.0_algoliasearch@5.18.0": { "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", "dependencies": [ "@algolia/autocomplete-shared", @@ -54,15 +54,15 @@ "algoliasearch" ] }, - "@algolia/autocomplete-shared@1.17.7_@algolia+client-search@5.17.0_algoliasearch@5.17.0": { + "@algolia/autocomplete-shared@1.17.7_@algolia+client-search@5.18.0_algoliasearch@5.18.0": { "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", "dependencies": [ "@algolia/client-search", "algoliasearch" ] }, - "@algolia/client-abtesting@5.17.0": { - "integrity": "sha512-6+7hPdOEPfJqjWNYPRaVcttLLAtVqQyp1U7xBA1e1uSya1ivIr9FtS/GBr31mfvwk2N2yxV4W7itxuBtST8SWg==", + "@algolia/client-abtesting@5.18.0": { + "integrity": "sha512-DLIrAukjsSrdMNNDx1ZTks72o4RH/1kOn8Wx5zZm8nnqFexG+JzY4SANnCNEjnFQPJTTvC+KpgiNW/CP2lumng==", "dependencies": [ "@algolia/client-common", "@algolia/requester-browser-xhr", @@ -70,8 +70,8 @@ "@algolia/requester-node-http" ] }, - "@algolia/client-analytics@5.17.0": { - "integrity": "sha512-nhJ+elL8h0Fts3xD9261zE2NvTs7nPMe9/SfAgMnWnbvxmuhJn7ZymnBsfm2VkTDb4Dy810ZAdBfzYEk7PjlAw==", + "@algolia/client-analytics@5.18.0": { + "integrity": "sha512-0VpGG2uQW+h2aejxbG8VbnMCQ9ary9/ot7OASXi6OjE0SRkYQ/+pkW+q09+IScif3pmsVVYggmlMPtAsmYWHng==", "dependencies": [ "@algolia/client-common", "@algolia/requester-browser-xhr", @@ -79,11 +79,11 @@ "@algolia/requester-node-http" ] }, - "@algolia/client-common@5.17.0": { - "integrity": "sha512-9eC8i41/4xcQ/wI6fVM4LwC/ZGcDl3mToqjM0wTZzePWhXgRrdzOzqy/XgP+L1yYCDfkMFBZZsruNL5U8aEOag==" + "@algolia/client-common@5.18.0": { + "integrity": "sha512-X1WMSC+1ve2qlMsemyTF5bIjwipOT+m99Ng1Tyl36ZjQKTa54oajBKE0BrmM8LD8jGdtukAgkUhFoYOaRbMcmQ==" }, - "@algolia/client-insights@5.17.0": { - "integrity": "sha512-JL/vWNPUIuScsJubyC4aPHkpMftlK2qGqMiR2gy0rGvrh8v0w+ec6Ebq+efoFgE8wO55HJPTxiKeerE1DaQgvA==", + "@algolia/client-insights@5.18.0": { + "integrity": "sha512-FAJRNANUOSs/FgYOJ/Njqp+YTe4TMz2GkeZtfsw1TMiA5mVNRS/nnMpxas9771aJz7KTEWvK9GwqPs0K6RMYWg==", "dependencies": [ "@algolia/client-common", "@algolia/requester-browser-xhr", @@ -91,8 +91,8 @@ "@algolia/requester-node-http" ] }, - "@algolia/client-personalization@5.17.0": { - "integrity": "sha512-PkMUfww8QiRpyLkW4kzmc7IJDcW90sfUpnTgUOVlug5zEE2iv1ruHrJxdcNRTXkA0fgVpHu3oxXmCQL/ie2p7A==", + "@algolia/client-personalization@5.18.0": { + "integrity": "sha512-I2dc94Oiwic3SEbrRp8kvTZtYpJjGtg5y5XnqubgnA15AgX59YIY8frKsFG8SOH1n2rIhUClcuDkxYQNXJLg+w==", "dependencies": [ "@algolia/client-common", "@algolia/requester-browser-xhr", @@ -100,8 +100,8 @@ "@algolia/requester-node-http" ] }, - "@algolia/client-query-suggestions@5.17.0": { - "integrity": "sha512-bokfgPN2whetLuiX9NB6C6d7Eke+dvHuASOPiB+jdI8Z6hacLHkcJjYeZY4Mppj0/oJ1KlyNivj+8WNpZeGhYA==", + "@algolia/client-query-suggestions@5.18.0": { + "integrity": "sha512-x6XKIQgKFTgK/bMasXhghoEjHhmgoP61pFPb9+TaUJ32aKOGc65b12usiGJ9A84yS73UDkXS452NjyP50Knh/g==", "dependencies": [ "@algolia/client-common", "@algolia/requester-browser-xhr", @@ -109,8 +109,8 @@ "@algolia/requester-node-http" ] }, - "@algolia/client-search@5.17.0": { - "integrity": "sha512-alY3U79fiEvlR/0optgt1LZp9MfthXFnuEA4GYS81svozDOF61gdvxgBjt6SYtmskmTQQZDWVgakvUvvHrDzMw==", + "@algolia/client-search@5.18.0": { + "integrity": "sha512-qI3LcFsVgtvpsBGR7aNSJYxhsR+Zl46+958ODzg8aCxIcdxiK7QEVLMJMZAR57jGqW0Lg/vrjtuLFDMfSE53qA==", "dependencies": [ "@algolia/client-common", "@algolia/requester-browser-xhr", @@ -118,8 +118,8 @@ "@algolia/requester-node-http" ] }, - "@algolia/ingestion@1.17.0": { - "integrity": "sha512-9+mO+FbIpWz6izh1lXzON9BcenBKx4K3qVjSWiFFmL8nv+7b7zpGq++LXWr/Lxv/bZ9+D71Go6QVL6AZQhFOmg==", + "@algolia/ingestion@1.18.0": { + "integrity": "sha512-bGvJg7HnGGm+XWYMDruZXWgMDPVt4yCbBqq8DM6EoaMBK71SYC4WMfIdJaw+ABqttjBhe6aKNRkWf/bbvYOGyw==", "dependencies": [ "@algolia/client-common", "@algolia/requester-browser-xhr", @@ -127,8 +127,8 @@ "@algolia/requester-node-http" ] }, - "@algolia/monitoring@1.17.0": { - "integrity": "sha512-Db7Qh51zVchmHa8d9nQFzTz2Ta6H2D4dpCnPj1giC/LE6UG/6e3iOnRxUzV+9ZR7etHKIrri2hbnkyNrvbqA9A==", + "@algolia/monitoring@1.18.0": { + "integrity": "sha512-lBssglINIeGIR+8KyzH05NAgAmn1BCrm5D2T6pMtr/8kbTHvvrm1Zvcltc5dKUQEFyyx3J5+MhNc7kfi8LdjVw==", "dependencies": [ "@algolia/client-common", "@algolia/requester-browser-xhr", @@ -136,8 +136,8 @@ "@algolia/requester-node-http" ] }, - "@algolia/recommend@5.17.0": { - "integrity": "sha512-7vM4+mfuLYbslj8+RNsP/ISwY7izu5HcQqQhA0l+q3EZRHF+PBeRaJXc3S1N0fTRxj8ystvwXWZPmjssB/xMLw==", + "@algolia/recommend@5.18.0": { + "integrity": "sha512-uSnkm0cdAuFwdMp4pGT5vHVQ84T6AYpTZ3I0b3k/M3wg4zXDhl3aCiY8NzokEyRLezz/kHLEEcgb/tTTobOYVw==", "dependencies": [ "@algolia/client-common", "@algolia/requester-browser-xhr", @@ -145,20 +145,20 @@ "@algolia/requester-node-http" ] }, - "@algolia/requester-browser-xhr@5.17.0": { - "integrity": "sha512-bXSiPL2R08s4e9qvNZsJA0bXZeyWH2A5D4shS8kRT22b8GgjtnGTuoZmi6MxtKOEaN0lpHPbjvjXAO7UIOhDog==", + "@algolia/requester-browser-xhr@5.18.0": { + "integrity": "sha512-1XFjW0C3pV0dS/9zXbV44cKI+QM4ZIz9cpatXpsjRlq6SUCpLID3DZHsXyE6sTb8IhyPaUjk78GEJT8/3hviqg==", "dependencies": [ "@algolia/client-common" ] }, - "@algolia/requester-fetch@5.17.0": { - "integrity": "sha512-mjJ6Xv7TlDDoZ6RLKrEzH1ved3g2GAq3YJjb94bA639INfxK1HM8A/wCAFSZ8ye+QM/jppwauDXe1PENkuareQ==", + "@algolia/requester-fetch@5.18.0": { + "integrity": "sha512-0uodeNdAHz1YbzJh6C5xeQ4T6x5WGiUxUq3GOaT/R4njh5t78dq+Rb187elr7KtnjUmETVVuCvmEYaThfTHzNg==", "dependencies": [ "@algolia/client-common" ] }, - "@algolia/requester-node-http@5.17.0": { - "integrity": "sha512-Z2BXTR7BctlGPNig21k2wf/5nlH+96lU2UElzXTKiptyn2iM8lDU8zdO+dRll0AxQUxUGWEnkBysst9xL3S2cg==", + "@algolia/requester-node-http@5.18.0": { + "integrity": "sha512-tZCqDrqJ2YE2I5ukCQrYN8oiF6u3JIdCxrtKq+eniuLkjkO78TKRnXrVcKZTmfFJyyDK8q47SfDcHzAA3nHi6w==", "dependencies": [ "@algolia/client-common" ] @@ -182,10 +182,10 @@ "@babel/helper-validator-identifier" ] }, - "@deno/vite-plugin@1.0.2_vite@6.0.3": { + "@deno/vite-plugin@1.0.2_vite@6.0.5": { "integrity": "sha512-ZaC5W1yCb1xdRURU5qHFHrxZABr1tC68tS73/YhXUUbe9nuytCV4CG/dpLxMkxS64Fs3YwcqEz2k1eAqE9W5SQ==", "dependencies": [ - "vite@6.0.3" + "vite@6.0.5" ] }, "@dependents/detective-less@5.0.0": { @@ -195,18 +195,18 @@ "node-source-walk" ] }, - "@docsearch/css@3.8.0": { - "integrity": "sha512-pieeipSOW4sQ0+bE5UFC51AOZp9NGxg89wAlZ1BAQFaiRAGK1IKUaPQ0UGZeNctJXyqZ1UvBtOQh2HH+U5GtmA==" + "@docsearch/css@3.8.2": { + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==" }, - "@docsearch/js@3.8.0": { - "integrity": "sha512-PVuV629f5UcYRtBWqK7ID6vNL5647+2ADJypwTjfeBIrJfwPuHtzLy39hMGMfFK+0xgRyhTR0FZ83EkdEraBlg==", + "@docsearch/js@3.8.2": { + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", "dependencies": [ "@docsearch/react", "preact" ] }, - "@docsearch/react@3.8.0_algoliasearch@5.17.0": { - "integrity": "sha512-WnFK720+iwTVt94CxY3u+FgX6exb3BfN5kE9xUY6uuAH/9W/UFboBZFLlrw/zxFRHoHZCOXRtOylsXF+6LHI+Q==", + "@docsearch/react@3.8.2_algoliasearch@5.18.0": { + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", "dependencies": [ "@algolia/autocomplete-core", "@algolia/autocomplete-preset-algolia", @@ -355,16 +355,16 @@ "@esbuild/win32-x64@0.24.0": { "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==" }, - "@gerrit0/mini-shiki@1.24.3": { - "integrity": "sha512-odxt2MTJvlT5NUmGOPXz5OW4D8SFL2+KeXBkmjVtdOXfA5aRZX6jgP/17jp6wu2r3KdW0wx3Bk5HDehgeV1Yng==", + "@gerrit0/mini-shiki@1.24.4": { + "integrity": "sha512-YEHW1QeAg6UmxEmswiQbOVEg1CW22b1XUD/lNTliOsu0LD0wqoyleFMnmbTp697QE0pcadQiR5cVtbbAPncvpw==", "dependencies": [ "@shikijs/engine-oniguruma", "@shikijs/types", "@shikijs/vscode-textmate" ] }, - "@iconify-json/simple-icons@1.2.15": { - "integrity": "sha512-4vxMQwkjsbjVIVGsPjKBnLMqAXu4wSlHmeN35KaJLK0UJNUj/ef6ES5c4bT/U4bSZjD2oZqOjOWTPD+HCrSUkg==", + "@iconify-json/simple-icons@1.2.17": { + "integrity": "sha512-1vXbM6a6HV2rwXxu8ptD2OYhqrqX0ZZRepOg7nIjkvKlKq90Iici4X++A8h36bEVlV2wGjqx8uVYB0pwnPZVSw==", "dependencies": [ "@iconify/types" ] @@ -433,65 +433,65 @@ "@pkgjs/parseargs@0.11.0": { "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==" }, - "@rollup/rollup-android-arm-eabi@4.28.1": { - "integrity": "sha512-2aZp8AES04KI2dy3Ss6/MDjXbwBzj+i0GqKtWXgw2/Ma6E4jJvujryO6gJAghIRVz7Vwr9Gtl/8na3nDUKpraQ==" + "@rollup/rollup-android-arm-eabi@4.29.1": { + "integrity": "sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==" }, - "@rollup/rollup-android-arm64@4.28.1": { - "integrity": "sha512-EbkK285O+1YMrg57xVA+Dp0tDBRB93/BZKph9XhMjezf6F4TpYjaUSuPt5J0fZXlSag0LmZAsTmdGGqPp4pQFA==" + "@rollup/rollup-android-arm64@4.29.1": { + "integrity": "sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==" }, - "@rollup/rollup-darwin-arm64@4.28.1": { - "integrity": "sha512-prduvrMKU6NzMq6nxzQw445zXgaDBbMQvmKSJaxpaZ5R1QDM8w+eGxo6Y/jhT/cLoCvnZI42oEqf9KQNYz1fqQ==" + "@rollup/rollup-darwin-arm64@4.29.1": { + "integrity": "sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==" }, - "@rollup/rollup-darwin-x64@4.28.1": { - "integrity": "sha512-WsvbOunsUk0wccO/TV4o7IKgloJ942hVFK1CLatwv6TJspcCZb9umQkPdvB7FihmdxgaKR5JyxDjWpCOp4uZlQ==" + "@rollup/rollup-darwin-x64@4.29.1": { + "integrity": "sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==" }, - "@rollup/rollup-freebsd-arm64@4.28.1": { - "integrity": "sha512-HTDPdY1caUcU4qK23FeeGxCdJF64cKkqajU0iBnTVxS8F7H/7BewvYoG+va1KPSL63kQ1PGNyiwKOfReavzvNA==" + "@rollup/rollup-freebsd-arm64@4.29.1": { + "integrity": "sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==" }, - "@rollup/rollup-freebsd-x64@4.28.1": { - "integrity": "sha512-m/uYasxkUevcFTeRSM9TeLyPe2QDuqtjkeoTpP9SW0XxUWfcYrGDMkO/m2tTw+4NMAF9P2fU3Mw4ahNvo7QmsQ==" + "@rollup/rollup-freebsd-x64@4.29.1": { + "integrity": "sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==" }, - "@rollup/rollup-linux-arm-gnueabihf@4.28.1": { - "integrity": "sha512-QAg11ZIt6mcmzpNE6JZBpKfJaKkqTm1A9+y9O+frdZJEuhQxiugM05gnCWiANHj4RmbgeVJpTdmKRmH/a+0QbA==" + "@rollup/rollup-linux-arm-gnueabihf@4.29.1": { + "integrity": "sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==" }, - "@rollup/rollup-linux-arm-musleabihf@4.28.1": { - "integrity": "sha512-dRP9PEBfolq1dmMcFqbEPSd9VlRuVWEGSmbxVEfiq2cs2jlZAl0YNxFzAQS2OrQmsLBLAATDMb3Z6MFv5vOcXg==" + "@rollup/rollup-linux-arm-musleabihf@4.29.1": { + "integrity": "sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==" }, - "@rollup/rollup-linux-arm64-gnu@4.28.1": { - "integrity": "sha512-uGr8khxO+CKT4XU8ZUH1TTEUtlktK6Kgtv0+6bIFSeiSlnGJHG1tSFSjm41uQ9sAO/5ULx9mWOz70jYLyv1QkA==" + "@rollup/rollup-linux-arm64-gnu@4.29.1": { + "integrity": "sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==" }, - "@rollup/rollup-linux-arm64-musl@4.28.1": { - "integrity": "sha512-QF54q8MYGAqMLrX2t7tNpi01nvq5RI59UBNx+3+37zoKX5KViPo/gk2QLhsuqok05sSCRluj0D00LzCwBikb0A==" + "@rollup/rollup-linux-arm64-musl@4.29.1": { + "integrity": "sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==" }, - "@rollup/rollup-linux-loongarch64-gnu@4.28.1": { - "integrity": "sha512-vPul4uodvWvLhRco2w0GcyZcdyBfpfDRgNKU+p35AWEbJ/HPs1tOUrkSueVbBS0RQHAf/A+nNtDpvw95PeVKOA==" + "@rollup/rollup-linux-loongarch64-gnu@4.29.1": { + "integrity": "sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==" }, - "@rollup/rollup-linux-powerpc64le-gnu@4.28.1": { - "integrity": "sha512-pTnTdBuC2+pt1Rmm2SV7JWRqzhYpEILML4PKODqLz+C7Ou2apEV52h19CR7es+u04KlqplggmN9sqZlekg3R1A==" + "@rollup/rollup-linux-powerpc64le-gnu@4.29.1": { + "integrity": "sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==" }, - "@rollup/rollup-linux-riscv64-gnu@4.28.1": { - "integrity": "sha512-vWXy1Nfg7TPBSuAncfInmAI/WZDd5vOklyLJDdIRKABcZWojNDY0NJwruY2AcnCLnRJKSaBgf/GiJfauu8cQZA==" + "@rollup/rollup-linux-riscv64-gnu@4.29.1": { + "integrity": "sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==" }, - "@rollup/rollup-linux-s390x-gnu@4.28.1": { - "integrity": "sha512-/yqC2Y53oZjb0yz8PVuGOQQNOTwxcizudunl/tFs1aLvObTclTwZ0JhXF2XcPT/zuaymemCDSuuUPXJJyqeDOg==" + "@rollup/rollup-linux-s390x-gnu@4.29.1": { + "integrity": "sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==" }, - "@rollup/rollup-linux-x64-gnu@4.28.1": { - "integrity": "sha512-fzgeABz7rrAlKYB0y2kSEiURrI0691CSL0+KXwKwhxvj92VULEDQLpBYLHpF49MSiPG4sq5CK3qHMnb9tlCjBw==" + "@rollup/rollup-linux-x64-gnu@4.29.1": { + "integrity": "sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==" }, - "@rollup/rollup-linux-x64-musl@4.28.1": { - "integrity": "sha512-xQTDVzSGiMlSshpJCtudbWyRfLaNiVPXt1WgdWTwWz9n0U12cI2ZVtWe/Jgwyv/6wjL7b66uu61Vg0POWVfz4g==" + "@rollup/rollup-linux-x64-musl@4.29.1": { + "integrity": "sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==" }, - "@rollup/rollup-win32-arm64-msvc@4.28.1": { - "integrity": "sha512-wSXmDRVupJstFP7elGMgv+2HqXelQhuNf+IS4V+nUpNVi/GUiBgDmfwD0UGN3pcAnWsgKG3I52wMOBnk1VHr/A==" + "@rollup/rollup-win32-arm64-msvc@4.29.1": { + "integrity": "sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==" }, - "@rollup/rollup-win32-ia32-msvc@4.28.1": { - "integrity": "sha512-ZkyTJ/9vkgrE/Rk9vhMXhf8l9D+eAhbAVbsGsXKy2ohmJaWg0LPQLnIxRdRp/bKyr8tXuPlXhIoGlEB5XpJnGA==" + "@rollup/rollup-win32-ia32-msvc@4.29.1": { + "integrity": "sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==" }, - "@rollup/rollup-win32-x64-msvc@4.28.1": { - "integrity": "sha512-ZvK2jBafvttJjoIdKm/Q/Bh7IJ1Ose9IBOwpOXcOvW3ikGTQGmKDgxTC6oCAzW6PynbkKP8+um1du81XJHZ0JA==" + "@rollup/rollup-win32-x64-msvc@4.29.1": { + "integrity": "sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==" }, - "@shikijs/core@1.24.2": { - "integrity": "sha512-BpbNUSKIwbKrRRA+BQj0BEWSw+8kOPKDJevWeSE/xIqGX7K0xrCZQ9kK0nnEQyrzsUoka1l81ZtJ2mGaCA32HQ==", + "@shikijs/core@1.24.4": { + "integrity": "sha512-jjLsld+xEEGYlxAXDyGwWsKJ1sw5Pc1pnp4ai2ORpjx2UX08YYTC0NNqQYO1PaghYaR+PvgMOGuvzw2he9sk0Q==", "dependencies": [ "@shikijs/engine-javascript", "@shikijs/engine-oniguruma", @@ -501,29 +501,29 @@ "hast-util-to-html" ] }, - "@shikijs/engine-javascript@1.24.2": { - "integrity": "sha512-EqsmYBJdLEwEiO4H+oExz34a5GhhnVp+jH9Q/XjPjmBPc6TE/x4/gD0X3i0EbkKKNqXYHHJTJUpOLRQNkEzS9Q==", + "@shikijs/engine-javascript@1.24.4": { + "integrity": "sha512-TClaQOLvo9WEMJv6GoUsykQ6QdynuKszuORFWCke8qvi6PeLm7FcD9+7y45UenysxEWYpDL5KJaVXTngTE+2BA==", "dependencies": [ "@shikijs/types", "@shikijs/vscode-textmate", "oniguruma-to-es" ] }, - "@shikijs/engine-oniguruma@1.24.2": { - "integrity": "sha512-ZN6k//aDNWRJs1uKB12pturKHh7GejKugowOFGAuG7TxDRLod1Bd5JhpOikOiFqPmKjKEPtEA6mRCf7q3ulDyQ==", + "@shikijs/engine-oniguruma@1.24.4": { + "integrity": "sha512-Do2ry6flp2HWdvpj2XOwwa0ljZBRy15HKZITzPcNIBOGSeprnA8gOooA/bLsSPuy8aJBa+Q/r34dMmC3KNL/zw==", "dependencies": [ "@shikijs/types", "@shikijs/vscode-textmate" ] }, - "@shikijs/transformers@1.24.2": { - "integrity": "sha512-cIwn8YSwO3bsWKJ+pezcXY1Vq0BVwvuLes1TZSC5+Awi6Tsfqhf3vBahOIqZK1rraMKOti2VEAEF/95oXMig1w==", + "@shikijs/transformers@1.24.4": { + "integrity": "sha512-0jq5p9WLB7ToM/O7RWfxuIwirTJbIQsUR06jxdG3h3CEuO5m7ik8GnDsxwHhyIEfgJSZczSnVUZWFrNKy5It6g==", "dependencies": [ "shiki" ] }, - "@shikijs/types@1.24.2": { - "integrity": "sha512-bdeWZiDtajGLG9BudI0AHet0b6e7FbR0EsE4jpGaI0YwHm/XJunI9+3uZnzFtX65gsyJ6ngCIWUfA4NWRPnBkQ==", + "@shikijs/types@1.24.4": { + "integrity": "sha512-0r0XU7Eaow0PuDxuWC1bVqmWCgm3XqizIaT7SM42K03vc69LGooT0U8ccSR44xP/hGlNx4FKhtYpV+BU6aaKAA==", "dependencies": [ "@shikijs/vscode-textmate", "@types/hast" @@ -591,7 +591,7 @@ "@typescript-eslint/types@7.18.0": { "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==" }, - "@typescript-eslint/typescript-estree@7.18.0_typescript@5.6.3": { + "@typescript-eslint/typescript-estree@7.18.0_typescript@5.7.2": { "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", "dependencies": [ "@typescript-eslint/types", @@ -743,8 +743,8 @@ "vue-demi" ] }, - "algoliasearch@5.17.0": { - "integrity": "sha512-BpuFprDFc3Pe9a1ZXLzLeqZ+l8Ur37AfzBswkOB4LwikqnRPbIGdluT/nFc/Xk+u/QMxMzUlTN+izqQJVb5vYA==", + "algoliasearch@5.18.0": { + "integrity": "sha512-/tfpK2A4FpS0o+S78o3YSdlqXr0MavJIDlFK3XZrlXLy7vaRXJvW5jYg3v5e/wCaF8y0IpMjkYLhoV6QqfpOgw==", "dependencies": [ "@algolia/client-abtesting", "@algolia/client-analytics", @@ -989,7 +989,7 @@ "detective-stylus@5.0.0": { "integrity": "sha512-KMHOsPY6aq3196WteVhkY5FF+6Nnc/r7q741E+Gq+Ax9mhE2iwj8Hlw8pl+749hPDRDBHZ2WlgOjP+twIG61vQ==" }, - "detective-typescript@13.0.0_typescript@5.6.3": { + "detective-typescript@13.0.0_typescript@5.7.2": { "integrity": "sha512-tcMYfiFWoUejSbvSblw90NDt76/4mNftYCX0SMnVRYzSXv8Fvo06hi4JOPdNvVNxRtCAKg3MJ3cBJh+ygEMH+A==", "dependencies": [ "@typescript-eslint/typescript-estree", @@ -998,7 +998,7 @@ "typescript" ] }, - "detective-vue2@2.1.0_typescript@5.6.3": { + "detective-vue2@2.1.0_typescript@5.7.2": { "integrity": "sha512-IHQVhwk7dKaJ+GHBsL27mS9NRO1/vLZJPSODqtJgKquij0/UL8NvrbXbADbYeTkwyh1ReW/v9u9IRyEO5dvGZg==", "dependencies": [ "@dependents/detective-less", @@ -1035,8 +1035,8 @@ "emoji-regex@9.2.2": { "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" }, - "enhanced-resolve@5.17.1": { - "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", + "enhanced-resolve@5.18.0": { + "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", "dependencies": [ "graceful-fs", "tapable" @@ -1145,8 +1145,8 @@ "micromatch" ] }, - "fastq@1.17.1": { - "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "fastq@1.18.0": { + "integrity": "sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==", "dependencies": [ "reusify" ] @@ -1271,8 +1271,8 @@ "function-bind" ] }, - "hast-util-to-html@9.0.3": { - "integrity": "sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==", + "hast-util-to-html@9.0.4": { + "integrity": "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==", "dependencies": [ "@types/hast", "@types/unist", @@ -1318,8 +1318,8 @@ "ini@1.3.8": { "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, - "is-core-module@2.15.1": { - "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "is-core-module@2.16.1": { + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dependencies": [ "hasown" ] @@ -1422,8 +1422,8 @@ "walkdir" ] }, - "magic-string@0.30.15": { - "integrity": "sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw==", + "magic-string@0.30.17": { + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dependencies": [ "@jridgewell/sourcemap-codec" ] @@ -1563,8 +1563,8 @@ "mimic-fn" ] }, - "oniguruma-to-es@0.7.0": { - "integrity": "sha512-HRaRh09cE0gRS3+wi2zxekB+I5L8C/gN60S+vb11eADHUaB/q4u8wGGOX3GvwvitG8ixaeycZfeoyruKQzUgNg==", + "oniguruma-to-es@0.8.1": { + "integrity": "sha512-dekySTEvCxCj0IgKcA2uUCO/e4ArsqpucDPcX26w9ajx+DvMWLc5eZeJaRQkd7oC/+rwif5gnT900tA34uN9Zw==", "dependencies": [ "emoji-regex-xs", "regex", @@ -1639,10 +1639,10 @@ "source-map-js" ] }, - "preact@10.25.2": { - "integrity": "sha512-GEts1EH3oMnqdOIeXhlbBSddZ9nrINd070WBOiPO2ous1orrKGUM4SMDbwyjSWD1iMS2dBvaDjAa5qUhz3TXqw==" + "preact@10.25.3": { + "integrity": "sha512-dzQmIFtM970z+fP9ziQ3yG4e3ULIbwZzJ734vaMVUTaKQ2+Ru1Ou/gjshOYVHCcd1rpAelC6ngjvjDXph98unQ==" }, - "precinct@12.1.2_postcss@8.4.49_typescript@5.6.3": { + "precinct@12.1.2_postcss@8.4.49_typescript@5.7.2": { "integrity": "sha512-x2qVN3oSOp3D05ihCd8XdkIPuEQsyte7PSxzLqiRgktu79S5Dr1I75/S+zAup8/0cwjoiJTQztE9h0/sWp9bJQ==", "dependencies": [ "@dependents/detective-less", @@ -1713,8 +1713,8 @@ "util-deprecate" ] }, - "regex-recursion@4.3.0": { - "integrity": "sha512-5LcLnizwjcQ2ALfOj95MjcatxyqF5RPySx9yT+PaXu3Gox2vyAtLDjHB8NTJLtMGkvyau6nI3CfpwFCjPUIs/A==", + "regex-recursion@5.0.0": { + "integrity": "sha512-UwyOqeobrCCqTXPcsSqH4gDhOjD5cI/b8kjngWgSZbxYh5yVjAwTjO5+hAuPRNiuR70+5RlWSs+U9PVcVcW9Lw==", "dependencies": [ "regex-utilities" ] @@ -1741,8 +1741,8 @@ "resolve-dependency-path@4.0.0": { "integrity": "sha512-hlY1SybBGm5aYN3PC4rp15MzsJLM1w+MEA/4KU3UBPfz4S0lL3FL6mgv7JgaA8a+ZTeEQAiF1a1BuN2nkqiIlg==" }, - "resolve@1.22.8": { - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "resolve@1.22.10": { + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", "dependencies": [ "is-core-module", "path-parse", @@ -1762,8 +1762,8 @@ "rfdc@1.4.1": { "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==" }, - "rollup@4.28.1": { - "integrity": "sha512-61fXYl/qNVinKmGSTHAZ6Yy8I3YIJC/r2m9feHo6SwVAVcLT5MPwOUFe7EuURA/4m0NR8lXG4BBXuo/IZEsjMg==", + "rollup@4.29.1": { + "integrity": "sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==", "dependencies": [ "@rollup/rollup-android-arm-eabi", "@rollup/rollup-android-arm64", @@ -1825,8 +1825,8 @@ "shebang-regex@3.0.0": { "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, - "shiki@1.24.2": { - "integrity": "sha512-TR1fi6mkRrzW+SKT5G6uKuc32Dj2EEa7Kj0k8kGqiBINb+C1TiflVOiT9ta6GqOJtC4fraxO5SLUaKBcSY38Fg==", + "shiki@1.24.4": { + "integrity": "sha512-aVGSFAOAr1v26Hh/+GBIsRVDWJ583XYV7CuNURKRWh9gpGv4OdbisZGq96B9arMYTZhTQkmRF5BrShOSTvNqhw==", "dependencies": [ "@shikijs/core", "@shikijs/engine-javascript", @@ -1960,7 +1960,7 @@ "trim-lines@3.0.1": { "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==" }, - "ts-api-utils@1.4.3_typescript@5.6.3": { + "ts-api-utils@1.4.3_typescript@5.7.2": { "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", "dependencies": [ "typescript" @@ -1983,19 +1983,19 @@ "strip-bom" ] }, - "typedoc-plugin-markdown@4.3.2_typedoc@0.27.4__typescript@5.6.3": { - "integrity": "sha512-hCF3V0axzbzGDYFW21XigWIJQBOJ2ZRVWWs7X+e62ew/pXnvz7iKF/zVdkBm3w8Mk4bmXWp/FT0IF4Zn9uBRww==", + "typedoc-plugin-markdown@4.3.3_typedoc@0.27.4__typescript@5.7.2": { + "integrity": "sha512-kESCcNRzOcFJATLML2FoCfaTF9c0ujmbZ+UXsJvmNlFLS3v8tDKfDifreJXvXWa9d8gUcetZqOqFcZ/7+Ba34Q==", "dependencies": [ "typedoc" ] }, - "typedoc-vitepress-theme@1.1.1_typedoc-plugin-markdown@4.3.2__typedoc@0.27.4___typescript@5.6.3_typedoc@0.27.4__typescript@5.6.3": { + "typedoc-vitepress-theme@1.1.1_typedoc-plugin-markdown@4.3.3__typedoc@0.27.4___typescript@5.7.2_typedoc@0.27.4__typescript@5.7.2": { "integrity": "sha512-1UbhZdQIkGKLkIZCbw8putrel+Vo7KKFfd8RhQRSBgetUZGUJkum89kIyF3+Kzy+1nqE56/MLKVxpPgQYubYYg==", "dependencies": [ "typedoc-plugin-markdown" ] }, - "typedoc@0.27.4_typescript@5.6.3": { + "typedoc@0.27.4_typescript@5.7.2": { "integrity": "sha512-wXPQs1AYC2Crk+1XFpNuutLIkNWleokZf1UNf/X8w9KsMnirkvT+LzxTXDvfF6ug3TSLf3Xu5ZXRKGfoXPX7IA==", "dependencies": [ "@gerrit0/mini-shiki", @@ -2006,8 +2006,8 @@ "yaml" ] }, - "typescript@5.6.3": { - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==" + "typescript@5.7.2": { + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==" }, "uc.micro@2.1.0": { "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" @@ -2071,8 +2071,8 @@ "rollup" ] }, - "vite@6.0.3": { - "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==", + "vite@6.0.5": { + "integrity": "sha512-akD5IAH/ID5imgue2DYhzsEwCi0/4VKY31uhMLEYJwPP4TiUp8pL5PIK+Wo7H8qT8JY9i+pVfPydcFPYD1EL7g==", "dependencies": [ "esbuild@0.24.0", "fsevents", @@ -2179,7 +2179,7 @@ "npm:@vue/runtime-dom@^3.5.12", "npm:@vueuse/core@^11.2.0", "npm:ansi-to-html@~0.7.2", - "npm:monaco-editor@0.52", + "npm:monaco-editor@~0.52.2", "npm:typedoc-plugin-markdown@^4.2.10", "npm:typedoc-vitepress-theme@^1.1.1", "npm:typedoc@0.27.4", @@ -2188,6 +2188,11 @@ "npm:vue@^3.5.12" ] }, + "monaco-language-provider": { + "dependencies": [ + "npm:monaco-editor@~0.52.2" + ] + }, "quickjs": { "dependencies": [ "npm:quickjs-emscripten-core@0.31", diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 88ae9d6..a01eeff 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -11,6 +11,10 @@ const SIDEBAR_CONFIG: VitePressSidebarOptions = { useTitleFromFrontmatter: true, } +const workspacePath = new URL('../..', import.meta.url).pathname + +console.log({ workspacePath }) + export default defineConfig( withSidebar( { @@ -35,10 +39,22 @@ export default defineConfig( }, }, vite: { + plugins: [pluginDeno()], + server: { + fs: { + allow: [workspacePath], + }, + }, build: { - minify: false, + rollupOptions: { + watch: { + include: [workspacePath], + }, + }, + }, + ssr: { + noExternal: ['monaco-editor'], }, - plugins: [pluginDeno()], }, }, SIDEBAR_CONFIG, diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index bbf86b5..ac9bbfc 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -1,5 +1,14 @@ import DefaultTheme from 'vitepress/theme-without-fonts' import './custom.css' +import type { Theme } from 'vitepress' -export default DefaultTheme +import CodeRunner from '../../_/code-runner/index.vue' + +export default { + extends: DefaultTheme, + enhanceApp({ app }) { + // register your custom global components + app.component('code-runner', CodeRunner) + }, +} satisfies Theme diff --git a/docs/_/code-runner.vue b/docs/_/code-runner/index.vue similarity index 87% rename from docs/_/code-runner.vue rename to docs/_/code-runner/index.vue index 8184ed9..ed51488 100644 --- a/docs/_/code-runner.vue +++ b/docs/_/code-runner/index.vue @@ -1,6 +1,8 @@ - ## 지금 달빛약속 코드 실행해보기 - + diff --git "a/docs/language/1. \354\213\234\354\236\221\355\225\230\352\270\260.md" "b/docs/language/1. \354\213\234\354\236\221\355\225\230\352\270\260.md" index 1c2b110..4a2065b 100644 --- "a/docs/language/1. \354\213\234\354\236\221\355\225\230\352\270\260.md" +++ "b/docs/language/1. \354\213\234\354\236\221\355\225\230\352\270\260.md" @@ -1,5 +1,4 @@ - # 약속 (함수) ::: tip @@ -17,7 +13,7 @@ import CodeRunner from "../_/code-runner.vue" 약속을 정의할 때는 `약속` 키워드로 시작하며, 다음과 같은 형식으로 만듭니다: - @@ -33,7 +29,7 @@ import CodeRunner from "../_/code-runner.vue" 약속은 여러 개의 매개변수를 가질 수 있습니다. - @@ -42,19 +38,19 @@ import CodeRunner from "../_/code-runner.vue" 한국어에는 단어 마지막 음절의 받침 유무에 따라 어울리는 조사가 달라집니다. 그렇기에, 약속을 정의할 때 다음과 같은 어색한 표현을 만날 수 있습니다. - 한국어에서 '재혁'이라는 단어에 어울리는 목적격 조사는 '을'이지만, 약속이 '를'로 정의되었기 때문에 코드가 어색해 보입니다. 이러한 경우를 피하기 위해 약속을 정의할 때 조사 변형을 반영할 수 있습니다. - 약속을 정의할 때 `/`를 사이에 두고 여러 조사를 작성하면 됩니다. 한글자 조사 뿐만 아니라, 다음과 같은 경우도 가능합니다. - @@ -69,7 +65,7 @@ import CodeRunner from "../_/code-runner.vue" 직접 정의한 약속을 사용해 **섭씨 25도를 화씨로 변환해보세요.** - -import CodeRunner from "../_/code-runner.vue" const challenge = { output: "100", @@ -19,4 +18,4 @@ const challenge = { `100`을 보여주는 코드를 작성해보세요. - + diff --git "a/docs/language/3. \354\210\253\354\236\220\354\231\200 \354\227\260\354\202\260.md" "b/docs/language/3. \354\210\253\354\236\220\354\231\200 \354\227\260\354\202\260.md" index 8353fbe..8d01056 100644 --- "a/docs/language/3. \354\210\253\354\236\220\354\231\200 \354\227\260\354\202\260.md" +++ "b/docs/language/3. \354\210\253\354\236\220\354\231\200 \354\227\260\354\202\260.md" @@ -1,5 +1,4 @@ - # 변수 변수는 **값을 저장하는 공간**입니다. 값에 이름을 붙혀 저장하고 나중에 다시 사용할 수 있습니다. @@ -15,7 +11,7 @@ import CodeRunner from "../_/code-runner.vue" - 의미있는 이름으로 값을 표현할 때 - 계산 결과를 저장할 때 - ### 계산에서 변수 활용하기 - @@ -63,7 +59,7 @@ import CodeRunner from "../_/code-runner.vue" 변수를 사용하여 섭씨온도 **10도**를 화씨온도로 변환해서 보여주세요. 섭씨온도를 화씨온도로 바꾸려면 9를 곱하고 5로 나누고 32를 더하면 됩니다. - -import CodeRunner from "../_/code-runner.vue" - - # 문자 ::: tip @@ -10,32 +6,32 @@ import CodeRunner from "../_/code-runner.vue" 약속에서는 숫자뿐만 아니라 문자도 다룰 수 있습니다. 문자를 표현하려면 따옴표 안에 글을 작성하면 됩니다. 큰 따옴표(`"`)와 작은 따옴표(`'`)를 사용할 수 있습니다. - + ## 문자 이어붙이기 덧셈 기호(`+`)를 사용해 문자를 이어붙일 수 있습니다. - 문자와 숫자도 함께 이어붙일 수 있습니다. - 변수에 저장하지 않고 문자를 직접 사용할 수도 있습니다. - + ::: tip 단어 사이 띄어쓰기 문자를 이어붙일 때 단어 사이에 띄어쓰기를 넣으려면 공백 문자(" ")를 포함해야 합니다. - ::: @@ -44,13 +40,13 @@ import CodeRunner from "../_/code-runner.vue" 곱셈 기호를 사용해 문자를 반복할 수 있습니다. - + ## 직접 해보기 영희의 이름과 나이가 변수에 저장되어 있습니다. **"제 이름은 영희이고, 12살입니다."** 를 보여주는 코드를 작성해보세요. - -import CodeRunner from "../_/code-runner.vue" - - # 목록 약속에서는 여러 개의 값을 하나로 묶어서 저장할 수 있습니다. 이를 **목록**이라고 부릅니다. @@ -10,14 +6,14 @@ import CodeRunner from "../_/code-runner.vue" 목록은 대괄호(`[` `]`) 안에 값들을 쉼표(`,`)로 구분하여 만듭니다. - ## 목록의 값 가져오기 목록의 특정 위치에 있는 값을 가져오려면 대괄호 안에 위치 번호를 적으면 됩니다. - @@ -25,7 +21,7 @@ import CodeRunner from "../_/code-runner.vue" 변수와 동일한 문법을 사용하여 목록의 내용을 바꿉니다. - @@ -33,7 +29,7 @@ import CodeRunner from "../_/code-runner.vue" 여러 위치의 값을 한번에 가져오려면 대괄호 안에 목록을 넣으면 됩니다. 이 경우 새로운 목록이 만들어집니다. - @@ -41,7 +37,7 @@ import CodeRunner from "../_/code-runner.vue" 좋아하는 과일들이 담긴 목록이 있습니다. 두 번째 과일을 "배"로 바꾸고 전체 목록을 출력해보세요. - -import CodeRunner from "../_/code-runner.vue" - - # 조건문 조건문은 **주어진 조건에 따라 다른 동작을 실행**하는 구문입니다. 특정 조건이 참일 때와 거짓일 때 서로 다른 코드를 실행할 수 있습니다. @@ -48,7 +44,7 @@ import CodeRunner from "../_/code-runner.vue" ## 조건문 사용하기 - 80 이면 "합격" 보여주기 "축하드립니다!" 보여주기' /> @@ -57,7 +53,7 @@ import CodeRunner from "../_/code-runner.vue" 조건문이 거짓일 때 실행할 코드는 `아니면`을 사용해 작성합니다. - 80 이면 "합격" 보여주기 "축하드립니다!" 보여주기 @@ -70,7 +66,7 @@ import CodeRunner from "../_/code-runner.vue" 둘 이상의 조건을 가늠할 때는 `만약`, `아니면 만약`, `아니면`을 사용해 다중 조건문을 작성할 수 있습니다. -= 90 이면 그레이드: "A" @@ -86,7 +82,7 @@ import CodeRunner from "../_/code-runner.vue" 조건문 본문 안에서도 조건문을 또 작성할 수 있습니다. 이 경우를 겹친 조건문이라고 합니다. -= 80 이면 만약 실기시험_점수 >= 80 이면 @@ -108,7 +104,7 @@ import CodeRunner from "../_/code-runner.vue" 지혜가 해낸 팔굽혀펴기 개수가 변수에 저장되어 있습니다. **"지혜의 팔굽혀펴기 등급은 [등급]입니다."** 를 보여주는 코드를 작성해보세요. - -import CodeRunner from "../_/code-runner.vue" - - # 논리 연산자 여러 개의 참/거짓을 비교할 때는 논리 연산자를 사용합니다. 이를 사용해 복잡한 조건문을 만들 수 있습니다. @@ -14,7 +10,7 @@ import CodeRunner from "../_/code-runner.vue" `이고` 또는 `그리고` 연산자는 **두 조건이 모두 참일 때**만 참이 됩니다. - 80 이고 재현_영어점수 > 80\n 만약 재현_통과 이면 @@ -28,7 +24,7 @@ import CodeRunner from "../_/code-runner.vue" `이거나` 또는 `거나` 연산자는 두 조건 중 하나라도 참이면 참이 됩니다. - 80 이고 영어점수 > 80 @@ -63,7 +59,7 @@ import CodeRunner from "../_/code-runner.vue" 합격했다면 "합격", 아니면 "불합격"을 출력하는 코드를 작성해보세요. - -import CodeRunner from "../_/code-runner.vue" - - # 반복문 반복문은 **특정 코드를 여러 번 실행**하는 구문입니다. 동일한 작업을 반복해야 할 때 유용합니다. @@ -14,7 +10,7 @@ import CodeRunner from "../_/code-runner.vue" 종료 조건을 설정하지 않으면 무한 반복되어 프로그램이나 컴퓨터에 과부하가 걸립니다. 종료 조건을 설정하는 것을 잊지 마세요. ::: - @@ -33,7 +29,7 @@ import CodeRunner from "../_/code-runner.vue" 1부터 10까지의 숫자 중 짝수만 출력하는 코드를 작성해보세요. -`이 됩니다. diff --git "a/docs/monaco/\354\202\254\354\232\251 \353\260\251\353\262\225.md" "b/docs/monaco/\354\202\254\354\232\251 \353\260\251\353\262\225.md" new file mode 100644 index 0000000..5d97f66 --- /dev/null +++ "b/docs/monaco/\354\202\254\354\232\251 \353\260\251\353\262\225.md" @@ -0,0 +1,53 @@ +# Monaco Editor에서 달빛약속 사용하기 + +[Monaco Editor](https://microsoft.github.io/monaco-editor/)는 Microsoft에서 개발한 코드 에디터 컴포넌트입니다. `jsr:@dalbit-yaksok/monaco-language-provider` 패키지는 Monaco Editor에서 문법 강조, 키워드 자동 완성 등의 편의 기능을 제공합니다. + +| 기능 | 상태 | +| --------------------------------- | ---- | +| 문법 강조 (Syntax Highlighting) | ✅ | +| 키워드 자동 완성 (Autocompletion) | ✅ | +| 툴팁 (Tooltip) | ❌ | +| 문법 오류 보기 (Syntax Error) | ❌ | + +## 설치하기 + +`jsr:@dalbit-yaksok/monaco-language-provider` 패키지를 설치합니다. + +```bash +# Deno +deno add jsr:@dalbit-yaksok/monaco-language-provider + +# NPM +npx jsr add @dalbit-yaksok/monaco-language-provider + +# Yarn +yarn dlx jsr add @dalbit-yaksok/monaco-language-provider + +# Pnpm +pnpm dlx jsr add @dalbit-yaksok/monaco-language-provider + +# Bun +bunx jsr add @dalbit-yaksok/monaco-language-provider +``` + +## 사용하기 + +`@dalbit-yaksok/monaco-language-provider` 패키지를 불러와 Monaco Editor에 적용합니다. + +Monaco Editor에서 불러온 languages 객체, editor의 인스턴스가 필요합니다. + +```ts{2-5,7-8,11,13} +import { languages, editor } from 'monaco-editor' +import { + DalbitYaksokApplier, + LANG_ID, +} from '@dalbit-yaksok/monaco-language-provider' + +const languageProvider = new DalbitYaksokApplier(code.value) +languageProvider.register(languages) + +const editorInstance = editor.create(element, { + language: LANG_ID, +}) +languageProvider.configEditor(editorInstance) +``` diff --git a/monaco-language-provider/ast-to-colorize/declare-function.ts b/monaco-language-provider/ast-to-colorize/declare-function.ts new file mode 100644 index 0000000..bc70b0e --- /dev/null +++ b/monaco-language-provider/ast-to-colorize/declare-function.ts @@ -0,0 +1,46 @@ +import { Token, TOKEN_TYPE } from '@dalbit-yaksok/core' +import { SCOPE } from './scope.ts' + +export function parseFunctionDeclareHeader(tokens: Token[]) { + const newLineIndex = tokens.findIndex( + (token) => token.type === TOKEN_TYPE.NEW_LINE, + ) + + const functionHeader = tokens.slice(2, newLineIndex) + + const colorParts = functionHeader.map((token, index) => { + const prevToken = functionHeader[index - 1] + + if (prevToken?.type === TOKEN_TYPE.OPENING_PARENTHESIS) { + return { + position: token.position, + scopes: SCOPE.VARIABLE_NAME, + } + } + + if (token.type === TOKEN_TYPE.IDENTIFIER) { + return { + position: token.position, + scopes: SCOPE.CALLABLE, + } + } + + return { + position: token.position, + scopes: SCOPE.PARENTHESIS, + } + }) + + const fixedPart = [ + { + position: tokens[0].position, + scopes: SCOPE.KEYWORD, + }, + { + position: tokens[1].position, + scopes: SCOPE.PUNCTUATION, + }, + ] + + return fixedPart.concat(colorParts) +} diff --git a/monaco-language-provider/ast-to-colorize/get-comment-color-part.ts b/monaco-language-provider/ast-to-colorize/get-comment-color-part.ts new file mode 100644 index 0000000..6682919 --- /dev/null +++ b/monaco-language-provider/ast-to-colorize/get-comment-color-part.ts @@ -0,0 +1,16 @@ +import { TOKEN_TYPE, type Token } from '@dalbit-yaksok/core' +import { ColorPart } from '../type.ts' +import { SCOPE } from './scope.ts' + +export function getCommentColorParts(tokens: Token[]): ColorPart[] { + const commentTokens = tokens.filter( + (token) => token.type === TOKEN_TYPE.LINE_COMMENT, + ) + + const colorParts = commentTokens.map((token) => ({ + position: token.position, + scopes: SCOPE.COMMENT, + })) + + return colorParts +} diff --git a/monaco-language-provider/ast-to-colorize/index.ts b/monaco-language-provider/ast-to-colorize/index.ts new file mode 100644 index 0000000..f4737db --- /dev/null +++ b/monaco-language-provider/ast-to-colorize/index.ts @@ -0,0 +1,417 @@ +import { + Block, + DeclareFunction, + EOL, + Formula, + FunctionInvoke, + Identifier, + IfStatement, + IndexFetch, + ListLiteral, + ListLoop, + Node, + NumberLiteral, + Operator, + Print, + Return, + SetToIndex, + SetVariable, + StringLiteral, + TOKEN_TYPE, + ValueWithParenthesis, +} from '@dalbit-yaksok/core' +import { ColorPart } from '../type.ts' +import { SCOPE } from './scope.ts' +import { parseFunctionDeclareHeader } from './declare-function.ts' +import { parseListLoopHeader } from './list-loop.ts' + +/** + * 코드 에디터에서 문법 강조 기능을 구현할 수 있도록, AST 노드를 색상 토큰으로 변환합니다. `Node`를 받아서 `ColorPart[]`를 반환합니다. + * + * @example + * ```ts + * const code = `"안녕!" 보여주기` + * const { ast } = new CodeFile(code) + * + * const colorTokens = nodeToColorTokens(ast) // [!code highlight] + * console.log(colorTokens) + * ``` + * + * @param node 색상 토큰으로 변환할 AST 노드 + * @returns {ColorPart[]} 추출된 색상 토큰 + */ +function node(node: Node): ColorPart[] { + if (node instanceof Block) { + return block(node) + } + + if (node instanceof DeclareFunction) { + return declareFunction(node) + } + + if (node instanceof SetVariable) { + return setVariable(node) + } + + if (node instanceof Identifier) { + return identifier(node) + } + + if (node instanceof Print) { + return visitPrint(node) + } + + if (node instanceof Formula) { + return formula(node) + } + + if (node instanceof Operator) { + return operator(node) + } + + if (node instanceof ValueWithParenthesis) { + return valueWithParenthesis(node) + } + + if (node instanceof NumberLiteral) { + return numberLiteral(node) + } + + if (node instanceof FunctionInvoke) { + return functionInvoke(node) + } + + if (node instanceof StringLiteral) { + return stringLiteral(node) + } + + if (node instanceof IfStatement) { + return ifStatement(node) + } + + if (node instanceof ListLoop) { + return listLoop(node) + } + + if (node instanceof ListLiteral) { + return listLiteral(node) + } + + if (node instanceof IndexFetch) { + return indexFetch(node) + } + + if (node instanceof SetToIndex) { + return setToIndex(node) + } + + if (node instanceof Return) { + return visitReturn(node) + } + + if (node instanceof EOL) { + return [] + } + + console.log('Unknown node:', node) + + return [] +} + +function block(ast: Block): ColorPart[] { + return ast.children.flatMap(node) +} + +function declareFunction(current: DeclareFunction) { + const headerParts: ColorPart[] = parseFunctionDeclareHeader(current.tokens) + return headerParts.concat(block(current.body)) +} + +function setVariable(current: SetVariable) { + const firstIdentifier = current.tokens.find( + (token) => token.type === TOKEN_TYPE.IDENTIFIER, + ) + + const firstColon = current.tokens.find( + (token) => token.type === TOKEN_TYPE.COLON, + ) + + const variableName: ColorPart[] = [ + { + position: firstIdentifier!.position, + scopes: SCOPE.VARIABLE_NAME, + }, + { + position: firstColon!.position, + scopes: SCOPE.PUNCTUATION, + }, + ] + + return variableName.concat(node(current.value)) +} + +function identifier(current: Identifier) { + return [ + { + position: current.tokens[0].position, + scopes: SCOPE.IDENTIFIER, + }, + ] +} + +function visitPrint(current: Print) { + const child = node(current.value) + + return child.concat([ + { + position: current.tokens.slice(-1)[0].position, + scopes: SCOPE.KEYWORD, + }, + ]) +} + +function formula(current: Formula) { + return current.terms.flatMap(node) +} + +function operator(current: Operator) { + return [ + { + position: current.tokens[0].position, + scopes: SCOPE.OPERATOR, + }, + ] +} + +function valueWithParenthesis(current: ValueWithParenthesis): ColorPart[] { + return [ + { + position: current.tokens[0].position, + scopes: SCOPE.PARENTHESIS, + } as ColorPart, + ] + .concat(node(current.value)) + .concat([ + { + position: current.tokens.slice(-1)[0].position, + scopes: SCOPE.PARENTHESIS, + }, + ]) +} + +function numberLiteral(current: NumberLiteral): ColorPart[] { + return [ + { + position: current.tokens[0].position, + scopes: SCOPE.NUMBER, + }, + ] +} + +function functionInvoke(current: FunctionInvoke): ColorPart[] { + let isInParameter = false + + let colorParts: ColorPart[] = [] + + for (let i = 0; i < current.tokens.length; i++) { + const token = current.tokens[i] + + if (token.type === TOKEN_TYPE.OPENING_PARENTHESIS) { + isInParameter = true + + colorParts.push({ + position: token.position, + scopes: SCOPE.PARENTHESIS, + }) + + continue + } + + if (token.type === TOKEN_TYPE.CLOSING_PARENTHESIS) { + isInParameter = false + + colorParts.push({ + position: token.position, + scopes: SCOPE.PARENTHESIS, + }) + + continue + } + + if (isInParameter) { + continue + } + + colorParts.push({ + position: token.position, + scopes: SCOPE.CALLABLE, + }) + } + + for (const paramName in current.params) { + colorParts = colorParts.concat(node(current.params[paramName])) + } + + return colorParts +} + +function stringLiteral(current: StringLiteral): ColorPart[] { + return [ + { + position: current.tokens[0].position, + scopes: SCOPE.STRING, + }, + ] +} + +function ifStatement(current: IfStatement): ColorPart[] { + let colorParts: ColorPart[] = [ + { + position: current.tokens[0].position, + scopes: SCOPE.KEYWORD, + }, + ] + + // const newLineIndex = current.tokens.findIndex( + // (token) => token.type === TOKEN_TYPE.NEW_LINE, + // ) + + // const ifStatementHeaderEnder: ColorPart = { + // position: current.tokens[newLineIndex - 1].position, + // scopes: SCOPE.KEYWORD, + // } + + // colorParts.push(ifStatementHeaderEnder) + + for (const caseBlock of current.cases) { + if (caseBlock.condition) { + if (caseBlock.condition.tokens) { + const conditionStartTokenIndex = current.tokens.indexOf( + caseBlock.condition.tokens[0], + ) + + const conditionEndTokenIndex = current.tokens.indexOf( + caseBlock.condition.tokens.slice(-1)[0], + ) + + const beforeConditionIdentifier = current.tokens + .slice(0, conditionStartTokenIndex) + .findLast((token) => token.type === TOKEN_TYPE.IDENTIFIER) + + if (beforeConditionIdentifier) { + colorParts.push({ + position: beforeConditionIdentifier.position, + scopes: SCOPE.KEYWORD, + }) + } + + const afterConditionIdentifier = current.tokens + .slice(conditionEndTokenIndex + 1) + .find((token) => token.type === TOKEN_TYPE.IDENTIFIER) + + if (afterConditionIdentifier) { + colorParts.push({ + position: afterConditionIdentifier.position, + scopes: SCOPE.KEYWORD, + }) + } + } + + colorParts = colorParts.concat(node(caseBlock.condition)) + } else { + const bodyStartTokenIndex = current.tokens.indexOf( + caseBlock.body.tokens[0], + ) + + const beforeBodyIdentifier = current.tokens + .slice(0, bodyStartTokenIndex) + .findLast((token) => token.type === TOKEN_TYPE.IDENTIFIER) + + if (beforeBodyIdentifier) { + colorParts.push({ + position: beforeBodyIdentifier.position, + scopes: SCOPE.KEYWORD, + }) + } + } + + colorParts = colorParts.concat(block(caseBlock.body)) + } + + return colorParts +} + +function listLoop(current: ListLoop): ColorPart[] { + let colorParts = parseListLoopHeader(current) + + colorParts = colorParts.concat(node(current.list)) + colorParts = colorParts.concat(block(current.body)) + + return colorParts +} + +function listLiteral(current: ListLiteral): ColorPart[] { + const itemTokens = new Set(current.items.flatMap((item) => item.tokens)) + const allTokens = new Set(current.tokens) + + const nonItemTokens = allTokens.difference(itemTokens) + const commas = Array.from(nonItemTokens).filter( + (token) => token.type === TOKEN_TYPE.COMMA, + ) + + let listColorParts: ColorPart[] = [] + + listColorParts.push({ + position: current.tokens[0].position, + scopes: SCOPE.PARENTHESIS, + }) + + const commaColorParts: ColorPart[] = commas.map((comma) => ({ + position: comma.position, + scopes: SCOPE.PUNCTUATION, + })) + + listColorParts = listColorParts.concat(commaColorParts) + + listColorParts.push({ + position: current.tokens.slice(-1)[0].position, + scopes: SCOPE.PARENTHESIS, + }) + + const itemColorParts = current.items.flatMap(node) + + const colorParts = listColorParts.concat(itemColorParts) + + return colorParts +} + +function indexFetch(current: IndexFetch): ColorPart[] { + let colorParts: ColorPart[] = [] + + colorParts = colorParts.concat(node(current.list)) + colorParts = colorParts.concat(node(current.index)) + + return colorParts +} + +function setToIndex(current: SetToIndex): ColorPart[] { + let colorParts: ColorPart[] = [] + + colorParts = colorParts.concat(node(current.target)) + colorParts = colorParts.concat(node(current.value)) + + return colorParts +} + +function visitReturn(current: Return): ColorPart[] { + const colorParts: ColorPart[] = [ + { + position: current.tokens[0].position, + scopes: SCOPE.KEYWORD, + }, + ] + + return colorParts +} + +export { node as nodeToColorTokens } diff --git a/monaco-language-provider/ast-to-colorize/list-loop.ts b/monaco-language-provider/ast-to-colorize/list-loop.ts new file mode 100644 index 0000000..1e77154 --- /dev/null +++ b/monaco-language-provider/ast-to-colorize/list-loop.ts @@ -0,0 +1,62 @@ +import { ListLoop, TOKEN_TYPE } from '@dalbit-yaksok/core' +import { ColorPart } from '../type.ts' +import { SCOPE } from './scope.ts' + +export function parseListLoopHeader(node: ListLoop) { + const colorParts: ColorPart[] = [ + { + position: node.tokens[0].position, + scopes: SCOPE.KEYWORD, + }, + ] + + const newLineIndex = node.tokens.findIndex( + (token) => token.type === TOKEN_TYPE.NEW_LINE, + ) + + const listLoopHeaderTokens = node.tokens.slice(1, newLineIndex) + + const listLoopHeaderEnder: ColorPart = { + position: + listLoopHeaderTokens[listLoopHeaderTokens.length - 1].position, + scopes: SCOPE.KEYWORD, + } + + colorParts.push(listLoopHeaderEnder) + + const indexVariableName = node.variableName + + const variableNameToken = listLoopHeaderTokens.find( + (token) => token.value === indexVariableName, + ) + + if (variableNameToken) { + const variableNameColorPart: ColorPart = { + position: variableNameToken.position, + scopes: SCOPE.VARIABLE_NAME, + } + + colorParts.push(variableNameColorPart) + } + + const listEndingToken = node.list.tokens?.[node.list.tokens.length - 1] + if (listEndingToken) { + const listEndingTokenIndex = + listLoopHeaderTokens.indexOf(listEndingToken) + + const afterListIdentifier = listLoopHeaderTokens + .slice(listEndingTokenIndex + 1) + .find((token) => token.type === TOKEN_TYPE.IDENTIFIER) + + if (afterListIdentifier && afterListIdentifier.value === '의') { + const inKeywordIdentifier: ColorPart = { + position: afterListIdentifier.position, + scopes: SCOPE.KEYWORD, + } + + colorParts.push(inKeywordIdentifier) + } + } + + return colorParts +} diff --git a/monaco-language-provider/ast-to-colorize/scope.ts b/monaco-language-provider/ast-to-colorize/scope.ts new file mode 100644 index 0000000..ff58f80 --- /dev/null +++ b/monaco-language-provider/ast-to-colorize/scope.ts @@ -0,0 +1,12 @@ +export enum SCOPE { + KEYWORD = 'keyword', + VARIABLE_NAME = 'variable.name', + IDENTIFIER = 'identifier', + PUNCTUATION = 'punctuation', + PARENTHESIS = 'delimiter.parenthesis', + OPERATOR = 'operators', + NUMBER = 'number', + CALLABLE = 'tag', + STRING = 'string', + COMMENT = 'comment', +} diff --git a/monaco-language-provider/const.ts b/monaco-language-provider/const.ts new file mode 100644 index 0000000..245ebfb --- /dev/null +++ b/monaco-language-provider/const.ts @@ -0,0 +1 @@ +export const LANG_ID = 'dalbityaksok' diff --git a/monaco-language-provider/deno.json b/monaco-language-provider/deno.json new file mode 100644 index 0000000..1ff8a11 --- /dev/null +++ b/monaco-language-provider/deno.json @@ -0,0 +1,8 @@ +{ + "imports": { + "monaco-editor": "npm:monaco-editor@^0.52.2" + }, + "name": "@dalbit-yaksok/monaco-language-provider", + "exports": "./mod.ts", + "version": "0.2.0-alpha.13+20250104.nightly" +} \ No newline at end of file diff --git a/monaco-language-provider/hangul-to-phoneme.ts b/monaco-language-provider/hangul-to-phoneme.ts new file mode 100644 index 0000000..665a32b --- /dev/null +++ b/monaco-language-provider/hangul-to-phoneme.ts @@ -0,0 +1,108 @@ +// 다음의 코드를 참고하였습니다: +// https://blex.me/@baealex/%ED%95%9C%EA%B8%80-%EB%B6%84%EB%A6%AC-%EB%B3%91%ED%95%A9#%EB%B6%84%EB%A6%AC%EA%B8%B0-%EA%B0%9C%EC%84%A0 + +const START_OF_HANGUL_IN_UNICODE = 44032 +const END_OF_HANGUL_IN_UNICODE = 55203 + +const 초성 = [ + 'ㄱ', + 'ㄲ', + 'ㄴ', + 'ㄷ', + 'ㄸ', + 'ㄹ', + 'ㅁ', + 'ㅂ', + 'ㅃ', + 'ㅅ', + 'ㅆ', + 'ㅇ', + 'ㅈ', + 'ㅉ', + 'ㅊ', + 'ㅋ', + 'ㅌ', + 'ㅍ', + 'ㅎ', +] +const 중성 = [ + 'ㅏ', + 'ㅐ', + 'ㅑ', + 'ㅒ', + 'ㅓ', + 'ㅔ', + 'ㅕ', + 'ㅖ', + 'ㅗ', + 'ㅘ', + 'ㅙ', + 'ㅚ', + 'ㅛ', + 'ㅜ', + 'ㅝ', + 'ㅞ', + 'ㅟ', + 'ㅠ', + 'ㅡ', + 'ㅢ', + 'ㅣ', +] + +const 종성 = [ + '', + 'ㄱ', + 'ㄲ', + 'ㄳ', + 'ㄴ', + 'ㄵ', + 'ㄶ', + 'ㄷ', + 'ㄹ', + 'ㄺ', + 'ㄻ', + 'ㄼ', + 'ㄽ', + 'ㄾ', + 'ㄿ', + 'ㅀ', + 'ㅁ', + 'ㅂ', + 'ㅄ', + 'ㅅ', + 'ㅆ', + 'ㅇ', + 'ㅈ', + 'ㅊ', + 'ㅋ', + 'ㅌ', + 'ㅍ', + 'ㅎ', +] + +function hangulSyllableToPhoneme(syllable: string) { + const unicodeOrder = syllable.charCodeAt(0) + + if ( + unicodeOrder < START_OF_HANGUL_IN_UNICODE || + unicodeOrder > END_OF_HANGUL_IN_UNICODE + ) { + return [syllable] + } + + const orderInHangulArea = unicodeOrder - START_OF_HANGUL_IN_UNICODE + + const 초성_인덱스 = Math.floor(orderInHangulArea / 588) + const 중성_인덱스 = Math.floor((orderInHangulArea - 초성_인덱스 * 588) / 28) + const 종성_인덱스 = orderInHangulArea % 28 + + if (종성[종성_인덱스]) { + return 초성[초성_인덱스] + 중성[중성_인덱스] + 종성[종성_인덱스] + } + + return 초성[초성_인덱스] + 중성[중성_인덱스] +} + +export function hangulToPhoneme(word: string) { + return word.split('').map(hangulSyllableToPhoneme).join('') +} diff --git a/monaco-language-provider/language.ts b/monaco-language-provider/language.ts new file mode 100644 index 0000000..f564cd0 --- /dev/null +++ b/monaco-language-provider/language.ts @@ -0,0 +1,75 @@ +import { languages, editor } from 'monaco-editor' +import { LANG_ID } from './const.ts' +import { BaseProvider } from './provider/base.ts' +import { + CompletionItemProvider, + setupCompletion, +} from './provider/completion-item.ts' +import { TokensProvider } from './provider/tokens.ts' + +export class DalbitYaksokApplier { + baseProvider: BaseProvider + + tokensProvider: TokensProvider + completionItemProvider: CompletionItemProvider + + constructor(initialCode: string) { + this.baseProvider = new BaseProvider(initialCode) + + this.tokensProvider = new TokensProvider(this.baseProvider) + this.completionItemProvider = new CompletionItemProvider( + this.baseProvider, + ) + } + + public async register(languagesInstance: typeof languages) { + languagesInstance.register({ id: LANG_ID }) + + await new Promise((resolve) => + languagesInstance.onLanguage(LANG_ID, resolve), + ) + + languagesInstance.setLanguageConfiguration(LANG_ID, { + comments: { + lineComment: '#', + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'], + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"', notIn: ['string'] }, + { open: "'", close: "'", notIn: ['string'] }, + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: "'", close: "'" }, + ], + }) + + languagesInstance.setTokensProvider(LANG_ID, this.tokensProvider) + languagesInstance.registerCompletionItemProvider( + LANG_ID, + this.completionItemProvider, + ) + } + + public configEditor(editorInstance: editor.IStandaloneCodeEditor) { + setupCompletion(editorInstance) + + editorInstance.onDidChangeModelContent(() => { + this.updateCode(editorInstance.getValue()) + }) + } + + updateCode(code: string) { + this.baseProvider.updateCode(code) + } +} diff --git a/monaco-language-provider/mod.ts b/monaco-language-provider/mod.ts new file mode 100644 index 0000000..a74243a --- /dev/null +++ b/monaco-language-provider/mod.ts @@ -0,0 +1,4 @@ +export { DalbitYaksokApplier } from './language.ts' +export { LANG_ID } from './const.ts' +export { nodeToColorTokens } from './ast-to-colorize/index.ts' +export type { ColorPart } from './type.ts' diff --git a/monaco-language-provider/provider/base.ts b/monaco-language-provider/provider/base.ts new file mode 100644 index 0000000..9caaf83 --- /dev/null +++ b/monaco-language-provider/provider/base.ts @@ -0,0 +1,45 @@ +import { CodeFile } from '@dalbit-yaksok/core' + +import { getCommentColorParts } from '../ast-to-colorize/get-comment-color-part.ts' +import { nodeToColorTokens } from '../ast-to-colorize/index.ts' +import { ColorPart } from '../type.ts' + +export class BaseProvider { + public colorPartsByLine: Map + public lines: string[] + + constructor(private code: string) { + this.lines = code.split('\n') + this.colorPartsByLine = this.createColorParts(code) + } + + createColorParts(code: string): Map { + const codeFile = new CodeFile(code) + const { ast } = codeFile + + const nodeColorParts = nodeToColorTokens(ast).toSorted( + (a, b) => a.position.column - b.position.column, + ) + const commentColorParts = getCommentColorParts(codeFile.tokens) + + const colorParts = nodeColorParts.concat(commentColorParts) + + const colorPartsByLine = new Map( + new Array(this.lines.length).fill(0).map((_, index) => [index, []]), + ) + for (const colorPart of colorParts) { + colorPartsByLine.get(colorPart.position.line - 1)!.push(colorPart) + } + + return colorPartsByLine + } + + updateCode(code: string) { + this.code = code + this.lines = code.split('\n') + + console.time('Parse') + this.colorPartsByLine = this.createColorParts(code) + console.timeEnd('Parse') + } +} diff --git a/monaco-language-provider/provider/completion-item.ts b/monaco-language-provider/provider/completion-item.ts new file mode 100644 index 0000000..d2821d2 --- /dev/null +++ b/monaco-language-provider/provider/completion-item.ts @@ -0,0 +1,102 @@ +import { editor, languages, Position } from 'monaco-editor' +import { BaseProvider } from './base.ts' + +const COMPLETION_SNIPPETS = [ + { + label: '보여주기', + kind: languages.CompletionItemKind.Keyword, + insertText: '보여주기', + detail: '값을 화면에 보여줘요', + }, + { + label: '약속', + kind: languages.CompletionItemKind.Snippet, + insertText: '약속, ', + detail: '새 약속을 만들어요', + }, + { + label: '결과', + kind: languages.CompletionItemKind.Snippet, + insertText: '결과: ', + detail: '약속의 결과를 설정해요', + }, +] + +export function setupCompletion(editorInstance: editor.IStandaloneCodeEditor) { + editorInstance.onDidChangeModelContent(() => { + const position = editorInstance.getPosition() + + if (!position) { + return + } + + const model = editorInstance.getModel() + + if (!model) { + return + } + + let { word } = model.getWordUntilPosition(position) + word = word.trim() + + if (word.length === 0) { + return + } + + const isMatched = COMPLETION_SNIPPETS.some((snippet) => + snippet.label.includes(word), + ) + + if (!isMatched) { + return + } + + editorInstance.trigger('dalbityaksok', 'hideSuggestWidget', {}) + editorInstance.trigger('dalbityaksok', 'editor.action.triggerSuggest', { + auto: true, + }) + }) +} + +export class CompletionItemProvider + implements languages.CompletionItemProvider +{ + constructor(private _base: BaseProvider) {} + + provideCompletionItems( + model: editor.ITextModel, + position: Position, + ): languages.ProviderResult { + const word = model.getWordUntilPosition(position).word.trim() + + if (word.length === 0) { + return { + suggestions: [], + } + } + + const matchedItems = COMPLETION_SNIPPETS.map((item) => ({ + score: item.label.startsWith(word) + ? 2 + : item.label.includes(word) + ? 1 + : 0, + item, + })) + .filter(({ score }) => score > 0) + .toSorted((a, b) => b.score - a.score) + .map(({ item }) => item) + + return { + suggestions: matchedItems.map((item) => ({ + ...item, + range: { + startLineNumber: position.lineNumber, + endLineNumber: position.lineNumber, + startColumn: position.column - word.length, + endColumn: position.column, + }, + })), + } + } +} diff --git a/monaco-language-provider/provider/tokens.ts b/monaco-language-provider/provider/tokens.ts new file mode 100644 index 0000000..08fe3bf --- /dev/null +++ b/monaco-language-provider/provider/tokens.ts @@ -0,0 +1,40 @@ +import type { languages } from 'monaco-editor' +import { BaseProvider } from './base.ts' + +export class TokensProvider implements languages.TokensProvider { + constructor(private base: BaseProvider) {} + + getInitialState(): languages.IState { + return { + clone() { + return this + }, + equals() { + return false + }, + } + } + + tokenize(line: string, state: any): languages.ILineTokens { + const lineNumber = this.base.lines.indexOf(line) + + const colorParts = this.base.colorPartsByLine.get(lineNumber) + + if (!colorParts) { + return { + tokens: [], + endState: state, + } + } + + const colorTokens = colorParts.map((part) => ({ + scopes: part.scopes, + startIndex: part.position.column - 1, + })) + + return { + tokens: colorTokens, + endState: state, + } + } +} diff --git a/monaco-language-provider/type.ts b/monaco-language-provider/type.ts new file mode 100644 index 0000000..148fe83 --- /dev/null +++ b/monaco-language-provider/type.ts @@ -0,0 +1,11 @@ +import { Position, Token } from '@dalbit-yaksok/core' + +export interface ColorToken extends Token { + position: Position + scope?: string +} + +export interface ColorPart { + position: Position + scopes: string +} diff --git a/quickjs/deno.json b/quickjs/deno.json index 006f532..fb97fc3 100644 --- a/quickjs/deno.json +++ b/quickjs/deno.json @@ -9,5 +9,5 @@ "check-deploy": "deno publish --dry-run --allow-dirty", "test": "deno test --quiet --allow-net --allow-read --parallel & deno lint & deno task check-deploy" }, - "version": "0.2.0-alpha.10+20241217.nightly" + "version": "0.2.0-alpha.13+20250104.nightly" } \ No newline at end of file diff --git a/runtest.ts b/runtest.ts index c35e4b4..a9e16e3 100644 --- a/runtest.ts +++ b/runtest.ts @@ -1,36 +1,9 @@ -import { tokenize } from './core/mod.ts' +import { yaksok } from './core/mod.ts' -const CODE = `약속, 회전설정 (회전) - 결과: "rotate:" + 회전 -내 이름: "정한"; -약속, 시간설정 (시간) - 결과: "time:" + 시간 +const CODE = `사과_갯수: 127 +사람_수: 4 -약속, (A) 합 (B) - 결과: A + "" + B +"사과가 " + 사과_갯수 + "개, 사람이 " + 사람_수 + "명 있습니다" 보여주기 +"한 사람당 " + 사과_갯수 // 사람_수 + "개씩 먹고, " + 사과_갯수 % 사람_수 + "개가 남습니다" 보여주기` -약속, (각도)도 회전하기 - 회전설정 각도 보여주기 - -약속, (시간)초 동안 (각도)도 회전하기 - (시간설정 시간) 합 (회전설정 각도) 보여주기 - -각도: 45 -시간: 30 - -(3)초 동안 (90)도 회전하기 -3 초 동안 90 도 회전하기 -(3)초 동안 90 도 회전하기 -3 초 동안 (90)도 회전하기 - -시간 초 동안 각도 도 회전하기 -(시간)초 동안 (각도)도 회전하기 -(시간)초 동안 각도 도 회전하기 -시간 초 동안 (각도)도 회전하기 - -(90)도 회전하기 -90 도 회전하기 -각도 도 회전하기 -(각도)도 회전하기` - -tokenize(CODE) +yaksok(CODE) diff --git a/test/errors/invalid-character-for-code.test.ts b/test/errors/invalid-character-for-code.test.ts deleted file mode 100644 index 645b77b..0000000 --- a/test/errors/invalid-character-for-code.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { assertIsError, unreachable } from 'assert' -import { yaksok } from '../../core/mod.ts' -import { UnexpectedCharError } from '../../core/error/index.ts' - -Deno.test('사용할 수 없는 문자', async () => { - try { - await yaksok(`내 이름: "정한";`) - unreachable() - } catch (error) { - assertIsError(error, UnexpectedCharError) - } -})