Skip to content

Python-like Script Language Grammar Specification

This document describes the actual syntax rules implemented in the current project, based on the actual code of the project's lexical analyzer, syntax analyzer, and AST structure.

Complete Grammar Specification (BNF Notation)

bnf
// Program structure
program ::= contract

// Contract definition
contract ::= "Contract" IDENTIFIER ":" NEWLINE INDENT [member]* DEDENT

member ::= function | struct | constructor

// Function definition
function ::= "def" IDENTIFIER "(" [parameter_list] ")" ["->" return_type] ":" block

constructor ::= "def" "__init__" "(" [parameter_list] ")" ":" block

parameter_list ::= parameter ["," parameter]*
parameter ::= IDENTIFIER ":" TYPE

// Return type definition
return_type ::= TYPE | brace_type_list

brace_type_list ::= "{" type_list "}"

type_list ::= TYPE ["," TYPE]*

// Struct definition
struct ::= "Struct" IDENTIFIER ":" NEWLINE INDENT [field]+ DEDENT
field ::= IDENTIFIER ":" TYPE NEWLINE

// Statements
statement ::= if_statement
           | return_statement
           | assignment_statement
           | destructure_assignment
           | variable_declaration
           | expression_statement

block ::= NEWLINE INDENT [statement]* DEDENT

if_statement ::= "if" expression ":" block ["else" ":" block]

return_statement ::= "return" expression NEWLINE

assignment_statement ::= (IDENTIFIER | field_access) "=" expression NEWLINE

destructure_assignment ::= "{" identifier_list "}" "=" expression NEWLINE

identifier_list ::= IDENTIFIER ["," IDENTIFIER]*

variable_declaration ::= IDENTIFIER ":" TYPE ["=" expression] NEWLINE

expression_statement ::= expression NEWLINE

// Expressions
expression ::= binary_expression
            | unary_expression
            | primary_expression

binary_expression ::= expression binary_operator expression

unary_expression ::= unary_operator expression

primary_expression ::= IDENTIFIER
                    | literal
                    | function_call
                    | method_call
                    | field_access
                    | Clone_expression
                    | brace_expression
                    | "(" expression ")"

brace_expression ::= "{" [expression_list] "}"

expression_list ::= expression ["," expression]*

function_call ::= IDENTIFIER "(" [argument_list] ")"

method_call ::= expression "." IDENTIFIER "(" [argument_list] ")"

field_access ::= expression "." IDENTIFIER

clone_expression ::= IDENTIFIER "." "Clone" "(" ")"

argument_list ::= expression ["," expression]*

// Literals
literal ::= NUMBER | HEX | STRING

// Operators
binary_operator ::= "+" | "-" | "*" | "/"
                 | "==" | "!=" | "<" | ">" | "<=" | ">="

unary_operator ::= "-" | "+"

// Lexical elements
IDENTIFIER ::= [a-zA-Z_][a-zA-Z0-9_]*
NUMBER ::= [0-9]+
HEX ::= "0x"[0-9a-fA-F]+
STRING ::= '"' [^"]* '"'
TYPE ::= IDENTIFIER
NEWLINE ::= '\n'
INDENT ::= increase indent level
DEDENT ::= decrease indent level

1. Program Structure

1.1 Program Definition

program ::= contract

A program consists of a single contract (single contract system).

1.2 Contract Definition

contract ::= "Contract" IDENTIFIER ":" NEWLINE INDENT [member]* DEDENT

member ::= function | struct | constructor

Contract starts with keyword Contract, followed by contract name, using Python-style indentation structure.

2. Data Structures

2.1 Struct Definition

struct ::= "Struct" IDENTIFIER ":" NEWLINE INDENT [field]+ DEDENT

field ::= IDENTIFIER ":" TYPE NEWLINE

Struct definition must contain at least one field, each field must have a type declaration.

Example:

python
Struct Person:
    name: string
    age: int
    publicKey: hex

3. Function Definition

3.1 Ordinary Function

function ::= "def" IDENTIFIER "(" [parameter_list] ")" ["->" TYPE] ":" block

parameter_list ::= parameter ["," parameter]*
parameter ::= IDENTIFIER ":" TYPE

Function parameters must include type declarations, return type is optional.

Example:

python
def main(sig: string, pubkey: hex) -> bool:
    result = CheckSig(pubkey, sig)
    return result

3.2 Constructor

constructor ::= "def" "__init__" "(" [parameter_list] ")" ":" block

Constructor uses special name __init__.

Example:

python
def __init__(pubKeyH: hex):
    self.pubKeyHash = pubKeyH

4. Statements

4.1 Statement Types

statement ::= if_statement
           | return_statement
           | assignment_statement
           | variable_declaration
           | expression_statement

block ::= NEWLINE INDENT [statement]* DEDENT

4.2 Conditional Statement

if_statement ::= "if" expression ":" block ["else" ":" block]

Example:

python
if check_lock_time(current_time, timeout):
    verify checkSig(sender, senderSig)
else:
    verify checkSig(recipient, recipientSig)

4.3 Return Statement

return_statement ::= "return" expression NEWLINE

4.4 Assignment Statement

assignment_statement ::= (IDENTIFIER | field_access) "=" expression NEWLINE

field_access ::= expression "." IDENTIFIER

Example:

python
x = 10
self.value = result
Sub.x = 0

4.5 Variable Declaration

variable_declaration ::= IDENTIFIER ":" TYPE ["=" expression] NEWLINE

Example:

python
count: int
name: string = "default"
result: bool = CheckSig(pubkey, sig)

5. Expressions

5.1 Expression Types

expression ::= binary_expression
            | unary_expression
            | primary_expression

primary_expression ::= IDENTIFIER
                    | literal
                    | function_call
                    | method_call
                    | field_access
                    | "(" expression ")"

literal ::= NUMBER | HEX | STRING

5.2 Function Call

function_call ::= IDENTIFIER "(" [argument_list] ")"

argument_list ::= expression ["," expression]*

Example:

python
CheckSig(pubkey, sig)
Sha256(data)
EqualVerify(hash1, hash2)

5.3 Method Call and Field Access

method_call ::= expression "." IDENTIFIER "(" [argument_list] ")"

field_access ::= expression "." IDENTIFIER

Supports chained calls:

python
obj.field1.field2
obj.method().field
self.pubKeyHash.a

5.4 Binary Operations

binary_expression ::= expression binary_operator expression

binary_operator ::= "+" | "-" | "*" | "/"
                 | "==" | "!=" | "<" | ">" | "<=" | ">="

5.5 Unary Operations

unary_expression ::= unary_operator expression

unary_operator ::= "-" | "+"

6. Lexical Elements

6.1 Keywords

keywords ::= "Contract" | "def" | "Struct" | "if" | "else" | "return"

6.2 Identifiers and Literals

IDENTIFIER ::= [a-zA-Z_][a-zA-Z0-9_]*
NUMBER ::= [0-9]+
HEX ::= "0x"[0-9a-fA-F]+
STRING ::= '"' [^"]* '"'
TYPE ::= IDENTIFIER  // Type identifiers, such as int, string, hex, bool

6.3 Operators and Delimiters

operators ::= "=" | "==" | "!=" | "<" | ">" | "<=" | ">="
           | "+" | "-" | "*" | "/" | "->"

delimiters ::= "(" | ")" | "[" | "]" | ":" | "," | "."

6.4 Indentation and Newlines

The language uses Python-style indentation to represent code block structure:

  • NEWLINE: newline character
  • INDENT: increase indent level
  • DEDENT: decrease indent level

7. Ownership System

The language implements ownership concepts similar to Rust, but only constrains data variables:

7.1 Ownership Rules

  • Variable Consumption: When a local variable is used (as function parameter, assigned to other variables, etc.), the ownership of the original variable is transferred
  • Contract Member Variables Exception: Contract member variables (like self.field) are directly replaced in the code during compilation, so they are not subject to ownership constraints and can be used multiple times without Clone
  • Clone Operation: For local variables, using the Clone() function can create a copy of the variable, avoiding ownership transfer
  • Field Access: Accessing fields of a struct instance consumes the ownership of that field, need to clone first if multiple access is needed; but contract member variables are not subject to this restriction
  • Special Built-in Functions: SetAlt() and SetMain() functions do not consume variable ownership

7.2 Clone Syntax

clone_expression ::= IDENTIFIER "." "Clone" "(" ")"

Example:

python
original_value: int = 42
cloned_value: int = original_value.Clone()
# original_value is still available

7.3 Built-in Functions

The language provides the following built-in functions:

  • Clone(): Create a copy of a variable, syntax is variable.Clone()
  • SetAlt(variable): Move variable to auxiliary stack, does not consume variable ownership
  • SetMain(variable): Move variable from auxiliary stack to main stack, does not consume variable ownership
  • Other built-in functions: Such as CheckSig(), Sha256(), etc., follow ordinary function call syntax and consume parameter ownership

7.4 Ownership System Examples

Ownership transfer of local variables:

python
data: hex = "0x1234"
result1: bool = CheckSig(data, signature)  # data is consumed, cannot be used again
# result2: bool = CheckSig(data, signature2)  # Error: data has been consumed

# Need to clone for multiple uses
data2: hex = "0x5678"
cloned_data: hex = data2.Clone()
result1: bool = CheckSig(data2, signature1)
result2: bool = CheckSig(cloned_data, signature2)  # Correct: use cloned copy

Special behavior of contract member variables:

python
def main(sig1: string, sig2: string):
    # Contract member variables can be used multiple times, no need for Clone
    result1: bool = CheckSig(self.pubKey, sig1)
    result2: bool = CheckSig(self.pubKey, sig2)  # Correct: self.pubKey can be reused
    return result1 and result2

Special built-in functions:

python
data: hex = "0x1234"
SetAlt(data)    # data is still available, ownership not consumed
SetMain(data)   # data is still available, ownership not consumed

8. Brace Syntax Sugar ({} Syntax)

8.1 Syntax Definition

brace_expression ::= "{" [expression_list] "}"
destructure_assignment ::= "{" identifier_list "}" "=" expression NEWLINE

8.2 Purpose and Semantics

Brace syntax sugar {} is a multi-functional syntax feature with different semantics based on context:

8.2.1 Multi-return Value Destructuring

Used to receive multiple return values from functions:

python
{a, b} = Split(data, 10)  # Split returns two values, assigned to a and b respectively
{x, y, z} = GetCoordinates()  # Receive three return values

8.2.2 Struct Literals

Used to create struct instances:

python
Struct Point:
    x: int
    y: int

# Use brace syntax sugar to create struct instance
p: Point = {10, 20}  # Equivalent to Point(10, 20)

8.2.3 Anonymous Structures

When there is no explicit type to receive, {} creates anonymous structures:

python
# In this case, {a, b} is just syntax sugar, actually a, b are independent variables
{a, b} = Split(data, 10)
# Can directly use a and b, no need to access through struct
result = a + b

8.3 Type Inference Rules

The compiler decides the semantics of {} based on the type declaration on the left side:

  1. With explicit struct type: Create instance of that type

    python
    point: Point = {x, y}  # Create Point instance
  2. Without type declaration: Destructure into independent variables

    python
    {a, b} = Split(data, 10)  # a, b are independent variables
  3. Mixed usage: Both destructure and create struct

    python
    {x, y}: Point = GetPoint()  # Destructure and create Point instance

8.4 Usage Examples

8.4.1 Multi-return Value Functions

python
def split_data(data: hex, pos: int) -> {hex, hex}:
    # Function implementation returns two values
    return {left_part, right_part}

# Use destructuring assignment
{left, right} = split_data("0x123456", 3)

8.4.2 Struct Construction

python
Struct Person:
    name: string
    age: int

# Use brace syntax sugar
person2: Person = {"Bob", 30}

8.4.3 Complex Scenarios

python
Struct Result:
    success: bool
    data: hex

def process_and_split(input: hex) -> (bool, hex, hex):
    # Process and return three values
    return success, left, right

# Mixed usage: partial destructuring, partial construction
{success, left, right} = process_and_split(input_data)
result: Result = {success, left}  # Use partial results to construct struct

9. Syntax Features

  1. Mandatory Type Declaration: All function parameters, struct fields, and variable declarations must include types
  2. Python-style Indentation: Use indentation instead of braces to represent code blocks
  3. Single Contract System: Each file can only contain one contract
  4. Chained Call Support: Supports chained calls in the form of obj.field.method()
  5. Constructor: Uses __init__ as constructor name
  6. Field Access: Supports field access in forms like self.field and StructName.field
  7. Ownership System: Variables have consumption property, support clone operations
  8. Brace Syntax Sugar: Supports multi-return value destructuring and struct literal syntax

10. Type System

Currently supported basic types:

  • int: integer type
  • string: string type
  • hex: hexadecimal data type
  • bool: boolean type
  • User-defined struct types

11. Notes

  1. All statements must end with newline character
  2. Code blocks must be properly indented
  3. Type declarations for function parameters and struct fields are mandatory
  4. Supports nested field access and method calls
  5. Assignment operations support simple variables and field access as left values

Released under the MIT License.