enso/app/gui2/shared/ast/token.ts
Kaz Wesley c811a5ae8b
Enable the Code Editor, with new apply-text-edits algo. (#9055)
- Fix the UI problems with our CodeMirror integration (Fixed view stability; Fixed a focus bug; Fixed errors caused by diagnostics range exceptions; Fixed linter invalidation--see https://discuss.codemirror.net/t/problem-trying-to-force-linting/5823; Implemented edit-coalescing for performance).
- Introduce an algorithm for applying text edits to an AST. Compared to the GUI1 approach, the new algorithm supports deeper identity-stability for expressions (which is important for subexpression metadata and Y.Js sync), as well as reordered-subtree identification.
- Enable the code editor.
2024-02-19 23:57:42 +00:00

128 lines
4.4 KiB
TypeScript

import type { AstId, Owned } from '.'
import { Ast, newExternalId } from '.'
import { assert } from '../util/assert'
import type { ExternalId } from '../yjsModel'
import { isUuid } from '../yjsModel'
import { is_ident_or_operator } from './ffi'
import * as RawAst from './generated/ast'
export function isToken(t: unknown): t is Token {
return t instanceof Token
}
declare const brandTokenId: unique symbol
export type TokenId = ExternalId & { [brandTokenId]: never }
function newTokenId(): TokenId {
return newExternalId() as TokenId
}
/** @internal */
export interface SyncTokenId {
readonly id: TokenId
code_: string
tokenType_: RawAst.Token.Type | undefined
}
export class Token implements SyncTokenId {
readonly id: TokenId
code_: string
tokenType_: RawAst.Token.Type | undefined
private constructor(code: string, type: RawAst.Token.Type | undefined, id: TokenId) {
this.id = id
this.code_ = code
this.tokenType_ = type
}
get externalId(): TokenId {
return this.id
}
static new(code: string, type?: RawAst.Token.Type) {
return new this(code, type, newTokenId())
}
static withId(code: string, type: RawAst.Token.Type | undefined, id: TokenId) {
assert(isUuid(id))
return new this(code, type, id)
}
static equal(a: SyncTokenId, b: SyncTokenId): boolean {
return a.tokenType_ === b.tokenType_ && a.code_ === b.code_
}
code(): string {
return this.code_
}
typeName(): string {
if (this.tokenType_) return RawAst.Token.typeNames[this.tokenType_]!
else return 'Raw'
}
}
// We haven't had much need to distinguish token types, but it's useful to know that an identifier token's code is a
// valid string for an identifier.
export interface IdentifierOrOperatorIdentifierToken extends Token {
code(): IdentifierOrOperatorIdentifier
}
export interface IdentifierToken extends Token {
code(): Identifier
}
declare const qualifiedNameBrand: unique symbol
declare const identifierBrand: unique symbol
declare const operatorBrand: unique symbol
/** A string representing a valid qualified name of our language.
*
* In our language, the segments are separated by `.`. All the segments except the last must be lexical identifiers. The
* last may be an identifier or a lexical operator. A single identifier is also a valid qualified name.
*/
export type QualifiedName = string & { [qualifiedNameBrand]: never }
/** A string representing a lexical identifier. */
export type Identifier = string & { [identifierBrand]: never; [qualifiedNameBrand]: never }
/** A string representing a lexical operator. */
export type Operator = string & { [operatorBrand]: never; [qualifiedNameBrand]: never }
/** A string that can be parsed as an identifier in some contexts.
*
* If it is lexically an identifier (see `StrictIdentifier`), it can be used as identifier anywhere.
*
* If it is lexically an operator (see `Operator`), it takes the syntactic role of an identifier if it is the RHS of
* a `PropertyAccess`, or it is the name of a `Function` being defined within a type. In all other cases, it is not
* valid to use a lexical operator as an identifier (rather, it will usually parse as an `OprApp` or `UnaryOprApp`).
*/
export type IdentifierOrOperatorIdentifier = Identifier | Operator
/** Returns true if `code` can be used as an identifier in some contexts.
*
* If it is lexically an identifier (see `isIdentifier`), it can be used as identifier anywhere.
*
* If it is lexically an operator (see `isOperator`), it takes the syntactic role of an identifier if it is the RHS of
* a `PropertyAccess`, or it is the name of a `Function` being defined within a type. In all other cases, it is not
* valid to use a lexical operator as an identifier (rather, it will usually parse as an `OprApp` or `UnaryOprApp`).
*/
export function isIdentifierOrOperatorIdentifier(
code: string,
): code is IdentifierOrOperatorIdentifier {
return is_ident_or_operator(code) !== 0
}
/** Returns true if `code` is lexically an identifier. */
export function isIdentifier(code: string): code is Identifier {
return is_ident_or_operator(code) === 1
}
/** Returns true if `code` is lexically an operator. */
export function isOperator(code: string): code is Operator {
return is_ident_or_operator(code) === 2
}
/** @internal */
export function isTokenId(t: SyncTokenId | AstId | Ast | Owned<Ast> | Owned): t is SyncTokenId {
return typeof t === 'object' && !(t instanceof Ast)
}