Skip to main content

Needle Specification

The Needle Specification is a formal description of the Needle language. It is intended to be a reference for developers who want to understand the language in detail.

It is a statically-typed language with a syntax that is familiar to developers who have experience with golang.

File Structure

In Needle language, the main code block structure includes Smart Contract, Data, Function, Settings.

Smart Contract

Use the contract keyword to declare a smart contract, followed by the name of the smart contract, and its content must be enclosed in curly braces.

ContractStmt = "contract" Identifier CodeBlockStmt.

Smart contract structure has three main parts: Data, Settings, Function.

contract Name {
data{}
settings{}
func name(){}
}

Data

Use data keyword partially describes the smart contract data input as well as the received form parameters. The optional indicates that the parameter is optional and not required.

DataStmt = "data" "{" ParamSign "}" .

ParamSign = Identifier Typename [ Tag ] .

Tag = "optional" .

use the symbol $ to get the corresponding variable value, it must be used in the Function within the contract, it is equivalent to the global variable of the contract. You can use it directly or reassign it.

contract Name {
data {
Param1 string "optional"
}
func name(){
$Param1 = 4
Println($Param1)
}
}

Settings

Use the settings keyword to declare constants, the type of constant value can be int, float, string, bool, it must be within the contract.

constants value only can be assigned once, and cannot be changed during the execution of the contract.

SettingsStmt = "settings" SettingsScope .

SettingsScope = "{" Identifier "=" ( Number Literal | String Literal | Boolean Literal ) "}" .

contract Name {
settings {
i = 1
f = 1.2
b = true
s = "this is a string"
}
}

Function

This function processes the Data and Settings in the smart contract. It performs operations such as arithmetic, type conversion, and establishing interactions between contracts.

Function Declaration

Functions are declared with the func keyword followed by the function name, parameters, type parameters, function tail, a return type and finally the function body.

FuncDecl = "func" FuncName FuncSign FuncBody .

FuncName = Identifier .

FuncBody = CodeBlockStmt .

FuncSign = [ FuncParams ] [ FuncTail ] [ FuncResult ] .

FuncParams = "(" [ FuncParamList ] ")" .

FuncParamList = FuncParam { FuncParamSeq } [ ( "," | " " ) IdentifierList "..." ] .

FuncParam = IdentifierList Typename .

FuncParamSeq = ( "," | " " ) FuncParam .

FuncResult = TypeList .

FuncTail = "." Identifier [ FuncParams ] .

The function can have multiple parameters, each parameter followed by a parameter name and type, separated by a space or comma. The return value cannot be enclosed in parentheses (), and the return type cannot declare its variable name. Use the keyword return to return one or more values.

func Add(a b, c int, s string) int string{
if a {
return a + b + c, s
}
// invalid: missing return statement.
}

If the function does not declare a parameter list, the parentheses () in the function signature can be omitted, and in this case, the type declaration after the function name is called the result parameter.

func Get string{
return "string"
}

The function signature can use ... to represent the type of variadic parameters, which must be the last parameter, and its data type is array. The variadic parameter contains all the variables starting from the call to pass the parameter. Any type of variable can be passed, but conflicts with data types need to be handled.

func sum(out string, values ...) {
//...
}

func Name() {
sum("Sum:", 10, "20", 30.3)
}

THe function thought the return statement returns a value, it will not be passed to other contracts. If you want to pass the return value of the contract to another contract, you need to assign the return value to the $result variable.

contract NameB {
action {
$result = 11
}
}
contract NameA {
action {
var a int
a = NameB()
}
}

If the function name is action or conditions, the func can be omitted.

contract Name {
action {}
conditions {}
}

Tail function

The function may have many parameters, but when calling them, you only want to pass some of them. In this case, you can declare multiple functions with a dot, such functions are called tail functions, and then you can call the specified parameters in any order, without having to call them in the order declared. In such a function body, you can use these parameters normally. If no parameters are passed, they will be assigned default values. Tail functions do not have return values, and the return values are part of the main function.

func myfunc(name string).Param1(p1 int).Param2(p2 string) int {
//...
}
func Name{
myfunc("name").Param2("p2")
}

Different functions can be called using a dot. When calling a function, the return value of this function can be used as the input of the next function, and the return value is obtained in the order of definition. Multiple tail functions are only visible to the main function, not to other functions. Tail functions cannot be called separately, they must be connected to the main function or other tail functions under the main function.

func A(int).tailA() int, string
func B(string,bool) string

func Name(){
B("B",true).A(2)
A(2).B(true).tailA()//invalid
tailA() //invalid
}

Syntax base

The source code must be encoded using UTF-8.

Code block

The curly braces {} specify a code block that can contain local variables. Variables in the code block can only be used in the code block and its sub-code block. The function body is also a code block.

CodeBlockStmt = "{" ... "}" .

By default, variables in a code block are not visible, and the scope of a variable can be extended to its sub-code block. In a code block, you can use the name of an existing variable to define a new variable. Therefore, it is not visible outside its scope. When the scope ends, the variable will be destroyed.

contract Name {
func block {
var a int
a = 3
if ture {
var a int
a = 4
}
}
}

Comment

Comments can be used as documentation, and the content of the comments will be ignored by the compiler. There are two types of comments, one is single-line comments, and the other is multi-line comments.

  1. Single line comments start with // and end at the end of the line.
func add(a int, b int) int {
// This is a comment
return a + b // This is also a comment
}
  1. Multi-line comments start with /* and end with */. Multi-line comments are not affected by newline characters, can span multiple lines, and can be commented out anywhere.
func /*here*/a() {
var b /*there*/ int
/*
here
*/
b = /*there*/ 2
}
/*everywhere*/

Newline

The newline character is a delimiter between expressions and statements, and the newline character is replaced by a semicolon ;, which can be used to separate multiple expressions or statements.

var a int
a = 1

//can be written as
var a int; as = 1

Delimiter

Delimiter are used to separate identifiers, such as variable names, function names, type names, etc.

Delimiter = "(" | ")" | "{" | "}" | "[" | "]" | "." | "," | "=" | ":" .

Expression

Expression refers to a statement that calculates a value. An expression consists of constants, variables, operators, and functions. A definite value can be obtained after calculation. The expression does not change the value, it just calculates a value.

Some examples of expressions, not limited to:

  • Literals, including string literals, numeric literals, such as: 100, 3.14, "hello".
  • Variable names, such as: x, sum.
  • Arithmetic expressions, such as: 1 + 2, a * b.
  • Function call expressions, such as: fnName().
  • Comparison expressions, such as: a == b, score > 90.
  • Logical expressions, such as: a && b, !done.
  • Array, slice, map index expression, such as: array[2], map["key"], slice[1:3].
  • Type conversion expression, such as: Int(a).

The value obtained by calculating the expression can be assigned to a variable, used as a parameter to a function, combined with other expressions to form more complex expressions, and used in if condition statements to control the program flow.

Identifier

Identifiers are used to identify variables, functions, constants, and other program names. Identifiers are composed of one or more letters, numbers, and underscores, and must begin with a letter. Identifiers cannot contain spaces and special characters. Identifiers are case-sensitive and cannot use keywords as identifiers.

Identifier = unicode_letter { letter | unicode_digit }

letter = unicode_letter | "_" .

unicode_letter = // a Unicode code point classified as "Letter".

unicode_digit = // a Unicode code point categorized as "Number, decimal digit".

IdentifierList = Identifier { IdentifierSeq } .

IdentifierSeq = ( "," | " " ) Identifier

a
x_123
αβ

Multiple identifiers can be combined into an identifier list, separated by commas or spaces.

Keyword

The following keywords are reserved and cannot be used as identifiers.

contractfuncdataactionconditions
returnifelifelsewhile
varnilbreakcontinuesettings
truefalseinfowarningerror
...

Number

Number literal values include: decimal integer, binary integer, octal integer, hexadecimal integer, and floating-point number and scientific notation.

There are two basic types: int and float. If the number contains a decimal point or eE, it is a float type, which conforms to the standard IEEE-754 64-bit floating-point number, otherwise it is an int type. int is equivalent to int64 in the Golang language, and float is equivalent to float64 in the Golang language.

int = DecimalLit | BinaryLit | OctalLit | HexLit .

float = FloatLit .

decimal_digit = "0"..."9" .

binary_digit = "01" .

octal_digit = "0"..."7" .

hex_digit = "0"..."9" | "A"..."F" | "a"..."f" .

binary_digits = binary_digit { [ "_" ] binary_digit.} .

decimal_digits= decimal_digit { [ "_" ] decimal_digit. } .

octal_digits = octal_digit { [ "_" ] octal_digit } .

hex_digits = hex_digit { [ "_" ] hex_digit } .

DecimalLit = decimal_digit [ "_" ] decimal_digits .

BinaryLit = "0" ( "b" | "B" ) [ "_" ] binary_digits .

OctalLit = "0" ( "o" | "O" ) [ "_" ] octal_digits .

HexLit = "0" ( "x" | "X" ) [ "_" ] hex_digits .

ExponentPart = ( "e" | "E" ) [ "+" | "-" ] decimal_digits .

FloatLit = decimal_digits "." [ decimal_digits ] [ ExponentPart ] | decimal_digits ExponentPart | "." decimal_digits [ ExponentPart ] .

0
123
0b101
0o123
0xaf
0.123
1.23e+2
.3e+2

String

String literals can be enclosed in double quotes " or backticks `, and string literals enclosed in backticks can span multiple lines. The string in double quotes can contain escape sequences for double quotes, newline, and carriage return. The string in backticks is not escaped.

StringLiteral = RawStringLiteral | InterpretedStringLiteral .

RawStringLiteral = "`" unicode_char "`" .

InterpretedStringLiteral = " unicode_value " .

unicode_char = // an arbitrary Unicode code point except newline.

unicode_value = // an arbitrary Unicode code point.

var str string
str = "This is \n a string"
str = `This is \n \t \r a other string`

Boolean

A boolean type has two values: true and false. It is used to represent the truth value of an expression.

Boolean = "true" | "false" .

Variable

Variables are used to store values, and the values allowed by variables are determined by their types. The type is immutable, but the value can be changed during program execution.

Local Variable

The keyword var is used to declare local variables, and the variable must be followed by a variable name and type.

LocalVarDecl = "var" IdentifierList Typename .

When declaring a variable, its value is the default value. To declare one or more variables, you can use a comma or space to separate multiple variable names and types. When the types of two or more consecutive named formal parameters of a function are the same, all types except the last one can be omitted.

var a int
var b b1, b2 string
var c bool, c1 float

b = "string"
b1, b2 = "string1", "string2"
c c1 = true 1.2

Variables cannot be initialized when declared, and must be assigned after declaration.

// invalid
var a int = 1

// good
var a int
a = 1

The types map and array do not support multiple assignments on the same line using {} and [], but multiple assignments on the same line can be done using variable names.

var a b int c c1 map d d1 array
a, b = 1, 2
c,d = {"a":a, "b":b}, [1, 2, 3] //invalid
c = {"a":a, "b":b}
d = [1, 2, 3]
c1, d1 = c, d
d[0], d[1] = c, d //invalid
d[0], d[1] = d[1], d[0] //invalid

Global Variable

The keyword symbol $ and Identifier is used to declare and use global variables. The syntax is as follows:

GlobalVarDecl = "$" Identifier .

Global variables can be declared in any function within a single contract scope, but must be declared before use. The parameters defined in the data section are also global variables, but can only be used within the current contract scope.

contract Name {
data {
param int
}
func set() {
$abc = 1
}
func get() int {
$abc = $abc + $param
return $abc
}
}

Predeclared global variables

Predeclared global variables can be used in any contract scope and these global variables can be specified as immutable during compilation, which is mutable by default.

Predeclared global variables include:

  • $original_contract - name of the contract that initially processed the transaction. It means the contract is called during transaction validation if the variable is an empty string. To check whether the contract is called by another contract or directly by the transaction, you need to compare the values of originalcontractandoriginal_contract and this_contract. It means that the contract is called by the transaction if they are equal.
  • $this_contract - name of the contract currently being executed.
  • $stack - contract array stack with a data type of array, containing all contracts executed. The first element of the array represents the name of the contract currently being executed, while the last element represents the name of the contract that initially processed the transaction.
  • $result - assigned with the return result of the contract.

Typename

All variables have types, and type names are used to represent the data types of variables.

Type = Typename | TypeList .

Typename = "int" | "string" | "float" | "bool" | "bytes" | "address" | "money" | "array" | "map" | "file" .

TypeList = Typename { ( "," | " " ) Typename } .

The following type names are reserved and cannot be used as identifiers, equivalent to the corresponding types in the Golang language.

  • int - int64, zero value is 0.
  • string - string, zero value is "".
  • float - float64, zero value is 0.0.
  • bool - bool, zero value is false.
  • bytes - []byte, zero value is []byte.
  • array - []interface, zero value is [].
  • map - map[string]interface, zero value is map[].
  • address - int64, zero value is 0.
  • money - decimal.Decimal, zero value is 0.
  • file - map[string]interface, zero value is map[].

Object and array literals

array and map types can be created using [] and {} operators or specified elements.

array type index must be int. map type index must be string. If a value is assigned to an index greater than the current maximum index of the array element, an empty element will be added to the array. The initialization value of these elements is nil.

var arr array m map
arr = [1,2,3]
arr[0] = 4
m = {"key": "value"}
m = {"key": myfunc()} // invalid
m = {"key": arr[0]} // invalid
m["key1"] = arr[5] // m["key1"] = nil

Operator

An operation expression consists of an operator and an operand. Needle supports the following operation operators: arithmetic operators, comparison operators, logical operators, bitwise operators, and assignment operators.

Follow are the currently supported operators:

  • arithmetic operators: +, -, *, /, %, ++, --.
  • comparison operators: ==, !=, >, >=, <, <=.
  • logical operators: &&, ||, !.
  • bitwise operators: &, |, ^, <<, >>.
  • assignment operators: =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=.

The priority of the operators is from high to low:

  • ++, --, !.
  • *, /, %.
  • +, -.
  • <<, >>.
  • <, <=, >, >=.
  • ==, !=.
  • &.
  • ^.
  • |.
  • &&.
  • ||.
  • =, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=.

The result type of the operation is the same as the type of the operand. Except for comparison operators and logical operators, their result type is bool. In logical expressions, the result type will be automatically converted to a logical value, if the operand type is not the default value, and the result is true.

a += b is equivalent to a = a + b, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= are also defined in this way. a++ is equivalent to a += 1.

Even if the types of the two operands are different, Needle allows the use of operators in expressions. In this case, the operands will be converted to the same type and then the operation will be performed. For example, to calculate z = x + y, where x is of type int and y is of type float, x and y will both be converted to type decimal. Then the addition operation is performed, and the result is of type decimal, which is then converted to type float and assigned to z.

It should be noted that when performing floating-point operations, the issue of precision loss should be considered to avoid incorrect results.

The following lists the operators and result types between operands of different types:

operandxyz
not(!)-booly to bool
unary(+,-)-intint
-floatfloat
<< , >>intintint
&,^,intintint
++ , --intintint
+,-,*,/,%stringstringstring(only +)
stringintintx to int
stringfloatfloatx to decimal, y to decimal
floatstringfloatx to decimal, y to decimal
floatintfloatx to decimal, y to decimal
floatfloatfloatx to decimal, y to decimal
intstringinty to int
intintint
intfloatfloatx to decimal, y to decimal
decimalstringdecimaly to decimal
decimalintdecimaly to decimal
decimalfloatdecimaly to decimal
decimaldecimaldecimal
&&,||boolx to bool, y to bool
== ,!= ,<,<=,>,>=nilnilboolonly(== ,!=)
boolboolboolonly(== ,!=)
stringstringbool
stringintbooly to string
stringfloatbooly to string
stringdecimalbooly to string
floatstringboolx to decimal, y to decimal
floatintboolx to decimal, y to decimal
floatfloatboolx to decimal, y to decimal
floatdecimalboolx to decimal
intstringbooly to int
intintbool
intfloatboolx to decimal, y to decimal
intdecimalbooly to int
decimalstringbooly to decimal
decimalintbooly to decimal
decimalfloatbooly to decimal
decimaldecimalbool

Slice

The slice operation only applies to the types array, string, and bytes. The slice operator [low:high] is used to get a part of the array.

arr[low:high]

The range of the index must be positive. If 0<=low<=high<=len(arr), the index range is valid, otherwise the index range is invalid. For convenience, any index can be omitted. The omitted index will be replaced by the first index or the last index of the array.

var a b c d e array str strA string
a = [1,2,3,4,5]
b = a[1:3] // b = [2,3]
c = a[1:] // c = [2,3,4,5]
d = a[:3] // d = [1,2,3]
e = a[:] // e = [1,2,3,4,5]

str = "abcd"
strA = str[1:3] // strA = "bc"

Increment and Decrement

++ and -- increment and decrement the variables of type int, float, and money, which can increase or decrease the variable value by 1.

var i int f float m money
i++
f--
m++

Control Statement

Control statements are used to control the execution flow of the program, including return statements, if statements, while statements, break statements, and continue statements.

ControlStmt = ReturnStmt | IfStmt | WhileStmt | BreakStmt | ContinueStmt .

In if statements, the conversion from non-boolean types to boolean types is supported. The following rules convert boolean types to false, otherwise true. So, code like if 1 {} is valid.

  • int and float, money, string, address type values are equal to the zero value.
  • array and map, bytes, file type values are equal to nil or their length is zero.

Return statement

The return statement is used in the function body to terminate the execution of the function prematurely. If the function declares result parameters, the return statement must return the same type and number of values.

ReturnStmt = "return" ExpressionList .

func add(a , b int) int {
return a + b
}

If statement

if statement executes the code block based on the value of the boolean expression. If the expression evaluates to true, the if code block is executed, otherwise the else code block is executed.

elif is actually equivalent to else if, it must be defined before the else statement.

IfStmt = "if" Expression CodeBlockStmt ElIfStmtList [ElseStmt] .

ElIfStmtList = "elif" Expression CodeBlockStmt .

ElseStmt = "else" CodeBlockStmt .

if a > b {
Println("a is greater than b")
} elif a == b {
Println("a and b are equal")
} else {
Println("b is greater than a")
}
tip

You should note that the boolean expression in the if statement does not require parentheses ().

while statement

The while statement provides the ability to repeatedly execute a code block as long as the expression evaluates to true. The condition is evaluated before each iteration.

WhileStmt = "while" Expression CodeBlockStmt .

var a int
while a < 10 {
a++
}
tip

If the condition is always true, the while statement will be executed repeatedly. Therefore, it should include a condition that is false at some point.

Break statement

The break statement terminates the innermost while statement.

BreakStmt = "break" .

var a int
a = 1
while a < 10 {
if a == 5 {
break
}
a++
}

Continue statement

continue statement skips the remaining code of the innermost while statement and continues with the next iteration of the loop.

ContinueStmt = "continue" .

var a int
a = 1
while a < 10 {
if a == 5 {
continue
}
a++
}