implement support for setting indexed values

This commit is contained in:
Alex Zenla 2025-07-26 17:01:24 -07:00
parent 48e19a8068
commit 3dcac2f9e6
No known key found for this signature in database
GPG Key ID: 067B238899B51269
26 changed files with 201 additions and 44 deletions

View File

@ -59,13 +59,22 @@ types:
type: Expression
- name: typeSpec
type: TypeSpec?
SetAssignment:
SymbolSetAssignment:
parent: Expression
values:
- name: symbol
type: Symbol
- name: value
type: Expression
IndexedSetAssignment:
parent: Expression
values:
- name: target
type: Expression
- name: index
type: Expression
- name: value
type: Expression
InfixOperator:
values:
- name: token

View File

@ -10,7 +10,8 @@ digraph A {
type_CompilationUnit [shape=box,label="CompilationUnit"]
type_LetAssignment [shape=box,label="LetAssignment"]
type_VarAssignment [shape=box,label="VarAssignment"]
type_SetAssignment [shape=box,label="SetAssignment"]
type_SymbolSetAssignment [shape=box,label="SymbolSetAssignment"]
type_IndexedSetAssignment [shape=box,label="IndexedSetAssignment"]
type_InfixOperator [shape=box,label="InfixOperator"]
type_InfixOperation [shape=box,label="InfixOperation"]
type_BooleanLiteral [shape=box,label="BooleanLiteral"]
@ -57,7 +58,8 @@ digraph A {
type_Node -> type_NativeTypeDescriptor
type_Expression -> type_LetAssignment
type_Expression -> type_VarAssignment
type_Expression -> type_SetAssignment
type_Expression -> type_SymbolSetAssignment
type_Expression -> type_IndexedSetAssignment
type_Expression -> type_InfixOperation
type_Expression -> type_BooleanLiteral
type_Expression -> type_FunctionCall
@ -94,8 +96,9 @@ digraph A {
type_VarAssignment -> type_Symbol [style=dotted]
type_VarAssignment -> type_Expression [style=dotted]
type_VarAssignment -> type_TypeSpec [style=dotted]
type_SetAssignment -> type_Symbol [style=dotted]
type_SetAssignment -> type_Expression [style=dotted]
type_SymbolSetAssignment -> type_Symbol [style=dotted]
type_SymbolSetAssignment -> type_Expression [style=dotted]
type_IndexedSetAssignment -> type_Expression [style=dotted]
type_InfixOperation -> type_Expression [style=dotted]
type_InfixOperation -> type_InfixOperator [style=dotted]
type_FunctionCall -> type_Symbol [style=dotted]

View File

@ -0,0 +1,30 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.ast.gen
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("indexedSetAssignment")
class IndexedSetAssignment(val target: Expression, val index: Expression, val value: Expression) : Expression() {
override val type: NodeType = NodeType.IndexedSetAssignment
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(target, index, value)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitIndexedSetAssignment(this)
override fun equals(other: Any?): Boolean {
if (other !is IndexedSetAssignment) return false
return other.target == target && other.index == index && other.value == value
}
override fun hashCode(): Int {
var result = target.hashCode()
result = 31 * result + index.hashCode()
result = 31 * result + value.hashCode()
result = 31 * result + type.hashCode()
return result
}
}

View File

@ -47,6 +47,9 @@ class NodeCoalescer(val followChildren: Boolean = true, val handler: (Node) -> U
override fun visitIndexedBy(node: IndexedBy): Unit =
handle(node)
override fun visitIndexedSetAssignment(node: IndexedSetAssignment): Unit =
handle(node)
override fun visitInfixOperation(node: InfixOperation): Unit =
handle(node)
@ -83,9 +86,6 @@ class NodeCoalescer(val followChildren: Boolean = true, val handler: (Node) -> U
override fun visitReturn(node: Return): Unit =
handle(node)
override fun visitSetAssignment(node: SetAssignment): Unit =
handle(node)
override fun visitStringLiteral(node: StringLiteral): Unit =
handle(node)
@ -98,6 +98,9 @@ class NodeCoalescer(val followChildren: Boolean = true, val handler: (Node) -> U
override fun visitSymbolReference(node: SymbolReference): Unit =
handle(node)
override fun visitSymbolSetAssignment(node: SymbolSetAssignment): Unit =
handle(node)
override fun visitTypeDefinition(node: TypeDefinition): Unit =
handle(node)

View File

@ -38,6 +38,8 @@ interface NodeParser {
fun parseIndexedBy(): IndexedBy
fun parseIndexedSetAssignment(): IndexedSetAssignment
fun parseInfixOperation(): InfixOperation
fun parseIntegerLiteral(): IntegerLiteral
@ -62,8 +64,6 @@ interface NodeParser {
fun parseReturn(): Return
fun parseSetAssignment(): SetAssignment
fun parseStringLiteral(): StringLiteral
fun parseSuffixOperation(): SuffixOperation
@ -72,6 +72,8 @@ interface NodeParser {
fun parseSymbolReference(): SymbolReference
fun parseSymbolSetAssignment(): SymbolSetAssignment
fun parseTypeDefinition(): TypeDefinition
fun parseTypeSpec(): TypeSpec

View File

@ -12,7 +12,8 @@ fun NodeParser.parse(type: NodeType): Node =
NodeType.CompilationUnit -> parseCompilationUnit()
NodeType.LetAssignment -> parseLetAssignment()
NodeType.VarAssignment -> parseVarAssignment()
NodeType.SetAssignment -> parseSetAssignment()
NodeType.SymbolSetAssignment -> parseSymbolSetAssignment()
NodeType.IndexedSetAssignment -> parseIndexedSetAssignment()
NodeType.InfixOperation -> parseInfixOperation()
NodeType.BooleanLiteral -> parseBooleanLiteral()
NodeType.FunctionCall -> parseFunctionCall()

View File

@ -21,6 +21,7 @@ enum class NodeType(val parent: NodeType? = null) {
ImportDeclaration(Declaration),
ImportPath(Node),
IndexedBy(Expression),
IndexedSetAssignment(Expression),
InfixOperation(Expression),
IntegerLiteral(Expression),
LetAssignment(Expression),
@ -33,11 +34,11 @@ enum class NodeType(val parent: NodeType? = null) {
Parentheses(Expression),
PrefixOperation(Expression),
Return(Expression),
SetAssignment(Expression),
StringLiteral(Expression),
SuffixOperation(Expression),
Symbol(Node),
SymbolReference(Expression),
SymbolSetAssignment(Expression),
TypeDefinition(Definition),
TypeSpec(Node),
VarAssignment(Expression),

View File

@ -32,6 +32,8 @@ interface NodeVisitor<T> {
fun visitIndexedBy(node: IndexedBy): T
fun visitIndexedSetAssignment(node: IndexedSetAssignment): T
fun visitInfixOperation(node: InfixOperation): T
fun visitIntegerLiteral(node: IntegerLiteral): T
@ -56,8 +58,6 @@ interface NodeVisitor<T> {
fun visitReturn(node: Return): T
fun visitSetAssignment(node: SetAssignment): T
fun visitStringLiteral(node: StringLiteral): T
fun visitSuffixOperation(node: SuffixOperation): T
@ -66,6 +66,8 @@ interface NodeVisitor<T> {
fun visitSymbolReference(node: SymbolReference): T
fun visitSymbolSetAssignment(node: SymbolSetAssignment): T
fun visitTypeDefinition(node: TypeDefinition): T
fun visitTypeSpec(node: TypeSpec): T

View File

@ -9,7 +9,8 @@ fun <T> NodeVisitor<T>.visit(node: Node): T =
is CompilationUnit -> visitCompilationUnit(node)
is LetAssignment -> visitLetAssignment(node)
is VarAssignment -> visitVarAssignment(node)
is SetAssignment -> visitSetAssignment(node)
is SymbolSetAssignment -> visitSymbolSetAssignment(node)
is IndexedSetAssignment -> visitIndexedSetAssignment(node)
is InfixOperation -> visitInfixOperation(node)
is BooleanLiteral -> visitBooleanLiteral(node)
is FunctionCall -> visitFunctionCall(node)

View File

@ -5,18 +5,18 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
@SerialName("setAssignment")
class SetAssignment(val symbol: Symbol, val value: Expression) : Expression() {
override val type: NodeType = NodeType.SetAssignment
@SerialName("symbolSetAssignment")
class SymbolSetAssignment(val symbol: Symbol, val value: Expression) : Expression() {
override val type: NodeType = NodeType.SymbolSetAssignment
override fun <T> visitChildren(visitor: NodeVisitor<T>): List<T> =
visitor.visitNodes(symbol, value)
override fun <T> visit(visitor: NodeVisitor<T>): T =
visitor.visitSetAssignment(this)
visitor.visitSymbolSetAssignment(this)
override fun equals(other: Any?): Boolean {
if (other !is SetAssignment) return false
if (other !is SymbolSetAssignment) return false
return other.symbol == symbol && other.value == value
}

View File

@ -3,9 +3,12 @@ package gay.pizza.pork.bir
import kotlinx.serialization.Serializable
@Serializable
data class IrIndex(var data: IrCodeElement, var index: IrCodeElement) : IrCodeElement() {
data class IrIndex(var data: IrCodeElement, var index: IrCodeElement, var value: IrCodeElement? = null) : IrCodeElement() {
override fun crawl(block: (IrElement) -> Unit) {
block(data)
block(index)
if (value != null) {
block(value!!)
}
}
}

View File

@ -40,6 +40,7 @@ enum class Opcode(val id: UByte) {
EuclideanModulo(37u),
Remainder(38u),
Index(39u),
IndexSet(44u),
ScopeIn(40u),
ScopeOut(41u),
ReturnAddress(42u),

View File

@ -228,6 +228,12 @@ class AstIrEmitter(
index = visit(node.index)
)
override fun visitIndexedSetAssignment(node: IndexedSetAssignment): IrCodeElement = IrIndex(
data = visit(node.target),
index = visit(node.index),
value = visit(node.value),
)
override fun visitInfixOperation(node: InfixOperation): IrCodeElement {
val op = when (node.op) {
InfixOperator.Plus -> IrInfixOp.Add
@ -288,7 +294,7 @@ class AstIrEmitter(
override fun visitReturn(node: Return): IrCodeElement =
IrReturn(from = self, value = node.value.visit(this))
override fun visitSetAssignment(node: SetAssignment): IrCodeElement {
override fun visitSymbolSetAssignment(node: SymbolSetAssignment): IrCodeElement {
val symbol = lookupLocalVariable(node.symbol) ?:
throw CompileError("Unable to find local variable target '${node.symbol.id}'", node)
return IrStore(symbol, node.value.visit(this))

View File

@ -248,9 +248,16 @@ class IrStubOpEmitter(val irDefinition: IrDefinition, val code: CodeBuilder) : I
}
override fun visitIrIndex(ir: IrIndex) {
if (ir.value != null) {
visit(ir.value!!)
}
visit(ir.index)
visit(ir.data)
code.emit(Opcode.Index)
if (ir.value != null) {
code.emit(Opcode.IndexSet)
} else {
code.emit(Opcode.Index)
}
}
override fun visitIrListSize(ir: IrListSize) {

View File

@ -177,7 +177,7 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : FunctionLevelVisito
return previousValue
}
override fun visitSetAssignment(node: SetAssignment): Any {
override fun visitSymbolSetAssignment(node: SymbolSetAssignment): Any {
val value = node.value.visit(this)
currentScope.set(node.symbol.id, value)
return value
@ -399,6 +399,24 @@ class EvaluationVisitor(root: Scope, val stack: CallStack) : FunctionLevelVisito
throw RuntimeException("Failed to index '${value}' by '${index}': Unsupported types used.")
}
override fun visitIndexedSetAssignment(node: IndexedSetAssignment): Any {
val value = node.value.visit(this)
val index = node.index.visit(this)
val target = node.target.visit(this)
if (target is MutableList<*> && index is Number) {
@Suppress("UNCHECKED_CAST")
(target as MutableList<Any?>)[index.toInt()] = value
return None
}
if (target is Array<*> && index is Number) {
@Suppress("UNCHECKED_CAST")
(target as MutableList<Any?>)[index.toInt()] = value
return None
}
return value
}
override fun visitNoneLiteral(node: NoneLiteral): Any = None
override fun visitContinue(node: Continue): Any = ContinueMarker

View File

@ -73,8 +73,8 @@ func setCell(cells, swap, x, y, state) {
let mask = if swap { 2 } else { 1 }
let idx = x + y * gridWidth
let value = cells[idx]
if state { listSet(cells, idx, value | mask) }
else { listSet(cells, idx, value & (~mask)) }
if state { cells[idx] = value | mask }
else { cells[idx] = value & (~mask) }
}
}

View File

@ -1,4 +1,6 @@
export func main() {
let items = [["Hello"], ["Goodbye"]]
println(items[0][0])
items[0][0] = "Goodbye"
println(items[0][0])
}

View File

@ -2,6 +2,7 @@ package gay.pizza.pork.frontend.scope
import gay.pizza.pork.ast.FunctionLevelVisitor
import gay.pizza.pork.ast.gen.*
import gay.pizza.pork.ast.gen.visit
class ExternalSymbolUsageAnalyzer : FunctionLevelVisitor<Unit>() {
private val symbols = mutableSetOf<Symbol>()
@ -116,8 +117,13 @@ class ExternalSymbolUsageAnalyzer : FunctionLevelVisitor<Unit>() {
node.visitChildren(this)
}
override fun visitSetAssignment(node: SetAssignment) {
node.value.visit(this)
override fun visitSymbolSetAssignment(node: SymbolSetAssignment) {
checkAndContribute(node.symbol)
node.visitChildren(this)
}
override fun visitIndexedSetAssignment(node: IndexedSetAssignment) {
node.visitChildren(this)
}
override fun visitStringLiteral(node: StringLiteral) {

View File

@ -53,16 +53,6 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
}
}
if (expression is SymbolReference && peek(TokenType.Equals)) {
val symbolReference = expression
expression = produce(NodeType.SetAssignment) {
attribution.adopt(expression)
expect(TokenType.Equals)
val value = parseExpression()
SetAssignment(symbolReference.symbol, value)
}
}
while (peek(TokenType.LeftBracket)) {
expression = produce(NodeType.IndexedBy) {
attribution.adopt(expression)
@ -73,12 +63,33 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
}
}
if (expression is SymbolReference && peek(TokenType.Equals)) {
val symbolReference = expression
expression = produce(NodeType.SymbolSetAssignment) {
attribution.adopt(expression)
expect(TokenType.Equals)
val value = parseExpression()
SymbolSetAssignment(symbolReference.symbol, value)
}
}
if (expression is IndexedBy && peek(TokenType.Equals)) {
val indexedBy = expression
expression = produce(NodeType.IndexedSetAssignment) {
attribution.adopt(indexedBy)
expect(TokenType.Equals)
val value = parseExpression()
IndexedSetAssignment(target = indexedBy.expression, index = indexedBy.index, value = value)
}
}
return if (peek(
TokenType.Plus, TokenType.Minus, TokenType.Multiply, TokenType.Divide, TokenType.Ampersand,
TokenType.Pipe, TokenType.Caret, TokenType.Equality, TokenType.Inequality, TokenType.Mod,
TokenType.Rem, TokenType.Lesser, TokenType.Greater, TokenType.LesserEqual, TokenType.GreaterEqual,
TokenType.And, TokenType.Or)) {
produce(NodeType.InfixOperation) {
attribution.adopt(expression)
val infixToken = next()
val infixOperator = ParserHelpers.convertInfixOperator(infixToken)
InfixOperation(expression, infixOperator, parseExpression())
@ -263,6 +274,16 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
IndexedBy(expression, index)
}
override fun parseIndexedSetAssignment(): IndexedSetAssignment = produce(NodeType.IndexedSetAssignment) {
val target = parseExpression()
expect(TokenType.LeftBracket)
val index = parseExpression()
expect(TokenType.RightBracket)
expect(TokenType.Equals)
val value = parseExpression()
IndexedSetAssignment(target, index, value)
}
override fun parseInfixOperation(): InfixOperation = produce(NodeType.InfixOperation) {
val infixToken = next()
val infixOperator = ParserHelpers.convertInfixOperator(infixToken)
@ -363,11 +384,11 @@ class Parser(source: TokenSource, attribution: NodeAttribution) :
Return(parseExpression())
}
override fun parseSetAssignment(): SetAssignment = produce(NodeType.SetAssignment) {
override fun parseSymbolSetAssignment(): SymbolSetAssignment = produce(NodeType.SymbolSetAssignment) {
val symbol = parseSymbol()
expect(TokenType.Equals)
val value = parseExpression()
SetAssignment(symbol, value)
SymbolSetAssignment(symbol, value)
}
override fun parseStringLiteral(): StringLiteral = produce(NodeType.StringLiteral) {

View File

@ -202,7 +202,7 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
append(node.op.token)
}
override fun visitSetAssignment(node: SetAssignment) {
override fun visitSymbolSetAssignment(node: SymbolSetAssignment) {
visit(node.symbol)
append(" = ")
visit(node.value)
@ -304,6 +304,15 @@ class Printer(buffer: StringBuilder) : NodeVisitor<Unit> {
append("]")
}
override fun visitIndexedSetAssignment(node: IndexedSetAssignment) {
visit(node.target)
append("[")
visit(node.index)
append("]")
append(" = ")
visit(node.value)
}
override fun visitCompilationUnit(node: CompilationUnit) {
for (declaration in node.declarations) {
visit(declaration)

View File

@ -0,0 +1,15 @@
// GENERATED CODE FROM PORK AST CODEGEN
package gay.pizza.pork.idea.psi.gen
import com.intellij.lang.ASTNode
import com.intellij.navigation.ItemPresentation
import gay.pizza.pork.idea.psi.PorkElementHelpers
import javax.swing.Icon
class IndexedSetAssignmentElement(node: ASTNode) : PorkElement(node) {
override fun getIcon(flags: Int): Icon? =
PorkElementHelpers.iconOf(this)
override fun getPresentation(): ItemPresentation? =
PorkElementHelpers.presentationOf(this)
}

View File

@ -16,7 +16,8 @@ object PorkElementFactory {
NodeType.CompilationUnit -> CompilationUnitElement(node)
NodeType.LetAssignment -> LetAssignmentElement(node)
NodeType.VarAssignment -> VarAssignmentElement(node)
NodeType.SetAssignment -> SetAssignmentElement(node)
NodeType.SymbolSetAssignment -> SymbolSetAssignmentElement(node)
NodeType.IndexedSetAssignment -> IndexedSetAssignmentElement(node)
NodeType.InfixOperation -> InfixOperationElement(node)
NodeType.BooleanLiteral -> BooleanLiteralElement(node)
NodeType.FunctionCall -> FunctionCallElement(node)

View File

@ -6,7 +6,7 @@ import com.intellij.navigation.ItemPresentation
import gay.pizza.pork.idea.psi.PorkElementHelpers
import javax.swing.Icon
class SetAssignmentElement(node: ASTNode) : PorkElement(node) {
class SymbolSetAssignmentElement(node: ASTNode) : PorkElement(node) {
override fun getIcon(flags: Int): Icon? =
PorkElementHelpers.iconOf(this)

View File

@ -15,6 +15,7 @@ val StandardOpHandlers: List<OpHandler> = listOf(
ListSizeOpHandler,
IndexOpHandler,
IndexSetOpHandler,
AndOpHandler,
OrOpHandler,

View File

@ -0,0 +1,15 @@
package gay.pizza.pork.vm.ops
import gay.pizza.pork.bytecode.Op
import gay.pizza.pork.bytecode.Opcode
import gay.pizza.pork.vm.InternalMachine
import gay.pizza.pork.vm.OpHandler
object IndexSetOpHandler : OpHandler(Opcode.IndexSet) {
override fun handle(machine: InternalMachine, op: Op) {
val list = machine.pop<MutableList<Any>>()
val index = machine.pop<Number>().toInt()
val value = machine.pop<Any>()
list[index] = value
}
}

View File

@ -13,6 +13,6 @@ object ListMakeOpHandler : OpHandler(Opcode.ListMake) {
val item = machine.popAnyValue()
list.add(item)
}
machine.push(list.reversed())
machine.push(list.reversed().toMutableList())
}
}