diff --git a/.eslintrc b/.eslintrc index 24cca27..f9ee1d0 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,7 +15,6 @@ "@typescript-eslint/no-unused-vars": "off", "import/no-anonymous-default-export": "off", "@typescript-eslint/prefer-ts-expect-error": "error", - "@typescript-eslint/ban-types": "error", "@typescript-eslint/explicit-module-boundary-types": [ "error", { "allowArgumentsExplicitlyTypedAsAny": true } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f5c18e3 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Ronen Amiel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ccb58b1 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +## 🐬 HypeScript + +> A simplified implementation of TypeScript's type-system written in TypeScript's own type-system + +### Introduction + +This is a simplified implementation of [TypeScript](https://github.com/microsoft/TypeScript)'s type-system that's written in [TypeScript](https://github.com/microsoft/TypeScript)'s type annotations. This means that it uses types only — with no runtime code whatsoever. + +You pass [TypeScript](https://github.com/microsoft/TypeScript) code as string to the `TypeCheck` generic and get possible type errors back (**[See the live demo]()**): + +```typescript +import type { TypeCheck } from 'hypescript'; + +type Errors = TypeCheck<` + +function foo(name: number) { + return name; +} + +foo('not a number'); + +`>; + +// Errors is now equal to the following type: +type Expected = [ + "7: Argument of type 'string' is not assignable to parameter of type 'number'." +]; +``` + +*☝ Please note that this project is meant to be used for fun and learning purposes and not for practical use.* + +### Try running the code + +See a live demo in your browser on the [TypeScript Playground](). + +Alternatively, install `hypescript` in your own project with `yarn` or `npm` ([TypeScript](https://github.com/microsoft/TypeScript) 4.7 or later is required): + +``` +yarn add hypescript +``` + +### Example showcase + +Some [TypeScript](https://github.com/microsoft/TypeScript) syntax and features haven't been implemented and won't work. Here's a list of examples (with browser demo links) for some capabilites: + +- [Calling a function with insufficient arguments]() +- [Calling a function with wrong argument types]() +- [Trying to access a variable that haven't been defined]() +- [Accessing a property that doesn't exist on an object]() +- [Trying to assign a value to a variable that doesn't match its type annotation]() +- [Trying to access a variable that haven't been defined]() + +### Additional links + +- [TypeScripts Type System is Turing Complete](https://github.com/microsoft/TypeScript/issues/14833) +- [Typing the Technical Interview in TypeScript](https://gal.hagever.com/posts/typing-the-technical-interview-in-typescript/) +- [Functions and algorithms implemented purely with TypeScript's type system](https://github.com/ronami/meta-typing) +- [A SQL database implemented purely in TypeScript type annotations](https://github.com/codemix/ts-sql) +- [Collection of TypeScript type challenges with online judge](https://github.com/type-challenges/type-challenges) diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..763f15b --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +### TODOs + +- Re-organize tests from a long list into reasonable groups +- Add comments to explain the implementation diff --git a/package.json b/package.json index 13fa795..c1a2274 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,5 @@ { - "name": "type-sys", - "private": true, + "name": "hypescript", "version": "0.0.0", "license": "MIT", "author": "Ronen Amiel", @@ -22,7 +21,7 @@ "eslint-plugin-react": "^7.30.1", "eslint-plugin-react-hooks": "^4.6.0", "prettier": "^2.7.1", - "typescript": "^4.7.4" + "typescript": "next" }, "prettier": { "printWidth": 80, @@ -30,4 +29,4 @@ "singleQuote": true, "trailingComma": "all" } -} \ No newline at end of file +} diff --git a/src/ast.ts b/src/ast.ts new file mode 100644 index 0000000..d4a0504 --- /dev/null +++ b/src/ast.ts @@ -0,0 +1,240 @@ +export type NodeData = { + startLineNumber: StartLine; + endLineNumber: EndLine; +}; + +export type NullLiteral> = { + type: 'NullLiteral'; + data: Data; +}; + +export type NumericLiteral< + Value extends string, + Data extends NodeData, +> = { + type: 'NumericLiteral'; + value: Value; + data: Data; +}; + +export type BooleanLiteral< + Value extends boolean, + Data extends NodeData, +> = { + type: 'BooleanLiteral'; + value: Value; + data: Data; +}; + +export type StringLiteral< + Value extends string, + Data extends NodeData, +> = { + type: 'StringLiteral'; + value: Value; + data: Data; +}; + +export type ArrayExpression< + Elements extends Array>, + Data extends NodeData, +> = { + type: 'ArrayExpression'; + elements: Elements; + data: Data; +}; + +export type ObjectExpression< + Properties extends Array>, + Data extends NodeData, +> = { + type: 'ObjectExpression'; + properties: Properties; + data: Data; +}; + +export type ObjectProperty< + Key extends Identifier, + Value extends BaseNode, + Data extends NodeData, +> = { + type: 'ObjectProperty'; + key: Key; + value: Value; + data: Data; +}; + +export type VariableDeclaration< + Declarations extends Array>, + Kind extends 'const' | 'let', + Data extends NodeData, +> = { + type: 'VariableDeclaration'; + declarations: Declarations; + kind: Kind; + data: Data; +}; + +export type VariableDeclarator< + Id extends BaseNode, + Init extends BaseNode, + Data extends NodeData, +> = { + type: 'VariableDeclarator'; + id: Id; + init: Init; + data: Data; +}; + +export type FunctionDeclaration< + Id extends Identifier, + Params extends Array>, + Body extends BaseNode, + Data extends NodeData, +> = { + type: 'FunctionDeclaration'; + id: Id; + params: Params; + body: Body; + data: Data; +}; + +export type Identifier< + Name extends string, + Annotation extends BaseNode | null, + Data extends NodeData, +> = { + type: 'Identifier'; + name: Name; + typeAnnotation: Annotation; + data: Data; +}; + +export type ExpressionStatement< + Expression extends BaseNode, + Data extends NodeData, +> = { + type: 'ExpressionStatement'; + expression: Expression; + data: Data; +}; + +export type CallExpression< + Callee extends BaseNode, + Arguments extends Array>, + Data extends NodeData, +> = { + type: 'CallExpression'; + callee: Callee; + arguments: Arguments; + data: Data; +}; + +export type MemberExpression< + Object extends BaseNode, + Property extends BaseNode, + Computed extends boolean, + Data extends NodeData, +> = { + type: 'MemberExpression'; + object: Object; + property: Property; + computed: Computed; + data: Data; +}; + +export type IfStatement< + Test extends BaseNode, + Consequent extends BaseNode, + Data extends NodeData, +> = { + type: 'IfStatement'; + test: Test; + consequent: Consequent; + data: Data; + // alternate: A; +}; + +export type ReturnStatement< + Argument extends BaseNode | null, + Data extends NodeData, +> = { + type: 'ReturnStatement'; + argument: Argument; + data: Data; +}; + +export type BlockStatement< + Body extends Array>, + Data extends NodeData, +> = { + type: 'BlockStatement'; + body: Body; + data: Data; +}; + +export type TypeAnnotation< + Annotation extends BaseNode, + Data extends NodeData, +> = { + type: 'TypeAnnotation'; + typeAnnotation: Annotation; + data: Data; +}; + +export type StringTypeAnnotation> = { + type: 'StringTypeAnnotation'; + data: Data; +}; + +export type NumberTypeAnnotation> = { + type: 'NumberTypeAnnotation'; + data: Data; +}; + +export type NullLiteralTypeAnnotation> = { + type: 'NullLiteralTypeAnnotation'; + data: Data; +}; + +export type BooleanTypeAnnotation> = { + type: 'BooleanTypeAnnotation'; + data: Data; +}; + +export type GenericTypeAnnotation> = { + type: 'GenericTypeAnnotation'; + id: I; + data: Data; +}; + +export type AnyTypeAnnotation> = { + type: 'AnyTypeAnnotation'; + data: Data; +}; + +export type BaseNode> = + | NumericLiteral + | BooleanLiteral + | StringLiteral + | ArrayExpression + | ObjectExpression + | ObjectProperty + | VariableDeclaration + | VariableDeclarator + | FunctionDeclaration + | Identifier + | NullLiteral + | ExpressionStatement + | CallExpression + | MemberExpression + | IfStatement + | ReturnStatement + | BlockStatement + | TypeAnnotation + | StringTypeAnnotation + | NumberTypeAnnotation + | NullLiteralTypeAnnotation + | BooleanTypeAnnotation + | GenericTypeAnnotation + | AnyTypeAnnotation; diff --git a/src/checker.ts b/src/checker.ts new file mode 100644 index 0000000..2f7ae52 --- /dev/null +++ b/src/checker.ts @@ -0,0 +1,871 @@ +import type { + AnyTypeAnnotation, + ArrayExpression, + BlockStatement, + BooleanLiteral, + BooleanTypeAnnotation, + CallExpression, + ExpressionStatement, + FunctionDeclaration, + Identifier, + MemberExpression, + BaseNode, + NodeData, + NullLiteral, + NullLiteralTypeAnnotation, + NumberTypeAnnotation, + NumericLiteral, + ObjectExpression, + ObjectProperty, + ReturnStatement, + StringLiteral, + StringTypeAnnotation, + TypeAnnotation, + VariableDeclaration, + VariableDeclarator, + IfStatement, +} from './ast'; +import type { TypeError } from './errors'; +import type { Serialize } from './serializer'; +import type { + AnyType, + ArrayType, + BooleanLiteralType, + BooleanType, + CallArgumentsType, + FunctionType, + NeverType, + NullType, + NumberLiteralType, + NumberType, + ObjectType, + StaticType, + StringLiteralType, + StringType, + UndefinedType, + UnionType, + UnknownType, + VoidType, +} from './types'; +import type { Concat, Push, Tail, Unshift } from './utils/arrayUtils'; +import type { MergeWithOverride } from './utils/generalUtils'; +import type { StateType, TypeResult } from './utils/utilityTypes'; + +export type Check>> = InferBlockStatement< + NodeList, + {} +> extends TypeResult + ? Errors + : never; + +type MergeFunctionTypes< + FunctionTypes extends Array>, + ReturnType extends FunctionType, +> = FunctionTypes extends [] + ? ReturnType + : FunctionTypes[0] extends FunctionType + ? MergeFunctionTypes< + Tail, + MergeFunctions + > + : never; + +type MergeFunctions< + Params extends Array<[string, StaticType]>, + Return extends StaticType, + Function extends FunctionType, +> = Function extends FunctionType + ? MergeFunctionParams extends infer P + ? P extends Array<[string, StaticType]> + ? MergeTypes extends infer ReturnType + ? ReturnType extends StaticType + ? FunctionType + : never + : never + : never + : never + : never; + +type MergeFunctionParams< + ParamsA extends Array<[string, StaticType]>, + ParamsB extends Array<[string, StaticType]>, + Return extends Array<[string, StaticType]> = [], +> = ParamsA extends [] + ? ParamsB extends [] + ? Return + : [...Return, ...ParamsB] + : ParamsB extends [] + ? [...Return, ...ParamsA] + : MatchType extends true + ? MergeFunctionParams, Tail, Push> + : MatchType extends true + ? MergeFunctionParams, Tail, Push> + : MergeFunctionParams< + Tail, + Tail, + Push + >; + +type MergeTypes< + TypeA extends StaticType, + TypeB extends StaticType, +> = TypeA extends NeverType + ? TypeB + : TypeB extends NeverType + ? TypeA + : TypeA extends AnyType + ? AnyType + : TypeB extends AnyType + ? AnyType + : MatchType extends true + ? TypeA + : MatchType extends true + ? TypeB + : TypeA extends UnionType + ? TypeB extends UnionType + ? UnionType<[...UnionTypesA, ...UnionTypesB]> + : UnionType<[...UnionTypesA, TypeB]> + : TypeB extends UnionType + ? UnionType<[...UnionTypesB, TypeA]> + : UnionType<[TypeA, TypeB]>; + +type InferBlockStatement< + NodeList extends Array>, + State extends StateType, + Result extends StaticType = NeverType, + Errors extends Array> = [], +> = NodeList extends [] + ? MergeTypes extends infer ReturnType + ? ReturnType extends StaticType + ? TypeResult + : never + : never + : NodeList[0] extends ExpressionStatement + ? InferExpression extends TypeResult< + any, + infer ExpressionState, + infer ExpressionErrors + > + ? InferBlockStatement< + Tail, + ExpressionState, + Result, + Concat + > + : never + : NodeList[0] extends VariableDeclaration< + [ + VariableDeclarator< + Identifier< + infer Name, + infer Annotation, + NodeData + >, + infer Init, + any + >, + ], + any, + any + > + ? InferVariableDeclaration< + Name, + Annotation, + Init, + State, + StartLine + > extends TypeResult + ? InferBlockStatement< + Tail, + DeclarationState, + Result, + Concat + > + : never + : NodeList[0] extends FunctionDeclaration< + Identifier, + infer Params, + BlockStatement, + any + > + ? InferFunctionDeclaration extends TypeResult< + any, + infer DeclarationState, + infer DeclarationErrors + > + ? InferBlockStatement< + Tail, + DeclarationState, + Result, + Concat + > + : never + : NodeList[0] extends ReturnStatement + ? InferReturnStatement extends TypeResult< + infer ReturnValue, + infer ReturnState, + infer ReturnErrors + > + ? MergeTypes extends infer ReturnType + ? ReturnType extends StaticType + ? TypeResult> + : never + : never + : never + : NodeList[0] extends IfStatement< + infer Test, + BlockStatement, + any + > + ? InferExpression extends TypeResult< + infer TestValue, + infer TestState, + infer TestErrors + > + ? InferBlockStatement extends TypeResult< + infer IfStatementValue, + any, + infer IfStatementErrors + > + ? InferBlockStatement< + Tail, + TestState, + MergeTypes extends infer ReturnType + ? ReturnType extends StaticType + ? IfStatementValue extends VoidType + ? Result + : ReturnType + : never + : never, + [...Errors, ...TestErrors, ...IfStatementErrors] + > + : never + : never + : InferBlockStatement, State, Result, Errors>; + +type InferReturnStatement< + ReturnExpression extends BaseNode | null, + State extends StateType, +> = ReturnExpression extends BaseNode + ? InferExpression extends TypeResult< + infer ExpressionValue, + infer ExpressionState, + infer ExpressionErrors + > + ? TypeResult< + MapLiteralToType, + ExpressionState, + ExpressionErrors + > + : never + : TypeResult; + +type MapAnnotationToType> = + AnnotationValue extends StringTypeAnnotation + ? StringType + : AnnotationValue extends NumberTypeAnnotation + ? NumberType + : AnnotationValue extends BooleanTypeAnnotation + ? BooleanType + : AnnotationValue extends NullLiteralTypeAnnotation + ? NullType + : AnnotationValue extends AnyTypeAnnotation + ? AnyType + : UnknownType; + +type InferFunctionParams< + Params extends Array>, + FunctionParams extends Array<[string, StaticType]> = [], + ParamsByName extends StateType = {}, + Errors extends Array> = [], +> = Params extends [] + ? [FunctionParams, ParamsByName, Errors] + : Params[0] extends Identifier< + infer Name, + infer Annotation, + NodeData + > + ? Annotation extends TypeAnnotation + ? InferFunctionParamsHelper< + Params, + FunctionParams, + ParamsByName, + MapAnnotationToType, + Name, + Errors + > + : InferFunctionParamsHelper< + Params, + FunctionParams, + ParamsByName, + AnyType, + Name, + Push< + Errors, + TypeError< + `Parameter '${Name}' implicitly has an 'any' type.`, + LineNumber + > + > + > + : never; + +type InferFunctionParamsHelper< + Params extends Array>, + FunctionParams extends Array<[string, StaticType]>, + ParamsByName extends StateType, + Type extends StaticType, + Name extends string, + Errors extends Array>, +> = InferFunctionParams< + Tail, + Push, + MergeWithOverride, + Errors +>; + +type InferFunctionDeclaration< + Name extends string, + Params extends Array>, + Body extends Array>, + State extends StateType, +> = InferFunctionParams extends [ + infer FunctionParams, + infer ParamsByName, + infer Errors, +] + ? FunctionParams extends Array<[string, StaticType]> + ? ParamsByName extends StateType + ? Errors extends Array> + ? InferBlockStatement< + Body, + MergeWithOverride + > extends TypeResult< + infer BlockStatementReturnType, + any, + infer BlockStatementErrors + > + ? TypeResult< + UndefinedType, + MergeWithOverride< + State, + { + [a in Name]: FunctionType< + FunctionParams, + BlockStatementReturnType + >; + } + >, + Concat + > + : never + : never + : never + : never + : never; + +type MatchType< + TypeA extends StaticType, + TypeB extends StaticType, +> = TypeA extends NeverType + ? false + : TypeB extends NeverType + ? false + : TypeA extends AnyType + ? true + : TypeB extends AnyType + ? true + : TypeA extends TypeB + ? TypeB extends TypeA + ? true + : false + : TypeA extends StringType + ? TypeB extends StringLiteralType + ? true + : false + : TypeA extends BooleanType + ? TypeB extends BooleanLiteralType + ? true + : false + : TypeA extends NumberType + ? TypeB extends NumberLiteralType + ? true + : false + : TypeA extends UnionType + ? TypeB extends UnionType + ? UnionMatchUnion + : TypeMatchUnion + : TypeB extends UnionType + ? UnionMatchType + : false; + +type UnionMatchUnion< + UnionTypesA extends Array, + UnionTypesB extends Array, +> = UnionTypesB extends [] + ? true + : TypeMatchUnion extends true + ? UnionMatchUnion> + : false; + +type TypeMatchUnion< + UnionTypes extends Array, + Type extends StaticType, +> = UnionTypes extends [] + ? false + : MatchType extends true + ? true + : TypeMatchUnion, Type>; + +type UnionMatchType< + Type extends StaticType, + UnionTypes extends Array, +> = UnionTypes extends [] + ? true + : MatchType extends true + ? UnionMatchType> + : false; + +type MatchTypeArrays< + ParamsType extends Array<[string, StaticType]>, + ArgumentsType extends Array, + StartLine extends number, +> = ParamsType extends [] + ? true + : MatchType extends true + ? MatchTypeArrays, Tail, StartLine> + : TypeError< + `Argument of type '${Serialize< + ArgumentsType[0] + >}' is not assignable to parameter of type '${Serialize< + ParamsType[0][1] + >}'.`, + StartLine + >; + +type InferVariableDeclaration< + Name extends string, + Annotation extends BaseNode | null, + Init extends BaseNode, + State extends StateType, + StartLine extends number, +> = InferExpression extends TypeResult< + infer InitExpressionValue, + infer InitExpressionState, + infer InitExpressionErrors +> + ? Annotation extends TypeAnnotation + ? MapAnnotationToType extends infer ExpectedType + ? ExpectedType extends StaticType + ? MatchType extends true + ? TypeResult< + UndefinedType, + MergeWithOverride< + InitExpressionState, + { [a in Name]: ExpectedType } + >, + InitExpressionErrors + > + : TypeResult< + UndefinedType, + MergeWithOverride< + InitExpressionState, + { [a in Name]: ExpectedType } + >, + Push< + InitExpressionErrors, + TypeError< + `Type '${Serialize}' is not assignable to type '${Serialize}'.`, + StartLine + > + > + > + : never + : never + : TypeResult< + UndefinedType, + MergeWithOverride, + InitExpressionErrors + > + : never; + +type InferExpression< + Node extends BaseNode, + State extends StateType, +> = Node extends StringLiteral + ? TypeResult, State> + : Node extends NumericLiteral + ? TypeResult, State> + : Node extends NullLiteral + ? TypeResult + : Node extends BooleanLiteral + ? TypeResult, State> + : Node extends Identifier> + ? Name extends keyof State + ? TypeResult + : TypeResult< + AnyType, + State, + [TypeError<`Cannot find name '${Name}'.`, StartLine>] + > + : Node extends ObjectExpression + ? InferObjectProperties + : Node extends MemberExpression< + infer Object, + infer Property, + infer Computed, + any + > + ? InferMemberExpression + : Node extends ArrayExpression + ? InferArrayElements + : Node extends CallExpression< + infer Callee, + infer Arguments, + NodeData + > + ? InferCallExpression + : UnknownType; + +type InferCallExpression< + Callee extends BaseNode, + Arguments extends Array>, + State extends StateType, + StartLine extends number, +> = InferExpression extends TypeResult< + infer CalleeValue, + infer CalleeState, + infer CalleeErrors +> + ? InferExpressionsArray extends TypeResult< + CallArgumentsType, + infer ArgumentsState, + infer ArgumentsErrors + > + ? InferCallExpressionHelper< + CalleeValue, + ArgumentsType, + ArgumentsState, + Concat, + StartLine + > + : never + : never; + +type InferCallExpressionHelper< + CalleeValue extends StaticType, + ArgumentsType extends Array, + State extends StateType, + Errors extends Array>, + StartLine extends number, +> = CalleeValue extends FunctionType + ? ParamsType['length'] extends ArgumentsType['length'] + ? MatchTypeArrays extends TypeError< + infer Message, + infer StartLine + > + ? TypeResult< + ReturnType, + State, + Push> + > + : TypeResult + : TypeResult< + ReturnType, + State, + Push< + Errors, + TypeError< + `Expected ${ParamsType['length']} arguments, but got ${ArgumentsType['length']}.`, + StartLine + > + > + > + : CalleeValue extends AnyType + ? TypeResult + : CalleeValue extends UnionType + ? InferCallExpressionUnionHelper< + CalleeValue, + UnionTypes, + ArgumentsType, + State, + StartLine, + Errors + > + : TypeResult< + AnyType, + State, + Unshift< + Errors, + TypeError< + `This expression is not callable. Type '${Serialize}' has no call signatures.`, + StartLine + > + > + >; + +type InferCallExpressionUnionHelper< + CalleeValue extends UnionType, + UnionTypes extends Array, + ArgumentsType extends Array, + State extends StateType, + StartLine extends number, + Errors extends Array>, +> = UnionTypes extends Array> + ? InferCallExpressionHelper< + MergeFunctionTypes, UnionTypes[0]>, + ArgumentsType, + State, + Errors, + StartLine + > + : TypeResult< + AnyType, + State, + Push< + Errors, + TypeError< + `This expression is not callable. Not all constituents of type '${Serialize}' are callable.`, + StartLine + > + > + >; + +type InferExpressionsArray< + NodeList extends Array>, + State extends StateType, + Result extends Array = [], + Errors extends Array> = [], +> = NodeList extends [] + ? TypeResult, State, Errors> + : InferExpression extends TypeResult< + infer ExpressionValue, + infer ExpressionState, + infer ExpressionErrors + > + ? InferExpressionsArray< + Tail, + MergeWithOverride, + Push, + Concat + > + : never; + +type InferArrayElements< + Elements extends Array>, + State extends StateType, + First extends boolean = true, + Result extends StaticType = AnyType, + Errors extends Array> = [], +> = Elements extends [] + ? TypeResult, State, Errors> + : Elements[0] extends BaseNode + ? InferExpression extends TypeResult< + infer ExpressionValue, + infer ExpressionState, + infer ExpressionErrors + > + ? MapLiteralToType extends infer LiteralType + ? LiteralType extends StaticType + ? MergeTypes extends infer ReturnType + ? ReturnType extends StaticType + ? InferArrayElements< + Tail, + ExpressionState, + false, + First extends true ? LiteralType : ReturnType, + Concat + > + : never + : never + : never + : never + : never + : never; + +type MapLiteralToType = + Type extends NumberLiteralType + ? NumberType + : Type extends StringLiteralType + ? StringType + : Type extends BooleanLiteralType + ? BooleanType + : Type extends ObjectType + ? ObjectType<{ + [P in keyof Properties]: Properties[P] extends [infer Key, infer Value] + ? Value extends StaticType + ? [Key, MapLiteralToType] + : never + : never; + }> + : Type; + +type InferMemberExpression< + Object extends BaseNode, + Property extends BaseNode, + Computed extends boolean, + State extends StateType, +> = InferExpression extends TypeResult< + infer ObjectExpressionValue, + infer ObjectExpressionState, + infer ObjectExpressionErrors +> + ? Computed extends false + ? Property extends Identifier< + infer Name, + any, + NodeData + > + ? InferMemberExpressionHelper< + ObjectExpressionValue, + Name, + ObjectExpressionState, + StartLine, + ObjectExpressionErrors + > + : never + : InferExpression extends TypeResult< + infer PropertyExpressionValue, + infer PropertyExpressionState, + infer PropertyExpressionErrors + > + ? Property extends BaseNode> + ? PropertyExpressionValue extends StringLiteralType + ? InferMemberExpressionHelper< + ObjectExpressionValue, + Value, + PropertyExpressionState, + StartLine, + Concat + > + : PropertyExpressionValue extends NumberLiteralType + ? InferMemberExpressionHelper< + ObjectExpressionValue, + Value, + PropertyExpressionState, + StartLine, + Concat + > + : PropertyExpressionValue extends AnyType + ? TypeResult< + AnyType, + PropertyExpressionState, + Concat + > + : TypeResult< + AnyType, + PropertyExpressionState, + Push< + Concat, + TypeError< + `Type '${Serialize}' cannot be used as an index type.`, + StartLine + > + > + > + : never + : never + : never; + +type GetObjectValueByKey< + ObjectProperties extends Array<[string, StaticType]>, + Key extends string, +> = ObjectProperties extends [] + ? null + : ObjectProperties[0] extends [infer PropertyName, infer PropertyValue] + ? PropertyName extends Key + ? PropertyValue + : GetObjectValueByKey, Key> + : never; + +type InferMemberExpressionHelper< + Object extends StaticType, + Key extends string, + State extends StateType, + StartLine extends number, + Errors extends Array>, +> = Object extends ObjectType + ? GetObjectValueByKey< + ObjectProperties, + Key + > extends infer MemberExpressionValue + ? MemberExpressionValue extends StaticType + ? TypeResult + : TypeResult< + UndefinedType, + State, + Push< + Errors, + TypeError< + `Property '${Key}' does not exist on type '${Serialize}'.`, + StartLine + > + > + > + : never + : Object extends ArrayType + ? TypeResult + : Object extends UnionType + ? InferMemberExpressionUnionHelper + : Object extends AnyType + ? TypeResult + : TypeError< + `Property '${Key}' does not exist on type '${Serialize}'.`, + StartLine + >; + +type InferMemberExpressionUnionHelper< + UnionTypes extends Array, + Key extends string, + State extends StateType, + StartLine extends number, + Errors extends Array>, + Result extends Array = [], +> = UnionTypes extends [] + ? TypeResult, State, Errors> + : InferMemberExpressionHelper< + UnionTypes[0], + Key, + State, + StartLine, + [] + > extends TypeResult< + infer ExpressionValue, + infer ExpressionState, + infer ExpressionErrors + > + ? InferMemberExpressionUnionHelper< + Tail, + Key, + ExpressionState, + StartLine, + Errors, + Push + > + : never; + +type InferObjectProperties< + Properties extends Array>, + State extends StateType, + Result extends Array = [], + Errors extends Array> = [], +> = Properties extends [] + ? TypeResult, State, Errors> + : Properties[0] extends ObjectProperty< + Identifier, + infer Value, + any + > + ? InferExpression extends TypeResult< + infer ExpressionValue, + infer ExpressionState, + infer ExpressionErrors + > + ? InferObjectProperties< + Tail, + ExpressionState, + Push]>, + Concat + > + : never + : never; diff --git a/src/dataTypes.ts b/src/dataTypes.ts deleted file mode 100644 index eee1119..0000000 --- a/src/dataTypes.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Tokenizer types -export type ParenToken = { type: 'paren'; value: V }; - -export type NumberToken = { type: 'number'; value: V }; - -export type StringToken = { type: 'string'; value: V }; - -export type SymbolToken = { type: 'symbol'; value: V }; - -export type Token = - | ParenToken - | NumberToken - | StringToken - | SymbolToken; diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 0000000..1573463 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,22 @@ +export type Error = + | SyntaxError + | ParsingError + | TypeError; + +export type SyntaxError = { + type: 'SyntaxError'; + message: Message; + lineNumber: LineNumber; +}; + +export type ParsingError = { + type: 'ParsingError'; + message: Message; + lineNumber: LineNumber; +}; + +export type TypeError = { + type: 'TypeError'; + message: Message; + lineNumber: LineNumber; +}; diff --git a/src/example.ts b/src/example.ts new file mode 100644 index 0000000..d231cab --- /dev/null +++ b/src/example.ts @@ -0,0 +1,16 @@ +// import type { TypeCheck } from '.'; +import type { Tokenize } from './tokenizer'; +import type { Parse } from './parser'; +import type { Check } from './checker'; +// import type { Format } from './formatter'; +// import type { Error } from './errors'; +// import type { Token } from './tokens'; +// import type { BaseNode } from './ast'; + +type T = Tokenize<` + +[111, 222] + +`>; +type P = Parse; +// type C = Check

; diff --git a/src/formatter.ts b/src/formatter.ts new file mode 100644 index 0000000..7642be3 --- /dev/null +++ b/src/formatter.ts @@ -0,0 +1,11 @@ +import type { Error } from './errors'; +import type { Push, Tail } from './utils/arrayUtils'; + +export type Format< + Errors extends Array>, + Result extends Array = [], +> = Errors extends [] + ? Result + : Errors[0] extends Error + ? Format, Push> + : never; diff --git a/src/index.ts b/src/index.ts index 60d1c59..a5f9ba0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,64 +1,22 @@ -import type { Reverse, Unshift } from './utils/arrayUtils'; -import type { - EatFirstChar, - FirstChar, - ConcatStrings, -} from './utils/stringUtils'; -import type { Numbers, Symbols } from './utils/generalUtils'; -import type { Token } from './dataTypes'; +import type { Tokenize } from './tokenizer'; +import type { Parse } from './parser'; +import type { Check } from './checker'; +import type { Format } from './formatter'; +import type { Error } from './errors'; +import type { Token } from './tokens'; +import type { BaseNode } from './ast'; -type TokenizeInput = FirstChar extends ' ' | '\n' - ? ['', EatFirstChar] - : FirstChar extends '(' - ? [ - { - type: 'paren'; - value: '('; - }, - EatFirstChar, - ] - : FirstChar extends ')' - ? [ - { - type: 'paren'; - value: ')'; - }, - EatFirstChar, - ] - : FirstChar extends Numbers - ? TokenizeNumber> - : FirstChar extends '"' - ? TokenizeString> - : FirstChar extends Symbols - ? TokenizeSymbol> - : never; - -type TokenizeNumber< - I extends string, - A extends string = '', - C extends string = FirstChar, -> = C extends Numbers - ? TokenizeNumber, ConcatStrings> - : [{ type: 'number'; value: A }, I]; - -type TokenizeString = I extends `${infer H}"${infer G}` - ? [{ type: 'string'; value: H }, G] - : never; - -type TokenizeSymbol< - I extends string, - A extends string = '', - C extends string = FirstChar, -> = C extends Symbols - ? TokenizeSymbol, ConcatStrings> - : [{ type: 'symbol'; value: A }, I]; - -export type TokenizeSequence< - I extends string, - R extends Array> = [], - P extends [any, string] = TokenizeInput, -> = I extends '' - ? R - : TokenizeSequence>; - -export type Tokenize = Reverse>; +export type TypeCheck = + Tokenize extends infer TokenList + ? TokenList extends Error + ? Format<[TokenList]> + : TokenList extends Array> + ? Parse extends infer NodeList + ? NodeList extends Error + ? Format<[NodeList]> + : NodeList extends Array> + ? Format> + : never + : never + : never + : never; diff --git a/src/parser.ts b/src/parser.ts new file mode 100644 index 0000000..f94a710 --- /dev/null +++ b/src/parser.ts @@ -0,0 +1,842 @@ +import type { + ArrayExpression, + BooleanLiteral, + NumericLiteral, + ObjectExpression, + StringLiteral, + ObjectProperty, + VariableDeclaration, + VariableDeclarator, + FunctionDeclaration, + Identifier, + NullLiteral, + ExpressionStatement, + CallExpression, + MemberExpression, + IfStatement, + ReturnStatement, + BlockStatement, + TypeAnnotation, + GenericTypeAnnotation, + StringTypeAnnotation, + BooleanTypeAnnotation, + NullLiteralTypeAnnotation, + NumberTypeAnnotation, + AnyTypeAnnotation, + NodeData, + BaseNode, +} from './ast'; +import type { ParsingError } from './errors'; +import type { + GenericToken, + NumberToken, + StringToken, + SymbolToken, + Token, + TokenData, +} from './tokens'; +import type { Push, Tail, TailBy } from './utils/arrayUtils'; +import type { + ParseArrayResult, + ParseError, + ParseErrorResult, + ParseResult, +} from './utils/utilityTypes'; + +type ParseIdentifier< + TokenList extends Array>, + CanBeAnnotated extends boolean, +> = TokenList[0] extends SymbolToken< + infer Name, + TokenData +> + ? CanBeAnnotated extends true + ? TokenList[1] extends GenericToken< + ':', + TokenData + > + ? ParseTypeAnnotation> extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : ParseResult< + Identifier< + Name, + Node, + NodeData + >, + TokenList + > + : ParseErrorResult<'Type expected.', ColonLineNumber> + : ParseResult< + Identifier< + Name, + null, + NodeData + >, + Tail + > + : ParseResult< + Identifier< + Name, + null, + NodeData + >, + Tail + > + : null; + +type ParseVariableDeclarationHelper< + TokenList extends Array>, + Id extends BaseNode, + KindLineNumber extends number, + IdentifierLineNumber extends number, + EqualsLineNumber extends number, +> = ParseExpression> extends ParseResult< + infer Node, + infer TokenList, + infer Error +> + ? Error extends ParsingError + ? ParseError + : ParseResult< + VariableDeclaration< + [ + VariableDeclarator< + Id, + Node, + NodeData + >, + ], + 'const', + NodeData + >, + TokenList + > + : ParseErrorResult<'Expression expected.', EqualsLineNumber>; + +type ParseTypeAnnotation>> = + TokenList[0] extends SymbolToken<'string', TokenData> + ? ParseResult< + TypeAnnotation< + StringTypeAnnotation>, + NodeData + >, + Tail + > + : TokenList[0] extends SymbolToken< + 'boolean', + TokenData + > + ? ParseResult< + TypeAnnotation< + BooleanTypeAnnotation>, + NodeData + >, + Tail + > + : TokenList[0] extends SymbolToken<'null', TokenData> + ? ParseResult< + TypeAnnotation< + NullLiteralTypeAnnotation>, + NodeData + >, + Tail + > + : TokenList[0] extends SymbolToken< + 'number', + TokenData + > + ? ParseResult< + TypeAnnotation< + NumberTypeAnnotation>, + NodeData + >, + Tail + > + : TokenList[0] extends SymbolToken<'any', TokenData> + ? ParseResult< + TypeAnnotation< + AnyTypeAnnotation>, + NodeData + >, + Tail + > + : TokenList[0] extends SymbolToken< + infer E, + TokenData + > + ? ParseResult< + TypeAnnotation< + GenericTypeAnnotation>, + NodeData + >, + Tail + > + : null; + +type ParseVariableDeclaration>> = + TokenList[0] extends SymbolToken< + 'const', + TokenData + > + ? ParseIdentifier, true> extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : TokenList[0] extends GenericToken< + '=', + TokenData + > + ? ParseVariableDeclarationHelper< + TokenList, + Node, + KindLineNumber, + Node['data']['startLineNumber'], + EqualsLineNumber + > + : ParseError< + ParsingError< + "'const' declarations must be initialized.", + Node['data']['startLineNumber'] + > + > + : ParseError< + ParsingError< + 'Variable declaration list cannot be empty.', + KindLineNumber + > + > + : null; + +type ParseMemberExpression< + Node extends BaseNode>, + TokenList extends Array>, +> = TokenList[0] extends GenericToken<'.', TokenData> + ? TokenList[1] extends SymbolToken< + infer Name, + TokenData + > + ? ParseResult< + MemberExpression< + Node, + Identifier< + Name, + null, + NodeData + >, + false, + NodeData + >, + TailBy + > + : ParseErrorResult<'Identifier expected.', DotLineNumber> + : TokenList[0] extends GenericToken< + '[', + TokenData + > + ? ParseExpression> extends ParseResult< + infer ExpressionNode, + infer ExpressionTokenList, + infer ExpressionError + > + ? ExpressionError extends ParsingError + ? ParseError + : ExpressionTokenList[0] extends GenericToken< + ']', + TokenData + > + ? ParseResult< + MemberExpression< + Node, + ExpressionNode, + true, + NodeData< + ExpressionNode['data']['startLineNumber'], + ClosingBracketLineNumber + > + >, + Tail + > + : ParseError< + ParsingError< + "']' expected.", + ExpressionNode['data']['startLineNumber'] + > + > + : ParseErrorResult<'Expression expected.', BracketLineNumber> + : null; + +type ParseCallExpression< + Node extends BaseNode>, + TokenList extends Array>, +> = TokenList[0] extends GenericToken< + '(', + TokenData +> + ? ParseCallExpressionArguments< + Tail, + ParenLineNumber, + ')' + > extends ParseArrayResult + ? Error extends ParsingError + ? ParseError + : ParseResult< + CallExpression< + Node, + NodeList, + NodeData< + Node['data']['startLineNumber'], + TokenList[0]['data']['lineNumber'] + > + >, + Tail + > + : null + : null; + +type ParseCallExpressionArguments< + TokenList extends Array>, + ParenLineNumber extends number, + ClosingString extends string, + NeedComma extends boolean = false, + Result extends Array> = [], +> = TokenList[0] extends GenericToken + ? ParseArrayResult + : TokenList extends [] + ? ParseErrorResult<`'${ClosingString}' expected.`, ParenLineNumber> + : NeedComma extends true + ? TokenList[0] extends GenericToken<',', any> + ? ParseCallExpressionArgumentsHelper< + Tail, + ParenLineNumber, + ClosingString, + Result + > + : TokenList[0] extends Token> + ? ParseErrorResult<"',' expected.", LineNumber> + : never + : ParseCallExpressionArgumentsHelper< + TokenList, + ParenLineNumber, + ClosingString, + Result + >; + +type ParseCallExpressionArgumentsHelper< + TokenList extends Array>, + ParenLineNumber extends number, + ClosingString extends string, + Result extends Array> = [], +> = ParseExpression extends ParseResult< + infer Node, + infer TokenList, + infer Error +> + ? Error extends ParsingError + ? ParseError + : ParseCallExpressionArguments< + TokenList, + ParenLineNumber, + ClosingString, + true, + Push + > + : null; + +type CheckExpression< + Node extends BaseNode, + TokenList extends Array>, +> = ParseMemberExpression extends ParseResult< + infer Node, + infer TokenList, + infer Error +> + ? Error extends ParsingError + ? ParseError + : CheckExpression + : ParseCallExpression extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : CheckExpression + : ParseResult; + +type ParseExpression>> = + ParseExpressionHelper> extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : CheckExpression + : null; + +type TokenToNodeData> = InputToken extends Token< + TokenData +> + ? NodeData + : never; + +type ParseExpressionHelper< + TokenList extends Array>, + TokenTail extends Array> = Tail, + Data extends NodeData = TokenToNodeData, +> = TokenList[0] extends SymbolToken<'true', any> + ? ParseResult, TokenTail> + : TokenList[0] extends SymbolToken<'false', any> + ? ParseResult, TokenTail> + : TokenList[0] extends SymbolToken<'null', any> + ? ParseResult, TokenTail> + : TokenList[0] extends NumberToken + ? ParseResult, TokenTail> + : TokenList[0] extends StringToken + ? ParseResult, TokenTail> + : TokenList[0] extends SymbolToken + ? ParseResult, TokenTail> + : TokenList[0] extends GenericToken<'[', TokenData> + ? ParseArrayExpression, LineNumber> + : TokenList[0] extends GenericToken<'{', TokenData> + ? ParseObject, LineNumber> + : null; + +type ParseObject< + TokenList extends Array>, + InitialLineNumber extends number, + Result extends Array> = [], + NeedComma extends boolean = false, +> = TokenList[0] extends GenericToken<'}', TokenData> + ? ParseResult< + ObjectExpression>, + Tail + > + : TokenList extends [] + ? ParseErrorResult<"'}' expected.", InitialLineNumber> + : NeedComma extends true + ? TokenList[0] extends GenericToken<',', any> + ? ParseObjectItem, InitialLineNumber, Result> + : TokenList[0] extends Token> + ? ParseErrorResult<"',' expected.", L> + : never + : ParseObjectItem; + +type ParseObjectItem< + TokenList extends Array>, + InitialLineNumber extends number, + Result extends Array> = [], +> = TokenList[0] extends SymbolToken< + infer Name, + TokenData +> + ? TokenList[1] extends GenericToken<':', any> + ? ParseExpression> extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : ParseObject< + TokenList, + InitialLineNumber, + Push< + Result, + ObjectProperty< + Identifier< + Name, + null, + NodeData + >, + Node, + NodeData + > + >, + true + > + : ParseErrorResult<'Expression expected.', InitialLineNumber> + : ParseErrorResult<"'}' expected.", InitialLineNumber> + : ParseErrorResult<"'}' expected.", InitialLineNumber>; + +type ParseArrayExpression< + TokenList extends Array>, + StartLineNumber extends number, +> = ParseCallExpressionArguments< + TokenList, + StartLineNumber, + ']' +> extends ParseArrayResult + ? Error extends ParsingError + ? ParseError + : ParseResult< + ArrayExpression< + NodeList, + NodeData + >, + Tail + > + : null; + +type ParseExpressionStatement>> = + ParseExpression extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : ParseResult, TokenList> + : null; + +type ParseFunctionDeclaration>> = + TokenList[0] extends SymbolToken< + 'function', + TokenData + > + ? TokenList[1] extends SymbolToken< + infer Name, + TokenData + > + ? TokenList[2] extends GenericToken< + '(', + TokenData + > + ? ParseFunctionParams< + TailBy, + ParenLineNumber + > extends ParseArrayResult< + infer NodeList, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : ParseBlockStatement< + Tail, + TokenList[0]['data']['lineNumber'], + true + > extends ParseResult + ? Error extends ParsingError + ? ParseError + : ParseResult< + FunctionDeclaration< + Identifier< + Name, + null, + NodeData + >, + NodeList, + Node, + NodeData + >, + TokenList + > + : never + : never + : ParseErrorResult<"'(' expected.", FunctionNameLineNumber> + : ParseErrorResult<'Identifier expected.', FunctionLineNumber> + : null; + +type ParseFunctionParams< + TokenList extends Array>, + InitialLineNumber extends number, + Result extends Array>> = [], + NeedSemicolon extends boolean = false, +> = TokenList[0] extends GenericToken< + ')', + TokenData +> + ? TokenList[1] extends GenericToken<'{', any> + ? ParseArrayResult> + : ParseErrorResult<"'{' expected.", ParenLineNumber> + : TokenList extends [] + ? ParseErrorResult<"')' expected.", InitialLineNumber> + : NeedSemicolon extends true + ? TokenList[0] extends GenericToken<',', any> + ? ParseFunctionParamsHelper, InitialLineNumber, Result> + : ParseError< + ParsingError<"',' expected.", Result[0]['data']['endLineNumber']> + > + : ParseFunctionParamsHelper; + +type ParseFunctionParamsHelper< + TokenList extends Array>, + LineNumber extends number, + Result extends Array>, +> = ParseIdentifier extends ParseResult< + infer Node, + infer TokenList, + infer Error +> + ? Error extends ParsingError + ? ParseError + : ParseFunctionParams, true> + : ParseErrorResult<'Identifier expected.', LineNumber>; + +type ParseBlockStatement< + TokenList extends Array>, + InitialLineNumber extends number, + InFunctionScope extends boolean, + Result extends Array> = [], + NeedSemicolon extends boolean = false, +> = TokenList extends [] + ? Result[0] extends BaseNode> + ? ParseErrorResult<"'}' expected.", LineNumber> + : ParseErrorResult<"'}' expected.", InitialLineNumber> + : TokenList[0] extends GenericToken<'}', TokenData> + ? ParseResult< + BlockStatement>, + Tail + > + : TokenList[0] extends GenericToken<';', any> + ? ParseBlockStatement< + Tail, + InitialLineNumber, + InFunctionScope, + Result, + false + > + : NeedSemicolon extends false + ? ParseBlockStatementHelper< + TokenList, + InitialLineNumber, + InFunctionScope, + Result + > + : TokenList[0] extends Token< + TokenData + > + ? PrecedingLinebreak extends true + ? ParseBlockStatementHelper + : ParseErrorResult<"';' expected.", LineNumber> + : never; + +type ParseTopLevel< + TokenList extends Array>, + Result extends Array> = [], + NeedSemicolon extends boolean = false, +> = TokenList extends [] + ? ParseArrayResult + : TokenList[0] extends GenericToken<';', any> + ? ParseTopLevel, Result, false> + : NeedSemicolon extends false + ? ParseTopLevelHelper + : TokenList[0] extends Token< + TokenData + > + ? PrecedingLinebreak extends true + ? ParseTopLevelHelper + : ParseErrorResult<"';' expected.", LineNumber> + : never; + +type ParseBlockStatementHelper< + TokenList extends Array>, + LineNumber extends number, + InFunctionScope extends boolean, + Result extends Array>, +> = ParseStatementHelper extends ParseResult< + infer Node, + infer TokenList, + infer Error, + infer Data +> + ? Error extends ParsingError + ? ParseError + : Data extends boolean + ? ParseBlockStatement< + TokenList, + LineNumber, + InFunctionScope, + Push, + Data + > + : never + : never; + +type ParseTopLevelHelper< + TokenList extends Array>, + Result extends Array>, +> = ParseStatementHelper extends ParseResult< + infer Node, + infer TokenList, + infer Error, + infer Data +> + ? Error extends ParsingError + ? ParseError + : Data extends boolean + ? ParseTopLevel, Data> + : never + : never; + +type ParseIfStatement< + TokenList extends Array>, + InFunctionScope extends boolean, +> = TokenList[0] extends SymbolToken<'if', TokenData> + ? TokenList[1] extends GenericToken< + '(', + TokenData + > + ? ParseExpression> extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : ParseIfStatementHelper< + Node, + TokenList, + IfLineNumber, + InFunctionScope, + Node['data']['endLineNumber'] + > + : ParseErrorResult<'Expression expected.', ParenLineNumber> + : ParseErrorResult<"'(' expected.", IfLineNumber> + : null; + +type ParseReturnStatementHelper< + TokenList extends Array>, + StartLineNumber extends number, +> = TokenList[0] extends GenericToken< + ';', + TokenData +> + ? ParseResult< + ReturnStatement>, + Tail + > + : ParseExpression extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : ParseResult< + ReturnStatement< + Node, + NodeData + >, + TokenList + > + : never; + +type ParseReturnStatement< + TokenList extends Array>, + InFunctionScope extends boolean, +> = TokenList[0] extends SymbolToken<'return', TokenData> + ? InFunctionScope extends true + ? TokenList[1] extends Token, any> + ? PrecedingLinebreak extends false + ? ParseReturnStatementHelper, LineNumber> + : ParseResult< + ReturnStatement>, + Tail + > + : ParseResult>, []> + : ParseError< + ParsingError< + "A 'return' statement can only be used within a function body.", + LineNumber + > + > + : null; + +type ParseIfStatementHelper< + Node extends BaseNode, + TokenList extends Array>, + StartLineNumber extends number, + InFunctionScope extends boolean, + IfExpressionLineNumber extends number, +> = TokenList[0] extends GenericToken< + ')', + TokenData +> + ? TokenList[1] extends GenericToken< + '{', + TokenData + > + ? ParseBlockStatement< + TailBy, + CurlyLineNumber, + InFunctionScope + > extends ParseResult + ? Error extends ParsingError + ? ParseError + : ParseResult< + IfStatement< + Node, + BlockNode, + NodeData + >, + TokenList + > + : never + : ParseErrorResult<"'{' expected.", ClosingParenLineNumber> + : ParseErrorResult<"')' expected.", IfExpressionLineNumber>; + +type ParseStatementHelper< + TokenList extends Array>, + InFunctionScope extends boolean, +> = ParseFunctionDeclaration extends ParseResult< + infer Node, + infer TokenList, + infer Error +> + ? Error extends ParsingError + ? ParseError + : ParseResult + : ParseVariableDeclaration extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : ParseResult + : ParseIfStatement extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : ParseResult + : ParseReturnStatement extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : ParseResult + : ParseExpressionStatement extends ParseResult< + infer Node, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? ParseError + : ParseResult + : ParseErrorResult<'Declaration or statement expected.', 1>; + +export type Parse>> = + ParseTopLevel extends ParseArrayResult< + infer NodeList, + infer TokenList, + infer Error + > + ? Error extends ParsingError + ? Error + : NodeList + : never; diff --git a/src/serializer.ts b/src/serializer.ts new file mode 100644 index 0000000..3b0d9b1 --- /dev/null +++ b/src/serializer.ts @@ -0,0 +1,148 @@ +import type { + AnyType, + ArrayType, + BooleanLiteralType, + BooleanType, + FunctionType, + NeverType, + NullType, + NumberLiteralType, + NumberType, + ObjectType, + StaticType, + StringLiteralType, + StringType, + UndefinedType, + UnionType, + UnknownType, + VoidType, +} from './types'; +import type { Tail } from './utils/arrayUtils'; + +export type Serialize = + MapLiteralToType extends infer MappedType + ? MappedType extends StringType + ? 'string' + : MappedType extends BooleanType + ? 'boolean' + : MappedType extends NumberType + ? 'number' + : MappedType extends NullType + ? 'null' + : MappedType extends UndefinedType + ? 'undefined' + : MappedType extends VoidType + ? 'void' + : MappedType extends AnyType + ? 'any' + : MappedType extends UnknownType + ? 'unknown' + : MappedType extends NeverType + ? 'never' + : MappedType extends ArrayType + ? SerializeArray + : MappedType extends UnionType + ? SerializeUnion + : MappedType extends ObjectType + ? SerializeObject + : MappedType extends FunctionType + ? SerializeFunction + : never + : never; + +type SerializeFunction< + Params extends Array<[string, StaticType]>, + Return extends StaticType, +> = SerializeFunctionParams extends infer SerializedParams + ? SerializedParams extends string + ? `(${SerializedParams}) => ${Serialize}` + : never + : never; + +type SerializeFunctionParams< + Params extends Array<[string, StaticType]>, + Result extends string = '', +> = Params extends [] + ? Result + : Params[0] extends [infer Key, infer Value] + ? Value extends StaticType + ? Key extends string + ? SerializeFunctionParams< + Tail, + `${Result}${Key}: ${Serialize}` extends infer SerializedSignature + ? SerializedSignature extends string + ? Params['length'] extends 1 + ? `${SerializedSignature}` + : `${SerializedSignature}, ` + : never + : never + > + : never + : never + : never; + +type MapLiteralToType = + Type extends NumberLiteralType + ? NumberType + : Type extends StringLiteralType + ? StringType + : Type extends BooleanLiteralType + ? BooleanType + : Type; + +type ShouldUseParens = Type extends UnionType + ? true + : Type extends FunctionType + ? true + : false; + +type SerializeArray = + Serialize extends infer SerializedElements + ? SerializedElements extends string + ? ShouldUseParens extends true + ? `(${SerializedElements})[]` + : `${SerializedElements}[]` + : never + : never; + +type SerializeUnion< + UnionTypes extends Array, + Result extends string = '', +> = UnionTypes extends [] + ? Result + : UnionTypes[0] extends StaticType + ? Serialize extends infer SerializedType + ? SerializedType extends string + ? SerializeUnion< + Tail, + UnionTypes['length'] extends 1 + ? `${Result}${SerializedType}` + : `${Result}${SerializedType} | ` + > + : never + : never + : never; + +type SerializeObject< + Properties extends Array<[string, StaticType]>, + Result extends string = '', +> = Properties extends [] + ? Result extends '' + ? '{}' + : `{ ${Result} }` + : Properties[0] extends [infer Key, infer Value] + ? Value extends StaticType + ? Key extends string + ? `${Key}: ${Serialize}` extends infer SerializedProperty + ? SerializedProperty extends string + ? SerializeObject< + Tail, + Properties['length'] extends 1 + ? `${Result}${SerializedProperty};` + : `${Result}${SerializedProperty}; ` + > + : never + : never + : never + : never + : never; diff --git a/src/test/checker.test.ts b/src/test/checker.test.ts new file mode 100644 index 0000000..8abc851 --- /dev/null +++ b/src/test/checker.test.ts @@ -0,0 +1,1127 @@ +import type { Tokenize } from '../tokenizer'; +import type { Parse } from '../parser'; +import type { Check } from '../checker'; +import { expectType } from './utils'; + +type TypeCheck = Tokenize extends infer G + ? G extends Array + ? Parse extends infer J + ? J extends Array + ? Check + : never + : never + : never + : never; + +expectType< + TypeCheck<` + +hello + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'hello'.", + lineNumber: 3, + }, +]); + +expectType< + TypeCheck<` + +world; + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'world'.", + lineNumber: 3, + }, +]); + +expectType< + TypeCheck<` + +"string" + +`> +>([]); + +expectType< + TypeCheck<` + +123; + +`> +>([]); + +expectType< + TypeCheck<` + +const a = null + +`> +>([]); + +expectType< + TypeCheck<` + +const b = "world"; + +`> +>([]); + +expectType< + TypeCheck<` + +const hello = 1; +hello; + +`> +>([]); + +expectType< + TypeCheck<` + +const hello = { foo: "bar" }; +hello.world + +`> +>([ + { + type: 'TypeError', + message: "Property 'world' does not exist on type '{ foo: string; }'.", + lineNumber: 4, + }, +]); + +expectType< + TypeCheck<` + +const hello = { foo: "bar" }; +hello.foo; + +`> +>([]); + +expectType< + TypeCheck<` + +const hello = { + foo: { + bar: "bazz" + } +}; + +hello.foo.bar; + +`> +>([]); + +expectType< + TypeCheck<` + +const hello = { + foo: { + bar: "bazz" + } +}; + +hello + .foo + .hey; + +`> +>([ + { + type: 'TypeError', + message: "Property 'hey' does not exist on type '{ bar: string; }'.", + lineNumber: 11, + }, +]); + +expectType< + TypeCheck<` + +const hello = "world"; + +hello.foo + +`> +>([ + { + type: 'TypeError', + message: "Property 'foo' does not exist on type 'string'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +const hello = "world"; +const foo = hello; + +`> +>([]); + +expectType< + TypeCheck<` + +const hello = "world"; +const foo = hello; + +foo; + +`> +>([]); + +expectType< + TypeCheck<` + +const hello: string = "hello"; + +`> +>([]); + +expectType< + TypeCheck<` + +const hello: number = 123; + +`> +>([]); + +expectType< + TypeCheck<` + +const hello: number = "hello"; + +`> +>([ + { + type: 'TypeError', + message: "Type 'string' is not assignable to type 'number'.", + lineNumber: 3, + }, +]); + +expectType< + TypeCheck<` + +const hello: string = 123; + +`> +>([ + { + type: 'TypeError', + message: "Type 'number' is not assignable to type 'string'.", + lineNumber: 3, + }, +]); + +expectType< + TypeCheck<` + +const hello = "world"; +const foo: number = hello; + +`> +>([ + { + type: 'TypeError', + message: "Type 'string' is not assignable to type 'number'.", + lineNumber: 4, + }, +]); + +expectType< + TypeCheck<` + +const hello = "world"; + +const foo: string = hello; + +`> +>([]); + +expectType< + TypeCheck<` + +const hello = {hey: "world"}; + +const foo: number = hello; + +`> +>([ + { + type: 'TypeError', + message: "Type '{ hey: string; }' is not assignable to type 'number'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +const hello = {hey: "world"}; + +const foo: number = hello.hey; + +`> +>([ + { + type: 'TypeError', + message: "Type 'string' is not assignable to type 'number'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +const hello = {hey: "world"}; + +const foo: string = hello.hey; + +`> +>([]); + +expectType< + TypeCheck<` + +const o = {}; + +o["hey"]; + +`> +>([ + { + type: 'TypeError', + message: "Property 'hey' does not exist on type '{}'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +const o = {hey: "ho"}; + +o["hey"]; + +`> +>([]); + +expectType< + TypeCheck<` + +const o = {}; +const k = "hey"; + +o + [k]; + +`> +>([ + { + type: 'TypeError', + message: "Property 'hey' does not exist on type '{}'.", + lineNumber: 7, + }, +]); + +expectType< + TypeCheck<` + +const o = {hey: "ho"}; +const k = "hey"; + +o[k]; + +`> +>([]); + +expectType< + TypeCheck<` + +const o = { + hey: { + ho:"let's go" + } +}; + +const k = "hey"; + +o[k]["ho"]; + +`> +>([]); + +expectType< + TypeCheck<` + +const o = { + hey: { + ho:"let's go" + } +}; + +const k = "hey"; + +o[k]["hi"]; + +`> +>([ + { + type: 'TypeError', + message: "Property 'hi' does not exist on type '{ ho: string; }'.", + lineNumber: 11, + }, +]); + +expectType< + TypeCheck<` + +const o = { + hey: { + ho:"let's go" + } +}; + +const k = "hey"; + +o[k][{}]; + +`> +>([ + { + type: 'TypeError', + message: "Type '{}' cannot be used as an index type.", + lineNumber: 11, + }, +]); + +expectType< + TypeCheck<` + +const o = { + hey: "ho:" +}; + +o[true]; + +`> +>([ + { + type: 'TypeError', + message: "Type 'boolean' cannot be used as an index type.", + lineNumber: 7, + }, +]); + +expectType< + TypeCheck<` + +const a = [1,2,3]; + +const b: string = a; + +`> +>([ + { + type: 'TypeError', + message: "Type 'number[]' is not assignable to type 'string'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +const a = [1,2,'3']; + +const b: string = a; + +`> +>([ + { + type: 'TypeError', + message: "Type '(number | string)[]' is not assignable to type 'string'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +const c: any = 1; + +const a = [c, 2, '3']; + +const b: string = a; + +`> +>([ + { + type: 'TypeError', + message: "Type 'any[]' is not assignable to type 'string'.", + lineNumber: 7, + }, +]); +expectType< + TypeCheck<` + +const a = [1,2,'3'][0]; + +const b: string = a; + +`> +>([ + { + type: 'TypeError', + message: "Type 'number | string' is not assignable to type 'string'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +const a = [[1, 2]]; + +const b: string = a; + +`> +>([ + { + type: 'TypeError', + message: "Type 'number[][]' is not assignable to type 'string'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +hello + +world + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'hello'.", + lineNumber: 3, + }, + { + type: 'TypeError', + message: "Cannot find name 'world'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +const a: number = hello + +const b: number = a + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'hello'.", + lineNumber: 3, + }, +]); + +expectType< + TypeCheck<` + +const a: string = hello + +const b: number = a + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'hello'.", + lineNumber: 3, + }, + { + type: 'TypeError', + message: "Type 'string' is not assignable to type 'number'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +const a: string = hello() + +const b: number = a + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'hello'.", + lineNumber: 3, + }, + { + type: 'TypeError', + message: "Type 'string' is not assignable to type 'number'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +const a: string = hello(foo, bar) + +const b: number = a + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'hello'.", + lineNumber: 3, + }, + { + type: 'TypeError', + message: "Cannot find name 'foo'.", + lineNumber: 3, + }, + { + type: 'TypeError', + message: "Cannot find name 'bar'.", + lineNumber: 3, + }, + { + type: 'TypeError', + message: "Type 'string' is not assignable to type 'number'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +function foo(a: string, b: number) {} + +foo(1, 'a') + +`> +>([ + { + type: 'TypeError', + message: + "Argument of type 'number' is not assignable to parameter of type 'string'.", + lineNumber: 5, + }, +]); + +expectType< + TypeCheck<` + +function foo(a: number) { + return 5 +} + +const a = foo('a', bar, bazz) + +const b: string = a; + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'bar'.", + lineNumber: 7, + }, + { + type: 'TypeError', + message: "Cannot find name 'bazz'.", + lineNumber: 7, + }, + { + type: 'TypeError', + message: 'Expected 1 arguments, but got 3.', + lineNumber: 7, + }, + { + type: 'TypeError', + message: "Type 'number' is not assignable to type 'string'.", + lineNumber: 9, + }, +]); + +expectType< + TypeCheck<` + +function foo(a: number, b: boolean) { + return 5 +} + +const b: string = foo; + +`> +>([ + { + type: 'TypeError', + message: + "Type '(a: number, b: boolean) => number' is not assignable to type 'string'.", + lineNumber: 7, + }, +]); + +expectType< + TypeCheck<` + +function foo(a: number, b: boolean) { + return 5 +} + +const b: string = { hello: true, world: foo, hey: [1, {}] }; + +`> +>([ + { + type: 'TypeError', + message: + "Type '{ hello: boolean; world: (a: number, b: boolean) => number; hey: (number | {})[]; }' is not assignable to type 'string'.", + lineNumber: 7, + }, +]); + +expectType< + TypeCheck<` + +function bar() { + if (a) { + return 2; + } + + return 1; +} + +const b: number = bar; + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'a'.", + lineNumber: 4, + }, + { + type: 'TypeError', + message: "Type '() => number' is not assignable to type 'number'.", + lineNumber: 11, + }, +]); + +expectType< + TypeCheck<` + +function bar() { + if (a) { + return 'foo'; + } + + return 1; +} + +const b: number = bar; + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'a'.", + lineNumber: 4, + }, + { + type: 'TypeError', + message: "Type '() => string | number' is not assignable to type 'number'.", + lineNumber: 11, + }, +]); + +expectType< + TypeCheck<` + +function bar() { + return { + hello: 'world' + } +} + +const b: number = bar; + +`> +>([ + { + type: 'TypeError', + message: + "Type '() => { hello: string; }' is not assignable to type 'number'.", + lineNumber: 9, + }, +]); + +expectType< + TypeCheck<` + +const a = [1, 'a']; +const b = [true, null]; +const c = [a[1], b[0]]; + +const num: number = c; + +`> +>([ + { + type: 'TypeError', + message: + "Type '(number | string | boolean | null)[]' is not assignable to type 'number'.", + lineNumber: 7, + }, +]); + +expectType< + TypeCheck<` + +function bar() { + if (a) { + return { + hello: 'world' + }; + } + + if (a) { + return { + hello: 123 + }; + } + + return { + hello: '1' + }; +} + +const b: string = bar().hello; + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'a'.", + lineNumber: 4, + }, + { + type: 'TypeError', + message: "Cannot find name 'a'.", + lineNumber: 10, + }, + { + type: 'TypeError', + message: "Type 'string | number' is not assignable to type 'string'.", + lineNumber: 21, + }, +]); + +expectType< + TypeCheck<` + +function bar() { + if (a) { + return { + hello: 'world' + }; + } + + if (a) { + return { + hello: '123' + }; + } + + return { + hello: '1' + }; +} + +const b: string = bar().hello; + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'a'.", + lineNumber: 4, + }, + { + type: 'TypeError', + message: "Cannot find name 'a'.", + lineNumber: 10, + }, +]); + +expectType< + TypeCheck<` + +function bar() { + if (a) { + return { + hello: 'world' + }; + } + + if (a) { + return { + foo: '123' + }; + } + + return { + hello: '1' + }; +} + +const b: string = bar().hello; + +`> +>([ + { + type: 'TypeError', + message: "Cannot find name 'a'.", + lineNumber: 4, + }, + { + type: 'TypeError', + message: "Cannot find name 'a'.", + lineNumber: 10, + }, + { + type: 'TypeError', + message: "Type 'string | undefined' is not assignable to type 'string'.", + lineNumber: 21, + }, +]); + +expectType< + TypeCheck<` + +const c: any = 1 + +const a = [1, c, 'a']; + +const b: string = a; + +`> +>([ + { + type: 'TypeError', + message: "Type 'any[]' is not assignable to type 'string'.", + lineNumber: 7, + }, +]); + +expectType< + TypeCheck<` + +function foo(bar, hello) { + hey() +} + +const a: number = foo; + +`> +>([ + { + type: 'TypeError', + message: "Parameter 'bar' implicitly has an 'any' type.", + lineNumber: 3, + }, + { + type: 'TypeError', + message: "Parameter 'hello' implicitly has an 'any' type.", + lineNumber: 3, + }, + { + type: 'TypeError', + message: "Cannot find name 'hey'.", + lineNumber: 4, + }, + { + type: 'TypeError', + message: + "Type '(bar: any, hello: any) => void' is not assignable to type 'number'.", + lineNumber: 7, + }, +]); + +expectType< + TypeCheck<` + +function a (a: number) { + return '1'; +} + +function b () { + return 1; +} + +const c = [a, 'b']; + +const d: number = c[0](1); + +`> +>([ + { + type: 'TypeError', + message: + "This expression is not callable. Not all constituents of type '(a: number) => string | string' are callable.", + lineNumber: 13, + }, +]); + +expectType< + TypeCheck<` + +function a (a: number) { + return '1'; +} + +function b () { + return 1; +} + +const c = [a, b]; + +const d: number = c[0](1); + +`> +>([ + { + type: 'TypeError', + message: "Type 'number | string' is not assignable to type 'number'.", + lineNumber: 13, + }, +]); + +expectType< + TypeCheck<` + +function a (a: number) { + return '1'; +} + +function b () { + return 1; +} + +const c = [a, b]; + +const d: number = c[0](); + +`> +>([ + { + type: 'TypeError', + message: 'Expected 1 arguments, but got 0.', + lineNumber: 13, + }, + { + type: 'TypeError', + message: "Type 'number | string' is not assignable to type 'number'.", + lineNumber: 13, + }, +]); + +expectType< + TypeCheck<` + +function a (a: number) { + return '1'; +} + +function b (b: string) { + return 1; +} + +const c = [a, b]; + +const d: number = c[0](1); + +`> +>([ + { + type: 'TypeError', + message: + "Argument of type 'number' is not assignable to parameter of type 'never'.", + lineNumber: 13, + }, + { + type: 'TypeError', + message: "Type 'number | string' is not assignable to type 'number'.", + lineNumber: 13, + }, +]); + +expectType< + TypeCheck<` + +function a (a: number) { + return '1'; +} + +function b (b: any) { + return 1; +} + +const c = [a, b]; + +const d: number = c[0](1); + +`> +>([ + { + type: 'TypeError', + message: "Type 'number | string' is not assignable to type 'number'.", + lineNumber: 13, + }, +]); + +expectType< + TypeCheck<` + +function a (a: number, c: boolean) { + return '1'; +} + +function b (b: any) { + return 1; +} + +const c = [a, b]; + +const d: number = c[0](1, false); + +`> +>([ + { + type: 'TypeError', + message: "Type 'number | string' is not assignable to type 'number'.", + lineNumber: 13, + }, +]); diff --git a/src/test/parser.test.ts b/src/test/parser.test.ts new file mode 100644 index 0000000..1d10817 --- /dev/null +++ b/src/test/parser.test.ts @@ -0,0 +1,2836 @@ +import type { Tokenize } from '../tokenizer'; +import type { Parse } from '../parser'; +import { expectType } from './utils'; + +type ParseAst = Tokenize extends infer G + ? G extends Array + ? Parse + : never + : never; + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'StringLiteral', + value: 'hello', + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'NumericLiteral', + value: '123', + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'BooleanLiteral', + value: true, + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'BooleanLiteral', + value: false, + data: { startLineNumber: 2, endLineNumber: 2 }, + }, + data: { + startLineNumber: 2, + endLineNumber: 2, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'NullLiteral', + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'NullLiteral', + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { + startLineNumber: 3, + endLineNumber: 3, + }, + }, +]); + +expectType>({ + type: 'ParsingError', + message: "';' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "';' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "';' expected.", + lineNumber: 1, +}); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'ExpressionStatement', + expression: { + type: 'StringLiteral', + value: 'world', + data: { + startLineNumber: 2, + endLineNumber: 2, + }, + }, + data: { + startLineNumber: 2, + endLineNumber: 2, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'ExpressionStatement', + expression: { + type: 'StringLiteral', + value: 'world', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'ExpressionStatement', + expression: { + type: 'StringLiteral', + value: 'world', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'ExpressionStatement', + expression: { + type: 'StringLiteral', + value: 'world', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'VariableDeclaration', + kind: 'const', + declarations: [ + { + type: 'VariableDeclarator', + init: { + type: 'StringLiteral', + value: 'world', + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + id: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'VariableDeclaration', + kind: 'const', + declarations: [ + { + type: 'VariableDeclarator', + init: { + type: 'NumericLiteral', + value: '123', + data: { startLineNumber: 5, endLineNumber: 5 }, + }, + id: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 3, endLineNumber: 5 }, + }, + ], + data: { startLineNumber: 2, endLineNumber: 5 }, + }, +]); + +expectType>({ + type: 'ParsingError', + message: 'Variable declaration list cannot be empty.', + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "'const' declarations must be initialized.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: 'Expression expected.', + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: 'Expression expected.', + lineNumber: 2, +}); + +expectType>({ + type: 'ParsingError', + message: 'Type expected.', + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: 'Identifier expected.', + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: 'Identifier expected.', + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: 'Identifier expected.', + lineNumber: 2, +}); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + property: { + type: 'Identifier', + name: 'world', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + computed: false, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'MemberExpression', + object: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + property: { + type: 'Identifier', + name: 'world', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + computed: false, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + property: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + computed: false, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'MemberExpression', + object: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + property: { + type: 'Identifier', + name: 'world', + typeAnnotation: null, + data: { + startLineNumber: 2, + endLineNumber: 2, + }, + }, + computed: false, + data: { + startLineNumber: 1, + endLineNumber: 2, + }, + }, + property: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 3, + endLineNumber: 3, + }, + }, + computed: false, + data: { + startLineNumber: 1, + endLineNumber: 3, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 3, + }, + }, +]); + +expectType>({ + type: 'ParsingError', + message: 'Identifier expected.', + lineNumber: 2, +}); + +expectType>({ + type: 'ParsingError', + message: 'Identifier expected.', + lineNumber: 2, +}); + +expectType>([ + { + type: 'VariableDeclaration', + declarations: [ + { + type: 'VariableDeclarator', + id: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + init: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + property: { + type: 'Identifier', + name: 'bar', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + computed: false, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + kind: 'const', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 3, + endLineNumber: 3, + }, + }, + arguments: [], + data: { startLineNumber: 3, endLineNumber: 4 }, + }, + data: { + startLineNumber: 3, + endLineNumber: 4, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [ + { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [ + { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'StringLiteral', + value: '2', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [ + { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'NullLiteral', + data: { + startLineNumber: 2, + endLineNumber: 2, + }, + }, + { + type: 'BooleanLiteral', + value: true, + data: { + startLineNumber: 3, + endLineNumber: 3, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 3 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 3, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + property: { + type: 'Identifier', + name: 'world', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + computed: false, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [ + { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + property: { + type: 'Identifier', + name: 'world', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + computed: false, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [ + { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [ + { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>({ + type: 'ParsingError', + message: "')' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "',' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "',' expected.", + lineNumber: 2, +}); +expectType>({ + type: 'ParsingError', + message: "']' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "',' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "',' expected.", + lineNumber: 2, +}); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'ArrayExpression', + elements: [], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'ArrayExpression', + elements: [ + { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'ArrayExpression', + elements: [ + { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 2, + endLineNumber: 2, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 2 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 2, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'ArrayExpression', + elements: [ + { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'StringLiteral', + value: 'hello', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'ArrayExpression', + elements: [ + { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'ArrayExpression', + elements: [ + { + type: 'NumericLiteral', + value: '2', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'VariableDeclaration', + declarations: [ + { + type: 'VariableDeclarator', + id: { + type: 'Identifier', + name: 'array', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + init: { + type: 'ArrayExpression', + elements: [ + { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + kind: 'const', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>({ + type: 'ParsingError', + message: "'}' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "'}' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "'}' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "'}' expected.", + lineNumber: 1, +}); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'ObjectExpression', + properties: [], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'ObjectExpression', + properties: [ + { + type: 'ObjectProperty', + key: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + value: { + type: 'StringLiteral', + value: 'world', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'ObjectExpression', + properties: [ + { + type: 'ObjectProperty', + key: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + value: { + type: 'StringLiteral', + value: 'world', + data: { + startLineNumber: 3, + endLineNumber: 3, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 3, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 4 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 4, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'ObjectExpression', + properties: [ + { + type: 'ObjectProperty', + key: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + value: { + type: 'StringLiteral', + value: 'hello', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'ObjectProperty', + key: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + value: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'bar', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'ObjectExpression', + properties: [ + { + type: 'ObjectProperty', + key: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + value: { + type: 'ObjectExpression', + properties: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'VariableDeclaration', + declarations: [ + { + type: 'VariableDeclarator', + id: { + type: 'Identifier', + name: 'array', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + init: { + type: 'ObjectExpression', + properties: [ + { + type: 'ObjectProperty', + key: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + value: { + type: 'StringLiteral', + value: 'world', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + kind: 'const', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [], + body: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [ + { + type: 'Identifier', + name: 'a', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + body: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [ + { + type: 'Identifier', + name: 'a', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'Identifier', + name: 'b', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + body: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [ + { + type: 'Identifier', + name: 'a', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'Identifier', + name: 'b', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + body: { + type: 'BlockStatement', + body: [ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [ + { + type: 'Identifier', + name: 'a', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'Identifier', + name: 'b', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + body: { + type: 'BlockStatement', + body: [ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'ExpressionStatement', + expression: { + type: 'StringLiteral', + value: 'bar', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [ + { + type: 'Identifier', + name: 'a', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'Identifier', + name: 'b', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + body: { + type: 'BlockStatement', + body: [ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'bar', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + body: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>({ + type: 'ParsingError', + message: 'Identifier expected.', + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "'(' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "')' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "'{' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "'{' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "'}' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: 'Identifier expected.', + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "',' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: 'Declaration or statement expected.', + lineNumber: 1, +}); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [ + { + type: 'Identifier', + name: 'a', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'Identifier', + name: 'b', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + body: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [ + { + type: 'Identifier', + name: 'a', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'Identifier', + name: 'b', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + body: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'IfStatement', + test: { + type: 'Identifier', + name: 'a', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + consequent: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'IfStatement', + test: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + consequent: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'IfStatement', + test: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + consequent: { + type: 'BlockStatement', + body: [ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'bar', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'IfStatement', + test: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + consequent: { + type: 'BlockStatement', + body: [ + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'bar', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + { + type: 'ExpressionStatement', + expression: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'bazz', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>({ + type: 'ParsingError', + message: "'(' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: 'Expression expected.', + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "')' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "'{' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "'}' expected.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: 'Expression expected.', + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "A 'return' statement can only be used within a function body.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "A 'return' statement can only be used within a function body.", + lineNumber: 1, +}); + +expectType>({ + type: 'ParsingError', + message: "A 'return' statement can only be used within a function body.", + lineNumber: 1, +}); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [], + body: { + type: 'BlockStatement', + body: [ + { + type: 'ReturnStatement', + argument: { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [], + body: { + type: 'BlockStatement', + body: [ + { + type: 'ReturnStatement', + argument: { + type: 'NumericLiteral', + value: '1', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [], + body: { + type: 'BlockStatement', + body: [ + { + type: 'ReturnStatement', + argument: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [], + body: { + type: 'BlockStatement', + body: [ + { + type: 'ReturnStatement', + argument: null, + data: { + startLineNumber: 2, + endLineNumber: 2, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 3, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 3, + }, + }, +]); + +expectType>([ + { + type: 'VariableDeclaration', + kind: 'const', + declarations: [ + { + type: 'VariableDeclarator', + init: { + type: 'StringLiteral', + value: 'world', + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + id: { + type: 'Identifier', + name: 'hello', + typeAnnotation: { + type: 'TypeAnnotation', + typeAnnotation: { + type: 'StringTypeAnnotation', + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { startLineNumber: 1, endLineNumber: 1 }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'VariableDeclaration', + kind: 'const', + declarations: [ + { + type: 'VariableDeclarator', + init: { + type: 'StringLiteral', + value: 'world', + data: { startLineNumber: 4, endLineNumber: 4 }, + }, + id: { + type: 'Identifier', + name: 'hello', + typeAnnotation: { + type: 'TypeAnnotation', + typeAnnotation: { + type: 'NumberTypeAnnotation', + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 2, endLineNumber: 2 }, + }, + data: { + startLineNumber: 2, + endLineNumber: 4, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 4, + }, + }, +]); + +expectType>([ + { + type: 'VariableDeclaration', + kind: 'const', + declarations: [ + { + type: 'VariableDeclarator', + init: { + type: 'StringLiteral', + value: 'world', + data: { startLineNumber: 4, endLineNumber: 4 }, + }, + id: { + type: 'Identifier', + name: 'hello', + typeAnnotation: { + type: 'TypeAnnotation', + typeAnnotation: { + type: 'NullLiteralTypeAnnotation', + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 2, endLineNumber: 2 }, + }, + data: { + startLineNumber: 2, + endLineNumber: 4, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 4, + }, + }, +]); + +expectType>([ + { + type: 'VariableDeclaration', + kind: 'const', + declarations: [ + { + type: 'VariableDeclarator', + init: { + type: 'StringLiteral', + value: 'world', + data: { startLineNumber: 4, endLineNumber: 4 }, + }, + id: { + type: 'Identifier', + name: 'hello', + typeAnnotation: { + type: 'TypeAnnotation', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 2, endLineNumber: 2 }, + }, + data: { + startLineNumber: 2, + endLineNumber: 4, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 4, + }, + }, +]); + +expectType>([ + { + type: 'VariableDeclaration', + kind: 'const', + declarations: [ + { + type: 'VariableDeclarator', + init: { + type: 'StringLiteral', + value: 'world', + data: { startLineNumber: 4, endLineNumber: 4 }, + }, + id: { + type: 'Identifier', + name: 'hello', + typeAnnotation: { + type: 'TypeAnnotation', + typeAnnotation: { + type: 'AnyTypeAnnotation', + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 2, endLineNumber: 2 }, + }, + data: { + startLineNumber: 2, + endLineNumber: 4, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 4, + }, + }, +]); + +expectType>([ + { + type: 'VariableDeclaration', + kind: 'const', + declarations: [ + { + type: 'VariableDeclarator', + init: { + type: 'StringLiteral', + value: 'world', + data: { startLineNumber: 4, endLineNumber: 4 }, + }, + id: { + type: 'Identifier', + name: 'hello', + typeAnnotation: { + type: 'TypeAnnotation', + typeAnnotation: { + type: 'GenericTypeAnnotation', + id: 'Foo', + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 3, endLineNumber: 3 }, + }, + data: { startLineNumber: 2, endLineNumber: 2 }, + }, + data: { + startLineNumber: 2, + endLineNumber: 4, + }, + }, + ], + data: { + startLineNumber: 1, + endLineNumber: 4, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + params: [ + { + type: 'Identifier', + name: 'a', + typeAnnotation: { + type: 'TypeAnnotation', + typeAnnotation: { + type: 'StringTypeAnnotation', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + ], + body: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 2, + endLineNumber: 2, + }, + }, + params: [ + { + type: 'Identifier', + name: 'a', + typeAnnotation: { + type: 'TypeAnnotation', + typeAnnotation: { + type: 'NumberTypeAnnotation', + data: { + startLineNumber: 4, + endLineNumber: 4, + }, + }, + data: { + startLineNumber: 4, + endLineNumber: 4, + }, + }, + data: { + startLineNumber: 4, + endLineNumber: 4, + }, + }, + ], + body: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 5, + endLineNumber: 6, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 6, + }, + }, +]); + +expectType>([ + { + type: 'FunctionDeclaration', + id: { + type: 'Identifier', + name: 'foo', + typeAnnotation: null, + data: { + startLineNumber: 2, + endLineNumber: 2, + }, + }, + params: [ + { + type: 'Identifier', + name: 'a', + typeAnnotation: { + type: 'TypeAnnotation', + typeAnnotation: { + type: 'NullLiteralTypeAnnotation', + data: { + startLineNumber: 4, + endLineNumber: 4, + }, + }, + data: { + startLineNumber: 4, + endLineNumber: 4, + }, + }, + data: { + startLineNumber: 4, + endLineNumber: 4, + }, + }, + { + type: 'Identifier', + name: 'b', + typeAnnotation: { + type: 'TypeAnnotation', + typeAnnotation: { + type: 'BooleanTypeAnnotation', + data: { + startLineNumber: 5, + endLineNumber: 5, + }, + }, + data: { + startLineNumber: 5, + endLineNumber: 5, + }, + }, + data: { + startLineNumber: 5, + endLineNumber: 5, + }, + }, + ], + body: { + type: 'BlockStatement', + body: [], + data: { + startLineNumber: 6, + endLineNumber: 7, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 7, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 3, + endLineNumber: 3, + }, + }, + property: { + type: 'Identifier', + name: 'world', + typeAnnotation: null, + data: { + startLineNumber: 3, + endLineNumber: 3, + }, + }, + computed: true, + data: { + startLineNumber: 3, + endLineNumber: 3, + }, + }, + data: { + startLineNumber: 3, + endLineNumber: 3, + }, + }, +]); + +expectType>([ + { + type: 'ExpressionStatement', + expression: { + type: 'MemberExpression', + object: { + type: 'MemberExpression', + object: { + type: 'Identifier', + name: 'hello', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + property: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'world', + typeAnnotation: null, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + arguments: [], + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + computed: true, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + property: { + type: 'StringLiteral', + value: 'foo', + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + computed: true, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, + data: { + startLineNumber: 1, + endLineNumber: 1, + }, + }, +]); diff --git a/src/test/tokenizer.test.ts b/src/test/tokenizer.test.ts new file mode 100644 index 0000000..9d6c421 --- /dev/null +++ b/src/test/tokenizer.test.ts @@ -0,0 +1,392 @@ +import type { Tokenize } from '../tokenizer'; +import { expectType } from './utils'; + +expectType>([ + { + type: 'symbol', + value: 'hello', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'symbol', + value: 'hello', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'symbol', + value: 'world', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'symbol', + value: 'hello', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'symbol', + value: 'world', + data: { + precedingLinebreak: true, + lineNumber: 2, + }, + }, +]); + +expectType>([ + { + type: 'symbol', + value: 'hello', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'symbol', + value: 'world', + data: { + precedingLinebreak: true, + lineNumber: 2, + }, + }, +]); + +expectType>([ + { + type: 'symbol', + value: 'hello', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'symbol', + value: 'world', + data: { + precedingLinebreak: true, + lineNumber: 3, + }, + }, +]); + +expectType>([ + { + type: 'string', + value: 'hello', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'string', + value: 'world', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'string', + value: 'hello', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'string', + value: 'world', + data: { + precedingLinebreak: true, + lineNumber: 2, + }, + }, +]); + +expectType>([ + { + type: 'number', + value: '123', + data: { + precedingLinebreak: true, + lineNumber: 2, + }, + }, + { + type: 'number', + value: '456', + data: { + precedingLinebreak: false, + lineNumber: 2, + }, + }, + { + type: 'number', + value: '789', + data: { + precedingLinebreak: true, + lineNumber: 4, + }, + }, +]); + +expectType>([ + { + type: 'string', + value: 'hello', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'string', + value: 'world', + data: { + precedingLinebreak: true, + lineNumber: 3, + }, + }, +]); + +expectType>([ + { + type: 'string', + value: 'hello', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'number', + value: '123', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'generic', + value: '[', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'number', + value: '1', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'generic', + value: ',', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'number', + value: '2', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'generic', + value: ',', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'number', + value: '3', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'generic', + value: ']', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'symbol', + value: 'foo', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'generic', + value: '(', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'generic', + value: ')', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'string', + value: 'foo', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'generic', + value: '(', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'generic', + value: ')', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, +]); + +expectType>([ + { + type: 'string', + value: 'foo', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'generic', + value: '(', + data: { + precedingLinebreak: true, + lineNumber: 2, + }, + }, + { + type: 'generic', + value: ')', + data: { + precedingLinebreak: false, + lineNumber: 2, + }, + }, +]); + +expectType>([ + { + type: 'string', + value: 'foo', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'generic', + value: '(', + data: { + precedingLinebreak: false, + lineNumber: 1, + }, + }, + { + type: 'generic', + value: ')', + data: { + precedingLinebreak: true, + lineNumber: 2, + }, + }, +]); + +expectType>({ + type: 'SyntaxError', + message: 'Unterminated string literal.', + lineNumber: 1, +}); + +expectType>({ + type: 'SyntaxError', + message: 'Unterminated string literal.', + lineNumber: 3, +}); + +expectType>({ + type: 'SyntaxError', + message: 'Invalid character.', + lineNumber: 1, +}); + +expectType>({ + type: 'SyntaxError', + message: 'Invalid character.', + lineNumber: 2, +}); + +expectType>({ + type: 'SyntaxError', + message: 'Unterminated string literal.', + lineNumber: 2, +}); diff --git a/src/test/utils.ts b/src/test/utils.ts new file mode 100644 index 0000000..2916b9e --- /dev/null +++ b/src/test/utils.ts @@ -0,0 +1 @@ +export const expectType = (_: T): any => {}; diff --git a/src/tokenizer.ts b/src/tokenizer.ts new file mode 100644 index 0000000..19396c2 --- /dev/null +++ b/src/tokenizer.ts @@ -0,0 +1,109 @@ +import type { Push } from './utils/arrayUtils'; +import type { + EatFirstChar, + GetFirstChar, + ConcatStrings, + StringContains, +} from './utils/stringUtils'; +import type { GenericTokens, Numbers, Symbols } from './utils/generalUtils'; +import type { + Token, + NumberToken, + SymbolToken, + StringToken, + TokenData, + GenericToken, +} from './tokens'; +import type { SyntaxError } from './errors'; +import type { Succ } from './utils/math'; + +type TokenizeInput< + Input extends string, + FirstChar extends string, + InputTail extends string, + PrecedingLinebreak extends boolean, + LineNumber extends number, + Data extends TokenData = TokenData, +> = FirstChar extends GenericTokens + ? [GenericToken, InputTail] + : FirstChar extends Numbers + ? TokenizeNumber + : FirstChar extends '"' + ? TokenizeString + : FirstChar extends "'" + ? TokenizeString + : FirstChar extends Symbols + ? TokenizeSymbol + : SyntaxError<`Invalid character.`, LineNumber>; + +type TokenizeNumber< + Input extends string, + Result extends string, + PrecedingLinebreak extends TokenData, + FirstChar extends string = GetFirstChar, +> = FirstChar extends Numbers + ? TokenizeNumber< + EatFirstChar, + ConcatStrings, + PrecedingLinebreak + > + : [NumberToken, Input]; + +type TokenizeString< + Input extends string, + QuoteType extends '"' | "'", + Data extends TokenData, +> = Input extends `${infer Before}${QuoteType}${infer After}` + ? StringContains extends true + ? SyntaxError<'Unterminated string literal.', Data['lineNumber']> + : [StringToken, After] + : SyntaxError<'Unterminated string literal.', Data['lineNumber']>; + +type TokenizeSymbol< + Input extends string, + Result extends string, + PrecedingLinebreak extends TokenData, + FirstChar extends string = GetFirstChar, +> = FirstChar extends Symbols + ? TokenizeSymbol< + EatFirstChar, + ConcatStrings, + PrecedingLinebreak + > + : [SymbolToken, Input]; + +export type Tokenize< + Input extends string, + Result extends Array> = [], + LineNumber extends number = 1, + PrecedingLinebreak extends boolean = false, + FirstChar extends string = GetFirstChar, + InputTail extends string = EatFirstChar, +> = Input extends '' + ? Result + : FirstChar extends ' ' + ? Tokenize + : FirstChar extends '\n' + ? Tokenize, true> + : TokenizeInput< + Input, + FirstChar, + InputTail, + PrecedingLinebreak, + LineNumber + > extends infer TokenizeResult + ? TokenizeHelper + : never; + +type TokenizeHelper< + TokenizeResult, + Result extends Array, + LineNumber extends number, +> = TokenizeResult extends Array + ? Tokenize< + TokenizeResult[1], + Push, + LineNumber, + false + > + : TokenizeResult; diff --git a/src/tokens.ts b/src/tokens.ts new file mode 100644 index 0000000..838428d --- /dev/null +++ b/src/tokens.ts @@ -0,0 +1,52 @@ +export type TokenData< + PrecedingLinebreak extends boolean, + LineNumber extends number, +> = { + precedingLinebreak: PrecedingLinebreak; + lineNumber: LineNumber; +}; + +export type GenericToken< + Value extends string, + Data extends TokenData, +> = { + type: 'generic'; + value: Value; + data: Data; +}; + +export type NumberToken< + Value extends string, + Data extends TokenData, +> = { + type: 'number'; + value: Value; + data: Data; +}; + +export type StringToken< + Value extends string, + Data extends TokenData, +> = { + type: 'string'; + value: Value; + data: Data; +}; + +export type SymbolToken< + Value extends string, + Data extends TokenData, +> = { + type: 'symbol'; + value: Value; + data: Data; +}; + +export type Token< + Data extends TokenData, + Value extends string = string, +> = + | GenericToken + | NumberToken + | StringToken + | SymbolToken; diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..7a42ac7 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,104 @@ +export type StringType = { + type: 'StringType'; +}; + +export type StringLiteralType = { + type: 'StringLiteralType'; + value: Value; +}; + +export type NumberType = { + type: 'NumberType'; +}; + +export type NumberLiteralType = { + type: 'NumberLiteralType'; + value: Value; +}; + +export type BooleanType = { + type: 'BooleanType'; +}; + +export type BooleanLiteralType = { + type: 'BooleanLiteralType'; + value: Value; +}; + +export type NullType = { + type: 'NullType'; +}; + +export type UndefinedType = { + type: 'UndefinedType'; +}; + +export type UnknownType = { + type: 'UnknownType'; +}; + +export type VoidType = { + type: 'VoidType'; +}; + +export type AnyType = { + type: 'AnyType'; +}; + +export type NeverType = { + type: 'NeverType'; +}; + +export type FunctionType< + Params extends Array<[string, StaticType]>, + Return extends StaticType, +> = { + type: 'FunctionType'; + params: Params; + return: Return; +}; + +export type ObjectType> = { + type: 'ObjectType'; + properties: Properties; +}; + +export type ArrayType = { + type: 'ArrayType'; + elements: ElementsType; +}; + +export type UnionType> = { + type: 'UnionType'; + types: Types; +}; + +export type CallArgumentsType> = { + type: 'CallArgumentsType'; + arguments: Arguments; +}; + +// export type GenericType = { +// type: 'GenericType'; +// id: T; +// }; + +export type StaticType = + | StringType + | StringLiteralType + | NumberType + | NumberLiteralType + | BooleanType + | BooleanLiteralType + | UnknownType + | VoidType + | AnyType + | NullType + | UndefinedType + | NeverType + | FunctionType + | ObjectType + | ArrayType + | UnionType + | CallArgumentsType; +// | GenericType; diff --git a/src/utils/arrayUtils.ts b/src/utils/arrayUtils.ts index be3bc30..0a99723 100644 --- a/src/utils/arrayUtils.ts +++ b/src/utils/arrayUtils.ts @@ -5,14 +5,22 @@ export type Tail> = ((...t: T) => void) extends ( ? R : never; -export type Unshift, E> = (( - h: E, - ...t: T -) => void) extends (...t: infer R) => void - ? R - : never; +export type Push, E> = [...T, E]; + +export type Unshift, E> = [E, ...T]; + +export type Reverse< + T extends Array, + R extends Array = [], +> = T extends [] ? R : Reverse, Unshift>; + +export type Concat, T2 extends Array> = [ + ...T1, + ...T2, +]; -export type Reverse, R extends Array = []> = { - finish: R; - next: Reverse, Unshift>; -}[T extends [] ? 'finish' : 'next']; +export type TailBy< + T extends Array, + B extends number, + A extends Array = [], +> = B extends A['length'] ? T : TailBy, B, Push>; diff --git a/src/utils/generalUtils.ts b/src/utils/generalUtils.ts index a1a5570..ed6e66c 100644 --- a/src/utils/generalUtils.ts +++ b/src/utils/generalUtils.ts @@ -1,7 +1,18 @@ -export type Cast = A extends B ? A : B; - export type MergeWithOverride = Omit & T2; +export type GenericTokens = + | ',' + | '(' + | ')' + | '[' + | ']' + | '{' + | '}' + | '.' + | ';' + | ':' + | '='; + export type Numbers = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; export type Symbols = @@ -68,8 +79,4 @@ export type Symbols = | '8' | '9' | '_' - | '-' - | '+' - | '=' - | '|' - | '&'; + | '$'; diff --git a/src/utils/math.ts b/src/utils/math.ts new file mode 100644 index 0000000..ba2c4ee --- /dev/null +++ b/src/utils/math.ts @@ -0,0 +1,64 @@ +type SuccTable = { + 0: 1; + 1: 2; + 2: 3; + 3: 4; + 4: 5; + 5: 6; + 6: 7; + 7: 8; + 8: 9; + 9: 10; + 10: 11; + 11: 12; + 12: 13; + 13: 14; + 14: 15; + 15: 16; + 16: 17; + 17: 18; + 18: 19; + 19: 20; + 20: 21; + 21: 22; + 22: 23; + 23: 24; + 24: 25; + 25: 26; + 26: 27; + 27: 28; + 28: 29; + 29: 30; + 30: 31; + 31: 32; + 32: 33; + 33: 34; + 34: 35; + 35: 36; + 36: 37; + 37: 38; + 38: 39; + 39: 40; + 40: 41; + 41: 42; + 42: 43; + 43: 44; + 44: 45; + 45: 46; + 46: 47; + 47: 48; + 48: 49; + 49: 50; + 50: 51; + 51: 52; + 52: 53; + 53: 54; + 54: 55; + 55: 56; + 56: 57; + 57: 58; + 58: 59; + 59: 60; +}; + +export type Succ = T extends keyof SuccTable ? SuccTable[T] : never; diff --git a/src/utils/stringUtils.ts b/src/utils/stringUtils.ts index d33c361..0295832 100644 --- a/src/utils/stringUtils.ts +++ b/src/utils/stringUtils.ts @@ -1,5 +1,15 @@ export type EatFirstChar = T extends `${infer A}${infer B}` ? B : ''; -export type FirstChar = T extends `${infer A}${infer B}` ? A : ''; +export type GetFirstChar = T extends `${infer A}${infer B}` ? A : ''; export type ConcatStrings = `${A}${B}`; + +export type StringContains = I extends T + ? true + : I extends `${T}${infer _}` + ? true + : I extends `${infer _0}${T}${infer _1}` + ? true + : I extends `${infer _}${T}` + ? true + : false; diff --git a/src/utils/utilityTypes.ts b/src/utils/utilityTypes.ts new file mode 100644 index 0000000..d9db8ee --- /dev/null +++ b/src/utils/utilityTypes.ts @@ -0,0 +1,54 @@ +import type { StaticType } from '../types'; +import type { ParsingError, TypeError } from '../errors'; +import type { BaseNode, NodeData } from '../ast'; +import type { Token, TokenData } from '../tokens'; + +export type TypeResult< + Value extends StaticType, + State extends StateType, + Errors extends Array> = [], +> = { + type: 'TypeResult'; + value: Value; + state: State; + errors: Errors; +}; + +export type ParseResult< + Node extends BaseNode>, + TokenList extends Array>, + Error extends ParsingError | null = null, + Data extends any = null, +> = { + type: 'ParseResult'; + node: Node; + tokenList: TokenList; + error: Error; + data: Data; +}; + +export type ParseArrayResult< + NodeList extends Array>>, + TokenList extends Array>>, + Error extends ParsingError | null = null, + Data extends any = null, +> = { + type: 'ParseResult'; + node: NodeList; + tokenList: TokenList; + error: Error; + data: Data; +}; + +export type ParseError> = ParseResult< + any, + any, + Error +>; + +export type ParseErrorResult< + Message extends string, + LineNumber extends number, +> = ParseError>; + +export type StateType = Record; diff --git a/tsconfig.json b/tsconfig.json index eef761d..0cfd4b8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,20 +2,23 @@ "compilerOptions": { "target": "es2019", "module": "es2022", - "lib": ["esnext", "dom"], - "declaration": true, - "composite": true, + "lib": ["esnext"], + "declaration": false, + "composite": false, "isolatedModules": true, "importsNotUsedAsValues": "error", - "jsx": "react", + "diagnostics": false, "strict": true, + "noEmit": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, "moduleResolution": "node", "esModuleInterop": true, "allowSyntheticDefaultImports": true, "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true, + "resolveJsonModule": false, "rootDir": "src", "outDir": "build" diff --git a/yarn.lock b/yarn.lock index fd6f210..635b045 100644 --- a/yarn.lock +++ b/yarn.lock @@ -851,10 +851,10 @@ dependencies: "@babel/helper-plugin-utils" "^7.17.12" -"@babel/plugin-transform-typescript@^7.17.12": +"@babel/plugin-transform-typescript@^7.17.12", "@babel/plugin-transform-typescript@^7.18.4": version "7.18.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.4.tgz#587eaf6a39edb8c06215e550dc939faeadd750bf" - integrity sha512-l4vHuSLUajptpHNEOUDEGsnpl9pfRLsN1XUoDQDD/YBuXTM+v37SHGS+c6n4jdcZy96QtuUuSvZYMLSSsjH8Mw== + resolved "https://repo.dev.wixpress.com/artifactory/api/npm/npm-repos/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.18.4.tgz#587eaf6a39edb8c06215e550dc939faeadd750bf" + integrity sha1-WH6vajntuMBiFeVQ3JOfrq3XUL8= dependencies: "@babel/helper-create-class-features-plugin" "^7.18.0" "@babel/helper-plugin-utils" "^7.17.12" @@ -2934,10 +2934,10 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== -typescript@^4.7.4: - version "4.7.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" - integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== +typescript@next: + version "4.8.0-dev.20220629" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.0-dev.20220629.tgz#9d719936f77a811d6859051f327c7868700ab250" + integrity sha512-QEp1M6iqlYpQXFF2f9ucXMkXbYcAoXcG0ws0IG8bWd7mjPGf8R5iVCbQVSm1dzV/GoOy0PsvfNe5/uL6D9TSPA== unbox-primitive@^1.0.2: version "1.0.2"