Skip to content

Commit

Permalink
CORE: Add 'is' operator
Browse files Browse the repository at this point in the history
  • Loading branch information
MineGame159 committed Jan 26, 2024
1 parent d6a0b60 commit 263e4c9
Show file tree
Hide file tree
Showing 11 changed files with 162 additions and 34 deletions.
7 changes: 5 additions & 2 deletions core/ast/cst2ast/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ func (c *converter) convertBinaryExpr(node cst.Node) ast.Expr {
if node.Contains(scanner.Dot) {
return c.convertMemberExpr(node)
}
if node.Contains(scanner.As) {
if node.ContainsAny(scanner.CastOperators) {
return c.convertCastExpr(node)
}

Expand Down Expand Up @@ -162,17 +162,20 @@ func (c *converter) convertIndexExpr(node cst.Node) ast.Expr {

func (c *converter) convertCastExpr(node cst.Node) ast.Expr {
var value ast.Expr
var operator *ast.Token
var target ast.Type

for _, child := range node.Children {
if child.Kind.IsExpr() {
value = c.convertExpr(child)
} else if child.Token.Kind.IsAny(scanner.CastOperators) {
operator = c.convertToken(child)
} else if child.Kind.IsType() {
target = c.convertType(child)
}
}

if c := ast.NewCast(node, value, target); c != nil {
if c := ast.NewCast(node, value, operator, target); c != nil {
return c
}

Expand Down
26 changes: 19 additions & 7 deletions core/ast/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -732,26 +732,31 @@ type Cast struct {
cst cst.Node
parent Node

Value Expr
Target Type
Value Expr
Operator *Token
Target Type

result ExprResult
}

func NewCast(node cst.Node, value Expr, target Type) *Cast {
if value == nil && target == nil {
func NewCast(node cst.Node, value Expr, operator *Token, target Type) *Cast {
if value == nil && operator == nil && target == nil {
return nil
}

c := &Cast{
cst: node,
Value: value,
Target: target,
cst: node,
Value: value,
Operator: operator,
Target: target,
}

if value != nil {
value.SetParent(c)
}
if operator != nil {
operator.SetParent(c)
}
if target != nil {
target.SetParent(c)
}
Expand Down Expand Up @@ -787,6 +792,9 @@ func (c *Cast) AcceptChildren(visitor Visitor) {
if c.Value != nil {
visitor.VisitNode(c.Value)
}
if c.Operator != nil {
visitor.VisitNode(c.Operator)
}
if c.Target != nil {
visitor.VisitNode(c.Target)
}
Expand All @@ -801,6 +809,10 @@ func (c *Cast) Clone() Node {
c2.Value = c.Value.Clone().(Expr)
c2.Value.SetParent(c2)
}
if c.Operator != nil {
c2.Operator = c.Operator.Clone().(*Token)
c2.Operator.SetParent(c2)
}
if c.Target != nil {
c2.Target = c.Target.Clone().(Type)
c2.Target.SetParent(c2)
Expand Down
21 changes: 17 additions & 4 deletions core/checker/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -550,11 +550,24 @@ func (c *checker) VisitCast(expr *ast.Cast) {
return
}

expr.Result().SetValue(expr.Target, 0, nil)
// Check based on the operator
switch expr.Operator.Token().Kind {
case scanner.As:
if _, ok := ast.GetCast(expr.Value.Result().Type, expr.Target); !ok {
c.error(expr, "Cannot cast type '%s' to type '%s'", ast.PrintType(expr.Value.Result().Type), ast.PrintType(expr.Target))
}

// Check type
if _, ok := ast.GetCast(expr.Value.Result().Type, expr.Target); !ok {
c.error(expr, "Cannot cast type '%s' to type '%s'", ast.PrintType(expr.Value.Result().Type), ast.PrintType(expr.Target))
expr.Result().SetValue(expr.Target, 0, nil)

case scanner.Is:
if _, ok := ast.As[*ast.Interface](expr.Value.Result().Type); !ok {
c.error(expr.Value, "Runtime type checking is only supported for interfaces")
}

expr.Result().SetValue(&ast.Primitive{Kind: ast.Bool}, 0, nil)

default:
panic("checker.VisitCast() - Not implemented")
}
}

Expand Down
73 changes: 62 additions & 11 deletions core/codegen/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,9 +522,57 @@ func (c *codegen) VisitAssignment(expr *ast.Assignment) {
}

func (c *codegen) VisitCast(expr *ast.Cast) {
value := c.acceptExpr(expr.Value)
switch expr.Operator.Token().Kind {
case scanner.As:
value := c.acceptExpr(expr.Value)
c.exprResult = c.cast(value, expr.Value.Result().Type, expr.Target, expr)

case scanner.Is:
value := c.loadExpr(expr.Value)

// Get vtable pointer
vtablePtr := c.block.Add(&ir.ExtractValueInst{
Value: value.v,
Indices: []uint32{uint32(0)},
})

// Get type id
typ := c.vtables.getType(expr.Value.Result().Type.Resolved().(*ast.Interface))
typPtr := &ir.PointerType{Pointee: typ}

typeIdPtr := c.block.Add(&ir.GetElementPtrInst{
PointerTyp: typPtr,
Typ: typ,
Pointer: vtablePtr,
Indices: []ir.Value{
&ir.IntConst{Typ: ir.I32, Value: ir.Unsigned(0)},
&ir.IntConst{Typ: ir.I32, Value: ir.Unsigned(0)},
},
Inbounds: true,
})

typeId := c.block.Add(&ir.LoadInst{
Typ: ir.I32,
Pointer: typeIdPtr,
})

c.exprResult = c.cast(value, expr.Value.Result().Type, expr.Target, expr)
// Compare
result := c.block.Add(&ir.ICmpInst{
Kind: ir.Eq,
Signed: false,
Left: typeId,
Right: &ir.IntConst{
Typ: ir.I32,
Value: ir.Unsigned(uint64(c.ctx.GetTypeID(expr.Target))),
},
})

c.setLocationMeta(result, expr.Operator)
c.exprResult = exprValue{v: result}

default:
panic("codegen.VisitCast() - Not implemented")
}
}

func (c *codegen) VisitTypeCall(expr *ast.TypeCall) {
Expand Down Expand Up @@ -731,41 +779,44 @@ func (c *codegen) VisitMember(expr *ast.Member) {
value = c.load(value, expr.Value.Result().Type)
}

// Get vtable pointer
vtablePtr := c.block.Add(&ir.ExtractValueInst{
Value: value.v,
Indices: []uint32{uint32(0)},
})

// Get function
_, index := inter.GetMethod(expr.Name.String())

void := ast.Primitive{Kind: ast.Void}
fnPtr := ast.Pointer{Pointee: &void}

typ := ast.Array{Base: &fnPtr, Count: uint32(len(inter.Methods))}
typPtr := ast.Pointer{Pointee: &typ}
typ := c.vtables.getType(inter)
typPtr := &ir.PointerType{Pointee: typ}

functionPtr := c.block.Add(&ir.GetElementPtrInst{
PointerTyp: c.types.get(&typPtr),
Typ: c.types.get(&typ),
PointerTyp: typPtr,
Typ: typ,
Pointer: vtablePtr,
Indices: []ir.Value{
&ir.IntConst{Typ: ir.I32, Value: ir.Unsigned(0)},
&ir.IntConst{Typ: ir.I32, Value: ir.Unsigned(1)},
&ir.IntConst{Typ: ir.I32, Value: ir.Unsigned(uint64(index))},
},
Inbounds: true,
})

fnPtr := typ.Fields[1].(*ir.ArrayType).Base

function := c.block.Add(&ir.LoadInst{
Typ: c.types.get(&fnPtr),
Typ: fnPtr,
Pointer: functionPtr,
Align: fnPtr.Align(),
})

// Get data pointer
dataPtr := c.block.Add(&ir.ExtractValueInst{
Value: value.v,
Indices: []uint32{uint32(1)},
})

// Return
c.exprResult = exprValue{v: function}
c.this = exprValue{v: dataPtr, addressable: true}

Expand Down
33 changes: 28 additions & 5 deletions core/codegen/vtables.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,22 @@ func (v *vtables) get(type_, inter ast.Type) ir.Value {
methods[i] = v.c.getFunction(method).v
}

typ := v.getType(inter.Resolved().(*ast.Interface))

value := v.c.module.Constant(
getVtableName(type_, inter),
&ir.ArrayConst{
Typ: &ir.ArrayType{
Count: uint32(len(methods)),
Base: &ir.PointerType{},
&ir.StructConst{
Typ: typ,
Fields: []ir.Value{
&ir.IntConst{
Typ: ir.I32,
Value: ir.Unsigned(uint64(v.c.ctx.GetTypeID(type_))),
},
&ir.ArrayConst{
Typ: typ.Fields[1],
Values: methods,
},
},
Values: methods,
},
)

Expand All @@ -54,6 +62,21 @@ func (v *vtables) get(type_, inter ast.Type) ir.Value {
return value
}

func (v *vtables) getType(inter *ast.Interface) *ir.StructType {
funcPtrArrayType := &ir.ArrayType{
Count: uint32(len(inter.Methods)),
Base: &ir.PointerType{},
}

return &ir.StructType{
Name: "",
Fields: []ir.Type{
ir.I32,
funcPtrArrayType,
},
}
}

func getVtableName(type_, inter ast.Type) string {
sb := strings.Builder{}
sb.WriteString("__fb_vtable__")
Expand Down
6 changes: 3 additions & 3 deletions core/cst/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func parseStructFieldExpr(p *parser) Node {

func parseInfixExprPratt(p *parser, op scanner.TokenKind, lhs Node, rightPower int) Node {
switch op {
case scanner.As:
case scanner.As, scanner.Is:
p.begin(BinaryExprNode)

p.childAdd(lhs)
Expand Down Expand Up @@ -326,8 +326,8 @@ func init() {
infix(false, scanner.Ampersand)
// ==, !=
infix(false, scanner.EqualEqual, scanner.BangEqual)
// >, <=, >, >=, as
infix(false, scanner.Less, scanner.LessEqual, scanner.Greater, scanner.GreaterEqual, scanner.As)
// >, <=, >, >=, as, is
infix(false, scanner.Less, scanner.LessEqual, scanner.Greater, scanner.GreaterEqual, scanner.As, scanner.Is)
// <<, >>
infix(false, scanner.LessLess, scanner.GreaterGreater)
// +, -
Expand Down
2 changes: 2 additions & 0 deletions core/scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ func (s *Scanner) identifierKind() TokenKind {
return s.checkKeyword(2, "pl", Impl)
case 'n':
return s.checkKeyword(2, "terface", Interface)
case 's':
return Is
}
}
case 'n':
Expand Down
17 changes: 16 additions & 1 deletion core/scanner/token.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package scanner

import "fireball/core"
import (
"fireball/core"
"slices"
)

type TokenKind uint8

Expand Down Expand Up @@ -68,6 +71,7 @@ const (
While
For
As
Is
Static
Func
Fn
Expand All @@ -93,6 +97,10 @@ const (
Eof
)

func (t TokenKind) IsAny(kinds []TokenKind) bool {
return slices.Contains(kinds, t)
}

var LogicalOperators = []TokenKind{
And,
Or,
Expand All @@ -115,6 +123,11 @@ var AssignmentOperators = []TokenKind{
GreaterGreaterEqual,
}

var CastOperators = []TokenKind{
As,
Is,
}

type Token struct {
Kind TokenKind
Lexeme string
Expand Down Expand Up @@ -304,6 +317,8 @@ func TokenKindStr(kind TokenKind) string {
return "'for'"
case As:
return "'as'"
case Is:
return "'is'"
case Static:
return "'static'"
case Func:
Expand Down
1 change: 1 addition & 0 deletions gen/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ var expressions = Group{
node(
"Cast",
field("value", type_("Expr")),
field("operator", type_("Token")),
field("target", type_("Type")),
),
node(
Expand Down
8 changes: 8 additions & 0 deletions tests/src/interfaces.fb
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,14 @@ func checkConcrete() bool {
return s == &f && s != &s;
}

#[Test("is")]
func _is() bool {
var f = Foo {};
var s Something = &f;

return (s is Foo) && !(s is i32);
}

func check(s Something, number i32) bool {
return s.getNumber() == number;
}
2 changes: 1 addition & 1 deletion vscode/fireball.tmGrammar.json
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@
"name": "string.quoted.double.fb"
},
"keyword": {
"match": "\\b(nil|true|false|and|or|var|if|else|while|for|as|static|func|continue|break|return|namespace|using|struct|impl|enum|interface|new|fn)\\b",
"match": "\\b(nil|true|false|and|or|var|if|else|while|for|as|is|static|func|continue|break|return|namespace|using|struct|impl|enum|interface|new|fn)\\b",
"name": "keyword.fb"
},
"attribute": {
Expand Down

0 comments on commit 263e4c9

Please sign in to comment.