diff --git a/compiler/compiler/compiler.go b/compiler/compiler/compiler.go index 8c11c65..8b6e553 100644 --- a/compiler/compiler/compiler.go +++ b/compiler/compiler/compiler.go @@ -250,6 +250,10 @@ func (c *Compiler) compile(instructions []parser.Node) { c.compileBreakNode(v) case *parser.ContinueNode: c.compileContinueNode(v) + case *parser.ExternNode: + for _, fn := range v.FuncNodes { + c.compileDefineFuncNode(fn) + } case *parser.DeclarePackageNode: // TODO: Make use of it diff --git a/compiler/compiler/func.go b/compiler/compiler/func.go index f8d78d9..d8be467 100644 --- a/compiler/compiler/func.go +++ b/compiler/compiler/func.go @@ -78,7 +78,7 @@ func (c *Compiler) funcType(params, returnTypes []parser.TypeNode) (retType type // ABI description // method: package + _method_ + type + _ + name // named variable: package + name -// cffi: function_name +// cffi(extern): function_name // lambda: package + anonName func (c *Compiler) compileDefineFuncNode(v *parser.DefineFuncNode) value.Value { var compiledName string @@ -102,7 +102,7 @@ func (c *Compiler) compileDefineFuncNode(v *parser.DefineFuncNode) value.Value { compiledName = c.currentPackageName + "_method_" + v.MethodOnType.TypeName + "_" + v.Name } else if v.IsNamed { // todo ffi set identifier - if c.currentPackageName == "tx" { + if v.IsExtern { compiledName = v.Name } else { compiledName = c.currentPackageName + "_" + v.Name @@ -141,7 +141,7 @@ func (c *Compiler) compileDefineFuncNode(v *parser.DefineFuncNode) value.Value { } else { fn = c.module.NewFunc(compiledName, funcRetType.LLVM(), llvmParams...) // register ffi function definnition for tx package and os package - if v.IsCFunc { + if v.IsExtern { // do not generate block } else { entry = fn.NewBlock(name.Block()) @@ -158,7 +158,7 @@ func (c *Compiler) compileDefineFuncNode(v *parser.DefineFuncNode) value.Value { // register ffi function definition for tx package // without generate func body - if v.IsCFunc { + if v.IsExtern { val := value.Value{ Type: typesFunc, Value: fn, diff --git a/compiler/lexer/keywords.go b/compiler/lexer/keywords.go index 78dc3fb..02e35c5 100644 --- a/compiler/lexer/keywords.go +++ b/compiler/lexer/keywords.go @@ -4,7 +4,7 @@ var keywords = map[string]struct{}{ "if": {}, "else": {}, "func": {}, - "cfunction": {}, + "extern": {}, "return": {}, "type": {}, "table": {}, diff --git a/compiler/parser/func.go b/compiler/parser/func.go new file mode 100644 index 0000000..8948f31 --- /dev/null +++ b/compiler/parser/func.go @@ -0,0 +1,224 @@ +package parser + +import ( + "fmt" + "strings" + + "github.com/cell-labs/cell-script/compiler/lexer" +) + +// DefineFuncNode creates a new named function +type DefineFuncNode struct { + baseNode + + Name string + IsNamed bool + + IsMethod bool + IsExtern bool + + MethodOnType *SingleTypeNode + IsPointerReceiver bool + InstanceName string + + Arguments []*NameNode + ReturnValues []*NameNode + Body []Node +} + +func (dfn DefineFuncNode) String() string { + var body []string + + for _, b := range dfn.Body { + body = append(body, fmt.Sprintf("%+v", b)) + } + + if dfn.IsMethod { + return fmt.Sprintf("func+m (%+v) %s(%+v) %+v {\n\t%s\n}", dfn.InstanceName, dfn.Name, dfn.Arguments, dfn.ReturnValues, strings.Join(body, "\n\t")) + } else if dfn.IsNamed { + return fmt.Sprintf("func+n %s(%+v) %+v {\n\t%s\n}", dfn.Name, dfn.Arguments, dfn.ReturnValues, strings.Join(body, "\n\t")) + } else { + return fmt.Sprintf("func+v (%+v) %+v {\n\t%s\n}", dfn.Arguments, dfn.ReturnValues, strings.Join(body, "\n\t")) + } +} + +type ExternNode struct { + baseNode + FuncNodes []*DefineFuncNode +} + +func (en ExternNode) String() string { + var fns []string + + for _, f := range en.FuncNodes { + fns = append(fns, fmt.Sprintf("%+v", f.String())) + } + + return fmt.Sprintf("extern {\n\t%s\n}", strings.Join(fns, "\n\t")) +} + +func (p *parser) parseExtern() *ExternNode { + // Single extern statement + expectFuncString := p.lookAhead(0) + if expectFuncString.Type == lexer.KEYWORD && expectFuncString.Val == "func" { + p.i++ + dfn := p.parseFuncDefinition() + dfn.IsExtern = true + return &ExternNode{ + FuncNodes: []*DefineFuncNode{dfn}, + } + } + + fns := []*DefineFuncNode{} + // Multiple extern + p.expect(lexer.Item{Type: lexer.OPERATOR, Val: "("}, p.lookAhead(0)) + p.i++ + for { + checkIfEndParen := p.lookAhead(0) + if checkIfEndParen.Type == lexer.OPERATOR && checkIfEndParen.Val == ")" { + break + } + if checkIfEndParen.Type == lexer.EOL { + p.i++ + continue + } + + if checkIfEndParen.Type == lexer.KEYWORD && checkIfEndParen.Val == "func" { + p.i++ + fn := p.parseFuncDefinition() + fn.IsExtern = true + fns = append(fns, fn) + continue + } + + panic(fmt.Sprintf("Failed to parse extern: %+v", checkIfEndParen)) + } + return &ExternNode{ + FuncNodes: fns, + } +} + +// The tokens after keyword "func" +func (p *parser) parseFuncDefinition() *DefineFuncNode { + defineFunc := &DefineFuncNode{} + var argsOrMethodType []*NameNode + var canBeMethod bool + + checkIfOpeningParen := p.lookAhead(0) + if checkIfOpeningParen.Type == lexer.OPERATOR && checkIfOpeningParen.Val == "(" { + p.i++ + argsOrMethodType = p.parseFunctionArguments() + canBeMethod = true + } + + checkIfIdentifier := p.lookAhead(0) + checkIfOpeningParen = p.lookAhead(1) + + if canBeMethod && checkIfIdentifier.Type == lexer.IDENTIFIER && + checkIfOpeningParen.Type == lexer.OPERATOR && checkIfOpeningParen.Val == "(" { + + defineFunc.IsMethod = true + defineFunc.IsNamed = true + defineFunc.Name = checkIfIdentifier.Val + + if len(argsOrMethodType) != 1 { + panic("Unexpected count of types in method") + } + + defineFunc.InstanceName = argsOrMethodType[0].Name + + methodOnType := argsOrMethodType[0].Type + + if pointerSingleTypeNode, ok := methodOnType.(*PointerTypeNode); ok { + defineFunc.IsPointerReceiver = true + methodOnType = pointerSingleTypeNode.ValueType + } + + if singleTypeNode, ok := methodOnType.(*SingleTypeNode); ok { + defineFunc.MethodOnType = singleTypeNode + } else { + panic(fmt.Sprintf("could not find type in method defitition: %T", methodOnType)) + } + } + + name := p.lookAhead(0) + openParen := p.lookAhead(1) + if name.Type == lexer.IDENTIFIER && openParen.Type == lexer.OPERATOR && openParen.Val == "(" { + defineFunc.Name = name.Val + defineFunc.IsNamed = true + + p.i++ + p.i++ + + // Parse argument list + defineFunc.Arguments = p.parseFunctionArguments() + } else { + defineFunc.Arguments = argsOrMethodType + } + + // Parse return types + var retTypesNodeNames []*NameNode + + checkIfOpeningCurly := p.lookAhead(0) + if checkIfOpeningCurly.Type != lexer.OPERATOR || checkIfOpeningCurly.Val != "{" { + + // Check if next is an opening parenthesis + // Is optional if there's only one return type, is required + // if there is multiple ones + var allowMultiRetVals bool + + checkIfOpenParen := p.lookAhead(0) + if checkIfOpenParen.Type == lexer.OPERATOR && checkIfOpenParen.Val == "(" { + allowMultiRetVals = true + p.i++ + } + + for { + nameNode := &NameNode{} + + // Support both named return values and when we only get the type + retTypeOrNamed, err := p.parseOneType() + if err != nil { + panic(err) + } + p.i++ + + // Next can be type, that means that the previous was the name of the var + isType := p.lookAhead(0) + if isType.Type == lexer.IDENTIFIER { + retType, err := p.parseOneType() + if err != nil { + panic(err) + } + p.i++ + + nameNode.Name = retTypeOrNamed.Type() + nameNode.Type = retType + } else { + nameNode.Type = retTypeOrNamed + } + + retTypesNodeNames = append(retTypesNodeNames, nameNode) + + if !allowMultiRetVals { + break + } + + // Check if comma or end parenthesis + commaOrEnd := p.lookAhead(0) + if commaOrEnd.Type == lexer.OPERATOR && commaOrEnd.Val == "," { + p.i++ + continue + } + + if commaOrEnd.Type == lexer.OPERATOR && commaOrEnd.Val == ")" { + p.i++ + break + } + panic("Could not parse function return types") + } + } + + defineFunc.ReturnValues = retTypesNodeNames + return defineFunc +} diff --git a/compiler/parser/node.go b/compiler/parser/node.go index 92074b1..50f749f 100644 --- a/compiler/parser/node.go +++ b/compiler/parser/node.go @@ -180,41 +180,6 @@ func (ConditionNode) String() string { return "condition" } -// DefineFuncNode creates a new named function -type DefineFuncNode struct { - baseNode - - Name string - IsNamed bool - - IsMethod bool - IsCFunc bool - - MethodOnType *SingleTypeNode - IsPointerReceiver bool - InstanceName string - - Arguments []*NameNode - ReturnValues []*NameNode - Body []Node -} - -func (dfn DefineFuncNode) String() string { - var body []string - - for _, b := range dfn.Body { - body = append(body, fmt.Sprintf("%+v", b)) - } - - if dfn.IsMethod { - return fmt.Sprintf("func+m (%+v) %s(%+v) %+v {\n\t%s\n}", dfn.InstanceName, dfn.Name, dfn.Arguments, dfn.ReturnValues, strings.Join(body, "\n\t")) - } else if dfn.IsNamed { - return fmt.Sprintf("func+n %s(%+v) %+v {\n\t%s\n}", dfn.Name, dfn.Arguments, dfn.ReturnValues, strings.Join(body, "\n\t")) - } else { - return fmt.Sprintf("func+v (%+v) %+v {\n\t%s\n}", dfn.Arguments, dfn.ReturnValues, strings.Join(body, "\n\t")) - } -} - // NameNode retreives a named variable type NameNode struct { baseNode diff --git a/compiler/parser/parser.go b/compiler/parser/parser.go index ca0039c..3879a1c 100644 --- a/compiler/parser/parser.go +++ b/compiler/parser/parser.go @@ -324,7 +324,17 @@ func (p *parser) parseOneWithOptions(withAheadParse, withArithAhead, withIdentif return outerConditionNode } - // "cfunction" is function without function body + // "extern" is external function without function body + + // single extern: extern func foo() int32 + // multiple extern: extern ( + // func foo() int32 + // func bar() int32 + // ) + if current.Val == "extern" { + p.i++ + return p.parseExtern() + } // "func" gets converted into a DefineFuncNode // the keyword "func" is followed by // - a IDENTIFIER (function name) @@ -340,136 +350,9 @@ func (p *parser) parseOneWithOptions(withAheadParse, withArithAhead, withIdentif // method: func (a abc) abc() { // value func: func (a abc) { - if current.Val == "func" || current.Val == "cfunction" { - defineFunc := &DefineFuncNode{} - if current.Val == "cfunction" { - defineFunc.IsCFunc = true - } + if current.Val == "func" { p.i++ - - var argsOrMethodType []*NameNode - var canBeMethod bool - - checkIfOpeningParen := p.lookAhead(0) - if checkIfOpeningParen.Type == lexer.OPERATOR && checkIfOpeningParen.Val == "(" { - p.i++ - argsOrMethodType = p.parseFunctionArguments() - canBeMethod = true - } - - checkIfIdentifier := p.lookAhead(0) - checkIfOpeningParen = p.lookAhead(1) - - if canBeMethod && checkIfIdentifier.Type == lexer.IDENTIFIER && - checkIfOpeningParen.Type == lexer.OPERATOR && checkIfOpeningParen.Val == "(" { - - defineFunc.IsMethod = true - defineFunc.IsNamed = true - defineFunc.Name = checkIfIdentifier.Val - - if len(argsOrMethodType) != 1 { - panic("Unexpected count of types in method") - } - - defineFunc.InstanceName = argsOrMethodType[0].Name - - methodOnType := argsOrMethodType[0].Type - - if pointerSingleTypeNode, ok := methodOnType.(*PointerTypeNode); ok { - defineFunc.IsPointerReceiver = true - methodOnType = pointerSingleTypeNode.ValueType - } - - if singleTypeNode, ok := methodOnType.(*SingleTypeNode); ok { - defineFunc.MethodOnType = singleTypeNode - } else { - panic(fmt.Sprintf("could not find type in method defitition: %T", methodOnType)) - } - } - - name := p.lookAhead(0) - openParen := p.lookAhead(1) - if name.Type == lexer.IDENTIFIER && openParen.Type == lexer.OPERATOR && openParen.Val == "(" { - defineFunc.Name = name.Val - defineFunc.IsNamed = true - - p.i++ - p.i++ - - // Parse argument list - defineFunc.Arguments = p.parseFunctionArguments() - } else { - defineFunc.Arguments = argsOrMethodType - } - - // Parse return types - var retTypesNodeNames []*NameNode - - checkIfOpeningCurly := p.lookAhead(0) - if checkIfOpeningCurly.Type != lexer.OPERATOR || checkIfOpeningCurly.Val != "{" { - - // Check if next is an opening parenthesis - // Is optional if there's only one return type, is required - // if there is multiple ones - var allowMultiRetVals bool - - checkIfOpenParen := p.lookAhead(0) - if checkIfOpenParen.Type == lexer.OPERATOR && checkIfOpenParen.Val == "(" { - allowMultiRetVals = true - p.i++ - } - - for { - nameNode := &NameNode{} - - // Support both named return values and when we only get the type - retTypeOrNamed, err := p.parseOneType() - if err != nil { - panic(err) - } - p.i++ - - // Next can be type, that means that the previous was the name of the var - isType := p.lookAhead(0) - if isType.Type == lexer.IDENTIFIER { - retType, err := p.parseOneType() - if err != nil { - panic(err) - } - p.i++ - - nameNode.Name = retTypeOrNamed.Type() - nameNode.Type = retType - } else { - nameNode.Type = retTypeOrNamed - } - - retTypesNodeNames = append(retTypesNodeNames, nameNode) - - if !allowMultiRetVals { - break - } - - // Check if comma or end parenthesis - commaOrEnd := p.lookAhead(0) - if commaOrEnd.Type == lexer.OPERATOR && commaOrEnd.Val == "," { - p.i++ - continue - } - - if commaOrEnd.Type == lexer.OPERATOR && commaOrEnd.Val == ")" { - p.i++ - break - } - panic("Could not parse function return types") - } - } - - defineFunc.ReturnValues = retTypesNodeNames - - if current.Val == "cfunction" { - return p.aheadParse(defineFunc) - } + defineFunc := p.parseFuncDefinition() openBracket := p.lookAhead(0) if openBracket.Type != lexer.OPERATOR || openBracket.Val != "{" { diff --git a/compiler/parser/parser_test.go b/compiler/parser/parser_test.go index d6900b3..470aad5 100644 --- a/compiler/parser/parser_test.go +++ b/compiler/parser/parser_test.go @@ -290,40 +290,51 @@ func TestPramga(t *testing.T) { func TestFunction(t *testing.T) { input := []lexer.Item{ - {Type: lexer.KEYWORD, Val: "cfunction", Line: 1}, + {Type: lexer.KEYWORD, Val: "extern", Line: 1}, + {Type: lexer.OPERATOR, Val: "(", Line: 1}, + {Type: lexer.KEYWORD, Val: "func", Line: 1}, {Type: lexer.IDENTIFIER, Val: "foo", Line: 1}, {Type: lexer.OPERATOR, Val: "(", Line: 1}, {Type: lexer.OPERATOR, Val: ")", Line: 1}, {Type: lexer.IDENTIFIER, Val: "int32", Line: 1}, + {Type: lexer.OPERATOR, Val: ")", Line: 1}, {Type: lexer.EOL}, {Type: lexer.EOF}, } /* - pramga cellscript 0.0.1 + extern ( + func foo() int32 + func bar() int32 + ) + extern func foo() int32 */ expected := &FileNode{ Instructions: []Node{ - &DefineFuncNode{ - Name: "foo", - IsNamed: true, - IsMethod: false, - IsCFunc: true, - - MethodOnType: nil, - IsPointerReceiver: false, - InstanceName: "", - - Arguments: nil, - ReturnValues: []*NameNode{ - &NameNode{ - Type: &SingleTypeNode{ - TypeName: "int32", + &ExternNode{ + FuncNodes: []*DefineFuncNode{ + &DefineFuncNode{ + Name: "foo", + IsNamed: true, + IsMethod: false, + IsExtern: true, + + MethodOnType: nil, + IsPointerReceiver: false, + InstanceName: "", + + Arguments: nil, + ReturnValues: []*NameNode{ + &NameNode{ + Type: &SingleTypeNode{ + TypeName: "int32", + }, + }, }, + Body: nil, }, }, - Body: nil, }, }, } diff --git a/compiler/parser/walk.go b/compiler/parser/walk.go index 99537e8..6e8c76b 100644 --- a/compiler/parser/walk.go +++ b/compiler/parser/walk.go @@ -136,6 +136,10 @@ func Walk(v Visitor, node Node) (r Node) { for i, a := range n.Items { n.Items[i] = Walk(v, a) } + case *ExternNode: + for _, fn := range n.FuncNodes { + Walk(v, fn) + } default: panic(fmt.Sprintf("unexpected type in Walk(): %T", node)) } diff --git a/pkg/tx/tx.cell b/pkg/tx/tx.cell index 8bfe0a2..64caf0c 100644 --- a/pkg/tx/tx.cell +++ b/pkg/tx/tx.cell @@ -12,10 +12,12 @@ const ( SUCCESS = iota ) -cfunction script_verify() bool -cfunction is_owner_mode() bool -cfunction get_utxo_inputs() []cell.Cell -cfunction get_utxo_outputs() []cell.Cell +extern ( + func script_verify() bool + func is_owner_mode() bool + func get_utxo_inputs() []cell.Cell + func get_utxo_outputs() []cell.Cell +) func scriptVerify() bool { return script_verify() @@ -31,10 +33,12 @@ func outputs() []cell.Cell { } // xudt related API and ffi functions -cfunction parse_args() *cell.XudtArgs -cfunction check_enhanced_owner_mode(args *cell.XudtArgs) bool -cfunction simple_udt(owner_mode bool) int64 -cfunction execute_scripts(args *cell.XudtArgs) bool +extern ( + func parse_args() *cell.XudtArgs + func check_enhanced_owner_mode(args *cell.XudtArgs) bool + func simple_udt(owner_mode bool) int64 + func execute_scripts(args *cell.XudtArgs) bool +) func xudtArgs() *cell.XudtArgs { return parse_args()