Skip to content

Commit

Permalink
Syntax: get rid of prefix ops; bind-op & unwrap-op
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanjermakov committed Feb 15, 2024
1 parent 947493e commit 0cbd58c
Show file tree
Hide file tree
Showing 20 changed files with 227 additions and 359 deletions.
19 changes: 10 additions & 9 deletions nois.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ statement ::= var-def | fn-def | trait-def | impl-def | type-def | r
;
expr ::= sub-expr (infix-op sub-expr)*
;
sub-expr ::= prefix-op? operand call?
sub-expr ::= operand postfix-op*
;
operand ::= if-expr
| if-let-expr
Expand All @@ -54,8 +54,7 @@ statement ::= var-def | fn-def | trait-def | impl-def | type-def | r
| list-expr
| STRING
| CHAR
| INT
| FLOAT
| number
| TRUE
| FALSE
| identifier
Expand All @@ -79,15 +78,15 @@ statement ::= var-def | fn-def | trait-def | impl-def | type-def | r
or-op ::= PIPE PIPE;
assign-op ::= EQUALS;

prefix-op ::= sub-op | not-op | spread-op
postfix-op ::= call-op | unwrap-op | bind-op
;
not-op ::= EXCL
call-op ::= O-PAREN (arg (COMMA arg)*)? COMMA? C-PAREN
;
spread-op ::= PERIOD PERIOD
arg ::= (NAME COLON)? expr
;
call ::= O-PAREN (arg (COMMA arg)*)? COMMA? C-PAREN
unwrap-op ::= EXCL
;
arg ::= (NAME COLON)? expr
bind-op ::= QMARK
;
identifier ::= (NAME COLON COLON)* NAME type-args?
;
Expand Down Expand Up @@ -133,7 +132,7 @@ pattern ::= pattern-bind? pattern-expr
;
pattern-bind ::= NAME AT
;
pattern-expr ::= NAME | con-pattern | STRING | CHAR | prefix-op? (INT | FLOAT) | hole
pattern-expr ::= NAME | con-pattern | STRING | CHAR | number | hole
;
con-pattern ::= identifier con-pattern-params
;
Expand All @@ -143,3 +142,5 @@ pattern ::= pattern-bind? pattern-expr
;
hole ::= UNDERSCORE
;
number ::= MINUS? (INT | FLOAT)
;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"publish:prepare": "cp package.json dist && find dist -name \"*.spec.*\" -delete",
"publish": "npm run build && npm run publish:prepare",
"run:node": "node dist",
"test": "bun test",
"test": "bun test src",
"ci": "npm run test",
"clean": "rm -rf dist"
},
Expand Down
65 changes: 8 additions & 57 deletions src/ast/ast.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { inspect } from 'util'
import { tokenize } from '../lexer/lexer'
import { Parser } from '../parser'
import { parseModule } from '../parser/fns'
Expand Down Expand Up @@ -131,56 +132,6 @@ describe('ast', () => {
)
})

it('prefix and infix', () => {
const ast = buildAst('-1 + 2')
// biome-ignore format: compact
expect(compactAstNode(ast.block)).toEqual(
{ kind: 'block',
statements:
[ { kind: 'binary-expr',
binaryOp: { kind: 'add-op' },
lOperand: { kind: 'unary-expr', prefixOp: { kind: 'neg-op' }, operand: { kind: 'int-literal', value: '1' } },
rOperand: { kind: 'int-literal', value: '2' } } ] }
)
})

it('prefix and method call', () => {
const ast = buildAst('-a.foo()')
// biome-ignore format: compact
expect(compactAstNode(ast.block)).toEqual(
{ kind: 'block',
statements:
[ { kind: 'unary-expr',
prefixOp: { kind: 'neg-op' },
operand:
{ kind: 'binary-expr',
binaryOp: { kind: 'access-op' },
lOperand: { kind: 'identifier', names: [ { kind: 'name', value: 'a' } ], typeArgs: [] },
rOperand:
{ kind: 'unary-expr',
call: { kind: 'call', args: [] },
operand: { kind: 'identifier', names: [ { kind: 'name', value: 'foo' } ], typeArgs: [] } } } } ] }
)
})

it('prefix and postfix on unary-expr', () => {
const ast = buildAst('let a = !foo()')
// biome-ignore format: compact
expect(compactAstNode(ast.block)).toEqual(
{ kind: 'block',
statements:
[ { kind: 'var-def',
pattern: { kind: 'pattern', name: undefined, expr: { kind: 'name', value: 'a' } },
varType: undefined,
expr:
{ kind: 'unary-expr',
prefixOp: { kind: 'not-op' },
call: { kind: 'call', args: [] },
operand: { kind: 'identifier', names: [ { kind: 'name', value: 'foo' } ], typeArgs: [] } },
pub: false } ] }
)
})

it('method call', () => {
const ast = buildAst('a.b()')
// biome-ignore format: compact
Expand All @@ -192,7 +143,7 @@ describe('ast', () => {
lOperand: { kind: 'identifier', names: [ { kind: 'name', value: 'a' } ], typeArgs: [] },
rOperand:
{ kind: 'unary-expr',
call: { kind: 'call', args: [] },
op: { kind: 'call-op', args: [] },
operand: { kind: 'identifier', names: [ { kind: 'name', value: 'b' } ], typeArgs: [] } } } ] }
)
})
Expand All @@ -214,16 +165,16 @@ describe('ast', () => {
lOperand: { kind: 'identifier', names: [ { kind: 'name', value: 'a' } ], typeArgs: [] },
rOperand:
{ kind: 'unary-expr',
call: { kind: 'call', args: [] },
operand: { kind: 'identifier', names: [ { kind: 'name', value: 'b' } ], typeArgs: [] } } },
operand: { kind: 'identifier', names: [ { kind: 'name', value: 'b' } ], typeArgs: [] },
op: { kind: 'call-op', args: [] } } },
rOperand:
{ kind: 'unary-expr',
call: { kind: 'call', args: [] },
operand: { kind: 'identifier', names: [ { kind: 'name', value: 'c' } ], typeArgs: [] } } },
operand: { kind: 'identifier', names: [ { kind: 'name', value: 'c' } ], typeArgs: [] },
op: { kind: 'call-op', args: [] } } },
rOperand:
{ kind: 'unary-expr',
call: { kind: 'call', args: [] },
operand: { kind: 'identifier', names: [ { kind: 'name', value: 'd' } ], typeArgs: [] } } } ] }
operand: { kind: 'identifier', names: [ { kind: 'name', value: 'd' } ], typeArgs: [] },
op: { kind: 'call-op', args: [] } } } ] }
)
})
})
Expand Down
142 changes: 41 additions & 101 deletions src/ast/expr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,7 @@ import { ParseNode, filterNonAstNodes } from '../parser'
import { Typed } from '../semantic'
import { assert } from '../util/todo'
import { AstNode } from './index'
import {
BinaryOp,
Call,
PrefixOp,
associativityMap,
buildBinaryOp,
buildCall,
buildPrefixOp,
isPrefixOp,
precedenceMap
} from './op'
import { BinaryOp, PostfixOp, associativityMap, buildBinaryOp, buildPostfixOp, precedenceMap } from './op'
import { Operand, buildOperand } from './operand'

export type Expr = OperandExpr | UnaryExpr | BinaryExpr
Expand All @@ -30,9 +20,8 @@ export const buildOperandExpr = (node: ParseNode): OperandExpr => {
}

export interface UnaryExpr extends AstNode<'unary-expr'>, Partial<Typed> {
prefixOp?: PrefixOp
call?: Call
operand: Operand
op: PostfixOp
}

export interface BinaryExpr extends AstNode<'binary-expr'>, Partial<Typed> {
Expand All @@ -43,25 +32,13 @@ export interface BinaryExpr extends AstNode<'binary-expr'>, Partial<Typed> {

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

const operatorStack: (BinaryOp | PrefixOp | Call)[] = []
const exprStack: (Operand | Expr)[] = []
const operatorStack: BinaryOp[] = []
const exprStack: (Expr | Operand)[] = []

for (const n of nodes) {
if (n.kind === 'sub-expr') {
const expr = buildSubExpr(n)
exprStack.push(expr.operand)
if (expr.kind === 'unary-expr') {
if (expr.prefixOp) {
operatorStack.push(expr.prefixOp)
}
if (expr.call) {
operatorStack.push(expr.call)
}
}
exprStack.push(expr)
} else {
const o1 = buildBinaryOp(n)
while (operatorStack.length !== 0) {
Expand All @@ -76,31 +53,14 @@ export const buildExpr = (node: ParseNode): Expr => {
if ((o1Assoc !== 'right' && o1Prec === o2Prec) || o1Prec < o2Prec) {
operatorStack.pop()
const rExp = exprStack.pop()!

if (isPrefixOp(o2)) {
exprStack.push({
kind: 'unary-expr',
parseNode: { kind: 'expr', nodes: [o2.parseNode, rExp.parseNode] },
prefixOp: o2,
operand: rExp
})
} else if (o2.kind === 'call') {
exprStack.push({
kind: 'unary-expr',
parseNode: { kind: 'expr', nodes: [rExp.parseNode, o2.parseNode] },
call: o2,
operand: rExp
})
} else {
const lExp = exprStack.pop()!
exprStack.push({
kind: 'binary-expr',
parseNode: { kind: 'expr', nodes: [lExp.parseNode, o2.parseNode, rExp.parseNode] },
binaryOp: o2,
lOperand: lExp,
rOperand: rExp
})
}
const lExp = exprStack.pop()!
exprStack.push({
kind: 'binary-expr',
parseNode: { kind: 'expr', nodes: [lExp.parseNode, o2.parseNode, rExp.parseNode] },
binaryOp: o2,
lOperand: lExp,
rOperand: rExp
})
} else {
break
}
Expand All @@ -112,59 +72,39 @@ export const buildExpr = (node: ParseNode): Expr => {
while (operatorStack.length !== 0) {
const op = operatorStack.pop()!
const rExp = exprStack.pop()!
if (isPrefixOp(op)) {
// no need to wrap in additional unary-expr
if (rExp.kind === 'unary-expr' && !rExp.prefixOp) {
rExp.prefixOp = op
} else {
exprStack.push({
kind: 'unary-expr',
parseNode: { kind: 'expr', nodes: [rExp.parseNode, op.parseNode] },
prefixOp: op,
operand: rExp
})
}
} else if (op.kind === 'call') {
// no need to wrap in additional unary-expr
if (rExp.kind === 'unary-expr' && !rExp.call) {
rExp.call = op
} else {
exprStack.push({
kind: 'unary-expr',
parseNode: { kind: 'expr', nodes: [rExp.parseNode, op.parseNode] },
call: op,
operand: rExp
})
}
} else {
const lExp = exprStack.pop()!
exprStack.push({
kind: 'binary-expr',
parseNode: { kind: 'expr', nodes: [lExp.parseNode, op.parseNode, rExp.parseNode] },
binaryOp: op,
lOperand: lExp,
rOperand: rExp
})
}
const lExp = exprStack.pop()!
exprStack.push({
kind: 'binary-expr',
parseNode: { kind: 'expr', nodes: [lExp.parseNode, op.parseNode, rExp.parseNode] },
binaryOp: op,
lOperand: lExp,
rOperand: rExp
})
}
assert(exprStack.length === 1, 'leftover expressions in the stack')
const result = exprStack.pop()!
assert(result.kind === 'operand-expr' || result.kind === 'unary-expr' || result.kind === 'binary-expr')
return <Expr>result
let result = exprStack.pop()!
if (result.kind !== 'operand-expr' && result.kind !== 'unary-expr' && result.kind !== 'binary-expr') {
result = { kind: 'operand-expr', parseNode: result.parseNode, operand: result }
}
return result
}

export const buildSubExpr = (node: ParseNode): UnaryExpr | OperandExpr => {
export const buildSubExpr = (node: ParseNode): UnaryExpr | Operand => {
const nodes = filterNonAstNodes(node)
if (nodes.length === 1) {
return buildOperandExpr(nodes[0])
const operand = buildOperand(nodes[0])
const ops = nodes.slice(1).map(buildPostfixOp)
if (ops.length === 0) {
return operand
}
const prefixOp = nodes[0].kind === 'prefix-op' ? buildPrefixOp(nodes[0]) : undefined
const call = nodes.at(-1)!.kind === 'call' ? buildCall(nodes.at(-1)!) : undefined
return {
kind: 'unary-expr',
parseNode: node,
prefixOp,
call,
operand: buildOperand(nodes[prefixOp ? 1 : 0])
let expr: UnaryExpr | Operand = operand
// fold a list of ops into left-associative unary-exprs
for (const op of ops) {
expr = {
kind: 'unary-expr',
parseNode: { kind: 'expr', nodes: [expr.parseNode, op.parseNode] },
operand: expr,
op
}
}
return <UnaryExpr>expr
}
8 changes: 4 additions & 4 deletions src/ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,6 @@ export const astDefKinds = <const>['var-def', 'fn-def', 'trait-def', 'impl-def',

export const astLiteralKinds = <const>['string-literal', 'char-literal', 'int-literal', 'float-literal', 'bool-literal']

export const astPrefixOpKinds = <const>['neg-op', 'not-op', 'spread-op']

export const astInfixOpKinds = <const>[
'add-op',
'sub-op',
Expand All @@ -52,6 +50,8 @@ export const astInfixOpKinds = <const>[
'assign-op'
]

export const astPostfixOpKinds = <const>['call-op', 'unwrap-op', 'bind-op']

export const astKinds = <const>[
'module',
'use-expr',
Expand All @@ -76,8 +76,8 @@ export const astKinds = <const>[
...astExprKinds,
...astDefKinds,
...astLiteralKinds,
...astPrefixOpKinds,
...astInfixOpKinds
...astInfixOpKinds,
...astPostfixOpKinds
]

export type AstNodeKind = (typeof astKinds)[number]
Expand Down
Loading

0 comments on commit 0cbd58c

Please sign in to comment.