Skip to content

Commit

Permalink
Ast: typed
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Jun 17, 2023
1 parent 886c949 commit a667382
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 107 deletions.
30 changes: 15 additions & 15 deletions src/ast/expr.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { associativityMap, BinaryOp, buildBinaryOp, buildUnaryOp, precedenceMap, UnaryOp } from './op'
import { AstNode, filterNonAstNodes } from './index'
import { AstNode, filterNonAstNodes, Typed } from './index'
import { ParseNode } from '../parser/parser'
import { buildOperand, Operand } from './operand'

export type Expr = OperandExpr | UnaryExpr | BinaryExpr

export interface OperandExpr extends AstNode<'operand-expr'> {
export interface OperandExpr extends AstNode<'operand-expr'>, Partial<Typed> {
operand: Operand
}

export const buildOperandExpr = (node: ParseNode): OperandExpr => {
return {
type: 'operand-expr',
kind: 'operand-expr',
parseNode: node,
operand: buildOperand(node)
operand: buildOperand(node),
}
}

export interface UnaryExpr extends AstNode<'unary-expr'> {
export interface UnaryExpr extends AstNode<'unary-expr'>, Partial<Typed> {
unaryOp: UnaryOp
operand: Operand
}

export interface BinaryExpr extends AstNode<'binary-expr'> {
export interface BinaryExpr extends AstNode<'binary-expr'>, Partial<Typed> {
binaryOp: BinaryOp
lOperand: Operand
rOperand: Operand
Expand All @@ -47,14 +47,14 @@ export const buildSubExpr = (node: ParseNode): Expr => {
const nodes = filterNonAstNodes(node)
if (nodes.length === 1) {
return {
type: 'operand-expr',
kind: 'operand-expr',
parseNode: node,
operand: buildOperandExpr(nodes[0])
}
}
const isPrefix = nodes[0].kind === 'prefix-op'
return {
type: 'unary-expr',
kind: 'unary-expr',
parseNode: node,
unaryOp: buildUnaryOp(nodes[isPrefix ? 0 : 1]),
operand: buildOperand(nodes[isPrefix ? 1 : 0])
Expand All @@ -71,19 +71,19 @@ export const buildBinaryExpr = (node: ParseNode): Expr => {
let o2
while (operatorStack.length !== 0) {
o2 = operatorStack.at(-1)!
const o1Prec = precedenceMap.get(o1.type)!
const o2Prec = precedenceMap.get(o2.type)!
const o1Assoc = associativityMap.get(o1.type)!
const o2Assoc = associativityMap.get(o2.type)!
const o1Prec = precedenceMap.get(o1.kind)!
const o2Prec = precedenceMap.get(o2.kind)!
const o1Assoc = associativityMap.get(o1.kind)!
const o2Assoc = associativityMap.get(o2.kind)!
if (o1Prec === o2Prec && o1Assoc === 'none' && o2Assoc === 'none') {
throw Error(`cannot chain operators \`${o1.type}\` and \`${o2.type}\``)
throw Error(`cannot chain operators \`${o1.kind}\` and \`${o2.kind}\``)
}
if ((o1Assoc !== 'right' && o1Prec === o2Prec) || o1Prec < o2Prec) {
operatorStack.pop()
const lExp = exprStack.pop()!
const rExp = exprStack.pop()!
exprStack.push({
type: 'binary-expr',
kind: 'binary-expr',
parseNode: lExp.parseNode,
binaryOp: o2,
lOperand: lExp,
Expand All @@ -105,7 +105,7 @@ export const buildBinaryExpr = (node: ParseNode): Expr => {
const rExp = exprStack.pop()!
const lExp = exprStack.pop()!
exprStack.push({
type: 'binary-expr',
kind: 'binary-expr',
parseNode: lExp.parseNode,
binaryOp: op,
lOperand: lExp,
Expand Down
30 changes: 15 additions & 15 deletions src/ast/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,32 +17,32 @@ describe('ast', () => {
expect(compactAstNode(ast)).toEqual({
'operand': {
'operand': {
'binaryOp': { 'type': 'add-op' },
'binaryOp': { 'kind': 'add-op' },
'lOperand': {
'operand': {
'operand': { 'type': 'int-literal', 'value': '1' },
'type': 'operand-expr'
}, 'type': 'operand-expr'
'operand': { 'kind': 'int-literal', 'value': '1' },
'kind': 'operand-expr'
}, 'kind': 'operand-expr'
},
'rOperand': {
'binaryOp': { 'type': 'mult-op' },
'binaryOp': { 'kind': 'mult-op' },
'lOperand': {
'operand': {
'operand': { 'type': 'int-literal', 'value': '2' },
'type': 'operand-expr'
}, 'type': 'operand-expr'
'operand': { 'kind': 'int-literal', 'value': '2' },
'kind': 'operand-expr'
}, 'kind': 'operand-expr'
},
'rOperand': {
'operand': {
'operand': { 'type': 'int-literal', 'value': '3' },
'type': 'operand-expr'
}, 'type': 'operand-expr'
'operand': { 'kind': 'int-literal', 'value': '3' },
'kind': 'operand-expr'
}, 'kind': 'operand-expr'
},
'type': 'binary-expr'
'kind': 'binary-expr'
},
'type': 'binary-expr'
}, 'type': 'operand-expr'
}, 'type': 'operand-expr'
'kind': 'binary-expr'
}, 'kind': 'operand-expr'
}, 'kind': 'operand-expr'
})
})

Expand Down
50 changes: 27 additions & 23 deletions src/ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ import { buildIdentifier, buildName, Identifier, Name } from './operand'
import { buildExpr, Expr } from './expr'

export interface AstNode<T extends AstNodeKind> {
type: T
kind: T
parseNode: ParseNode
}

export interface Typed {
type: Type
}

export type AstNodeKind
= 'module'
| 'use-expr'
Expand Down Expand Up @@ -74,13 +78,30 @@ export const astNodes: NodeKind[] = [...lexerDynamicKinds, ...treeKinds]
export const filterNonAstNodes = (node: ParseNode): ParseNode[] =>
(<ParseTree>node).nodes.filter(n => astNodes.includes(n.kind))

export const compactAstNode = (node: AstNode<any>): any => {
if (typeof node !== 'object') return node
return Object.fromEntries(
Object.entries(node)
.filter(([p,]) => p !== 'parseNode')
.map(([p, v]) => {
if (Array.isArray(v)) {
return [p, v.map(compactAstNode)]
}
if (typeof v === 'object' && 'parseNode' in v) {
return [p, compactAstNode(v)]
}
return [p, v]
})
)
}

export interface Module extends AstNode<'module'> {
statements: Statement[]
}

export const buildModule = (node: ParseNode): Module => {
return {
type: 'module',
kind: 'module',
parseNode: node,
statements: filterNonAstNodes(node).filter(n => n.kind === 'statement').map(n => buildStatement(n))
}
Expand All @@ -106,7 +127,7 @@ export const buildType = (node: ParseNode): Type => {
const nameNode = nodes[0]
const paramsNode = nodes.at(1)
return {
type: 'type',
kind: 'type',
parseNode: node,
identifier: buildIdentifier(nameNode),
typeParams: paramsNode
Expand All @@ -124,7 +145,7 @@ export const buildTypeParam = (node: ParseNode): TypeParam => {
} else {
const name = buildName(nodes[0])
const bounds = filterNonAstNodes(nodes[1]).map(buildType)
return { type: 'generic', parseNode: node, name, bounds }
return { kind: 'generic', parseNode: node, name, bounds }
}
}

Expand All @@ -137,7 +158,7 @@ export const buildParam = (node: ParseNode): Param => {
const nodes = filterNonAstNodes(node)
const pattern = buildPattern(nodes[0])
const typeNode = nodes.at(1)
return { type: 'param', parseNode: node, pattern, paramType: typeNode ? buildType(typeNode) : undefined, }
return { kind: 'param', parseNode: node, pattern, paramType: typeNode ? buildType(typeNode) : undefined, }
}

export interface FieldInit extends AstNode<'field-init'> {
Expand All @@ -149,23 +170,6 @@ export const buildFieldInit = (node: ParseNode): FieldInit => {
const nodes = filterNonAstNodes(node)
const name = buildName(nodes[0])
const expr = buildExpr(nodes[1])
return { type: 'field-init', parseNode: node, name, expr }
}

export const compactAstNode = (node: AstNode<any>): any => {
if (typeof node !== 'object') return node
return Object.fromEntries(
Object.entries(node)
.filter(([p,]) => p !== 'parseNode')
.map(([p, v]) => {
if (Array.isArray(v)) {
return [p, v.map(compactAstNode)]
}
if (typeof v === 'object' && 'parseNode' in v) {
return [p, compactAstNode(v)]
}
return [p, v]
})
)
return { kind: 'field-init', parseNode: node, name, expr }
}

20 changes: 10 additions & 10 deletions src/ast/match.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { buildExpr, buildOperandExpr, Expr } from './expr'
import { AstNode, filterNonAstNodes } from './index'
import { AstNode, filterNonAstNodes, Typed } from './index'
import { ParseNode } from '../parser/parser'
import { buildIdentifier, buildName, buildOperand, Identifier, Name } from './operand'
import { buildUnaryOp, SpreadOp } from './op'
import { Block, buildBlock } from './statement'

export interface MatchExpr extends AstNode<'match-expr'> {
export interface MatchExpr extends AstNode<'match-expr'>, Partial<Typed> {
expr: Expr
clauses: MatchClause[]
}
Expand All @@ -14,10 +14,10 @@ export const buildMatchExpr = (node: ParseNode): MatchExpr => {
const nodes = filterNonAstNodes(node)
const expr = buildExpr(nodes[0])
const clauses = filterNonAstNodes(nodes[1]).map(buildMatchClause)
return { type: 'match-expr', parseNode: node, expr, clauses }
return { kind: 'match-expr', parseNode: node, expr, clauses }
}

export interface MatchClause extends AstNode<'match-clause'> {
export interface MatchClause extends AstNode<'match-clause'>, Partial<Typed> {
pattern: Pattern
block: Block
guard?: Expr
Expand All @@ -29,7 +29,7 @@ export const buildMatchClause = (node: ParseNode): MatchClause => {
const pattern = buildPattern(nodes[idx++])
const guard = nodes[idx].kind === 'guard' ? buildExpr(filterNonAstNodes(nodes[idx++])[0]) : undefined
const block = nodes[idx].kind === 'expr' ? <Block>{ statements: [buildExpr(nodes[idx++])] } : buildBlock(nodes[idx++])
return { type: 'match-clause', parseNode: node, pattern, guard, block }
return { kind: 'match-clause', parseNode: node, pattern, guard, block }
}

export type Pattern = ConPattern | Expr | Hole
Expand All @@ -45,7 +45,7 @@ export const buildPattern = (node: ParseNode): Pattern => {
if (nodes[0].kind === 'prefix-op') {
const unaryOp = buildUnaryOp(nodes[0])
const operand = buildOperand(nodes[1])
return { type: 'unary-expr', parseNode: node, unaryOp, operand }
return { kind: 'unary-expr', parseNode: node, unaryOp, operand }
}
return buildOperandExpr(node)
}
Expand All @@ -59,7 +59,7 @@ export const buildConPattern = (node: ParseNode): ConPattern => {
const nodes = filterNonAstNodes(node)
const identifier = buildIdentifier(nodes[0])
const fieldPatterns = filterNonAstNodes(nodes[1]).map(buildFieldPattern)
return { type: 'con-pattern', parseNode: node, identifier, fieldPatterns }
return { kind: 'con-pattern', parseNode: node, identifier, fieldPatterns }
}

export interface FieldPattern extends AstNode<'field-pattern'> {
Expand All @@ -70,16 +70,16 @@ export interface FieldPattern extends AstNode<'field-pattern'> {
export const buildFieldPattern = (node: ParseNode): FieldPattern | SpreadOp => {
const nodes = filterNonAstNodes(node)
if (nodes[0].kind === 'spread-op') {
return { type: 'spread-op', parseNode: node }
return { kind: 'spread-op', parseNode: node }
}
const name = buildName(nodes[0])
const pattern = nodes.at(1) ? buildPattern(nodes[1]) : undefined
return { type: 'field-pattern', parseNode: node, name, pattern }
return { kind: 'field-pattern', parseNode: node, name, pattern }
}

export interface Hole extends AstNode<'hole'> {
}

export const buildHole = (node: ParseNode): Hole => {
return { type: 'hole', parseNode: node }
return { kind: 'hole', parseNode: node }
}
8 changes: 4 additions & 4 deletions src/ast/op.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const buildUnaryOp = (node: ParseNode): UnaryOp => {
if (!['add-op', 'sub-op', 'not-op', 'spread-op'].includes(n.kind)) {
throw Error(`expected unary-op, got ${node.kind}`)
}
return { type: <any>n.kind, parseNode: node }
return { kind: <any>n.kind, parseNode: node }
}

export type BinaryOp
Expand Down Expand Up @@ -83,7 +83,7 @@ export const buildBinaryOp = (node: ParseNode): BinaryOp => {
].includes(node.kind)) {
throw Error(`expected binary-op, got ${node.kind}`)
}
return { type: <any>node.kind, parseNode: node }
return { kind: <any>node.kind, parseNode: node }
}

export interface NotOp extends AstNode<'not-op'> {}
Expand All @@ -98,7 +98,7 @@ export const buildCallOp = (node: ParseNode): CallOp => {
const nodes = filterNonAstNodes(node)
const argExprs = filterNonAstNodes(<ParseTree>nodes[0])
return {
type: 'call-op',
kind: 'call-op',
parseNode: node,
args: argExprs.map(n => buildExpr(n))
}
Expand All @@ -111,7 +111,7 @@ export interface ConOp extends AstNode<'con-op'> {
export const buildConOp = (node: ParseNode): ConOp => {
const nodes = filterNonAstNodes(node)
return {
type: 'con-op',
kind: 'con-op',
parseNode: node,
fields: nodes.map(n => buildFieldInit(n))
}
Expand Down
Loading

0 comments on commit a667382

Please sign in to comment.