diff --git a/server/src/compiler_analyzer/analyzer.ts b/server/src/compiler_analyzer/analyzer.ts index 269f282d..fd17a73a 100644 --- a/server/src/compiler_analyzer/analyzer.ts +++ b/server/src/compiler_analyzer/analyzer.ts @@ -800,7 +800,7 @@ function analyzeExprPostOp1(scope: SymbolScope, exprPostOp: NodeExprPostOp1, exp // ('[' [IDENTIFIER ':'] ASSIGN {',' [IDENTIFIER ':' ASSIGN} ']') function analyzeExprPostOp2(scope: SymbolScope, exprPostOp: NodeExprPostOp2, exprValue: ResolvedType, exprRange: TokenRange) { - const args = exprPostOp.indexerList.map(indexer => analyzeAssign(scope, indexer.assign)); + const args = exprPostOp.indexingList.map(indexer => analyzeAssign(scope, indexer.assign)); return analyzeOperatorAlias( scope, exprPostOp.nodeRange.end, diff --git a/server/src/compiler_analyzer/hoist.ts b/server/src/compiler_analyzer/hoist.ts index 5c6da75b..ac1cb43e 100644 --- a/server/src/compiler_analyzer/hoist.ts +++ b/server/src/compiler_analyzer/hoist.ts @@ -9,7 +9,7 @@ import { import { AccessModifier, funcHeadDestructor, - isFunctionHeadReturnValue, + isFuncHeadReturnValue, NodeClass, NodeEnum, NodeFunc, @@ -33,9 +33,8 @@ import {findSymbolWithParent, insertSymbolObject, tryInsertSymbolObject} from ". import {ResolvedType} from "./resolvedType"; import {getGlobalSettings} from "../code/settings"; import {builtinSetterValueToken, builtinThisToken, tryGetBuiltInType} from "./symbolBuiltin"; -import {Mutable} from "../utils/utilities"; -import {TokenIdentifier, TokenKind, TokenObject} from "../compiler_tokenizer/tokenObject"; -import {getIdentifierInType} from "../compiler_parser/nodesUtils"; +import {TokenIdentifier, TokenObject} from "../compiler_tokenizer/tokenObject"; +import {getIdentifierInNodeType} from "../compiler_parser/nodesUtils"; import { analyzeFunc, AnalyzeQueue, @@ -183,14 +182,14 @@ function hoistClassTemplateTypes(scope: SymbolScope, types: NodeType[] | undefin const templateTypes: TokenObject[] = []; for (const type of types ?? []) { insertSymbolObject(scope.symbolMap, SymbolType.create({ - declaredPlace: getIdentifierInType(type), + declaredPlace: getIdentifierInNodeType(type), declaredScope: scope, sourceNode: undefined, membersScope: undefined, isTypeParameter: true, })); - templateTypes.push(getIdentifierInType(type)); + templateTypes.push(getIdentifierInNodeType(type)); } return templateTypes; } @@ -274,7 +273,7 @@ function hoistFunc( ) { if (nodeFunc.head === funcHeadDestructor) return; - const returnType = isFunctionHeadReturnValue(nodeFunc.head) ? analyzeType( + const returnType = isFuncHeadReturnValue(nodeFunc.head) ? analyzeType( parentScope, nodeFunc.head.returnType) : undefined; const symbol: SymbolFunction = SymbolFunction.create({ diff --git a/server/src/compiler_parser/nodes.ts b/server/src/compiler_parser/nodes.ts index 08ec6a14..92068d6d 100644 --- a/server/src/compiler_parser/nodes.ts +++ b/server/src/compiler_parser/nodes.ts @@ -160,7 +160,7 @@ export interface NodeFunc extends NodesBase { readonly nodeName: NodeName.Func; readonly entity: EntityAttribute | undefined; readonly accessor: AccessModifier | undefined; - readonly head: FuncHeads; + readonly head: FuncHead; readonly identifier: TokenObject; readonly paramList: NodeParamList; readonly isConst: boolean; @@ -179,9 +179,9 @@ export type FuncHeadDestructor = typeof funcHeadDestructor; export const funcHeadConstructor = Symbol(); export type FuncHeadConstructor = typeof funcHeadConstructor; -export type FuncHeads = FuncHeadReturnValue | FuncHeadDestructor | FuncHeadConstructor; +export type FuncHead = FuncHeadReturnValue | FuncHeadDestructor | FuncHeadConstructor; -export function isFunctionHeadReturnValue(head: FuncHeads): head is FuncHeadReturnValue { +export function isFuncHeadReturnValue(head: FuncHead): head is FuncHeadReturnValue { return head !== funcHeadDestructor && head !== funcHeadConstructor; } @@ -471,10 +471,10 @@ export function isMemberMethodInPostOp(member: NodeFuncCall | TokenObject | unde export interface NodeExprPostOp2 extends NodesBase { readonly nodeName: NodeName.ExprPostOp; readonly postOp: 2; - readonly indexerList: ParsedPostIndexer[]; + readonly indexingList: ParsedPostIndexing[]; } -export interface ParsedPostIndexer { +export interface ParsedPostIndexing { readonly identifier: TokenObject | undefined, readonly assign: NodeAssign } diff --git a/server/src/compiler_parser/nodesUtils.ts b/server/src/compiler_parser/nodesUtils.ts index 92345fa2..f46aaf5d 100644 --- a/server/src/compiler_parser/nodesUtils.ts +++ b/server/src/compiler_parser/nodesUtils.ts @@ -1,40 +1,28 @@ import {TokenObject} from "../compiler_tokenizer/tokenObject"; -import {EntityAttribute, FunctionAttribute, NodeType, ReferenceModifier} from "./nodes"; -import {Mutable} from "../utils/utilities"; - -export function setEntityAttribute(attribute: Mutable, token: 'shared' | 'external' | 'abstract' | 'final') { - if (token === 'shared') attribute.isShared = true; - else if (token === 'external') attribute.isExternal = true; - else if (token === 'abstract') attribute.isAbstract = true; - else if (token === 'final') attribute.isFinal = true; -} +import {EntityAttribute, NodeType, ReferenceModifier} from "./nodes"; export function isEntityModifierForClass(modifier: EntityAttribute) { return modifier.isAbstract || modifier.isFinal; } -export function setFunctionAttribute(attribute: Mutable, token: 'override' | 'final' | 'explicit' | 'property') { - if (token === 'override') attribute.isOverride = true; - else if (token === 'final') attribute.isFinal = true; - else if (token === 'explicit') attribute.isExplicit = true; - else if (token === 'property') attribute.isProperty = true; -} - export function stringifyNodeType(type: NodeType): string { let str = type.isConst ? 'const ' : ''; str += type.dataType.identifier.text; if (type.typeTemplates.length > 0) { str += '<' + type.typeTemplates.map(stringifyNodeType).join(', ') + '>'; } + if (type.isArray) { str += '[]'; } + if (type.refModifier !== undefined) { str += (type.refModifier === ReferenceModifier.AtConst ? '@const' : '@'); } + return str; } -export function getIdentifierInType(type: NodeType): TokenObject { +export function getIdentifierInNodeType(type: NodeType): TokenObject { return type.dataType.identifier; } \ No newline at end of file diff --git a/server/src/compiler_parser/parsedCache.ts b/server/src/compiler_parser/parsedCache.ts deleted file mode 100644 index 183ed205..00000000 --- a/server/src/compiler_parser/parsedCache.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {EntityAttribute, NodeScope, NodeType} from "./nodes"; - -export enum ParsedCacheKind { - EntityAttribute = 'EntityAttribute', - Scope = 'Scope', - TypeTemplates = 'TypeTemplates', -} - -export type ParsedCacheTargets = TargetEntityAttribute; - -type TargetEntityAttribute = - T extends ParsedCacheKind.EntityAttribute ? EntityAttribute : TargetScope; - -type TargetScope = - T extends ParsedCacheKind.Scope ? NodeScope : TargetTypeTemplates; - -type TargetTypeTemplates = - T extends ParsedCacheKind.TypeTemplates ? NodeType[] : never; - -export interface ParsedCachedData { - kind: T; - rangeEnd: number; - data: ParsedCacheTargets | undefined; -} - -export interface ParsedCacheServices { - restore: (() => ParsedCacheTargets | undefined) | undefined; - store: (cache: ParsedCacheTargets | undefined) => void; -} diff --git a/server/src/compiler_parser/parser.ts b/server/src/compiler_parser/parser.ts index 27361bd1..c5cf19d3 100644 --- a/server/src/compiler_parser/parser.ts +++ b/server/src/compiler_parser/parser.ts @@ -3,11 +3,11 @@ import { AccessModifier, EntityAttribute, + FuncHead, funcHeadConstructor, funcHeadDestructor, - FuncHeads, FunctionAttribute, - isFunctionHeadReturnValue, + isFuncHeadReturnValue, NodeArgList, NodeAssign, NodeBreak, @@ -60,18 +60,17 @@ import { ParsedArgument, ParsedEnumMember, ParsedGetterSetter, - ParsedPostIndexer, + ParsedPostIndexing, ParsedVariableInit, ReferenceModifier, TypeModifier } from "./nodes"; import {HighlightForToken} from "../code/highlight"; import {TokenKind, TokenObject, TokenReserved} from "../compiler_tokenizer/tokenObject"; -import {BreakOrThrough, ParsedResult, ParseFailure, ParserState} from "./parserState"; -import {ParsedCacheKind} from "./parsedCache"; +import {BreakOrThrough, ParseFailure, ParseResult, ParserState} from "./parserState"; +import {ParserCacheKind} from "./parserCache"; import {areTokensJoinedBy} from "../compiler_tokenizer/tokenUtils"; import {Mutable} from "../utils/utilities"; -import {setEntityAttribute, setFunctionAttribute} from "./nodesUtils"; import {getBoundingLocationBetween, TokenRange} from "./tokenRange"; // SCRIPT ::= {IMPORT | ENUM | TYPEDEF | CLASS | MIXIN | INTERFACE | FUNCDEF | VIRTPROP | VAR | FUNC | NAMESPACE | ';'} @@ -160,23 +159,26 @@ function parseScript(parser: ParserState): NodeScript { break; } + return script; } // NAMESPACE ::= 'namespace' IDENTIFIER {'::' IDENTIFIER} '{' SCRIPT '}' -function parseNamespace(parser: ParserState): ParsedResult { +function parseNamespace(parser: ParserState): ParseResult { if (parser.next().text !== 'namespace') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Builtin); const namespaceList: TokenObject[] = []; while (parser.isEnd() === false) { + const loopStart = parser.next(); + const identifier = expectIdentifier(parser, HighlightForToken.Namespace); if (identifier !== undefined) namespaceList.push(identifier); - if (expectContinuousOrClose(parser, '::', '{', true) === BreakOrThrough.Break) break; + if (expectSeparatorOrClose(parser, '::', '{', true) === BreakOrThrough.Break) break; - if (identifier === undefined) parser.step(); + if (parser.next() === loopStart) parser.step(); } if (namespaceList.length === 0) { @@ -220,7 +222,7 @@ function expectContextualKeyword(parser: ParserState, keyword: string): boolean } // ENUM ::= {'shared' | 'external'} 'enum' IDENTIFIER (';' | ('{' IDENTIFIER ['=' EXPR] {',' IDENTIFIER ['=' EXPR]} [','] '}')) -function parseEnum(parser: ParserState): ParsedResult { +function parseEnum(parser: ParserState): ParseResult { const rangeStart = parser.next(); const entity = parseEntityAttribute(parser); @@ -258,7 +260,7 @@ function expectEnumMembers(parser: ParserState): ParsedEnumMember[] { const members: ParsedEnumMember[] = []; parser.expect('{', HighlightForToken.Operator); while (parser.isEnd() === false) { - if (expectContinuousOrClose(parser, ',', '}', members.length > 0) === BreakOrThrough.Break) break; + if (expectSeparatorOrClose(parser, ',', '}', members.length > 0) === BreakOrThrough.Break) break; if (parser.next().text === '}') { parser.commit(HighlightForToken.Operator); @@ -283,20 +285,23 @@ function expectEnumMembers(parser: ParserState): ParsedEnumMember[] { // {'shared' | 'abstract' | 'final' | 'external'} function parseEntityAttribute(parser: ParserState): EntityAttribute | undefined { - const cache = parser.cache(ParsedCacheKind.EntityAttribute); + const cache = parser.cache(ParserCacheKind.EntityAttribute); if (cache.restore !== undefined) return cache.restore(); let attribute: EntityAttribute | undefined = undefined; while (parser.isEnd() === false) { const next = parser.next().text; + const isEntityToken = next === 'shared' || next === 'external' || next === 'abstract' || next === 'final'; if (isEntityToken === false) break; - if (attribute === undefined) attribute = { + + attribute = attribute ?? { isShared: false, isExternal: false, isAbstract: false, isFinal: false }; + setEntityAttribute(attribute, next); parser.commit(HighlightForToken.Builtin); } @@ -305,8 +310,15 @@ function parseEntityAttribute(parser: ParserState): EntityAttribute | undefined return attribute; } +function setEntityAttribute(attribute: Mutable, token: 'shared' | 'external' | 'abstract' | 'final') { + if (token === 'shared') attribute.isShared = true; + else if (token === 'external') attribute.isExternal = true; + else if (token === 'abstract') attribute.isAbstract = true; + else if (token === 'final') attribute.isFinal = true; +} + // CLASS ::= {'shared' | 'abstract' | 'final' | 'external'} 'class' IDENTIFIER (';' | ([':' IDENTIFIER {',' IDENTIFIER}] '{' {VIRTPROP | FUNC | VAR | FUNCDEF} '}')) -function parseClass(parser: ParserState): ParsedResult { +function parseClass(parser: ParserState): ParseResult { const rangeStart = parser.next(); const metadata = parseMetadata(parser); @@ -328,12 +340,14 @@ function parseClass(parser: ParserState): ParsedResult { if (parser.next().text === ':') { parser.commit(HighlightForToken.Operator); while (parser.isEnd() === false) { + const loopStart = parser.next(); + const identifier = expectIdentifier(parser, HighlightForToken.Type); if (identifier !== undefined) baseList.push(identifier); - if (expectContinuousOrClose(parser, ',', '{', true) === BreakOrThrough.Break) break; + if (expectSeparatorOrClose(parser, ',', '{', true) === BreakOrThrough.Break) break; - if (identifier === undefined) parser.step(); + if (parser.next() === loopStart) parser.step(); } } else { parser.expect('{', HighlightForToken.Operator); @@ -396,7 +410,7 @@ function expectClassMembers(parser: ParserState) { } // TYPEDEF ::= 'typedef' PRIMTYPE IDENTIFIER ';' -function parseTypeDef(parser: ParserState): ParsedResult { +function parseTypeDef(parser: ParserState): ParseResult { if (parser.next().text !== 'typedef') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Builtin); @@ -430,7 +444,7 @@ function parseFunc(parser: ParserState): NodeFunc | undefined { const accessor = parseAccessModifier(parser); - let head: FuncHeads; + let head: FuncHead; if (parser.next().text === '~') { parser.commit(HighlightForToken.Operator); head = funcHeadDestructor; @@ -448,7 +462,7 @@ function parseFunc(parser: ParserState): NodeFunc | undefined { head = {returnType: returnType, isRef: isRef}; } const identifier = parser.next(); - parser.commit(isFunctionHeadReturnValue(head) ? HighlightForToken.Function : HighlightForToken.Type); + parser.commit(isFuncHeadReturnValue(head) ? HighlightForToken.Function : HighlightForToken.Type); const paramList = parseParamList(parser); if (paramList === undefined) { @@ -554,7 +568,7 @@ function parseAccessModifier(parser: ParserState): AccessModifier | undefined { } // INTERFACE ::= {'external' | 'shared'} 'interface' IDENTIFIER (';' | ([':' IDENTIFIER {',' IDENTIFIER}] '{' {VIRTPROP | INTFMTHD} '}')) -function parseInterface(parser: ParserState): ParsedResult { +function parseInterface(parser: ParserState): ParseResult { const rangeStart = parser.next(); const entity = parseEntityAttribute(parser); @@ -585,12 +599,14 @@ function parseInterface(parser: ParserState): ParsedResult { if (parser.next().text === ':') { parser.commit(HighlightForToken.Operator); while (parser.isEnd() === false) { + const loopStart = parser.next(); + const identifier = expectIdentifier(parser, HighlightForToken.Type); if (identifier !== undefined) result.baseList.push(identifier); - if (expectContinuousOrClose(parser, ',', '{', true) === BreakOrThrough.Break) break; + if (expectSeparatorOrClose(parser, ',', '{', true) === BreakOrThrough.Break) break; - if (identifier === undefined) parser.step(); + if (parser.next() === loopStart) parser.step(); } } else { parser.expect('{', HighlightForToken.Operator); @@ -648,11 +664,9 @@ function parseVar(parser: ParserState): NodeVar | undefined { const variables: ParsedVariableInit[] = []; while (parser.isEnd() === false) { - // 識別子 const identifier = expectIdentifier(parser, HighlightForToken.Variable); if (identifier === undefined) break; - // 初期化子 if (parser.next().text === '=') { parser.commit(HighlightForToken.Operator); @@ -663,8 +677,7 @@ function parseVar(parser: ParserState): NodeVar | undefined { variables.push({identifier: identifier, initializer: argList}); } - // 追加または終了判定 - if (expectContinuousOrClose(parser, ',', ';', true) === BreakOrThrough.Break) break; + if (expectSeparatorOrClose(parser, ',', ';', true) === BreakOrThrough.Break) break; } return { @@ -691,7 +704,7 @@ function expectInitListOrExpr(parser: ParserState) { } // IMPORT ::= 'import' TYPE ['&'] IDENTIFIER PARAMLIST FUNCATTR 'from' STRING ';' -function parseImport(parser: ParserState): ParsedResult { +function parseImport(parser: ParserState): ParseResult { const rangeStart = parser.next(); if (parser.next().text !== 'import') return ParseFailure.Mismatch; @@ -734,7 +747,7 @@ function parseImport(parser: ParserState): ParsedResult { } // FUNCDEF ::= {'external' | 'shared'} 'funcdef' TYPE ['&'] IDENTIFIER PARAMLIST ';' -function parseFuncDef(parser: ParserState): ParsedResult { +function parseFuncDef(parser: ParserState): ParseResult { const rangeStart = parser.next(); const entity = parseEntityAttribute(parser); @@ -801,6 +814,7 @@ function parseVirtualProp(parser: ParserState): NodeVirtualProp | undefined { let setter: ParsedGetterSetter | undefined = undefined; while (parser.isEnd() === false) { const next = parser.next().text; + if (parseCloseOperator(parser, '}') === BreakOrThrough.Break) break; else if (next === 'get') getter = expectGetterSetter(parser); else if (next === 'set') setter = expectGetterSetter(parser); @@ -838,7 +852,7 @@ function expectGetterSetter(parser: ParserState): ParsedGetterSetter { } // MIXIN ::= 'mixin' CLASS -function parseMixin(parser: ParserState): ParsedResult { +function parseMixin(parser: ParserState): ParseResult { if (parser.next().text !== 'mixin') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Builtin); @@ -963,6 +977,7 @@ function parseParamList(parser: ParserState): NodeParamList | undefined { parser.commit(HighlightForToken.Operator); defaultExpr = expectExpr(parser); } + paramList.push({type: type, modifier: typeMod, identifier: identifier, defaultExpr: defaultExpr}); } @@ -978,34 +993,35 @@ function expectParamList(parser: ParserState): NodeParamList | undefined { } function expectCommaOrParensClose(parser: ParserState, canColon: boolean): BreakOrThrough { - return expectContinuousOrClose(parser, ',', ')', canColon); + return expectSeparatorOrClose(parser, ',', ')', canColon); } function isCommaOrParensClose(character: string): boolean { return character === ',' || character === ')'; } -function parseContinuousOrClose( - parser: ParserState, continuousOp: string, closeOp: string, canColon: boolean +function parseSeparatorOrClose( + parser: ParserState, separatorOp: string, closeOp: string, canColon: boolean ): BreakOrThrough | undefined { const next = parser.next().text; if (next === closeOp) { parser.commit(HighlightForToken.Operator); return BreakOrThrough.Break; } else if (canColon) { - if (next !== continuousOp) return undefined; + if (next !== separatorOp) return undefined; parser.commit(HighlightForToken.Operator); } + return BreakOrThrough.Through; } -function expectContinuousOrClose( - parser: ParserState, continuousOp: string, closeOp: string, canColon: boolean +function expectSeparatorOrClose( + parser: ParserState, separatorOp: string, closeOp: string, canColon: boolean ): BreakOrThrough { - const parsed = parseContinuousOrClose(parser, continuousOp, closeOp, canColon); + const parsed = parseSeparatorOrClose(parser, separatorOp, closeOp, canColon); if (parsed !== undefined) return parsed; - parser.error(`Expected '${continuousOp}' or '${closeOp}'.`); + parser.error(`Expected '${separatorOp}' or '${closeOp}'.`); return BreakOrThrough.Break; } @@ -1081,6 +1097,7 @@ function parseTypeTail(parser: ParserState) { } continue; } + break; } return {isArray, refModifier}; @@ -1091,12 +1108,13 @@ function expectType(parser: ParserState): NodeType | undefined { if (type === undefined) { parser.error("Expected type."); } + return type; } // '<' TYPE {',' TYPE} '>' function parseTypeTemplates(parser: ParserState): NodeType[] | undefined { - const cache = parser.cache(ParsedCacheKind.TypeTemplates); + const cache = parser.cache(ParserCacheKind.TypeTemplates); if (cache.restore !== undefined) return cache.restore(); const rangeStart = parser.next(); @@ -1113,9 +1131,10 @@ function parseTypeTemplates(parser: ParserState): NodeType[] | undefined { typeTemplates.push(type); - const continuous = parseContinuousOrClose(parser, ',', '>', typeTemplates.length > 0); - if (continuous === BreakOrThrough.Break) break; - else if (continuous === undefined) { + const breakOrThrough = parseSeparatorOrClose(parser, ',', '>', typeTemplates.length > 0); + if (breakOrThrough === BreakOrThrough.Break) { + break; + } else if (breakOrThrough === undefined) { parser.backtrack(rangeStart); cache.store(undefined); return undefined; @@ -1134,7 +1153,7 @@ function parseInitList(parser: ParserState): NodeInitList | undefined { const initList: (NodeAssign | NodeInitList)[] = []; while (parser.isEnd() === false) { - if (expectContinuousOrClose(parser, ',', '}', initList.length > 0) === BreakOrThrough.Break) break; + if (expectSeparatorOrClose(parser, ',', '}', initList.length > 0) === BreakOrThrough.Break) break; const assign = parseAssign(parser); if (assign !== undefined) { @@ -1160,7 +1179,7 @@ function parseInitList(parser: ParserState): NodeInitList | undefined { // SCOPE ::= ['::'] {IDENTIFIER '::'} [IDENTIFIER ['<' TYPE {',' TYPE} '>'] '::'] function parseScope(parser: ParserState): NodeScope | undefined { - const cache = parser.cache(ParsedCacheKind.Scope); + const cache = parser.cache(ParserCacheKind.Scope); if (cache.restore !== undefined) return cache.restore(); const rangeStart = parser.next(); @@ -1196,6 +1215,7 @@ function parseScope(parser: ParserState): NodeScope | undefined { scopeList.push(identifier); } } + break; } @@ -1259,22 +1279,32 @@ function parseFuncAttr(parser: ParserState): FunctionAttribute | undefined { let attribute: FunctionAttribute | undefined = undefined; while (parser.isEnd() === false) { const next = parser.next().text; + const isFuncAttrToken = next === 'override' || next === 'final' || next === 'explicit' || next === 'property'; if (isFuncAttrToken === false) break; - if (attribute === undefined) attribute = { + + attribute = attribute ?? { isOverride: false, isFinal: false, isExplicit: false, isProperty: false }; + setFunctionAttribute(attribute, next); parser.commit(HighlightForToken.Builtin); } return attribute; } +function setFunctionAttribute(attribute: Mutable, token: 'override' | 'final' | 'explicit' | 'property') { + if (token === 'override') attribute.isOverride = true; + else if (token === 'final') attribute.isFinal = true; + else if (token === 'explicit') attribute.isExplicit = true; + else if (token === 'property') attribute.isProperty = true; +} + // STATEMENT ::= (IF | FOR | WHILE | RETURN | STATBLOCK | BREAK | CONTINUE | DOWHILE | SWITCH | EXPRSTAT | TRY) -function parseStatement(parser: ParserState): ParsedResult { +function parseStatement(parser: ParserState): ParseResult { const parsedIf = parseIf(parser); if (parsedIf === ParseFailure.Pending) return ParseFailure.Pending; if (parsedIf !== ParseFailure.Mismatch) return parsedIf; @@ -1329,7 +1359,7 @@ function expectStatement(parser: ParserState): NodeStatement | undefined { } // SWITCH ::= 'switch' '(' ASSIGN ')' '{' {CASE} '}' -function parseSwitch(parser: ParserState): ParsedResult { +function parseSwitch(parser: ParserState): ParseResult { if (parser.next().text !== 'switch') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Keyword); @@ -1352,7 +1382,9 @@ function parseSwitch(parser: ParserState): ParsedResult { parser.step(); continue; } + if (parsedCase === ParseFailure.Pending) continue; + cases.push(parsedCase); } @@ -1375,7 +1407,7 @@ function parseBreak(parser: ParserState): NodeBreak | undefined { } // FOR ::= 'for' '(' (VAR | EXPRSTAT) EXPRSTAT [ASSIGN {',' ASSIGN}] ')' STATEMENT -function parseFor(parser: ParserState): ParsedResult { +function parseFor(parser: ParserState): ParseResult { if (parser.next().text !== 'for') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Keyword); @@ -1401,7 +1433,7 @@ function parseFor(parser: ParserState): ParsedResult { if (result.condition === undefined) return appliedNodeEnd(parser, result); while (parser.isEnd() === false) { - if (expectContinuousOrClose(parser, ',', ')', result.incrementList.length > 0) === BreakOrThrough.Break) break; + if (expectSeparatorOrClose(parser, ',', ')', result.incrementList.length > 0) === BreakOrThrough.Break) break; const assign = expectAssign(parser); if (assign === undefined) break; @@ -1414,7 +1446,7 @@ function parseFor(parser: ParserState): ParsedResult { } // WHILE ::= 'while' '(' ASSIGN ')' STATEMENT -function parseWhile(parser: ParserState): ParsedResult { +function parseWhile(parser: ParserState): ParseResult { if (parser.next().text !== 'while') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Keyword); @@ -1438,7 +1470,7 @@ function parseWhile(parser: ParserState): ParsedResult { } // DOWHILE ::= 'do' STATEMENT 'while' '(' ASSIGN ')' ';' -function parseDoWhile(parser: ParserState): ParsedResult { +function parseDoWhile(parser: ParserState): ParseResult { if (parser.next().text !== 'do') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Keyword); @@ -1466,7 +1498,7 @@ function parseDoWhile(parser: ParserState): ParsedResult { } // IF ::= 'if' '(' ASSIGN ')' STATEMENT ['else' STATEMENT] -function parseIf(parser: ParserState): ParsedResult { +function parseIf(parser: ParserState): ParseResult { if (parser.next().text !== 'if') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Keyword); @@ -1545,7 +1577,7 @@ function expectExprStat(parser: ParserState): NodeExprStat | undefined { } // TRY ::= 'try' STATBLOCK 'catch' STATBLOCK -function parseTry(parser: ParserState): ParsedResult { +function parseTry(parser: ParserState): ParseResult { if (parser.next().text !== 'try') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Keyword); @@ -1567,7 +1599,7 @@ function parseTry(parser: ParserState): ParsedResult { } // RETURN ::= 'return' [ASSIGN] ';' -function parseReturn(parser: ParserState): ParsedResult { +function parseReturn(parser: ParserState): ParseResult { if (parser.next().text !== 'return') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Keyword); @@ -1591,7 +1623,7 @@ function parseReturn(parser: ParserState): ParsedResult { } // CASE ::= (('case' EXPR) | 'default') ':' {STATEMENT} -function parseCase(parser: ParserState): ParsedResult { +function parseCase(parser: ParserState): ParseResult { const rangeStart = parser.next(); let expr = undefined; @@ -1613,6 +1645,7 @@ function parseCase(parser: ParserState): ParsedResult { const statement = parseStatement(parser); if (statement === ParseFailure.Mismatch) break; if (statement === ParseFailure.Pending) continue; + statements.push(statement); } @@ -1713,6 +1746,7 @@ function parseExprTerm2(parser: ParserState): NodeExprTerm2 | undefined { while (parser.isEnd() === false) { const next = parser.next(); if (next.isReservedToken() === false || next.property.isExprPreOp === false) break; + preOps.push(parser.next()); parser.commit(HighlightForToken.Operator); } @@ -1727,6 +1761,7 @@ function parseExprTerm2(parser: ParserState): NodeExprTerm2 | undefined { while (parser.isEnd() === false) { const parsed = parseExprPostOp(parser); if (parsed === undefined) break; + postOps.push(parsed); } @@ -1741,7 +1776,7 @@ function parseExprTerm2(parser: ParserState): NodeExprTerm2 | undefined { } // EXPRVALUE ::= 'void' | CONSTRUCTCALL | FUNCCALL | VARACCESS | CAST | LITERAL | '(' ASSIGN ')' | LAMBDA -function parseExprValue(parser: ParserState): ParsedResult { +function parseExprValue(parser: ParserState): ParseResult { const cast = parseCast(parser); if (cast === ParseFailure.Pending) return ParseFailure.Pending; if (cast !== ParseFailure.Mismatch) return cast; @@ -1860,26 +1895,25 @@ function parseExprPostOp2(parser: ParserState): NodeExprPostOp2 | undefined { const rangeStart = parser.next(); parser.commit(HighlightForToken.Operator); - const indexerList: ParsedPostIndexer[] = []; + const indexerList: ParsedPostIndexing[] = []; while (parser.isEnd() === false) { const loopStart = parser.next(); + const identifier = parseIdentifierWithColon(parser); const assign = expectAssign(parser); if (assign !== undefined) indexerList.push({identifier: identifier, assign: assign}); - if (expectContinuousOrClose(parser, ',', ']', indexerList.length > 0) === BreakOrThrough.Break) break; + if (expectSeparatorOrClose(parser, ',', ']', indexerList.length > 0) === BreakOrThrough.Break) break; - // Cancel infinite loop - // FIXME: check other places too? - if (loopStart === parser.next()) break; + if (parser.next() === loopStart) parser.step(); } return { nodeName: NodeName.ExprPostOp, nodeRange: new TokenRange(rangeStart, parser.prev()), postOp: 2, - indexerList: indexerList + indexingList: indexerList }; } @@ -1895,7 +1929,7 @@ function parseIdentifierWithColon(parser: ParserState): TokenObject | undefined } // CAST ::= 'cast' '<' TYPE '>' '(' ASSIGN ')' -function parseCast(parser: ParserState): ParsedResult { +function parseCast(parser: ParserState): ParseResult { if (parser.next().text !== 'cast') return ParseFailure.Mismatch; const rangeStart = parser.next(); parser.commit(HighlightForToken.Keyword); @@ -1922,8 +1956,8 @@ function parseCast(parser: ParserState): ParsedResult { } // LAMBDA ::= 'function' '(' [[TYPE TYPEMOD] [IDENTIFIER] {',' [TYPE TYPEMOD] [IDENTIFIER]}] ')' STATBLOCK -const parseLambda = (parser: ParserState): ParsedResult => { - // ラムダ式の判定は、呼び出し末尾の「(」の後に「{」があるかどうかで判定する +const parseLambda = (parser: ParserState): ParseResult => { + // A lambda expression is determined by checking whether a '{' appears after the '(' at the end of a function call. if (canParseLambda(parser) === false) return ParseFailure.Mismatch; const rangeStart = parser.next(); @@ -1948,8 +1982,11 @@ const parseLambda = (parser: ParserState): ParsedResult => { } const type = parseType(parser); + const typeMod = type !== undefined ? parseTypeMod(parser) : undefined; + const identifier: TokenObject | undefined = parseIdentifier(parser, HighlightForToken.Parameter); + result.paramList.push({type: type, typeMod: typeMod, identifier: identifier}); } @@ -1958,15 +1995,19 @@ const parseLambda = (parser: ParserState): ParsedResult => { }; function canParseLambda(parser: ParserState): boolean { - if (parser.next().text !== 'function') return false; + if (parser.next(0).text !== 'function') return false; + if (parser.next(1).text !== '(') return false; + let i = 2; - while (parser.isEnd() === false) { + while (parser.hasNext(i)) { if (parser.next(i).text === ')') { return parser.next(i + 1).text === '{'; } + i++; } + return false; } @@ -2098,6 +2139,7 @@ function expectAssign(parser: ParserState): NodeAssign | undefined { if (assign === undefined) { parser.error("Expected assignment."); } + return assign; } @@ -2208,6 +2250,7 @@ export function parseAfterTokenized(tokens: TokenObject[]): NodeScript { const script: NodeScript = []; while (parser.isEnd() === false) { script.push(...parseScript(parser)); + if (parser.isEnd() === false) { parser.error("Unexpected token."); parser.step(); diff --git a/server/src/compiler_parser/parserCache.ts b/server/src/compiler_parser/parserCache.ts new file mode 100644 index 00000000..0c028124 --- /dev/null +++ b/server/src/compiler_parser/parserCache.ts @@ -0,0 +1,29 @@ +import {EntityAttribute, NodeScope, NodeType} from "./nodes"; + +export enum ParserCacheKind { + EntityAttribute = 'EntityAttribute', + Scope = 'Scope', + TypeTemplates = 'TypeTemplates', +} + +export type ParserCacheTargets = TargetEntityAttribute; + +type TargetEntityAttribute = + T extends ParserCacheKind.EntityAttribute ? EntityAttribute : TargetScope; + +type TargetScope = + T extends ParserCacheKind.Scope ? NodeScope : TargetTypeTemplates; + +type TargetTypeTemplates = + T extends ParserCacheKind.TypeTemplates ? NodeType[] : never; + +export interface ParserCachedData { + kind: T; + rangeEnd: number; + data: ParserCacheTargets | undefined; +} + +export interface ParserCacheServices { + restore: (() => ParserCacheTargets | undefined) | undefined; + store: (cache: ParserCacheTargets | undefined) => void; +} diff --git a/server/src/compiler_parser/parserState.ts b/server/src/compiler_parser/parserState.ts index bec2f78b..3ca473e7 100644 --- a/server/src/compiler_parser/parserState.ts +++ b/server/src/compiler_parser/parserState.ts @@ -2,84 +2,114 @@ import {HighlightForToken} from "../code/highlight"; import {diagnostic} from "../code/diagnostic"; import {TokenKind, TokenObject} from "../compiler_tokenizer/tokenObject"; import { - ParsedCachedData, - ParsedCacheKind, - ParsedCacheServices, ParsedCacheTargets -} from "./parsedCache"; + ParserCachedData, + ParserCacheKind, + ParserCacheServices, ParserCacheTargets +} from "./parserCache"; export enum ParseFailure { + /** + * The parser visited a function, but the input does not conform to the expected grammar. + */ Mismatch = 'Mismatch', + /** + * The parser visited a function where the input conforms to the expected grammar, + * but parsing fails due to missing elements. + */ Pending = 'Pending', } export enum BreakOrThrough { + /** + * The parser should stop parsing and return the current result. + */ Break = 'Break', + /** + * The parser should continue parsing. + */ Through = 'Through', } /** - * When a parsing error occurs, the parser may return a `ParsedResult`. - * If the parser visits a function and the input is not in an acceptable formatter, 'Mismatch' is returned. - * If the input is in an acceptable formatter but parsing fails due to missing elements (e.g., an incomplete expression), 'Pending' is returned. - * No diagnostic message is issued when a 'Mismatch' occurs, but when 'Pending' is returned, a diagnostic message is generated at that node. + * When a parsing error occurs, the parser may return a `ParseResult`. + * If the parser encounters a function and the input does not conform to the expected format, 'Mismatch' is returned. + * If the input follows the expected format but parsing fails due to missing elements (e.g., an incomplete expression), 'Pending' is returned. + * No diagnostic message is issued for 'Mismatch', but when 'Pending' is returned, a diagnostic message is generated at that node. */ -export type ParsedResult = T | ParseFailure; +export type ParseResult = T | ParseFailure; export class ParserState { - private readonly caches: (ParsedCachedData | undefined)[] = []; + private readonly _caches: (ParserCachedData | undefined)[] = []; + + private _lastTokenAtError: TokenObject | undefined; public constructor( - private readonly tokens: TokenObject[], - private cursorIndex: number = 0 + private readonly _tokens: TokenObject[], + private _cursorIndex: number = 0 ) { - this.caches = new Array(tokens.length); + this._caches = new Array(_tokens.length); } public backtrack(token: TokenObject) { - this.cursorIndex = token.index; + this._cursorIndex = token.index; } public isEnd(): boolean { - return this.cursorIndex >= this.tokens.length; + return this._cursorIndex >= this._tokens.length; } public next(step: number = 0): TokenObject { - if (this.cursorIndex + step >= this.tokens.length) return this.tokens[this.tokens.length - 1]; - return this.tokens[this.cursorIndex + step]; + if (this._cursorIndex + step >= this._tokens.length) return this._tokens[this._tokens.length - 1]; + return this._tokens[this._cursorIndex + step]; + } + + public hasNext(step: number = 0): boolean { + return this._cursorIndex + step < this._tokens.length; } public prev(): TokenObject { - if (this.cursorIndex <= 0) return this.tokens[0]; - return this.tokens[this.cursorIndex - 1]; + if (this._cursorIndex <= 0) return this._tokens[0]; + return this._tokens[this._cursorIndex - 1]; } public step() { - this.cursorIndex++; + this._cursorIndex++; } + /** + * Set the highlight for the current token and move the cursor to the next token. + */ public commit(highlightForToken: HighlightForToken) { const next = this.next(); if (next.isVirtual() === false) next.setHighlight(highlightForToken); + this.step(); } - public expect(word: string, analyzeToken: HighlightForToken) { + /** + * Check if the next token is a reserved word. + */ + public expect(reservedWord: string, highlight: HighlightForToken) { if (this.isEnd()) { diagnostic.addError(this.next().location, "Unexpected end of file."); return false; } - const isExpectedWord = this.next().kind === TokenKind.Reserved && this.next().text === word; + + const isExpectedWord = this.next().kind === TokenKind.Reserved && this.next().text === reservedWord; if (isExpectedWord === false) { - diagnostic.addError(this.next().location, `Expected '${word}'.`); - // this.step(); + diagnostic.addError(this.next().location, `Expected '${reservedWord}'.`); return false; } - this.commit(analyzeToken); + + this.commit(highlight); return true; } public error(message: string) { + if (this._lastTokenAtError === this.next()) return; + diagnostic.addError(this.next().location, message); + this._lastTokenAtError = this.next(); } /** @@ -89,19 +119,20 @@ export class ParserState { * @param key The cache key that identifies the type of parsing result to cache. * @returns An object that allows restoring a cached result or storing a new one. */ - public cache(key: T): ParsedCacheServices { - const rangeStart = this.cursorIndex; - const data = this.caches[rangeStart]; - let restore: (() => ParsedCacheTargets | undefined) | undefined = undefined; + public cache(key: T): Readonly> { + const rangeStart = this._cursorIndex; + const data = this._caches[rangeStart]; + + let restore: (() => ParserCacheTargets | undefined) | undefined = undefined; if (data !== undefined && data.kind === key) restore = () => { - this.cursorIndex = data.rangeEnd; - return data.data as ParsedCacheTargets | undefined; + this._cursorIndex = data.rangeEnd; + return data.data as ParserCacheTargets | undefined; }; - const store = (cache: ParsedCacheTargets | undefined) => { - this.caches[rangeStart] = { + const store = (cache: ParserCacheTargets | undefined) => { + this._caches[rangeStart] = { kind: key, - rangeEnd: this.cursorIndex, + rangeEnd: this._cursorIndex, data: cache, }; }; diff --git a/server/src/compiler_tokenizer/tokenObject.ts b/server/src/compiler_tokenizer/tokenObject.ts index 4bfc6331..c4c4cc6f 100644 --- a/server/src/compiler_tokenizer/tokenObject.ts +++ b/server/src/compiler_tokenizer/tokenObject.ts @@ -14,7 +14,7 @@ export enum TokenKind { Comment = 'Comment', } -export interface HighlightInfo { +interface HighlightInfo { token: HighlightForToken; modifier: HighlightForModifier; } diff --git a/server/src/formatter/formatter.ts b/server/src/formatter/formatter.ts index 47524596..2ada24a3 100644 --- a/server/src/formatter/formatter.ts +++ b/server/src/formatter/formatter.ts @@ -1,6 +1,6 @@ import { funcHeadDestructor, - isFunctionHeadReturnValue, + isFuncHeadReturnValue, NodeArgList, NodeAssign, NodeBreak, NodeCase, NodeCast, NodeClass, @@ -181,7 +181,7 @@ function formatFunc(format: FormatterState, nodeFunc: NodeFunc) { formatEntityModifier(format); formatAccessModifier(format); - if (isFunctionHeadReturnValue(nodeFunc.head)) { + if (isFuncHeadReturnValue(nodeFunc.head)) { formatType(format, nodeFunc.head.returnType); if (nodeFunc.head.isRef) formatTargetBy(format, '&', {condenseLeft: true}); } else if (nodeFunc.head === funcHeadDestructor) { @@ -869,10 +869,10 @@ function formatExprPostOp(format: FormatterState, postOp: NodeExprPostOp) { } } else if (postOp.postOp === 2) { formatBracketsBlock(format, () => { - for (let i = 0; i < postOp.indexerList.length; i++) { + for (let i = 0; i < postOp.indexingList.length; i++) { if (i > 0) formatTargetBy(format, ',', {condenseLeft: true}); - const index = postOp.indexerList[i]; + const index = postOp.indexingList[i]; if (index.identifier !== undefined) { formatTargetBy(format, index.identifier.text, {}); formatTargetBy(format, ':', {condenseLeft: true, connectTail: true});