Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring before text edits #8956

Merged
merged 7 commits into from
Feb 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
492 changes: 291 additions & 201 deletions app/gui2/shared/ast/parse.ts

Large diffs are not rendered by default.

170 changes: 98 additions & 72 deletions app/gui2/shared/ast/tree.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as map from 'lib0/map'
import type {
Identifier,
IdentifierOrOperatorIdentifier,
Expand All @@ -18,12 +17,12 @@ import {
isToken,
isTokenId,
newExternalId,
nodeKey,
parentId,
parse,
parseBlock,
print,
tokenKey,
printAst,
printBlock,
} from '.'
import { assert, assertDefined, assertEqual, bail } from '../util/assert'
import type { Result } from '../util/data/result'
Expand Down Expand Up @@ -123,35 +122,7 @@ export abstract class Ast {
parentIndent: string | undefined,
verbatim?: boolean,
): string {
let code = ''
for (const child of this.concreteChildren(verbatim)) {
if (!isTokenId(child.node) && this.module.checkedGet(child.node) === undefined) continue
if (child.whitespace != null) {
code += child.whitespace
} else if (code.length != 0) {
code += ' '
}
if (isTokenId(child.node)) {
const tokenStart = offset + code.length
const token = this.module.getToken(child.node)
const span = tokenKey(tokenStart, token.code().length)
info.tokens.set(span, token)
code += token.code()
} else {
const childNode = this.module.checkedGet(child.node)
assert(childNode != null)
code += childNode.printSubtree(info, offset + code.length, parentIndent, verbatim)
// Extra structural validation.
assertEqual(childNode.id, child.node)
if (parentId(childNode) !== this.id) {
console.error(`Inconsistent parent pointer (expected ${this.id})`, childNode)
}
assertEqual(parentId(childNode), this.id)
}
}
const span = nodeKey(offset, code.length)
map.setIfUndefined(info.nodes, span, (): Ast[] => []).unshift(this)
return code
return printAst(this, info, offset, parentIndent, verbatim)
}

/** Returns child subtrees, without information about the whitespace between them. */
Expand Down Expand Up @@ -237,6 +208,12 @@ export abstract class MutableAst extends Ast {
return old
}

replaceValueChecked<T extends MutableAst>(replacement: Owned<T>): Owned<typeof this> {
const parentId = this.fields.get('parent')
assertDefined(parentId)
return this.replaceValue(replacement)
}

/** Replace the parent of this object with a reference to a new placeholder object.
* Returns the object, now parentless, and the placeholder. */
takeToReplace(): Removed<this> {
Expand Down Expand Up @@ -354,11 +331,15 @@ interface AppFields {
}
export class App extends Ast {
declare fields: FixedMap<AstFields & AppFields>

constructor(module: Module, fields: FixedMapView<AstFields & AppFields>) {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableApp> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableApp) return parsed
}

static concrete(
module: MutableModule,
func: NodeChild<Owned>,
Expand Down Expand Up @@ -486,6 +467,11 @@ export class UnaryOprApp extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableUnaryOprApp> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableUnaryOprApp) return parsed
}

static concrete(
module: MutableModule,
operator: NodeChild<Token>,
Expand Down Expand Up @@ -549,6 +535,11 @@ export class NegationApp extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableNegationApp> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableNegationApp) return parsed
}

static concrete(module: MutableModule, operator: NodeChild<Token>, argument: NodeChild<Owned>) {
const base = module.baseObject('NegationApp')
const id_ = base.get('id')
Expand Down Expand Up @@ -606,6 +597,11 @@ export class OprApp extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableOprApp> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableOprApp) return parsed
}

static concrete(
module: MutableModule,
lhs: NodeChild<Owned> | undefined,
Expand Down Expand Up @@ -693,6 +689,14 @@ export class PropertyAccess extends Ast {
super(module, fields)
}

static tryParse(
source: string,
module?: MutableModule,
): Owned<MutablePropertyAccess> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutablePropertyAccess) return parsed
}

static new(module: MutableModule, lhs: Owned, rhs: IdentLike) {
const dot = unspaced(Token.new('.', RawAst.Token.Type.Operator))
return this.concrete(
Expand Down Expand Up @@ -894,6 +898,11 @@ export class Import extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableImport> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableImport) return parsed
}

get polyglot(): Ast | undefined {
return this.module.checkedGet(this.fields.get('polyglot')?.body?.node)
}
Expand Down Expand Up @@ -1022,17 +1031,17 @@ export class MutableImport extends Import implements MutableAst {

replaceChild<T extends MutableAst>(target: AstId, replacement: Owned<T>) {
const { polyglot, from, import: import_, as, hiding } = getAll(this.fields)
;(polyglot?.body?.node === target
? this.setPolyglot
polyglot?.body?.node === target
? this.setPolyglot(replacement)
: from?.body?.node === target
? this.setFrom
? this.setFrom(replacement)
: import_.body?.node === target
? this.setImport
? this.setImport(replacement)
: as?.body?.node === target
? this.setAs
? this.setAs(replacement)
: hiding?.body?.node === target
? this.setHiding
: bail(`Failed to find child ${target} in node ${this.externalId}.`))(replacement)
? this.setHiding(replacement)
: bail(`Failed to find child ${target} in node ${this.externalId}.`)
}
}
export interface MutableImport extends Import, MutableAst {
Expand Down Expand Up @@ -1074,6 +1083,11 @@ export class TextLiteral extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableTextLiteral> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableTextLiteral) return parsed
}

static concrete(
module: MutableModule,
open: NodeChild<Token> | undefined,
Expand Down Expand Up @@ -1134,6 +1148,11 @@ export class Documented extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableDocumented> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableDocumented) return parsed
}

static concrete(
module: MutableModule,
open: NodeChild<Token> | undefined,
Expand Down Expand Up @@ -1259,6 +1278,11 @@ export class Group extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableGroup> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableGroup) return parsed
}

static concrete(
module: MutableModule,
open: NodeChild<Token> | undefined,
Expand Down Expand Up @@ -1315,6 +1339,14 @@ export class NumericLiteral extends Ast {
super(module, fields)
}

static tryParse(
source: string,
module?: MutableModule,
): Owned<MutableNumericLiteral> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableNumericLiteral) return parsed
}

static concrete(module: MutableModule, tokens: NodeChild<Token>[]) {
const base = module.baseObject('NumericLiteral')
const fields = setAll(base, { tokens })
Expand Down Expand Up @@ -1365,6 +1397,11 @@ export class Function extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableFunction> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableFunction) return parsed
}

get name(): Ast {
return this.module.checkedGet(this.fields.get('name').node)
}
Expand Down Expand Up @@ -1506,6 +1543,11 @@ export class Assignment extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableAssignment> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableAssignment) return parsed
}

static concrete(
module: MutableModule,
pattern: NodeChild<Owned>,
Expand Down Expand Up @@ -1580,6 +1622,11 @@ export class BodyBlock extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableBodyBlock> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableBodyBlock) return parsed
}

static concrete(module: MutableModule, lines: OwnedBlockLine[]) {
const base = module.baseObject('BodyBlock')
const id_ = base.get('id')
Expand Down Expand Up @@ -1616,38 +1663,7 @@ export class BodyBlock extends Ast {
parentIndent: string | undefined,
verbatim?: boolean,
): string {
let blockIndent: string | undefined
let code = ''
for (const line of this.fields.get('lines')) {
code += line.newline.whitespace ?? ''
const newlineCode = this.module.getToken(line.newline.node).code()
// Only print a newline if this isn't the first line in the output, or it's a comment.
if (offset || code || newlineCode.startsWith('#')) {
// If this isn't the first line in the output, but there is a concrete newline token:
// if it's a zero-length newline, ignore it and print a normal newline.
code += newlineCode || '\n'
}
if (line.expression) {
if (blockIndent === undefined) {
if ((line.expression.whitespace?.length ?? 0) > (parentIndent?.length ?? 0)) {
blockIndent = line.expression.whitespace!
} else if (parentIndent !== undefined) {
blockIndent = parentIndent + ' '
} else {
blockIndent = ''
}
}
const validIndent = (line.expression.whitespace?.length ?? 0) > (parentIndent?.length ?? 0)
code += validIndent ? line.expression.whitespace : blockIndent
const lineNode = this.module.checkedGet(line.expression.node)
assertEqual(lineNode.id, line.expression.node)
assertEqual(parentId(lineNode), this.id)
code += lineNode.printSubtree(info, offset + code.length, blockIndent, verbatim)
}
}
const span = nodeKey(offset, code.length)
map.setIfUndefined(info.nodes, span, (): Ast[] => []).unshift(this)
return code
return printBlock(this, info, offset, parentIndent, verbatim)
}
}
export class MutableBodyBlock extends BodyBlock implements MutableAst {
Expand Down Expand Up @@ -1772,6 +1788,11 @@ export class Ident extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableIdent> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableIdent) return parsed
}

get token(): IdentifierToken {
return this.module.getToken(this.fields.get('token').node) as IdentifierToken
}
Expand Down Expand Up @@ -1825,6 +1846,11 @@ export class Wildcard extends Ast {
super(module, fields)
}

static tryParse(source: string, module?: MutableModule): Owned<MutableWildcard> | undefined {
const parsed = parse(source, module)
if (parsed instanceof MutableWildcard) return parsed
}

get token(): Token {
return this.module.getToken(this.fields.get('token').node)
}
Expand Down
42 changes: 22 additions & 20 deletions app/gui2/src/components/GraphEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -265,34 +265,36 @@ const graphBindingsHandler = graphBindings.handler({
bail(`Cannot get the method name for the current execution stack item. ${currentMethod}`)
}
const currentFunctionEnv = environmentForNodes(selected.values())
const module = graphStore.astModule
const topLevel = graphStore.topLevel
if (!topLevel) {
bail('BUG: no top level, collapsing not possible.')
}
const edit = module.edit()
const { refactoredNodeId, collapsedNodeIds, outputNodeId } = performCollapse(
info,
edit.getVersion(topLevel),
graphStore.db,
currentMethodName,
)
const collapsedFunctionEnv = environmentForNodes(collapsedNodeIds.values())
// For collapsed function, only selected nodes would affect placement of the output node.
collapsedFunctionEnv.nodeRects = collapsedFunctionEnv.selectedNodeRects
const { position } = collapsedNodePlacement(DEFAULT_NODE_SIZE, currentFunctionEnv)
edit
.checkedGet(refactoredNodeId)
.mutableNodeMetadata()
.set('position', { x: position.x, y: position.y })
if (outputNodeId != null) {
const { position } = previousNodeDictatedPlacement(DEFAULT_NODE_SIZE, collapsedFunctionEnv)
graphStore.edit((edit) => {
const { refactoredNodeId, collapsedNodeIds, outputNodeId } = performCollapse(
info,
edit.getVersion(topLevel),
graphStore.db,
currentMethodName,
)
const collapsedFunctionEnv = environmentForNodes(collapsedNodeIds.values())
// For collapsed function, only selected nodes would affect placement of the output node.
collapsedFunctionEnv.nodeRects = collapsedFunctionEnv.selectedNodeRects
edit
.checkedGet(outputNodeId)
.checkedGet(refactoredNodeId)
.mutableNodeMetadata()
.set('position', { x: position.x, y: position.y })
}
graphStore.commitEdit(edit)
if (outputNodeId != null) {
const { position } = previousNodeDictatedPlacement(
DEFAULT_NODE_SIZE,
collapsedFunctionEnv,
)
edit
.checkedGet(outputNodeId)
.mutableNodeMetadata()
.set('position', { x: position.x, y: position.y })
}
})
} catch (err) {
console.log('Error while collapsing, this is not normal.', err)
}
Expand Down
Loading
Loading