Skip to content

Commit

Permalink
Merge pull request #3747 from onflow/bastian/compile-dictionary
Browse files Browse the repository at this point in the history
[Compiler] Compile dictionary expressions, implement NewDictionary instruction
  • Loading branch information
turbolent authored Jan 30, 2025
2 parents 113c09f + c74ade0 commit 03ea43b
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 18 deletions.
29 changes: 24 additions & 5 deletions bbq/compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -748,9 +748,7 @@ func (c *Compiler[_]) VisitArrayExpression(array *ast.ArrayExpression) (_ struct
}

for _, expression := range array.Values {
//EmitDup()
c.compileExpression(expression)
//EmitSetIndex(index)
}

c.codeGen.Emit(
Expand All @@ -764,9 +762,30 @@ func (c *Compiler[_]) VisitArrayExpression(array *ast.ArrayExpression) (_ struct
return
}

func (c *Compiler[_]) VisitDictionaryExpression(_ *ast.DictionaryExpression) (_ struct{}) {
// TODO
panic(errors.NewUnreachableError())
func (c *Compiler[_]) VisitDictionaryExpression(dictionary *ast.DictionaryExpression) (_ struct{}) {
dictionaryTypes := c.ExtendedElaboration.DictionaryExpressionTypes(dictionary)

typeIndex := c.getOrAddType(dictionaryTypes.DictionaryType)

size := len(dictionary.Entries)
if size >= math.MaxUint16 {
panic(errors.NewDefaultUserError("invalid dictionary expression"))
}

for _, entry := range dictionary.Entries {
c.compileExpression(entry.Key)
c.compileExpression(entry.Value)
}

c.codeGen.Emit(
opcode.InstructionNewDictionary{
TypeIndex: typeIndex,
Size: uint16(size),
IsResource: dictionaryTypes.DictionaryType.IsResourceType(),
},
)

return
}

func (c *Compiler[_]) VisitIdentifierExpression(expression *ast.IdentifierExpression) (_ struct{}) {
Expand Down
118 changes: 114 additions & 4 deletions bbq/compiler/compiler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func TestCompileRecursionFib(t *testing.T) {
}
return fib(n - 1) + fib(n - 2)
}
`)
`)
require.NoError(t, err)

compiler := NewBytecodeCompiler(checker)
Expand Down Expand Up @@ -115,7 +115,7 @@ func TestCompileImperativeFib(t *testing.T) {
}
return fibonacci
}
`)
`)
require.NoError(t, err)

compiler := NewBytecodeCompiler(checker)
Expand Down Expand Up @@ -208,7 +208,7 @@ func TestCompileBreak(t *testing.T) {
}
return i
}
`)
`)
require.NoError(t, err)

compiler := NewBytecodeCompiler(checker)
Expand Down Expand Up @@ -285,7 +285,7 @@ func TestCompileContinue(t *testing.T) {
}
return i
}
`)
`)
require.NoError(t, err)

compiler := NewBytecodeCompiler(checker)
Expand Down Expand Up @@ -347,3 +347,113 @@ func TestCompileContinue(t *testing.T) {
program.Constants,
)
}

func TestCompileArray(t *testing.T) {

t.Parallel()

checker, err := ParseAndCheck(t, `
fun test() {
let xs: [Int] = [1, 2, 3]
}
`)
require.NoError(t, err)

compiler := NewBytecodeCompiler(checker)
program := compiler.Compile()

require.Len(t, program.Functions, 1)

require.Equal(t,
[]byte{
byte(opcode.GetConstant), 0, 0,
byte(opcode.GetConstant), 0, 1,
byte(opcode.GetConstant), 0, 2,
byte(opcode.NewArray), 0, 0, 0, 3, 0,
byte(opcode.Transfer), 0, 0,
byte(opcode.SetLocal), 0, 0,
byte(opcode.Return),
},
compiler.ExportFunctions()[0].Code,
)

require.Equal(t,
[]*bbq.Constant{
{
Data: []byte{0x1},
Kind: constantkind.Int,
},
{
Data: []byte{0x2},
Kind: constantkind.Int,
},
{
Data: []byte{0x3},
Kind: constantkind.Int,
},
},
program.Constants,
)
}
func TestCompileDictionary(t *testing.T) {

t.Parallel()

checker, err := ParseAndCheck(t, `
fun test() {
let xs: {String: Int} = {"a": 1, "b": 2, "c": 3}
}
`)
require.NoError(t, err)

compiler := NewBytecodeCompiler(checker)
program := compiler.Compile()

require.Len(t, program.Functions, 1)

require.Equal(t,
[]byte{
byte(opcode.GetConstant), 0, 0,
byte(opcode.GetConstant), 0, 1,
byte(opcode.GetConstant), 0, 2,
byte(opcode.GetConstant), 0, 3,
byte(opcode.GetConstant), 0, 4,
byte(opcode.GetConstant), 0, 5,
byte(opcode.NewDictionary), 0, 0, 0, 3, 0,
byte(opcode.Transfer), 0, 0,
byte(opcode.SetLocal), 0, 0,
byte(opcode.Return),
},
compiler.ExportFunctions()[0].Code,
)

require.Equal(t,
[]*bbq.Constant{
{
Data: []byte{'a'},
Kind: constantkind.String,
},
{
Data: []byte{0x1},
Kind: constantkind.Int,
},
{
Data: []byte{'b'},
Kind: constantkind.String,
},
{
Data: []byte{0x2},
Kind: constantkind.Int,
},
{
Data: []byte{'c'},
Kind: constantkind.String,
},
{
Data: []byte{0x3},
Kind: constantkind.Int,
},
},
program.Constants,
)
}
4 changes: 4 additions & 0 deletions bbq/compiler/extended_elaboration.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,10 @@ func (e *ExtendedElaboration) ArrayExpressionTypes(expression *ast.ArrayExpressi
return e.elaboration.ArrayExpressionTypes(expression)
}

func (e *ExtendedElaboration) DictionaryExpressionTypes(expression *ast.DictionaryExpression) sema.DictionaryExpressionTypes {
return e.elaboration.DictionaryExpressionTypes(expression)
}

func (e *ExtendedElaboration) CastingExpressionTypes(expression *ast.CastingExpression) sema.CastingExpressionTypes {
return e.elaboration.CastingExpressionTypes(expression)
}
Expand Down
40 changes: 40 additions & 0 deletions bbq/opcode/instructions.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions bbq/opcode/instructions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,26 @@
- name: "array"
type: "array"

- name: "newDictionary"
description: Creates a new dictionary with the given type and size.
operands:
- name: "typeIndex"
type: "index"
- name: "size"
type: "size"
- name: "isResource"
type: "bool"
valueEffects:
pop:
- name: "entries"
type: "value"
# The number of elements taken from the stack is equal to the size operand of the opcode, multiplied by 2.
count: "size * 2"
push:
- name: "dictionary"
type: "dictionary"


- name: "newRef"
description: Creates a new reference with the given type.
operands:
Expand Down
38 changes: 38 additions & 0 deletions bbq/vm/test/vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1945,6 +1945,44 @@ func TestArrayLiteral(t *testing.T) {
})
}

func TestDictionaryLiteral(t *testing.T) {

t.Parallel()

t.Run("dictionary literal", func(t *testing.T) {
t.Parallel()

checker, err := ParseAndCheck(t, `
fun test(): {String: Int} {
return {"b": 2, "e": 5}
}
`)
require.NoError(t, err)

comp := compiler.NewInstructionCompiler(checker)
program := comp.Compile()

vmConfig := &vm.Config{}
vmInstance := vm.NewVM(scriptLocation(), program, vmConfig)

result, err := vmInstance.Invoke("test")
require.NoError(t, err)
require.Equal(t, 0, vmInstance.StackSize())

require.IsType(t, &vm.DictionaryValue{}, result)
dictionary := result.(*vm.DictionaryValue)
assert.Equal(t, 2, dictionary.Count())
assert.Equal(t,
vm.NewSomeValueNonCopying(vm.NewIntValue(2)),
dictionary.GetKey(vmConfig, vm.NewStringValue("b")),
)
assert.Equal(t,
vm.NewSomeValueNonCopying(vm.NewIntValue(5)),
dictionary.GetKey(vmConfig, vm.NewStringValue("e")),
)
})
}

func TestReference(t *testing.T) {

t.Parallel()
Expand Down
Loading

0 comments on commit 03ea43b

Please sign in to comment.