Skip to content

Commit

Permalink
Ast: expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Jun 15, 2023
1 parent aca9ee7 commit 80b5d70
Show file tree
Hide file tree
Showing 12 changed files with 357 additions and 124 deletions.
4 changes: 2 additions & 2 deletions nois.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ statement ::= var-def | fn-def | kind-def | impl-def | type-def | retu
| FLOAT
| IDENTIFIER
;
infix-op ::= add-op | sub-op | mul-op | div-op | exp-op | mod-op | access-op | eq-op | ne-op
infix-op ::= add-op | sub-op | mult-op | div-op | exp-op | mod-op | access-op | eq-op | ne-op
| ge-op | le-op | gt-op | lt-op | and-op | or-op | assign-op;
add-op ::= PLUS;
sub-op ::= MINUS;
mul-op ::= ASTERISK;
mult-op ::= ASTERISK;
div-op ::= SLASH;
exp-op ::= CARET;
mod-op ::= PERCENT;
Expand Down
102 changes: 87 additions & 15 deletions src/ast/expr.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,110 @@
import { BinaryOp, UnaryOp } from './op'
import { AstNode } from './index'
import { associativityMap, BinaryOp, buildBinaryOp, buildUnaryOp, precedenceMap, UnaryOp } from './op'
import { AstNode, filterNonAstNodes } from './index'
import { ParseNode } from '../parser/parser'
import { todo } from '../todo'
import { Operand } from './operand'
import { buildOperand, Operand } from './operand'

export type Expr = OperandExpr | UnaryExpr | BinaryExpr

export const buildExpr = (node: ParseNode): Expr => {
return todo()
}

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

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

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

export const buildUnaryExpr = (node: ParseNode): UnaryExpr => {
return todo()
}

export interface BinaryExpr extends AstNode<'binary-expr'> {
binaryOp: BinaryOp
lOperand: Operand
rOperand: Operand
}

export const buildBinaryExpr = (node: ParseNode): BinaryExpr => {
return todo()
export const buildExpr = (node: ParseNode): Expr => {
const nodes = filterNonAstNodes(node)
if (nodes.length === 1) {
return buildSubExpr(nodes[0])
} else {
return buildBinaryExpr(node)
}
}

export const buildSubExpr = (node: ParseNode): Expr => {
const nodes = filterNonAstNodes(node)
if (nodes.length === 1) {
return {
type: 'operand-expr',
parseNode: node,
operand: buildOperandExpr(node)
}
}
const isPrefix = nodes[0].kind === 'prefix-op'
return {
type: 'unary-expr',
parseNode: node,
unaryOp: buildUnaryOp(nodes[isPrefix ? 0 : 1]),
operand: buildOperand(nodes[isPrefix ? 1 : 0])
}
}
export const buildBinaryExpr = (node: ParseNode): Expr => {
const nodes = filterNonAstNodes(node)
const operatorStack: BinaryOp[] = []
const exprStack: Expr[] = []

for (const n of nodes) {
if (n.kind.endsWith('-op')) {
const o1 = buildBinaryOp(n)
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)!
if (o1Prec === o2Prec && o1Assoc === 'none' && o2Assoc === 'none') {
throw Error(`cannot chain operators \`${o1.type}\` and \`${o2.type}\``)
}
if ((o1Assoc !== 'right' && o1Prec === o2Prec) || o1Prec < o2Prec) {
operatorStack.pop()
const lExp = exprStack.pop()!
const rExp = exprStack.pop()!
exprStack.push({
type: 'binary-expr',
parseNode: lExp.parseNode,
binaryOp: o2,
lOperand: lExp,
rOperand: rExp
})
} else {
break
}
}
operatorStack.push(o1)
} else {
const expr = buildExpr(n)
exprStack.push(expr)
}
}

while (operatorStack.length !== 0) {
const op = operatorStack.pop()!
const rExp = exprStack.pop()!
const lExp = exprStack.pop()!
exprStack.push({
type: 'binary-expr',
parseNode: lExp.parseNode,
binaryOp: op,
lOperand: lExp,
rOperand: rExp
})
}
return exprStack.pop()!
}
61 changes: 53 additions & 8 deletions src/ast/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { buildStatement, Statement } from './statement'
import { buildPattern, Pattern } from './match'
import { ParseNode, ParseTree } from '../parser/parser'
import { independentTokenKinds } from '../lexer/lexer'
import { ParseNode, ParseTree, treeKinds } from '../parser/parser'
import { lexerDynamicKinds } from '../lexer/lexer'
import { buildIdentifier, Identifier } from './operand'
import { Expr } from './expr'

export interface AstNode<T extends AstNodeKind> {
type: T
Expand All @@ -19,10 +20,10 @@ export type AstNodeKind
| 'field-def'
| 'type-con'
| 'return-stmt'
| 'spread-op'
| 'operand-expr'
| 'unary-expr'
| 'binary-expr'
| 'field-init'
| 'block'
| 'closure-expr'
| 'list-expr'
Expand All @@ -42,8 +43,32 @@ export type AstNodeKind
| 'int-literal'
| 'float-literal'

export const filterIndependent = (node: ParseNode): ParseNode[] =>
(<ParseTree>node).nodes.filter(n => !independentTokenKinds.includes(n.kind))
| 'not-op'
| 'spread-op'
| 'call-op'
| 'con-op'

| 'add-op'
| 'sub-op'
| 'mult-op'
| 'div-op'
| 'exp-op'
| 'mod-op'
| 'access-op'
| 'eq-op'
| 'ne-op'
| 'ge-op'
| 'le-op'
| 'gt-op'
| 'lt-op'
| 'and-op'
| 'or-op'
| 'assign-op'

export const astNodes = [...lexerDynamicKinds, ...treeKinds]

export const filterNonAstNodes = (node: ParseNode): ParseNode[] =>
(<ParseTree>node).nodes.filter(n => astNodes.includes(n.kind))

export interface Module extends AstNode<'module'> {
statements: Statement[]
Expand All @@ -53,7 +78,7 @@ export const buildModule = (node: ParseNode): Module => {
return {
type: 'module',
parseNode: node,
statements: filterIndependent(node).filter(n => n.kind === 'statement').map(n => buildStatement(n))
statements: filterNonAstNodes(node).filter(n => n.kind === 'statement').map(n => buildStatement(n))
}
}

Expand All @@ -63,7 +88,7 @@ export interface Type extends AstNode<'type'> {
}

export const buildType = (node: ParseNode): Type => {
const nodes = filterIndependent(node)
const nodes = filterNonAstNodes(node)
const nameNode = nodes[0]
const paramsNode = nodes.at(1)
return {
Expand All @@ -84,7 +109,7 @@ export interface Param extends AstNode<'param'> {
}

export const buildParam = (node: ParseNode): Param => {
const nodes = filterIndependent(node)
const nodes = filterNonAstNodes(node)
const paramNode = nodes[0]
const typeNode = nodes.at(1)
return {
Expand All @@ -94,3 +119,23 @@ export const buildParam = (node: ParseNode): Param => {
paramType: typeNode ? buildType(typeNode) : undefined,
}
}

export interface FieldInit extends AstNode<'field-init'> {
identifier: Identifier
expr: Expr
}

export const compactAstNode = (node: AstNode<any>): any => 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]
})
)

4 changes: 2 additions & 2 deletions src/ast/match.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { buildOperandExpr, Expr } from './expr'
import { Block } from './statement'
import { AstNode, filterIndependent } from './index'
import { AstNode, filterNonAstNodes } from './index'
import { ParseNode } from '../parser/parser'
import { todo } from '../todo'
import { buildOperand, Identifier } from './operand'
Expand All @@ -9,7 +9,7 @@ import { buildUnaryOp, SpreadOp } from './op'
export type Pattern = ConPattern | Expr | Hole

export const buildPattern = (node: ParseNode): Pattern => {
const nodes = filterIndependent(node)
const nodes = filterNonAstNodes(node)
if (nodes[0].kind === 'con-pattern') {
return buildConPattern(nodes[0])
}
Expand Down
Loading

0 comments on commit 80b5d70

Please sign in to comment.